mirror of
https://github.com/open62541/open62541.git
synced 2025-06-03 04:00:21 +00:00
Merge remote-tracking branch 'origin/master' into merge_14_master_25
This commit is contained in:
commit
0e62e11afb
2
.github/dependabot.yml
vendored
2
.github/dependabot.yml
vendored
@ -10,7 +10,7 @@ updates:
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: ".github"
|
||||
directory: "/"
|
||||
target-branch: "master"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
|
4
.github/workflows/codeql.yml
vendored
4
.github/workflows/codeql.yml
vendored
@ -2,9 +2,9 @@ name: "Code Scanning"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master, 1\.1, 1\.2 ]
|
||||
branches: [ master, 1.* ]
|
||||
pull_request:
|
||||
branches: [ master, 1\.1, 1\.2 ]
|
||||
branches: [ master, 1.* ]
|
||||
paths-ignore:
|
||||
- '**/*.md'
|
||||
- '**/*.txt'
|
||||
|
6
.github/workflows/coverage.yml
vendored
6
.github/workflows/coverage.yml
vendored
@ -6,8 +6,6 @@ on:
|
||||
- v1.*
|
||||
pull_request:
|
||||
branches: [ main, master ]
|
||||
tags:
|
||||
- v1.*
|
||||
|
||||
jobs:
|
||||
run:
|
||||
@ -18,7 +16,7 @@ jobs:
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y -qq python3-sphinx graphviz check libmbedtls-dev mosquitto
|
||||
- name: Fetch
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: true
|
||||
- name: Execute Tests
|
||||
@ -29,4 +27,4 @@ jobs:
|
||||
run: |
|
||||
tree .
|
||||
- name: Upload coverage to Codecov
|
||||
uses: codecov/codecov-action@v4
|
||||
uses: codecov/codecov-action@v5
|
||||
|
4
.github/workflows/doc_upload.yml
vendored
4
.github/workflows/doc_upload.yml
vendored
@ -20,7 +20,7 @@ jobs:
|
||||
- name: Install Dependencies
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y -qq python3-sphinx graphviz python-six texlive-fonts-recommended texlive-latex-extra texlive-plain-generic texlive-latex-recommended latexmk texlive-fonts-extra
|
||||
sudo apt-get install -y -qq python3-sphinx graphviz texlive-fonts-recommended texlive-latex-extra texlive-plain-generic texlive-latex-recommended latexmk texlive-fonts-extra
|
||||
pip install sphinx-rtd-theme
|
||||
- name: Build Documentation
|
||||
run: source tools/ci/ci.sh && build_docs_pdf
|
||||
@ -33,7 +33,7 @@ jobs:
|
||||
persist-credentials: false
|
||||
- name: Get the current branch name
|
||||
shell: bash
|
||||
run: echo "::set-output name=branch::${GITHUB_REF##*/}"
|
||||
run: echo "branch=${GITHUB_REF##*/}" >> "$GITHUB_OUTPUT"
|
||||
id: myref
|
||||
- name: Copy Documentation Files to Website repository
|
||||
run: |
|
||||
|
14
.gitignore
vendored
14
.gitignore
vendored
@ -58,27 +58,13 @@ coverage_report
|
||||
/.settings
|
||||
.autotools
|
||||
test-driver
|
||||
include/opcua.h
|
||||
include/ua_namespace_0.h
|
||||
src/opcua.c
|
||||
src/ua_namespace_0.c
|
||||
tests/check_builtin
|
||||
tests/check_create
|
||||
tests/check_decode
|
||||
tests/check_delete
|
||||
tests/check_encode
|
||||
tests/check_memory
|
||||
tests/check_namespace
|
||||
tests/coverage
|
||||
tests/**/CMakeFiles
|
||||
Testing
|
||||
Makefile
|
||||
/CMakeCache.txt
|
||||
/CMakeFiles/
|
||||
/cmake_install.cmake
|
||||
doc/
|
||||
doc_src/
|
||||
examples/
|
||||
/exampleClient
|
||||
/exampleClient_legacy
|
||||
/src_generated/
|
||||
|
26
CHANGES.md
26
CHANGES.md
@ -3,6 +3,32 @@ refactorings and bug fixes are not reported here.
|
||||
|
||||
# Development
|
||||
|
||||
### New Realtime-PubSub model
|
||||
|
||||
The new Realtime-PubSub model builds upon two new public APIS: (i) The
|
||||
possibility to integrate custom state machines to control the state of
|
||||
PubSub-components and (ii) the generation of offset-tables for the content of
|
||||
PubSub NetworkMessages. The approach is described in
|
||||
/examples/pubsub_realtime/README.md with code examples in the same folder.
|
||||
|
||||
### JSON encoding changed with the v1.05 specification
|
||||
|
||||
The JSON encoding was reworked for the v1.05 version of the OPC UA
|
||||
specification. The change breaks backwards compatibility. The legacy JSON
|
||||
encoding is still available throught the UA_ENABLE_JSON_ENCODING_LEGACY build
|
||||
option. This legacy feature wil get removed at some point in the future.
|
||||
|
||||
### PubSub NetworkMessage structure has an explicit DataSetMessageSize
|
||||
|
||||
In prior versions of the standard, when the PayloadHeader was missing, the
|
||||
PubSub NetworkMessage needed to have exactly one DataSetMessage. In the current
|
||||
standard there can be several DataSetMessages also without a PayloadHeader. To
|
||||
allow for this the UA_NetworkMessage structure now contains an explicit
|
||||
DataSetMessageSize field outside of the PayloadHeader.
|
||||
|
||||
Note that code could before rely on the default of one DataSetMessage. This code
|
||||
needs to be revised to set the new DataSetMessageSize field to one.
|
||||
|
||||
### PubSub Components are disabled initially
|
||||
|
||||
PubSubComponents (PubSubConnections, ReaderGroups, ...) are no longer enabled
|
||||
|
114
CMakeLists.txt
114
CMakeLists.txt
@ -48,9 +48,11 @@ set(OPEN62541_VER_PATCH 8)
|
||||
set(OPEN62541_VER_LABEL "-undefined") # like "-rc1" or "-g4538abcd" or "-g4538abcd-dirty"
|
||||
set(OPEN62541_VER_COMMIT "unknown-commit")
|
||||
|
||||
# Overwrite the version information based on git if available
|
||||
include(SetGitBasedVersion)
|
||||
set_open62541_version()
|
||||
# Overwrite the version information based on git if available and we are the main cmake project.
|
||||
if (CMAKE_SOURCE_DIR STREQUAL PROJECT_SOURCE_DIR)
|
||||
include(SetGitBasedVersion)
|
||||
set_open62541_version()
|
||||
endif()
|
||||
|
||||
# Examples for the version string are:
|
||||
# v1.2
|
||||
@ -71,11 +73,11 @@ set(UA_ARCHITECTURE "" CACHE STRING "Architecture to build open62541 for")
|
||||
SET_PROPERTY(CACHE UA_ARCHITECTURE PROPERTY STRINGS "" ${UA_ARCHITECTURES})
|
||||
|
||||
if("${UA_ARCHITECTURE}" STREQUAL "")
|
||||
if(UNIX)
|
||||
set(UA_ARCHITECTURE "posix" CACHE STRING "" FORCE)
|
||||
elseif(WIN32)
|
||||
if(WIN32)
|
||||
set(UA_ARCHITECTURE "win32" CACHE STRING "" FORCE)
|
||||
endif(UNIX)
|
||||
else()
|
||||
set(UA_ARCHITECTURE "posix" CACHE STRING "" FORCE)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
message(STATUS "The selected architecture is: ${UA_ARCHITECTURE}")
|
||||
@ -115,6 +117,12 @@ option(UA_ENABLE_NODESETLOADER "Enable nodesetLoader public API" OFF)
|
||||
option(UA_ENABLE_GDS_PUSHMANAGEMENT "Enable GDS pushManagement support" OFF)
|
||||
option(UA_ENABLE_DATATYPES_ALL "Generate all datatypes for namespace zero (uses more binary space)" ON)
|
||||
|
||||
option(UA_ENABLE_JSON_ENCODING_LEGACY "Use the old JSON encoding before the v1.05 spec" OFF)
|
||||
mark_as_advanced(UA_ENABLE_JSON_ENCODING_LEGACY)
|
||||
if(UA_ENABLE_JSON_ENCODING_LEGACY AND NOT UA_ENABLE_JSON_ENCODING)
|
||||
message(FATAL_ERROR "UA_ENABLE_JSON_ENCODING_LEGACY requires UA_ENABLE_JSON_ENCODING also")
|
||||
endif()
|
||||
|
||||
if(UA_INFORMATION_MODEL_AUTOLOAD AND NOT UA_BUILD_FUZZING)
|
||||
set(UA_ENABLE_NODESET_INJECTOR ON)
|
||||
endif()
|
||||
@ -139,8 +147,42 @@ mark_as_advanced(UA_ENABLE_PARSING)
|
||||
option(UA_ENABLE_INLINABLE_EXPORT "Export 'static inline' methods as regular API" OFF)
|
||||
mark_as_advanced(UA_ENABLE_INLINABLE_EXPORT)
|
||||
|
||||
option(UA_ENABLE_DISCOVERY_MULTICAST "Enable Discovery Service with multicast support (LDS-ME)" OFF)
|
||||
# mDNS provider
|
||||
set(UA_MDNS_PLUGINS "MDNSD" "AVAHI")
|
||||
set(UA_ENABLE_DISCOVERY_MULTICAST "OFF" CACHE STRING "mDNS discovery support")
|
||||
mark_as_advanced(UA_ENABLE_DISCOVERY_MULTICAST)
|
||||
SET_PROPERTY(CACHE UA_ENABLE_DISCOVERY_MULTICAST PROPERTY STRINGS "OFF" ${UA_MDNS_PLUGINS})
|
||||
option(UA_ENABLE_DISCOVERY_MULTICAST_MDNSD "Enable mDNS discovery support (uses mdnsd)" OFF)
|
||||
mark_as_advanced(UA_ENABLE_DISCOVERY_MULTICAST_MDNSD)
|
||||
option(UA_ENABLE_DISCOVERY_MULTICAST_AVAHI "Enable mDNS discovery support (uses Avahi)" OFF)
|
||||
mark_as_advanced(UA_ENABLE_DISCOVERY_MULTICAST_AVAHI)
|
||||
|
||||
list (FIND UA_MDNS_PLUGINS ${UA_ENABLE_DISCOVERY_MULTICAST} _tmp)
|
||||
if(UA_ENABLE_DISCOVERY_MULTICAST STREQUAL "OFF" OR ${_tmp} GREATER -1)
|
||||
set(UA_ENABLE_DISCOVERY_MULTICAST_MDNSD OFF)
|
||||
set(UA_ENABLE_DISCOVERY_MULTICAST_AVAHI OFF)
|
||||
if(UA_ENABLE_DISCOVERY_MULTICAST STREQUAL "MDNSD")
|
||||
set(UA_ENABLE_DISCOVERY_MULTICAST_MDNSD ON)
|
||||
elseif(UA_ENABLE_DISCOVERY_MULTICAST STREQUAL "AVAHI")
|
||||
set(UA_ENABLE_DISCOVERY_MULTICAST_AVAHI ON)
|
||||
endif()
|
||||
# Only for backward compatability
|
||||
elseif(UA_ENABLE_DISCOVERY_MULTICAST OR UA_ENABLE_DISCOVERY_MULTICAST STREQUAL "ON")
|
||||
message(DEPRECATION "Set UA_ENABLE_DISCOVERY_MULTICAST to the desired mDNS library." )
|
||||
if(NOT UA_ENABLE_DISCOVERY_MULTICAST_AVAHI)
|
||||
set(UA_ENABLE_DISCOVERY_MULTICAST_MDNSD ON)
|
||||
endif()
|
||||
else()
|
||||
message(DEPRECATION "Set UA_ENABLE_DISCOVERY_MULTICAST to the desired mDNS library." )
|
||||
if(UA_ENABLE_DISCOVERY_MULTICAST_MDNSD)
|
||||
set(UA_ENABLE_DISCOVERY_MULTICAST "MDNSD")
|
||||
set(UA_ENABLE_DISCOVERY_MULTICAST_AVAHI OFF)
|
||||
endif()
|
||||
if(UA_ENABLE_DISCOVERY_MULTICAST_AVAHI)
|
||||
set(UA_ENABLE_DISCOVERY_MULTICAST "AVAHI")
|
||||
set(UA_ENABLE_DISCOVERY_MULTICAST_MDNSD OFF)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# security provider
|
||||
set(UA_ENCRYPTION_PLUGINS "MBEDTLS" "OPENSSL" "LIBRESSL")
|
||||
@ -231,7 +273,8 @@ endif()
|
||||
if(UA_BUILD_FUZZING OR UA_BUILD_OSS_FUZZ OR UA_BUILD_FUZZING_CORPUS)
|
||||
# Force enable options not passed in the build script, to also fuzzy-test this code
|
||||
set(UA_ENABLE_DISCOVERY ON CACHE STRING "" FORCE)
|
||||
set(UA_ENABLE_DISCOVERY_MULTICAST ON CACHE STRING "" FORCE)
|
||||
set(UA_ENABLE_DISCOVERY_MULTICAST MDNSD CACHE STRING "" FORCE)
|
||||
set(UA_ENABLE_DISCOVERY_MULTICAST_MDNSD ON CACHE STRING "" FORCE)
|
||||
set(UA_ENABLE_ENCRYPTION ON CACHE STRING "OFF" FORCE)
|
||||
set(UA_ENABLE_ENCRYPTION_MBEDTLS ON CACHE STRING "" FORCE)
|
||||
set(UA_ENABLE_HISTORIZING ON CACHE STRING "" FORCE)
|
||||
@ -252,7 +295,7 @@ endif()
|
||||
|
||||
if(UA_ENABLE_DISCOVERY_MULTICAST AND NOT UA_ENABLE_DISCOVERY)
|
||||
MESSAGE(WARNING "UA_ENABLE_DISCOVERY_MULTICAST is enabled, but not UA_ENABLE_DISCOVERY. UA_ENABLE_DISCOVERY_MULTICAST will be set to OFF")
|
||||
SET(UA_ENABLE_DISCOVERY_MULTICAST OFF CACHE BOOL "Enable Discovery Service with multicast support (LDS-ME)" FORCE)
|
||||
SET(UA_ENABLE_DISCOVERY_MULTICAST_MDNSD OFF CACHE BOOL "Enable Discovery Service with multicast support (LDS-ME)" FORCE)
|
||||
endif()
|
||||
|
||||
# Advanced options
|
||||
@ -498,6 +541,13 @@ if(MINGW)
|
||||
list(APPEND open62541_LIBRARIES ws2_32 ssp)
|
||||
endif()
|
||||
|
||||
if(UA_ENABLE_DISCOVERY_MULTICAST_AVAHI)
|
||||
find_package(PkgConfig REQUIRED)
|
||||
pkg_search_module(AVAHI REQUIRED avahi-client avahi-common)
|
||||
include_directories(${AVAHI_INCLUDE_DIRS})
|
||||
list(APPEND open62541_LIBRARIES "${AVAHI_LIBRARIES}")
|
||||
endif()
|
||||
|
||||
#####################
|
||||
# Compiler Settings #
|
||||
#####################
|
||||
@ -691,7 +741,7 @@ file(MAKE_DIRECTORY "${PROJECT_BINARY_DIR}/src_generated")
|
||||
# Generate the config.h
|
||||
configure_file(include/open62541/config.h.in ${PROJECT_BINARY_DIR}/src_generated/open62541/config.h)
|
||||
|
||||
if(UA_ENABLE_DISCOVERY_MULTICAST)
|
||||
if(UA_ENABLE_DISCOVERY_MULTICAST_MDNSD)
|
||||
include(GenerateExportHeader)
|
||||
set(MDNSD_LOGLEVEL 300 CACHE STRING "Level at which logs shall be reported" FORCE)
|
||||
|
||||
@ -742,6 +792,7 @@ set(lib_headers ${PROJECT_SOURCE_DIR}/deps/open62541_queue.h
|
||||
${PROJECT_SOURCE_DIR}/deps/base64.h
|
||||
${PROJECT_SOURCE_DIR}/deps/dtoa.h
|
||||
${PROJECT_SOURCE_DIR}/deps/mp_printf.h
|
||||
${PROJECT_SOURCE_DIR}/deps/utf8.h
|
||||
${PROJECT_SOURCE_DIR}/deps/itoa.h
|
||||
${PROJECT_SOURCE_DIR}/deps/ziptree.h
|
||||
${PROJECT_SOURCE_DIR}/src/ua_types_encoding_binary.h
|
||||
@ -758,6 +809,10 @@ set(lib_headers ${PROJECT_SOURCE_DIR}/deps/open62541_queue.h
|
||||
${PROJECT_SOURCE_DIR}/src/pubsub/ua_pubsub_internal.h
|
||||
${PROJECT_SOURCE_DIR}/src/pubsub/ua_pubsub_keystorage.h)
|
||||
|
||||
if(UA_ENABLE_ENCRYPTION AND UA_ARCHITECTURE_WIN32)
|
||||
list(APPEND lib_headers ${PROJECT_SOURCE_DIR}/deps/tr_dirent.h)
|
||||
endif()
|
||||
|
||||
set(lib_sources ${PROJECT_SOURCE_DIR}/src/ua_types.c
|
||||
${PROJECT_SOURCE_DIR}/src/ua_types_encoding_binary.c
|
||||
${PROJECT_BINARY_DIR}/src_generated/open62541/types_generated.c
|
||||
@ -812,6 +867,7 @@ set(lib_sources ${PROJECT_SOURCE_DIR}/src/ua_types.c
|
||||
${PROJECT_SOURCE_DIR}/deps/base64.c
|
||||
${PROJECT_SOURCE_DIR}/deps/dtoa.c
|
||||
${PROJECT_SOURCE_DIR}/deps/mp_printf.c
|
||||
${PROJECT_SOURCE_DIR}/deps/utf8.c
|
||||
${PROJECT_SOURCE_DIR}/deps/itoa.c
|
||||
${PROJECT_SOURCE_DIR}/deps/ziptree.c)
|
||||
|
||||
@ -837,6 +893,7 @@ if(UA_ENABLE_JSON_ENCODING)
|
||||
list(APPEND lib_sources ${PROJECT_SOURCE_DIR}/deps/cj5.c
|
||||
${PROJECT_SOURCE_DIR}/deps/parse_num.c
|
||||
${PROJECT_SOURCE_DIR}/src/ua_types_encoding_json.c
|
||||
${PROJECT_SOURCE_DIR}/src/ua_types_encoding_json_105.c
|
||||
${PROJECT_SOURCE_DIR}/src/pubsub/ua_pubsub_networkmessage_json.c)
|
||||
endif()
|
||||
|
||||
@ -869,20 +926,24 @@ if(UA_DEBUG_DUMP_PKGS)
|
||||
list(APPEND lib_sources ${PROJECT_SOURCE_DIR}/plugins/ua_debug_dump_pkgs.c)
|
||||
endif()
|
||||
|
||||
if(UA_ENABLE_DISCOVERY_MULTICAST)
|
||||
if(UA_ENABLE_DISCOVERY_MULTICAST_MDNSD)
|
||||
# prepend in list, otherwise it complains that winsock2.h has to be included before windows.h
|
||||
list(APPEND lib_headers
|
||||
${PROJECT_BINARY_DIR}/src_generated/mdnsd_config.h
|
||||
${PROJECT_SOURCE_DIR}/deps/mdnsd/libmdnsd/1035.h
|
||||
${PROJECT_SOURCE_DIR}/deps/mdnsd/libmdnsd/xht.h
|
||||
${PROJECT_SOURCE_DIR}/deps/mdnsd/libmdnsd/sdtxt.h
|
||||
${PROJECT_SOURCE_DIR}/deps/mdnsd/libmdnsd/mdnsd.h)
|
||||
${PROJECT_BINARY_DIR}/src_generated/mdnsd_config.h
|
||||
${PROJECT_SOURCE_DIR}/deps/mdnsd/libmdnsd/1035.h
|
||||
${PROJECT_SOURCE_DIR}/deps/mdnsd/libmdnsd/xht.h
|
||||
${PROJECT_SOURCE_DIR}/deps/mdnsd/libmdnsd/sdtxt.h
|
||||
${PROJECT_SOURCE_DIR}/deps/mdnsd/libmdnsd/mdnsd.h)
|
||||
list(APPEND lib_sources
|
||||
${PROJECT_SOURCE_DIR}/src/server/ua_discovery_mdns.c
|
||||
${PROJECT_SOURCE_DIR}/deps/mdnsd/libmdnsd/1035.c
|
||||
${PROJECT_SOURCE_DIR}/deps/mdnsd/libmdnsd/xht.c
|
||||
${PROJECT_SOURCE_DIR}/deps/mdnsd/libmdnsd/sdtxt.c
|
||||
${PROJECT_SOURCE_DIR}/deps/mdnsd/libmdnsd/mdnsd.c)
|
||||
${PROJECT_SOURCE_DIR}/src/server/ua_discovery_mdns.c
|
||||
${PROJECT_SOURCE_DIR}/deps/mdnsd/libmdnsd/1035.c
|
||||
${PROJECT_SOURCE_DIR}/deps/mdnsd/libmdnsd/xht.c
|
||||
${PROJECT_SOURCE_DIR}/deps/mdnsd/libmdnsd/sdtxt.c
|
||||
${PROJECT_SOURCE_DIR}/deps/mdnsd/libmdnsd/mdnsd.c)
|
||||
elseif(UA_ENABLE_DISCOVERY_MULTICAST_AVAHI)
|
||||
list(APPEND lib_sources
|
||||
${PROJECT_SOURCE_DIR}/src/server/ua_discovery_mdns_avahi.c
|
||||
)
|
||||
endif()
|
||||
|
||||
if(UA_BUILD_FUZZING OR UA_BUILD_OSS_FUZZ)
|
||||
@ -966,7 +1027,7 @@ endif()
|
||||
# Always include encryption plugins into the amalgamation
|
||||
# Use guards in the files to ensure that UA_ENABLE_ENCRYPTON_MBEDTLS and UA_ENABLE_ENCRYPTION_OPENSSL are honored.
|
||||
|
||||
if((UNIX AND UA_ENABLE_ENCRYPTION) OR UA_ENABLE_AMALGAMATION)
|
||||
if(((UNIX OR UA_ARCHITECTURE_WIN32) AND UA_ENABLE_ENCRYPTION) OR UA_ENABLE_AMALGAMATION)
|
||||
list(APPEND plugin_sources ${PROJECT_SOURCE_DIR}/plugins/crypto/ua_filestore_common.h
|
||||
${PROJECT_SOURCE_DIR}/plugins/crypto/ua_filestore_common.c
|
||||
${PROJECT_SOURCE_DIR}/plugins/crypto/ua_certificategroup_filestore.c
|
||||
@ -1037,7 +1098,7 @@ endif()
|
||||
set(UA_FILE_NODESETS) # List of nodeset-xml files to be considered in the generated information model
|
||||
set(UA_NODESET_DIR ${PROJECT_SOURCE_DIR}/deps/ua-nodeset CACHE STRING "The path to the node-set directory (e.g. from https://github.com/OPCFoundation/UA-Nodeset)")
|
||||
|
||||
unset(UA_FILE_NS0_PRIVATE)
|
||||
set(UA_FILE_NS0_PRIVATE "")
|
||||
if(UA_FILE_NS0)
|
||||
set(UA_FILE_NS0_PRIVATE "${UA_FILE_NS0}")
|
||||
endif()
|
||||
@ -1301,7 +1362,7 @@ add_library(open62541::open62541 ALIAS open62541)
|
||||
target_compile_definitions(open62541-object PRIVATE -DUA_DYNAMIC_LINKING_EXPORT)
|
||||
target_compile_definitions(open62541-plugins PRIVATE -DUA_DYNAMIC_LINKING_EXPORT)
|
||||
target_compile_definitions(open62541 PRIVATE -DUA_DYNAMIC_LINKING_EXPORT)
|
||||
if(UA_ENABLE_DISCOVERY_MULTICAST)
|
||||
if(UA_ENABLE_DISCOVERY_MULTICAST_MDNSD)
|
||||
target_compile_definitions(open62541-object PRIVATE -DMDNSD_DYNAMIC_LINKING_EXPORT)
|
||||
target_compile_definitions(open62541 PRIVATE -DMDNSD_DYNAMIC_LINKING_EXPORT)
|
||||
endif()
|
||||
@ -1323,6 +1384,9 @@ SET_TARGET_PROPERTIES(open62541 PROPERTIES
|
||||
# DLL requires linking to dependencies
|
||||
target_link_libraries(open62541 PUBLIC ${open62541_PUBLIC_LIBRARIES})
|
||||
target_link_libraries(open62541 PRIVATE ${open62541_LIBRARIES})
|
||||
if(UA_ENABLE_DISCOVERY_MULTICAST_AVAHI)
|
||||
target_link_libraries(open62541 PUBLIC ${AVAHI_LIBRARIES})
|
||||
endif()
|
||||
|
||||
##########################
|
||||
# Build Selected Targets #
|
||||
|
@ -122,6 +122,7 @@ typedef SSIZE_T ssize_t;
|
||||
/* POSIX Definitions */
|
||||
/*********************/
|
||||
|
||||
#include <sys/socket.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <netinet/in.h>
|
||||
#include <netinet/tcp.h>
|
||||
|
@ -72,7 +72,7 @@ typedef enum {
|
||||
} MultiCastType;
|
||||
|
||||
typedef union {
|
||||
#ifdef _WIN32
|
||||
#if !defined(ip_mreqn)
|
||||
struct ip_mreq ipv4;
|
||||
#else
|
||||
struct ip_mreqn ipv4;
|
||||
@ -197,9 +197,11 @@ setMulticastInterface(const char *netif, struct addrinfo *info,
|
||||
if(ifa->ifa_addr->sa_family != info->ai_family)
|
||||
continue;
|
||||
|
||||
#if defined(_WIN32) || defined(ip_mreqn)
|
||||
idx = UA_if_nametoindex(ifa->ifa_name);
|
||||
if(idx == 0)
|
||||
continue;
|
||||
#endif
|
||||
|
||||
/* Found network interface by name */
|
||||
if(strcmp(ifa->ifa_name, netif) == 0)
|
||||
@ -228,12 +230,15 @@ setMulticastInterface(const char *netif, struct addrinfo *info,
|
||||
return UA_STATUSCODE_BADINTERNALERROR;
|
||||
|
||||
/* Write the interface index */
|
||||
if(info->ai_family == AF_INET)
|
||||
if(info->ai_family == AF_INET) {
|
||||
#if defined(ip_mreqn)
|
||||
req->ipv4.imr_ifindex = idx;
|
||||
#endif
|
||||
#if UA_IPV6
|
||||
else /* if(info->ai_family == AF_INET6) */
|
||||
} else { /* if(info->ai_family == AF_INET6) */
|
||||
req->ipv6.ipv6mr_interface = idx;
|
||||
#endif
|
||||
}
|
||||
return UA_STATUSCODE_GOOD;
|
||||
}
|
||||
|
||||
@ -246,7 +251,7 @@ setupMulticastRequest(UA_FD socket, MulticastRequest *req, const UA_KeyValueMap
|
||||
if(info->ai_family == AF_INET) {
|
||||
struct sockaddr_in *sin = (struct sockaddr_in *)info->ai_addr;
|
||||
req->ipv4.imr_multiaddr = sin->sin_addr;
|
||||
#ifdef _WIN32
|
||||
#if !defined(ip_mreqn)
|
||||
req->ipv4.imr_interface.s_addr = htonl(INADDR_ANY); /* default ANY */
|
||||
#else
|
||||
req->ipv4.imr_address.s_addr = htonl(INADDR_ANY); /* default ANY */
|
||||
|
2
deps/README.md
vendored
2
deps/README.md
vendored
@ -21,3 +21,5 @@ The following third party libraries may be included -- depending on the activate
|
||||
| mqtt-c | MIT | a portable MQTT client in C |
|
||||
| dtoa | BSL (Boost) | Printing of float numbers |
|
||||
| mp_printf | MIT | Our version of github:mpaland/printf |
|
||||
| utf8 | MPL 2.0 | Lightweight utf8 de/encoding |
|
||||
| tr_dirent | MIT | Dirent interface for Microsoft Visual Studio |
|
||||
|
14
deps/base64.c
vendored
14
deps/base64.c
vendored
@ -59,7 +59,7 @@ UA_base64_buf(const unsigned char *src, size_t len, unsigned char *out) {
|
||||
return (size_t)(pos - out);
|
||||
}
|
||||
|
||||
static unsigned char dtable[256] = {
|
||||
static unsigned char dtable[128] = {
|
||||
0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
|
||||
0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
|
||||
0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 62 , 0x80, 62 , 0x80, 63 ,
|
||||
@ -67,15 +67,7 @@ static unsigned char dtable[256] = {
|
||||
0x80, 0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10 , 11 , 12 , 13 , 14 ,
|
||||
15 , 16 , 17 , 18 , 19 , 20 , 21 , 22 , 23 , 24 , 25 , 0x80, 0x80, 0x80, 0x80, 63 ,
|
||||
0x80, 26 , 27 , 28 , 29 , 30 , 31 , 32 , 33 , 34 , 35 , 36 , 37 , 38 , 39 , 40 ,
|
||||
41 , 42 , 43 , 44 , 45 , 46 , 47 , 48 , 49 , 50 , 51 , 0x80, 0x80, 0x80, 0x80, 0x80,
|
||||
0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
|
||||
0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
|
||||
0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
|
||||
0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
|
||||
0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
|
||||
0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
|
||||
0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
|
||||
0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80
|
||||
41 , 42 , 43 , 44 , 45 , 46 , 47 , 48 , 49 , 50 , 51 , 0x80, 0x80, 0x80, 0x80, 0x80
|
||||
};
|
||||
|
||||
unsigned char *
|
||||
@ -102,7 +94,7 @@ UA_unbase64(const unsigned char *src, size_t len, size_t *out_len) {
|
||||
unsigned char block[4];
|
||||
unsigned char *pos = out;
|
||||
for(size_t i = 0; i < len; i++) {
|
||||
unsigned char tmp = dtable[src[i]];
|
||||
unsigned char tmp = dtable[src[i] & 0x07f];
|
||||
if(tmp == 0x80)
|
||||
goto error; /* Invalid input */
|
||||
|
||||
|
170
deps/cj5.c
vendored
170
deps/cj5.c
vendored
@ -23,6 +23,7 @@
|
||||
|
||||
#include "cj5.h"
|
||||
#include "parse_num.h"
|
||||
#include "utf8.h"
|
||||
|
||||
#include <math.h>
|
||||
#include <float.h>
|
||||
@ -131,49 +132,16 @@ cj5__parse_string(cj5__parser *parser) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Escape char
|
||||
// Skip escape character
|
||||
if(c == '\\') {
|
||||
if(parser->pos + 1 >= len) {
|
||||
parser->error = CJ5_ERROR_INCOMPLETE;
|
||||
return;
|
||||
}
|
||||
parser->pos++;
|
||||
switch(json5[parser->pos]) {
|
||||
case '\"':
|
||||
case '/':
|
||||
case '\\':
|
||||
case 'b':
|
||||
case 'f':
|
||||
case 'r':
|
||||
case 'n':
|
||||
case 't':
|
||||
break;
|
||||
case 'u': // The next four characters are an utf8 code
|
||||
parser->pos++;
|
||||
if(parser->pos + 4 >= len) {
|
||||
parser->error = CJ5_ERROR_INVALID;
|
||||
return;
|
||||
}
|
||||
for(unsigned int i = 0; i < 4; i++) {
|
||||
// If it isn't a hex character we have an error
|
||||
if(!(json5[parser->pos] >= 48 && json5[parser->pos] <= 57) && /* 0-9 */
|
||||
!(json5[parser->pos] >= 65 && json5[parser->pos] <= 70) && /* A-F */
|
||||
!(json5[parser->pos] >= 97 && json5[parser->pos] <= 102)) /* a-f */
|
||||
{
|
||||
parser->error = CJ5_ERROR_INVALID;
|
||||
return;
|
||||
}
|
||||
parser->pos++;
|
||||
}
|
||||
parser->pos--;
|
||||
break;
|
||||
case '\n': // Escape break line
|
||||
if(json5[parser->pos] == '\n') {
|
||||
parser->line++;
|
||||
parser->line_start = parser->pos;
|
||||
break;
|
||||
default:
|
||||
parser->error = CJ5_ERROR_INVALID;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -683,87 +651,67 @@ cj5_get_str(const cj5_result *r, unsigned int tok_index,
|
||||
unsigned int outpos = 0;
|
||||
for(; pos < end; pos++) {
|
||||
uint8_t c = (uint8_t)*pos;
|
||||
// Unprintable ascii characters must be escaped
|
||||
if(c < ' ' || c == 127)
|
||||
return CJ5_ERROR_INVALID;
|
||||
|
||||
// Process an escape character
|
||||
if(c == '\\') {
|
||||
if(pos + 1 >= end)
|
||||
return CJ5_ERROR_INCOMPLETE;
|
||||
pos++;
|
||||
c = (uint8_t)*pos;
|
||||
switch(c) {
|
||||
case '\"': buf[outpos++] = '\"'; break;
|
||||
case '\\': buf[outpos++] = '\\'; break;
|
||||
case '\n': buf[outpos++] = '\n'; break; // escape newline
|
||||
case '/': buf[outpos++] = '/'; break;
|
||||
case 'b': buf[outpos++] = '\b'; break;
|
||||
case 'f': buf[outpos++] = '\f'; break;
|
||||
case 'r': buf[outpos++] = '\r'; break;
|
||||
case 'n': buf[outpos++] = '\n'; break;
|
||||
case 't': buf[outpos++] = '\t'; break;
|
||||
case 'u': {
|
||||
// Parse the unicode code point
|
||||
if(pos + 4 >= end)
|
||||
return CJ5_ERROR_INCOMPLETE;
|
||||
pos++;
|
||||
uint32_t utf;
|
||||
cj5_error_code err = parse_codepoint(pos, &utf);
|
||||
if(err != CJ5_ERROR_NONE)
|
||||
return err;
|
||||
pos += 3;
|
||||
|
||||
if(0xD800 <= utf && utf <= 0xDBFF) {
|
||||
// Parse a surrogate pair
|
||||
if(pos + 6 >= end)
|
||||
return CJ5_ERROR_INVALID;
|
||||
if(pos[1] != '\\' && pos[3] != 'u')
|
||||
return CJ5_ERROR_INVALID;
|
||||
pos += 3;
|
||||
uint32_t trail;
|
||||
err = parse_codepoint(pos, &trail);
|
||||
if(err != CJ5_ERROR_NONE)
|
||||
return err;
|
||||
pos += 3;
|
||||
utf = (utf << 10) + trail + SURROGATE_OFFSET;
|
||||
} else if(0xDC00 <= utf && utf <= 0xDFFF) {
|
||||
// Invalid Unicode '\\u%04X'
|
||||
return CJ5_ERROR_INVALID;
|
||||
}
|
||||
|
||||
// Write the utf8 bytes of the code point
|
||||
if(utf <= 0x7F) { // Plain ASCII
|
||||
buf[outpos++] = (char)utf;
|
||||
} else if(utf <= 0x07FF) { // 2-byte unicode
|
||||
buf[outpos++] = (char)(((utf >> 6) & 0x1F) | 0xC0);
|
||||
buf[outpos++] = (char)(((utf >> 0) & 0x3F) | 0x80);
|
||||
} else if(utf <= 0xFFFF) { // 3-byte unicode
|
||||
buf[outpos++] = (char)(((utf >> 12) & 0x0F) | 0xE0);
|
||||
buf[outpos++] = (char)(((utf >> 6) & 0x3F) | 0x80);
|
||||
buf[outpos++] = (char)(((utf >> 0) & 0x3F) | 0x80);
|
||||
} else if(utf <= 0x10FFFF) { // 4-byte unicode
|
||||
buf[outpos++] = (char)(((utf >> 18) & 0x07) | 0xF0);
|
||||
buf[outpos++] = (char)(((utf >> 12) & 0x3F) | 0x80);
|
||||
buf[outpos++] = (char)(((utf >> 6) & 0x3F) | 0x80);
|
||||
buf[outpos++] = (char)(((utf >> 0) & 0x3F) | 0x80);
|
||||
} else {
|
||||
return CJ5_ERROR_INVALID; // Not a utf8 string
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
return CJ5_ERROR_INVALID;
|
||||
}
|
||||
// Unescaped Ascii character or utf8 byte
|
||||
if(c != '\\') {
|
||||
buf[outpos++] = (char)c;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Unprintable ascii characters must be escaped. JSON5 allows nested
|
||||
// quotes if the quote character is not the same as the surrounding
|
||||
// quote character, e.g. 'this is my "quote"'. This logic is in the
|
||||
// token parsing code and not in this "string extraction" method.
|
||||
if(c < ' ' || c == 127)
|
||||
return CJ5_ERROR_INVALID;
|
||||
// End of input before the escaped character
|
||||
if(pos + 1 >= end)
|
||||
return CJ5_ERROR_INCOMPLETE;
|
||||
|
||||
// Ascii character or utf8 byte
|
||||
buf[outpos++] = (char)c;
|
||||
// Process escaped character
|
||||
pos++;
|
||||
c = (uint8_t)*pos;
|
||||
switch(c) {
|
||||
case 'b': buf[outpos++] = '\b'; break;
|
||||
case 'f': buf[outpos++] = '\f'; break;
|
||||
case 'r': buf[outpos++] = '\r'; break;
|
||||
case 'n': buf[outpos++] = '\n'; break;
|
||||
case 't': buf[outpos++] = '\t'; break;
|
||||
default: buf[outpos++] = c; break;
|
||||
case 'u': {
|
||||
// Parse a unicode code point
|
||||
if(pos + 4 >= end)
|
||||
return CJ5_ERROR_INCOMPLETE;
|
||||
pos++;
|
||||
uint32_t utf;
|
||||
cj5_error_code err = parse_codepoint(pos, &utf);
|
||||
if(err != CJ5_ERROR_NONE)
|
||||
return err;
|
||||
pos += 3;
|
||||
|
||||
// Parse a surrogate pair
|
||||
if(0xd800 <= utf && utf <= 0xdfff) {
|
||||
if(pos + 6 >= end)
|
||||
return CJ5_ERROR_INVALID;
|
||||
if(pos[1] != '\\' && pos[2] != 'u')
|
||||
return CJ5_ERROR_INVALID;
|
||||
pos += 3;
|
||||
uint32_t utf2;
|
||||
err = parse_codepoint(pos, &utf2);
|
||||
if(err != CJ5_ERROR_NONE)
|
||||
return err;
|
||||
pos += 3;
|
||||
// High or low surrogate pair
|
||||
utf = (utf <= 0xdbff) ?
|
||||
(utf << 10) + utf2 + SURROGATE_OFFSET :
|
||||
(utf2 << 10) + utf + SURROGATE_OFFSET;
|
||||
}
|
||||
|
||||
// Write the utf8 bytes of the code point
|
||||
unsigned len = utf8_from_codepoint((unsigned char*)buf + outpos, utf);
|
||||
if(len == 0)
|
||||
return CJ5_ERROR_INVALID; // Not a utf8 string
|
||||
outpos += len;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Terminate with \0
|
||||
|
1239
deps/tr_dirent.h
vendored
Normal file
1239
deps/tr_dirent.h
vendored
Normal file
File diff suppressed because it is too large
Load Diff
67
deps/utf8.c
vendored
Normal file
67
deps/utf8.c
vendored
Normal file
@ -0,0 +1,67 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*
|
||||
* Copyright 2024 (c) Fraunhofer IOSB (Author: Julius Pfrommer)
|
||||
*/
|
||||
|
||||
#include "utf8.h"
|
||||
|
||||
#define UTF_PARSE_BYTE(pos) do { \
|
||||
byte = str[pos]; \
|
||||
if(UTF_UNLIKELY(byte < 0x80 || byte > 0xBF)) \
|
||||
return 0; /* Not a continuation byte */ \
|
||||
*codepoint = (*codepoint << 6) + (byte & 0x3F); \
|
||||
} while(0)
|
||||
|
||||
unsigned
|
||||
utf8_to_codepoint(const unsigned char *str, size_t len, unsigned *codepoint) {
|
||||
/* Ensure there is a character to read */
|
||||
if(UTF_UNLIKELY(len == 0))
|
||||
return 0;
|
||||
|
||||
*codepoint = str[0];
|
||||
if(UTF_LIKELY(*codepoint < 0x80))
|
||||
return 1; /* Normal ASCII */
|
||||
|
||||
if(UTF_UNLIKELY(*codepoint <= 0xC1))
|
||||
return 0; /* Continuation byte not allowed here */
|
||||
|
||||
unsigned count;
|
||||
unsigned char byte;
|
||||
if(*codepoint <= 0xDF) { /* 2-byte sequence */
|
||||
if(len < 2)
|
||||
return 0;
|
||||
count = 2;
|
||||
*codepoint &= 0x1F;
|
||||
UTF_PARSE_BYTE(1);
|
||||
if(UTF_UNLIKELY(*codepoint < 0x80))
|
||||
return 0; /* Too small for the encoding length */
|
||||
} else if(*codepoint <= 0xEF) { /* 3-byte sequence */
|
||||
if(len < 3)
|
||||
return 0;
|
||||
count = 3;
|
||||
*codepoint &= 0xF;
|
||||
UTF_PARSE_BYTE(1);
|
||||
UTF_PARSE_BYTE(2);
|
||||
if(UTF_UNLIKELY(*codepoint < 0x800))
|
||||
return 0; /* Too small for the encoding length */
|
||||
} else if(*codepoint <= 0xF4) { /* 4-byte sequence */
|
||||
if(len < 4)
|
||||
return 0;
|
||||
count = 4;
|
||||
*codepoint &= 0x7;
|
||||
UTF_PARSE_BYTE(1);
|
||||
UTF_PARSE_BYTE(2);
|
||||
UTF_PARSE_BYTE(3);
|
||||
if(UTF_UNLIKELY(*codepoint < 0x10000))
|
||||
return 0; /* Too small for the encoding length */
|
||||
} else {
|
||||
return 0; /* Invalid utf8 encoding */
|
||||
}
|
||||
|
||||
if(UTF_UNLIKELY(*codepoint > 0x10FFFF))
|
||||
return 0; /* Not in the Unicode range */
|
||||
|
||||
return count;
|
||||
}
|
83
deps/utf8.h
vendored
Normal file
83
deps/utf8.h
vendored
Normal file
@ -0,0 +1,83 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*
|
||||
* Copyright 2024 (c) Fraunhofer IOSB (Author: Julius Pfrommer)
|
||||
*/
|
||||
|
||||
#ifndef UTF8_H_
|
||||
#define UTF8_H_
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#ifdef _MSC_VER
|
||||
# define UTF_INLINE __inline
|
||||
#else
|
||||
# define UTF_INLINE inline
|
||||
#endif
|
||||
|
||||
#if defined(__GNUC__) || defined(__clang__)
|
||||
# define UTF_LIKELY(x) __builtin_expect((x), 1)
|
||||
# define UTF_UNLIKELY(x) __builtin_expect((x), 0)
|
||||
#else
|
||||
# define UTF_LIKELY(x) (x)
|
||||
# define UTF_UNLIKELY(x) (x)
|
||||
#endif
|
||||
|
||||
/* Extract the next utf8 codepoint from the buffer. Returns the length (1-4) of
|
||||
* the codepoint encoding or 0 upon an error. */
|
||||
unsigned
|
||||
utf8_to_codepoint(const unsigned char *str, size_t len, unsigned *codepoint);
|
||||
|
||||
/* Encodes the codepoint in utf8. The string needs to have enough space (at most
|
||||
* four byte) available. Returns the encoding length. */
|
||||
static UTF_INLINE unsigned
|
||||
utf8_from_codepoint(unsigned char *str, unsigned codepoint) {
|
||||
if(UTF_LIKELY(codepoint <= 0x7F)) { /* Plain ASCII */
|
||||
str[0] = (unsigned char)codepoint;
|
||||
return 1;
|
||||
}
|
||||
if(UTF_LIKELY(codepoint <= 0x07FF)) { /* 2-byte unicode */
|
||||
str[0] = (unsigned char)(((codepoint >> 6) & 0x1F) | 0xC0);
|
||||
str[1] = (unsigned char)(((codepoint >> 0) & 0x3F) | 0x80);
|
||||
return 2;
|
||||
}
|
||||
if(UTF_LIKELY(codepoint <= 0xFFFF)) { /* 3-byte unicode */
|
||||
str[0] = (unsigned char)(((codepoint >> 12) & 0x0F) | 0xE0);
|
||||
str[1] = (unsigned char)(((codepoint >> 6) & 0x3F) | 0x80);
|
||||
str[2] = (unsigned char)(((codepoint >> 0) & 0x3F) | 0x80);
|
||||
return 3;
|
||||
}
|
||||
if(UTF_LIKELY(codepoint <= 0x10FFFF)) { /* 4-byte unicode */
|
||||
str[0] = (unsigned char)(((codepoint >> 18) & 0x07) | 0xF0);
|
||||
str[1] = (unsigned char)(((codepoint >> 12) & 0x3F) | 0x80);
|
||||
str[2] = (unsigned char)(((codepoint >> 6) & 0x3F) | 0x80);
|
||||
str[3] = (unsigned char)(((codepoint >> 0) & 0x3F) | 0x80);
|
||||
return 4;
|
||||
}
|
||||
return 0; /* Not a unicode codepoint */
|
||||
}
|
||||
|
||||
/* Returns the encoding length of the codepoint */
|
||||
static UTF_INLINE unsigned
|
||||
utf8_length(unsigned codepoint) {
|
||||
if(UTF_LIKELY(codepoint <= 0x7F))
|
||||
return 1; /* Plain ASCII */
|
||||
if(UTF_LIKELY(codepoint <= 0x07FF))
|
||||
return 2; /* 2-byte unicode */
|
||||
if(UTF_LIKELY(codepoint <= 0xFFFF))
|
||||
return 3; /* 3-byte unicode */
|
||||
if(UTF_LIKELY(codepoint <= 0x10FFFF))
|
||||
return 4; /* 4-byte unicode */
|
||||
return 0; /* Not a unicode codepoint */
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* UTF8_H_ */
|
2
deps/ziptree.c
vendored
2
deps/ziptree.c
vendored
@ -127,7 +127,7 @@ __ZIP_INSERT(void *h, zip_cmp_cb cmp, unsigned short fieldoffset,
|
||||
* than "x" */
|
||||
zip_elem *prev = NULL;
|
||||
zip_elem *cur = head->root;
|
||||
enum ZIP_CMP cur_order, prev_order;
|
||||
enum ZIP_CMP cur_order, prev_order = ZIP_CMP_EQ;
|
||||
do {
|
||||
cur_order = __ZIP_UNIQUE_CMP(cmp, x_key, ZIP_KEY_PTR(cur));
|
||||
if(cur_order == ZIP_CMP_EQ)
|
||||
|
@ -23,6 +23,7 @@ Building with CMake on Ubuntu or Debian
|
||||
sudo apt-get install check libsubunit-dev # for unit tests
|
||||
sudo apt-get install python3-sphinx graphviz # for documentation generation
|
||||
sudo apt-get install python3-sphinx-rtd-theme # documentation style
|
||||
sudo apt-get install libavahi-client-dev libavahi-common-dev # for LDS-ME (multicast discovery)
|
||||
|
||||
git clone https://github.com/open62541/open62541.git
|
||||
cd open62541
|
||||
@ -294,7 +295,12 @@ Detailed SDK Features
|
||||
Enable Discovery Service (LDS)
|
||||
|
||||
**UA_ENABLE_DISCOVERY_MULTICAST**
|
||||
Enable Discovery Service with multicast support (LDS-ME)
|
||||
Enable Discovery Service with multicast support (LDS-ME) and specify the
|
||||
multicast backend. The possible options are:
|
||||
|
||||
- ``OFF`` No multicast support. (default)
|
||||
- ``MDNSD`` Multicast support using libmdnsd
|
||||
- ``AVAHI`` Multicast support using Avahi
|
||||
|
||||
**UA_ENABLE_DISCOVERY_SEMAPHORE**
|
||||
Enable Discovery Semaphore support
|
||||
@ -330,7 +336,10 @@ Detailed SDK Features
|
||||
Enable diagnostics information exposed by the server. Enabled by default.
|
||||
|
||||
**UA_ENABLE_JSON_ENCODING**
|
||||
Enable JSON encoding. Enabled by default.
|
||||
Enable JSON encoding. Enabled by default. The JSON encoding changed with the
|
||||
1.05 version of the OPC UA specification. The legacy encoding can be enabled
|
||||
via the ``UA_ENABLE_JSON_ENCODING_LEGACY`` option. Note that this legacy
|
||||
feature wil get removed at some point in the future.
|
||||
|
||||
Some options are marked as advanced. The advanced options need to be toggled to
|
||||
be visible in the cmake GUIs.
|
||||
|
@ -1,45 +1,34 @@
|
||||
cmake_minimum_required(VERSION 3.0...3.12)
|
||||
cmake_minimum_required(VERSION 3.13)
|
||||
project(open62541-examples C)
|
||||
if(${CMAKE_VERSION} VERSION_LESS 3.12)
|
||||
cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION})
|
||||
endif()
|
||||
# This examples folder can also be built standalone.
|
||||
# First install open62541 using `make install` then
|
||||
# copy this folder to any other location and call CMake directly:
|
||||
|
||||
# The examples folder can be built standalone.
|
||||
# First install open62541 system-wide using `make install`.
|
||||
# Then copy this folder to any other location and call CMake directly:
|
||||
#
|
||||
# cp -r open62541/examples $HOME/open62541_examples
|
||||
# cd $HOME/open62541_examples
|
||||
# cd ./open62541_examples
|
||||
# mkdir build && cd build
|
||||
# cmake -DUA_NAMESPACE_ZERO=FULL ..
|
||||
# cmake ..
|
||||
# make -j
|
||||
|
||||
if(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME)
|
||||
# Examples are built standalone. Find installed open62541
|
||||
find_package(open62541 REQUIRED)
|
||||
|
||||
if(UA_NAMESPACE_ZERO STREQUAL "FULL")
|
||||
find_package(open62541 REQUIRED COMPONENTS FullNamespace)
|
||||
else()
|
||||
find_package(open62541 REQUIRED)
|
||||
endif()
|
||||
|
||||
if(NOT UA_TOOLS_DIR)
|
||||
set(UA_TOOLS_DIR ${open62541_TOOLS_DIR})
|
||||
endif()
|
||||
|
||||
# Define empty function. We don't need it in standalone
|
||||
function(assign_source_group)
|
||||
# define empty function. We don't need it in standalone
|
||||
endfunction(assign_source_group)
|
||||
|
||||
include_directories(${PROJECT_BINARY_DIR}/src_generated)
|
||||
endif()
|
||||
|
||||
#####################
|
||||
# CMake Definitions #
|
||||
#####################
|
||||
|
||||
# Required for common.h header file used in examples
|
||||
include_directories(${CMAKE_CURRENT_LIST_DIR})
|
||||
|
||||
#############################
|
||||
# Compiled binaries folders #
|
||||
#############################
|
||||
|
||||
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin/examples)
|
||||
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG ${CMAKE_BINARY_DIR}/bin/examples)
|
||||
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE ${CMAKE_BINARY_DIR}/bin/examples)
|
||||
@ -47,19 +36,21 @@ set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELWITHDEBINFO ${CMAKE_BINARY_DIR}/bin/exampl
|
||||
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_MINSIZEREL ${CMAKE_BINARY_DIR}/bin/examples)
|
||||
|
||||
macro(add_example EXAMPLE_NAME EXAMPLE_SOURCE)
|
||||
add_executable(${EXAMPLE_NAME} ${STATIC_OBJECTS} ${EXAMPLE_SOURCE} ${ARGN} ${PROJECT_SOURCE_DIR}/common.h)
|
||||
add_executable(${EXAMPLE_NAME} ${STATIC_OBJECTS}
|
||||
${EXAMPLE_SOURCE} ${ARGN} ${PROJECT_SOURCE_DIR}/common.h)
|
||||
target_link_libraries(${EXAMPLE_NAME} open62541::open62541)
|
||||
assign_source_group(${EXAMPLE_SOURCE})
|
||||
set_target_properties(${EXAMPLE_NAME} PROPERTIES FOLDER "open62541/examples")
|
||||
set_target_properties(${EXAMPLE_NAME} PROPERTIES VS_DEBUGGER_WORKING_DIRECTORY "${CMAKE_BINARY_DIR}/bin")
|
||||
set_target_properties(${EXAMPLE_NAME} PROPERTIES
|
||||
VS_DEBUGGER_WORKING_DIRECTORY "${CMAKE_BINARY_DIR}/bin")
|
||||
|
||||
if(UA_ENABLE_NODESET_INJECTOR)
|
||||
set(UA_NODESETINJECTOR_EXAMPLE_NAMES ${EXAMPLE_NAME} ${UA_NODESETINJECTOR_EXAMPLE_NAMES})
|
||||
# If the nodeset injector is activated, the target must be built twice.
|
||||
# Otherwise, it may result in the nodesets not being inserted.
|
||||
add_custom_command(TARGET ${EXAMPLE_NAME} POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} --build ${CMAKE_BINARY_DIR} --target ${EXAMPLE_NAME}
|
||||
)
|
||||
COMMAND ${CMAKE_COMMAND} --build ${CMAKE_BINARY_DIR}
|
||||
--target ${EXAMPLE_NAME})
|
||||
endif()
|
||||
endmacro()
|
||||
|
||||
@ -68,25 +59,17 @@ endmacro()
|
||||
#############
|
||||
|
||||
add_example(tutorial_datatypes tutorial_datatypes.c)
|
||||
|
||||
add_example(tutorial_server_firststeps tutorial_server_firststeps.c)
|
||||
|
||||
add_example(tutorial_server_variable tutorial_server_variable.c)
|
||||
|
||||
add_example(tutorial_server_datasource tutorial_server_datasource.c)
|
||||
|
||||
add_example(server_settimestamp server_settimestamp.c)
|
||||
add_example(tutorial_server_variabletype tutorial_server_variabletype.c)
|
||||
add_example(tutorial_server_object tutorial_server_object.c)
|
||||
add_example(tutorial_server_reverseconnect tutorial_server_reverseconnect.c)
|
||||
|
||||
if(UA_ENABLE_SUBSCRIPTIONS)
|
||||
add_example(tutorial_server_monitoreditems tutorial_server_monitoreditems.c)
|
||||
endif()
|
||||
|
||||
add_example(tutorial_server_variabletype tutorial_server_variabletype.c)
|
||||
|
||||
add_example(tutorial_server_object tutorial_server_object.c)
|
||||
|
||||
add_example(tutorial_server_reverseconnect tutorial_server_reverseconnect.c)
|
||||
|
||||
if(UA_ENABLE_METHODCALLS)
|
||||
add_example(tutorial_server_method tutorial_server_method.c)
|
||||
if (UA_MULTITHREADING GREATER_EQUAL 100)
|
||||
@ -94,26 +77,15 @@ if(UA_ENABLE_METHODCALLS)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(UA_ENABLE_SUBSCRIPTIONS_ALARMS_CONDITIONS)
|
||||
add_example(tutorial_server_alarms_conditions tutorial_server_alarms_conditions.c)
|
||||
endif()
|
||||
|
||||
add_example(tutorial_client_firststeps tutorial_client_firststeps.c)
|
||||
|
||||
if(UA_ENABLE_SUBSCRIPTIONS_EVENTS)
|
||||
add_example(tutorial_client_events tutorial_client_events.c)
|
||||
add_example(tutorial_server_events tutorial_server_events.c)
|
||||
add_example(server_events_random events/server_random_events.c)
|
||||
add_example(client_event_filter events/client_eventfilter.c)
|
||||
if(UA_ENABLE_PARSING)
|
||||
add_example(client_event_filter_queries events/client_filter_queries.c)
|
||||
file(STRINGS ${PROJECT_SOURCE_DIR}/events/example_queries/case_1.txt CASE_1 NEWLINE_CONSUME)
|
||||
file(STRINGS ${PROJECT_SOURCE_DIR}/events/example_queries/case_2.txt CASE_2 NEWLINE_CONSUME)
|
||||
STRING(REGEX REPLACE "\n" " " CASE_1 ${CASE_1})
|
||||
STRING(REGEX REPLACE "\"" "\\\\\"" CASE_1 "${CASE_1}")
|
||||
STRING(REGEX REPLACE "\n" " " CASE_2 ${CASE_2})
|
||||
STRING(REGEX REPLACE "\"" "\\\\\"" CASE_2 "${CASE_2}")
|
||||
configure_file(${PROJECT_SOURCE_DIR}/events/eventfilter_parser_examples.h.in ${PROJECT_BINARY_DIR}/../src_generated/open62541/eventfilter_parser_examples.h @ONLY)
|
||||
endif()
|
||||
if(UA_ENABLE_SUBSCRIPTIONS_ALARMS_CONDITIONS)
|
||||
add_example(tutorial_server_alarms_conditions tutorial_server_alarms_conditions.c)
|
||||
endif()
|
||||
add_example(tutorial_client_events tutorial_client_events.c)
|
||||
add_example(tutorial_server_events tutorial_server_events.c)
|
||||
endif()
|
||||
|
||||
##################
|
||||
@ -121,27 +93,18 @@ endif()
|
||||
##################
|
||||
|
||||
add_example(client client.c)
|
||||
|
||||
add_example(client_connect client_connect.c)
|
||||
add_example(client_async client_async.c)
|
||||
add_example(client_connect_loop client_connect_loop.c)
|
||||
|
||||
if(UA_ENABLE_HISTORIZING)
|
||||
add_example(client_historical client_historical.c)
|
||||
endif()
|
||||
|
||||
install(PROGRAMS $<TARGET_FILE:client>
|
||||
DESTINATION bin
|
||||
RENAME ua_client)
|
||||
|
||||
add_example(client_async client_async.c)
|
||||
|
||||
if(UA_ENABLE_METHODCALLS)
|
||||
if (UA_MULTITHREADING GREATER_EQUAL 100)
|
||||
add_example(client_method_async client_method_async.c)
|
||||
endif()
|
||||
if(UA_ENABLE_METHODCALLS AND UA_MULTITHREADING GREATER_EQUAL 100)
|
||||
add_example(client_method_async client_method_async.c)
|
||||
endif()
|
||||
|
||||
add_example(client_connect_loop client_connect_loop.c)
|
||||
|
||||
if(UA_ENABLE_SUBSCRIPTIONS)
|
||||
add_example(client_subscription_loop client_subscription_loop.c)
|
||||
endif()
|
||||
@ -151,19 +114,25 @@ endif()
|
||||
####################
|
||||
|
||||
add_example(ci_server ci_server.c)
|
||||
|
||||
add_example(server_settimestamp server_settimestamp.c)
|
||||
add_example(server_mainloop server_mainloop.c)
|
||||
|
||||
add_example(server_instantiation server_instantiation.c)
|
||||
|
||||
add_example(server_repeated_job server_repeated_job.c)
|
||||
|
||||
add_example(server_inheritance server_inheritance.c)
|
||||
|
||||
add_example(server_loglevel server_loglevel.c)
|
||||
add_example(custom_datatype_client custom_datatype/client_types_custom.c)
|
||||
add_example(custom_datatype_server custom_datatype/server_types_custom.c)
|
||||
|
||||
if(UA_ENABLE_SUBSCRIPTIONS_EVENTS)
|
||||
add_example(server_events_random events/server_random_events.c)
|
||||
add_example(client_event_filter events/client_eventfilter.c)
|
||||
if(UA_ENABLE_PARSING)
|
||||
add_example(client_event_filter_queries events/client_filter_queries.c)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(UA_ENABLE_JSON_ENCODING)
|
||||
add_example(server_file_based_config file_based_server/server_file_based_config.c)
|
||||
add_example(server_json_config server_json_config.c)
|
||||
endif()
|
||||
|
||||
if(UA_ENABLE_HISTORIZING)
|
||||
@ -171,7 +140,9 @@ if(UA_ENABLE_HISTORIZING)
|
||||
add_example(tutorial_server_historicaldata_circular tutorial_server_historicaldata_circular.c)
|
||||
endif()
|
||||
|
||||
if(UA_ENABLE_ENCRYPTION OR UA_ENABLE_ENCRYPTION STREQUAL "MBEDTLS" OR UA_ENABLE_ENCRYPTION STREQUAL "OPENSSL")
|
||||
if(UA_ENABLE_ENCRYPTION OR
|
||||
UA_ENABLE_ENCRYPTION STREQUAL "MBEDTLS" OR
|
||||
UA_ENABLE_ENCRYPTION STREQUAL "OPENSSL")
|
||||
add_example(server_encryption encryption/server_encryption.c)
|
||||
add_example(client_encryption encryption/client_encryption.c)
|
||||
target_include_directories(server_encryption PRIVATE "${PROJECT_SOURCE_DIR}/examples")
|
||||
@ -184,19 +155,13 @@ if(UA_ENABLE_ENCRYPTION OR UA_ENABLE_ENCRYPTION STREQUAL "MBEDTLS" OR UA_ENABLE_
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if (NOT (BUILD_SHARED_LIBS AND WIN32))
|
||||
add_example(custom_datatype_client custom_datatype/client_types_custom.c)
|
||||
add_example(custom_datatype_server custom_datatype/server_types_custom.c)
|
||||
else()
|
||||
MESSAGE(WARNING "Can't build custom datatype examples on WIN32 when BUILD_SHARED_LIBS enabled. Skipping
|
||||
custom_datatype_client and custom_datatype_server!")
|
||||
endif()
|
||||
|
||||
if(UA_ENABLE_NODEMANAGEMENT)
|
||||
add_example(access_control_server access_control/server_access_control.c)
|
||||
add_example(access_control_client access_control/client_access_control.c)
|
||||
if(UA_ENABLE_ENCRYPTION OR UA_ENABLE_ENCRYPTION STREQUAL "MBEDTLS" OR UA_ENABLE_ENCRYPTION STREQUAL "OPENSSL")
|
||||
add_example(access_control_client_encrypt access_control_encrypt/client_access_control_encrypt.c)
|
||||
if(UA_ENABLE_ENCRYPTION OR
|
||||
UA_ENABLE_ENCRYPTION STREQUAL "MBEDTLS" OR
|
||||
UA_ENABLE_ENCRYPTION STREQUAL "OPENSSL")
|
||||
add_example(access_control_client_encrypt access_control/client_access_control_encrypt.c)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
@ -208,6 +173,7 @@ if(UA_ENABLE_DISCOVERY_MULTICAST)
|
||||
endif()
|
||||
|
||||
add_subdirectory(nodeset)
|
||||
|
||||
####################
|
||||
# Example PubSub #
|
||||
####################
|
||||
@ -215,19 +181,23 @@ add_subdirectory(nodeset)
|
||||
if(UA_ENABLE_PUBSUB)
|
||||
add_example(tutorial_pubsub_connection pubsub/tutorial_pubsub_connection.c)
|
||||
add_example(tutorial_pubsub_publish pubsub/tutorial_pubsub_publish.c)
|
||||
add_example(tutorial_pubsub_subscribe pubsub/tutorial_pubsub_subscribe.c)
|
||||
add_example(server_pubsub_publish_on_demand pubsub/server_pubsub_publisher_on_demand.c)
|
||||
add_example(server_pubsub_publisher_iop pubsub/server_pubsub_publisher_iop.c)
|
||||
if(NOT WIN32)
|
||||
add_example(server_pubsub_publish_rt_level pubsub_realtime/server_pubsub_publisher_rt_level.c)
|
||||
add_example(server_pubsub_subscribe_rt_level pubsub_realtime/server_pubsub_subscriber_rt_level.c)
|
||||
add_example(server_pubsub_rt_information_model pubsub_realtime/server_pubsub_rt_field_information_model.c)
|
||||
endif()
|
||||
add_example(tutorial_pubsub_subscribe pubsub/tutorial_pubsub_subscribe.c)
|
||||
if (BUILD_SHARED_LIBS)
|
||||
message(WARNING "Build option BUILD_SHARED_LIBS not supported for standalone subscriber and realtime examples. Skipping these examples.")
|
||||
else (NOT BUILD_SHARED_LIBS)
|
||||
add_example(pubsub_subscribe_standalone_dataset pubsub/pubsub_subscribe_standalone_dataset.c)
|
||||
add_example(pubsub_subscribe_standalone_dataset pubsub/pubsub_subscribe_standalone_dataset.c)
|
||||
|
||||
add_example(server_pubsub_publish_rt_offsets pubsub_realtime/server_pubsub_publish_rt_offsets.c)
|
||||
add_example(server_pubsub_subscribe_rt_offsets pubsub_realtime/server_pubsub_subscribe_rt_offsets.c)
|
||||
|
||||
if(UA_ARCHITECTURE_POSIX)
|
||||
add_example(server_pubsub_publish_rt_state_machine
|
||||
pubsub_realtime/server_pubsub_publish_rt_state_machine.c)
|
||||
target_link_libraries(server_pubsub_publish_rt_state_machine "rt")
|
||||
add_example(server_pubsub_subscribe_rt_state_machine
|
||||
pubsub_realtime/server_pubsub_subscribe_rt_state_machine.c)
|
||||
target_link_libraries(server_pubsub_subscribe_rt_state_machine "rt")
|
||||
endif()
|
||||
|
||||
if(UA_ENABLE_ENCRYPTION_MBEDTLS)
|
||||
add_example(pubsub_publish_encrypted pubsub/pubsub_publish_encrypted.c)
|
||||
add_example(pubsub_subscribe_encrypted pubsub/pubsub_subscribe_encrypted.c)
|
||||
@ -236,8 +206,10 @@ if(UA_ENABLE_PUBSUB)
|
||||
add_example(pubsub_subscribe_encrypted_tpm pubsub/pubsub_subscribe_encrypted_tpm.c)
|
||||
endif()
|
||||
if(UA_ENABLE_TPM2_KEYSTORE)
|
||||
add_example(pubsub_publish_encrypted_tpm_keystore pubsub/pubsub_publish_encrypted_tpm_keystore.c)
|
||||
add_example(pubsub_subscribe_encrypted_tpm_keystore pubsub/pubsub_subscribe_encrypted_tpm_keystore.c)
|
||||
add_example(pubsub_publish_encrypted_tpm_keystore
|
||||
pubsub/pubsub_publish_encrypted_tpm_keystore.c)
|
||||
add_example(pubsub_subscribe_encrypted_tpm_keystore
|
||||
pubsub/pubsub_subscribe_encrypted_tpm_keystore.c)
|
||||
target_link_libraries(pubsub_publish_encrypted_tpm_keystore tpm2_pkcs11 ssl crypto)
|
||||
target_link_libraries(pubsub_subscribe_encrypted_tpm_keystore tpm2_pkcs11 ssl crypto)
|
||||
endif()
|
||||
@ -252,10 +224,7 @@ if(UA_ENABLE_PUBSUB)
|
||||
endif()
|
||||
|
||||
if(UA_ENABLE_PUBSUB_FILE_CONFIG)
|
||||
# TODO: This example accesses a private API
|
||||
add_example(server_pubsub_file_configuration pubsub/server_pubsub_file_configuration.c)
|
||||
target_include_directories(server_pubsub_file_configuration PUBLIC "${PROJECT_SOURCE_DIR}/../src/pubsub")
|
||||
target_include_directories(server_pubsub_file_configuration PUBLIC "${PROJECT_SOURCE_DIR}/../deps")
|
||||
endif()
|
||||
|
||||
add_example(server_pubsub_subscribe_custom_monitoring
|
||||
@ -265,6 +234,7 @@ endif()
|
||||
###########################
|
||||
# Nodeser Loader Examples #
|
||||
###########################
|
||||
|
||||
if(UA_ENABLE_NODESETLOADER)
|
||||
add_subdirectory(nodeset_loader)
|
||||
endif()
|
||||
|
@ -46,8 +46,6 @@ int main(int argc, char* argv[]) {
|
||||
UA_Client *client = UA_Client_new();
|
||||
UA_ClientConfig *config = UA_Client_getConfig(client);
|
||||
config->securityMode = UA_MESSAGESECURITYMODE_SIGNANDENCRYPT;
|
||||
UA_String_clear(&config->clientDescription.applicationUri);
|
||||
config->clientDescription.applicationUri = UA_STRING_ALLOC("urn:open62541.server.application");
|
||||
UA_ClientConfig_setDefaultEncryption(config, certificate, privateKey,
|
||||
trustList, trustListSize,
|
||||
revocationList, revocationListSize);
|
@ -153,13 +153,6 @@ int main(int argc, char *argv[]) {
|
||||
UA_ClientConfig_setDefault(cc);
|
||||
#endif
|
||||
|
||||
/* The application URI must be the same as the one in the certificate.
|
||||
* The script for creating a self-created certificate generates a certificate
|
||||
* with the Uri specified below.*/
|
||||
UA_ApplicationDescription_clear(&cc->clientDescription);
|
||||
cc->clientDescription.applicationUri = UA_STRING_ALLOC("urn:open62541.server.application");
|
||||
cc->clientDescription.applicationType = UA_APPLICATIONTYPE_CLIENT;
|
||||
|
||||
/* Connect to the server */
|
||||
UA_StatusCode retval = UA_STATUSCODE_GOOD;
|
||||
if(username) {
|
||||
|
@ -50,6 +50,55 @@ subscriptionInactivityCallback (UA_Client *client, UA_UInt32 subId, void *subCon
|
||||
UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "Inactivity for subscription %u", subId);
|
||||
}
|
||||
|
||||
static void
|
||||
monCallback(UA_Client *client, void *userdata,
|
||||
UA_UInt32 requestId, void *r) {
|
||||
UA_CreateMonitoredItemsResponse *monResponse = (UA_CreateMonitoredItemsResponse *)r;
|
||||
if(0 < monResponse->resultsSize && monResponse->results[0].statusCode == UA_STATUSCODE_GOOD)
|
||||
{
|
||||
UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
|
||||
"Monitoring UA_NS0ID_SERVER_SERVERSTATUS_CURRENTTIME', id %u",
|
||||
monResponse->results[0].monitoredItemId);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
createSubscriptionCallback(UA_Client *client, void *userdata, UA_UInt32 requestId, void *r)
|
||||
{
|
||||
UA_CreateSubscriptionResponse *response = (UA_CreateSubscriptionResponse *)r;
|
||||
if (response->subscriptionId == 0)
|
||||
UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
|
||||
"response->subscriptionId == 0, %u", response->subscriptionId);
|
||||
else if (response->responseHeader.serviceResult != UA_STATUSCODE_GOOD)
|
||||
UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
|
||||
"Create subscription failed, serviceResult %u",
|
||||
response->responseHeader.serviceResult);
|
||||
else
|
||||
{
|
||||
UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
|
||||
"Create subscription succeeded, id %u",
|
||||
response->subscriptionId);
|
||||
|
||||
/* Add a MonitoredItem */
|
||||
UA_NodeId currentTime =
|
||||
UA_NODEID_NUMERIC(0, UA_NS0ID_SERVER_SERVERSTATUS_CURRENTTIME);
|
||||
UA_CreateMonitoredItemsRequest req;
|
||||
UA_CreateMonitoredItemsRequest_init(&req);
|
||||
UA_MonitoredItemCreateRequest monRequest =
|
||||
UA_MonitoredItemCreateRequest_default(currentTime);
|
||||
req.itemsToCreate = &monRequest;
|
||||
req.itemsToCreateSize = 1;
|
||||
req.subscriptionId = response->subscriptionId;
|
||||
|
||||
UA_Client_DataChangeNotificationCallback dataChangeNotificationCallback[1] = { handler_currentTimeChanged };
|
||||
UA_StatusCode retval =
|
||||
UA_Client_MonitoredItems_createDataChanges_async(client, req, NULL, dataChangeNotificationCallback, NULL, monCallback, NULL, NULL);
|
||||
if (retval != UA_STATUSCODE_GOOD)
|
||||
UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
|
||||
"UA_Client_MonitoredItems_createDataChanges_async ", UA_StatusCode_name(retval));
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
stateCallback(UA_Client *client, UA_SecureChannelState channelState,
|
||||
UA_SessionState sessionState, UA_StatusCode recoveryStatus) {
|
||||
@ -76,28 +125,12 @@ stateCallback(UA_Client *client, UA_SecureChannelState channelState,
|
||||
/* A new session was created. We need to create the subscription. */
|
||||
/* Create a subscription */
|
||||
UA_CreateSubscriptionRequest request = UA_CreateSubscriptionRequest_default();
|
||||
UA_CreateSubscriptionResponse response =
|
||||
UA_Client_Subscriptions_create(client, request, NULL, NULL, deleteSubscriptionCallback);
|
||||
if(response.responseHeader.serviceResult == UA_STATUSCODE_GOOD)
|
||||
UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
|
||||
"Create subscription succeeded, id %u",
|
||||
response.subscriptionId);
|
||||
else
|
||||
return;
|
||||
|
||||
/* Add a MonitoredItem */
|
||||
UA_NodeId currentTimeNode = UA_NS0ID(SERVER_SERVERSTATUS_CURRENTTIME);
|
||||
UA_MonitoredItemCreateRequest monRequest =
|
||||
UA_MonitoredItemCreateRequest_default(currentTimeNode);
|
||||
|
||||
UA_MonitoredItemCreateResult monResponse =
|
||||
UA_Client_MonitoredItems_createDataChange(client, response.subscriptionId,
|
||||
UA_TIMESTAMPSTORETURN_BOTH, monRequest,
|
||||
NULL, handler_currentTimeChanged, NULL);
|
||||
if(monResponse.statusCode == UA_STATUSCODE_GOOD)
|
||||
UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
|
||||
"Monitoring UA_NS0ID_SERVER_SERVERSTATUS_CURRENTTIME', id %u",
|
||||
monResponse.monitoredItemId);
|
||||
UA_StatusCode retval =
|
||||
UA_Client_Subscriptions_create_async(client, request, NULL, NULL, deleteSubscriptionCallback,
|
||||
createSubscriptionCallback, NULL, NULL);
|
||||
if (retval != UA_STATUSCODE_GOOD)
|
||||
UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
|
||||
"UA_Client_Subscriptions_create_async ", UA_StatusCode_name(retval));
|
||||
}
|
||||
break;
|
||||
case UA_SESSIONSTATE_CLOSED:
|
||||
|
@ -12,6 +12,8 @@
|
||||
#define STRING_BUFFER_SIZE 20
|
||||
|
||||
int main(void) {
|
||||
setupCustomTypes();
|
||||
|
||||
/* Make your custom datatype known to the stack */
|
||||
UA_DataType types[4];
|
||||
types[0] = PointType;
|
||||
|
@ -1,6 +1,10 @@
|
||||
/* This work is licensed under a Creative Commons CCZero 1.0 Universal License.
|
||||
* See http://creativecommons.org/publicdomain/zero/1.0/ for more information. */
|
||||
|
||||
/* For dynamic linking (with .dll) on Windows, the memory locations from the dll
|
||||
* are not available during compilation. This prevents the use of constant initializers.
|
||||
* So the datatype definitions need to be set up in a method call at runtime. */
|
||||
|
||||
typedef struct {
|
||||
UA_Float x;
|
||||
UA_Float y;
|
||||
@ -17,48 +21,8 @@ typedef struct {
|
||||
#define Opt_binary_encoding_id 3
|
||||
#define Uni_binary_encoding_id 4
|
||||
|
||||
|
||||
static UA_DataTypeMember Point_members[3] = {
|
||||
/* x */
|
||||
{
|
||||
UA_TYPENAME("x") /* .memberName */
|
||||
&UA_TYPES[UA_TYPES_FLOAT], /* .memberType */
|
||||
0, /* .padding */
|
||||
false, /* .isArray */
|
||||
false /* .isOptional */
|
||||
},
|
||||
/* y */
|
||||
{
|
||||
UA_TYPENAME("y") /* .memberName */
|
||||
&UA_TYPES[UA_TYPES_FLOAT], /* .memberType */
|
||||
Point_padding_y, /* .padding */
|
||||
false, /* .isArray */
|
||||
false /* .isOptional */
|
||||
},
|
||||
/* z */
|
||||
{
|
||||
UA_TYPENAME("z") /* .memberName */
|
||||
&UA_TYPES[UA_TYPES_FLOAT], /* .memberType */
|
||||
Point_padding_z, /* .padding */
|
||||
false, /* .isArray */
|
||||
false /* .isOptional */
|
||||
}
|
||||
};
|
||||
|
||||
static const UA_DataType PointType = {
|
||||
UA_TYPENAME("Point") /* .tyspeName */
|
||||
{1, UA_NODEIDTYPE_NUMERIC, {4242}}, /* .typeId */
|
||||
{1, UA_NODEIDTYPE_NUMERIC, {Point_binary_encoding_id}}, /* .binaryEncodingId, the numeric
|
||||
identifier used on the wire (the
|
||||
namespaceindex is from .typeId) */
|
||||
sizeof(Point), /* .memSize */
|
||||
UA_DATATYPEKIND_STRUCTURE, /* .typeKind */
|
||||
true, /* .pointerFree */
|
||||
false, /* .overlayable (depends on endianness and
|
||||
the absence of padding) */
|
||||
3, /* .membersSize */
|
||||
Point_members
|
||||
};
|
||||
static UA_DataTypeMember Point_members[3];
|
||||
static UA_DataType PointType;
|
||||
|
||||
/* The datatype description for the Measurement-Series datatype (Array Example)*/
|
||||
typedef struct {
|
||||
@ -67,38 +31,8 @@ typedef struct {
|
||||
UA_Float *measurement;
|
||||
} Measurements;
|
||||
|
||||
static UA_DataTypeMember Measurements_members[2] = {
|
||||
{
|
||||
UA_TYPENAME("Measurement description") /* .memberName */
|
||||
&UA_TYPES[UA_TYPES_STRING], /* .memberType */
|
||||
0, /* .padding */
|
||||
false, /* .isArray */
|
||||
false /* .isOptional */
|
||||
},
|
||||
{
|
||||
UA_TYPENAME("Measurements") /* .memberName */
|
||||
&UA_TYPES[UA_TYPES_FLOAT], /* .memberType */
|
||||
0, /* .padding */
|
||||
true, /* .isArray */
|
||||
false /* .isOptional */
|
||||
}
|
||||
};
|
||||
|
||||
static const UA_DataType MeasurementType = {
|
||||
UA_TYPENAME("Measurement") /* .typeName */
|
||||
{1, UA_NODEIDTYPE_NUMERIC, {4443}}, /* .typeId */
|
||||
{1, UA_NODEIDTYPE_NUMERIC, {Measurement_binary_encoding_id}}, /* .binaryEncodingId, the numeric
|
||||
identifier used on the wire (the
|
||||
namespaceindex is from .typeId) */
|
||||
sizeof(Measurements), /* .memSize */
|
||||
UA_DATATYPEKIND_STRUCTURE, /* .typeKind */
|
||||
false, /* .pointerFree */
|
||||
false, /* .overlayable (depends on endianness and
|
||||
the absence of padding) */
|
||||
2, /* .membersSize */
|
||||
Measurements_members
|
||||
};
|
||||
|
||||
static UA_DataTypeMember Measurements_members[2];
|
||||
static UA_DataType MeasurementType;
|
||||
|
||||
/* The datatype description for the Opt datatype (Structure with optional fields example)*/
|
||||
typedef struct {
|
||||
@ -107,50 +41,15 @@ typedef struct {
|
||||
UA_Float *c;
|
||||
} Opt;
|
||||
|
||||
static UA_DataTypeMember Opt_members[3] = {
|
||||
/* a */
|
||||
{
|
||||
UA_TYPENAME("a") /* .memberName */
|
||||
&UA_TYPES[UA_TYPES_INT16], /* .memberType */
|
||||
0, /* .padding */
|
||||
false, /* .isArray */
|
||||
false /* .isOptional */
|
||||
},
|
||||
/* b */
|
||||
{
|
||||
UA_TYPENAME("b") /* .memberName */
|
||||
&UA_TYPES[UA_TYPES_FLOAT], /* .memberType */
|
||||
offsetof(Opt,b) - offsetof(Opt,a) - sizeof(UA_Int16), /* .padding */
|
||||
false, /* .isArray */
|
||||
true /* .isOptional */
|
||||
},
|
||||
/* c */
|
||||
{
|
||||
UA_TYPENAME("c") /* .memberName */
|
||||
&UA_TYPES[UA_TYPES_FLOAT], /* .memberType */
|
||||
offsetof(Opt,c) - offsetof(Opt,b) - sizeof(void *), /* .padding */
|
||||
false, /* .isArray */
|
||||
true /* .isOptional */
|
||||
}
|
||||
};
|
||||
|
||||
static const UA_DataType OptType = {
|
||||
UA_TYPENAME("Opt") /* .typeName */
|
||||
{1, UA_NODEIDTYPE_NUMERIC, {4644}}, /* .typeId */
|
||||
{1, UA_NODEIDTYPE_NUMERIC, {Opt_binary_encoding_id}}, /* .binaryEncodingId, the numeric
|
||||
identifier used on the wire (the
|
||||
namespaceindex is from .typeId) */
|
||||
sizeof(Opt), /* .memSize */
|
||||
UA_DATATYPEKIND_OPTSTRUCT, /* .typeKind */
|
||||
false, /* .pointerFree */
|
||||
false, /* .overlayable (depends on endianness and
|
||||
the absence of padding) */
|
||||
3, /* .membersSize */
|
||||
Opt_members
|
||||
};
|
||||
static UA_DataTypeMember Opt_members[3];
|
||||
static UA_DataType OptType;
|
||||
|
||||
/* The datatype description for the Uni datatype (Union example) */
|
||||
typedef enum {UA_UNISWITCH_NONE = 0, UA_UNISWITCH_OPTIONA = 1, UA_UNISWITCH_OPTIONB = 2} UA_UniSwitch;
|
||||
typedef enum {
|
||||
UA_UNISWITCH_NONE = 0,
|
||||
UA_UNISWITCH_OPTIONA = 1,
|
||||
UA_UNISWITCH_OPTIONB = 2
|
||||
} UA_UniSwitch;
|
||||
|
||||
typedef struct {
|
||||
UA_UniSwitch switchField;
|
||||
@ -160,34 +59,153 @@ typedef struct {
|
||||
} fields;
|
||||
} Uni;
|
||||
|
||||
static UA_DataTypeMember Uni_members[2] = {
|
||||
{
|
||||
static UA_DataTypeMember Uni_members[2];
|
||||
static UA_DataType UniType;
|
||||
|
||||
static void setupCustomTypes(void) {
|
||||
/* x */
|
||||
Point_members[0] = (UA_DataTypeMember){
|
||||
UA_TYPENAME("x") /* .memberName */
|
||||
&UA_TYPES[UA_TYPES_FLOAT], /* .memberType */
|
||||
0, /* .padding */
|
||||
false, /* .isArray */
|
||||
false /* .isOptional */
|
||||
};
|
||||
|
||||
/* y */
|
||||
Point_members[1] = (UA_DataTypeMember){
|
||||
UA_TYPENAME("y") /* .memberName */
|
||||
&UA_TYPES[UA_TYPES_FLOAT], /* .memberType */
|
||||
Point_padding_y, /* .padding */
|
||||
false, /* .isArray */
|
||||
false /* .isOptional */
|
||||
};
|
||||
|
||||
/* z */
|
||||
Point_members[2] = (UA_DataTypeMember){
|
||||
UA_TYPENAME("z") /* .memberName */
|
||||
&UA_TYPES[UA_TYPES_FLOAT], /* .memberType */
|
||||
Point_padding_z, /* .padding */
|
||||
false, /* .isArray */
|
||||
false /* .isOptional */
|
||||
};
|
||||
|
||||
PointType = (UA_DataType){
|
||||
UA_TYPENAME("Point") /* .typeName */
|
||||
{1, UA_NODEIDTYPE_NUMERIC, { 4242 }}, /* .typeId */
|
||||
{ 1, UA_NODEIDTYPE_NUMERIC, { Point_binary_encoding_id } }, /* .binaryEncodingId, the numeric
|
||||
identifier used on the wire (the
|
||||
namespaceindex is from .typeId) */
|
||||
sizeof(Point), /* .memSize */
|
||||
UA_DATATYPEKIND_STRUCTURE, /* .typeKind */
|
||||
true, /* .pointerFree */
|
||||
false, /* .overlayable (depends on endianness and
|
||||
the absence of padding) */
|
||||
3, /* .membersSize */
|
||||
Point_members
|
||||
};
|
||||
|
||||
Measurements_members[0] = (UA_DataTypeMember) {
|
||||
UA_TYPENAME("Measurement description") /* .memberName */
|
||||
&UA_TYPES[UA_TYPES_STRING], /* .memberType */
|
||||
0, /* .padding */
|
||||
false, /* .isArray */
|
||||
false /* .isOptional */
|
||||
};
|
||||
|
||||
Measurements_members[1] = (UA_DataTypeMember) {
|
||||
UA_TYPENAME("Measurements") /* .memberName */
|
||||
&UA_TYPES[UA_TYPES_FLOAT], /* .memberType */
|
||||
0, /* .padding */
|
||||
true, /* .isArray */
|
||||
false /* .isOptional */
|
||||
};
|
||||
|
||||
MeasurementType = (UA_DataType) {
|
||||
UA_TYPENAME("Measurement") /* .typeName */
|
||||
{1, UA_NODEIDTYPE_NUMERIC, { 4443 }}, /* .typeId */
|
||||
{ 1, UA_NODEIDTYPE_NUMERIC, { Measurement_binary_encoding_id } }, /* .binaryEncodingId, the numeric
|
||||
identifier used on the wire (the
|
||||
namespaceindex is from .typeId) */
|
||||
sizeof(Measurements), /* .memSize */
|
||||
UA_DATATYPEKIND_STRUCTURE, /* .typeKind */
|
||||
false, /* .pointerFree */
|
||||
false, /* .overlayable (depends on endianness and
|
||||
the absence of padding) */
|
||||
2, /* .membersSize */
|
||||
Measurements_members
|
||||
};
|
||||
|
||||
/* a */
|
||||
Opt_members[0] = (UA_DataTypeMember) {
|
||||
UA_TYPENAME("a") /* .memberName */
|
||||
&UA_TYPES[UA_TYPES_INT16], /* .memberType */
|
||||
0, /* .padding */
|
||||
false, /* .isArray */
|
||||
false /* .isOptional */
|
||||
};
|
||||
|
||||
/* b */
|
||||
Opt_members[1] = (UA_DataTypeMember) {
|
||||
UA_TYPENAME("b") /* .memberName */
|
||||
&UA_TYPES[UA_TYPES_FLOAT], /* .memberType */
|
||||
offsetof(Opt, b) - offsetof(Opt, a) - sizeof(UA_Int16), /* .padding */
|
||||
false, /* .isArray */
|
||||
true /* .isOptional */
|
||||
};
|
||||
|
||||
/* c */
|
||||
Opt_members[2] = (UA_DataTypeMember) {
|
||||
UA_TYPENAME("c") /* .memberName */
|
||||
&UA_TYPES[UA_TYPES_FLOAT], /* .memberType */
|
||||
offsetof(Opt, c) - offsetof(Opt, b) - sizeof(void *), /* .padding */
|
||||
false, /* .isArray */
|
||||
true /* .isOptional */
|
||||
};
|
||||
|
||||
OptType = (UA_DataType) {
|
||||
UA_TYPENAME("Opt") /* .typeName */
|
||||
{1, UA_NODEIDTYPE_NUMERIC, { 4644 }}, /* .typeId */
|
||||
{ 1, UA_NODEIDTYPE_NUMERIC, { Opt_binary_encoding_id } }, /* .binaryEncodingId, the numeric
|
||||
identifier used on the wire (the
|
||||
namespaceindex is from .typeId) */
|
||||
sizeof(Opt), /* .memSize */
|
||||
UA_DATATYPEKIND_OPTSTRUCT, /* .typeKind */
|
||||
false, /* .pointerFree */
|
||||
false, /* .overlayable (depends on endianness and
|
||||
the absence of padding) */
|
||||
3, /* .membersSize */
|
||||
Opt_members
|
||||
};
|
||||
|
||||
Uni_members[0] = (UA_DataTypeMember) {
|
||||
UA_TYPENAME("optionA") /* .memberName */
|
||||
&UA_TYPES[UA_TYPES_DOUBLE], /* .memberType */
|
||||
offsetof(Uni, fields.optionA), /* .padding */
|
||||
(UA_Byte)(offsetof(Uni, fields.optionA) - 0), /* .padding */
|
||||
false, /* .isArray */
|
||||
false /* .isOptional */
|
||||
},
|
||||
{
|
||||
};
|
||||
|
||||
Uni_members[1] = (UA_DataTypeMember) {
|
||||
UA_TYPENAME("optionB") /* .memberName */
|
||||
&UA_TYPES[UA_TYPES_STRING], /* .memberType */
|
||||
offsetof(Uni, fields.optionB), /* .padding */
|
||||
(UA_Byte)(offsetof(Uni, fields.optionB) - 0), /* .padding */
|
||||
false, /* .isArray */
|
||||
false /* .isOptional */
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
static const UA_DataType UniType = {
|
||||
UA_TYPENAME("Uni") /* .typeName */
|
||||
{1, UA_NODEIDTYPE_NUMERIC, {4845}}, /* .typeId */
|
||||
{1, UA_NODEIDTYPE_NUMERIC, {Uni_binary_encoding_id}}, /* .binaryEncodingId, the numeric
|
||||
identifier used on the wire (the
|
||||
namespaceindex is from .typeId) */
|
||||
sizeof(Uni), /* .memSize */
|
||||
UA_DATATYPEKIND_UNION, /* .typeKind */
|
||||
false, /* .pointerFree */
|
||||
false, /* .overlayable (depends on endianness and
|
||||
the absence of padding) */
|
||||
2, /* .membersSize */
|
||||
Uni_members
|
||||
};
|
||||
UniType = (UA_DataType) {
|
||||
UA_TYPENAME("Uni") /* .typeName */
|
||||
{1, UA_NODEIDTYPE_NUMERIC, { 4845 }}, /* .typeId */
|
||||
{ 1, UA_NODEIDTYPE_NUMERIC, { Uni_binary_encoding_id } }, /* .binaryEncodingId, the numeric
|
||||
identifier used on the wire (the
|
||||
namespaceindex is from .typeId) */
|
||||
sizeof(Uni), /* .memSize */
|
||||
UA_DATATYPEKIND_UNION, /* .typeKind */
|
||||
false, /* .pointerFree */
|
||||
false, /* .overlayable (depends on endianness and
|
||||
the absence of padding) */
|
||||
2, /* .membersSize */
|
||||
Uni_members
|
||||
};
|
||||
}
|
||||
|
@ -251,6 +251,8 @@ int main(void) {
|
||||
UA_ServerConfig *config = UA_Server_getConfig(server);
|
||||
UA_ServerConfig_setDefault(config);
|
||||
|
||||
setupCustomTypes();
|
||||
|
||||
/* Make your custom datatype known to the stack */
|
||||
UA_DataType *types = (UA_DataType*)UA_malloc(4 * sizeof(UA_DataType));
|
||||
UA_DataTypeMember *pointMembers = (UA_DataTypeMember*)UA_malloc(sizeof(UA_DataTypeMember) * 3);
|
||||
|
@ -240,8 +240,10 @@ int main(int argc, char **argv) {
|
||||
|
||||
config->mdnsConfig.mdnsServerName = UA_String_fromChars("Sample-Multicast-Server");
|
||||
|
||||
//setting custom outbound interface
|
||||
#ifdef UA_ENABLE_DISCOVERY_MULTICAST_MDNSD
|
||||
//setting custom outbound interface for libmdnsd
|
||||
config->mdnsInterfaceIP = UA_String_fromChars("0.0.0.0");
|
||||
#endif
|
||||
|
||||
// See http://www.opcfoundation.org/UA/schemas/1.03/ServerCapabilities.csv
|
||||
// For a LDS server, you should only indicate the LDS capability.
|
||||
|
@ -88,10 +88,10 @@ Create encryption and signing key in both the server and client node filesystems
|
||||
cd open62541/tools/tpm_keystore/
|
||||
|
||||
In server,
|
||||
python3 ../certs/create_self-signed.py -u urn:open62541.server.application
|
||||
python3 ../certs/create_self-signed.py -u urn:open62541.unconfigured.application -c server
|
||||
|
||||
In client,
|
||||
python3 ../certs/create_self-signed.py -u urn:unconfigured:application -c client
|
||||
python3 ../certs/create_self-signed.py -u urn:open62541.unconfigured.application -c client
|
||||
|
||||
Seal the encryption and signing key files using the key available in TPM
|
||||
gcc cert_encrypt_tpm.c -o cert_encrypt_tpm -ltpm2_pkcs11 -lssl -lcrypto
|
||||
|
@ -45,11 +45,9 @@ int main(int argc, char* argv[]) {
|
||||
UA_Client *client = UA_Client_new();
|
||||
UA_ClientConfig *cc = UA_Client_getConfig(client);
|
||||
cc->securityMode = UA_MESSAGESECURITYMODE_SIGNANDENCRYPT;
|
||||
UA_String_clear(&cc->clientDescription.applicationUri);
|
||||
cc->clientDescription.applicationUri = UA_STRING_ALLOC("urn:open62541.server.application");
|
||||
UA_StatusCode retval = UA_ClientConfig_setDefaultEncryption(cc, certificate, privateKey,
|
||||
trustList, trustListSize,
|
||||
revocationList, revocationListSize);
|
||||
trustList, trustListSize,
|
||||
revocationList, revocationListSize);
|
||||
if(retval != UA_STATUSCODE_GOOD) {
|
||||
UA_LOG_FATAL(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
|
||||
"Failed to set encryption." );
|
||||
|
@ -12,6 +12,7 @@
|
||||
#include <open62541/plugin/securitypolicy.h>
|
||||
#include <open62541/server.h>
|
||||
#include <open62541/server_config_default.h>
|
||||
#include <open62541/plugin/certificategroup_default.h>
|
||||
|
||||
#include <signal.h>
|
||||
#include <stdlib.h>
|
||||
@ -46,7 +47,7 @@ int main(int argc, char* argv[]) {
|
||||
UA_UInt32 lenSubject = 3;
|
||||
UA_String subjectAltName[2]= {
|
||||
UA_STRING_STATIC("DNS:localhost"),
|
||||
UA_STRING_STATIC("URI:urn:open62541.server.application")
|
||||
UA_STRING_STATIC("URI:urn:open62541.unconfigured.application")
|
||||
};
|
||||
UA_UInt32 lenSubjectAltName = 2;
|
||||
UA_KeyValueMap *kvm = UA_KeyValueMap_new();
|
||||
@ -93,6 +94,13 @@ int main(int argc, char* argv[]) {
|
||||
issuerList, issuerListSize,
|
||||
revocationList, revocationListSize);
|
||||
|
||||
/* Accept all certificates */
|
||||
config->secureChannelPKI.clear(&config->secureChannelPKI);
|
||||
UA_CertificateGroup_AcceptAll(&config->secureChannelPKI);
|
||||
|
||||
config->sessionPKI.clear(&config->sessionPKI);
|
||||
UA_CertificateGroup_AcceptAll(&config->sessionPKI);
|
||||
|
||||
UA_ByteString_clear(&certificate);
|
||||
UA_ByteString_clear(&privateKey);
|
||||
for(size_t i = 0; i < trustListSize; i++)
|
||||
|
@ -9,7 +9,6 @@
|
||||
#include <open62541/client_config_default.h>
|
||||
#include <open62541/client_subscriptions.h>
|
||||
#include <open62541/plugin/log_stdout.h>
|
||||
#include <open62541/eventfilter_parser_examples.h>
|
||||
|
||||
#include <signal.h>
|
||||
#include <stdio.h>
|
||||
@ -18,94 +17,9 @@
|
||||
* This Tutorial repeats the client_eventfilter.c tutorial,
|
||||
* however the filter are created based on the Query Language for Eventfilter
|
||||
*/
|
||||
static void
|
||||
check_eventfilter(UA_EventFilter *filter, UA_Boolean print_filter){
|
||||
UA_EventFilter empty_filter;
|
||||
UA_EventFilter_init(&empty_filter);
|
||||
if(memcmp(&empty_filter, filter, sizeof(UA_EventFilter)) == 0){
|
||||
UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
|
||||
"Failed to parse the EventFilter");
|
||||
}
|
||||
else{
|
||||
UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
|
||||
"EventFilter parsing succeeded");
|
||||
if(print_filter){
|
||||
UA_String out = UA_STRING_NULL;
|
||||
UA_print(filter, &UA_TYPES[UA_TYPES_EVENTFILTER], &out);
|
||||
printf("%.*s\n", (int)out.length, out.data);
|
||||
UA_String_clear(&out);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static UA_Boolean running = true;
|
||||
|
||||
static UA_StatusCode
|
||||
read_queries(UA_UInt16 filterSelection, UA_EventFilter *filter){
|
||||
switch(filterSelection){
|
||||
case 0 : {
|
||||
char *inp = "SELECT /Message, /0:Severity /EventType "
|
||||
"WHERE OR($ref_1, $ref_2) "
|
||||
"FOR "
|
||||
"$ref2 := OFTYPE(ns=1;i=5003) AND "
|
||||
"$ref1 := (OFTYPE i=3035)";
|
||||
|
||||
UA_ByteString case_0 = UA_String_fromChars(inp);
|
||||
UA_EventFilter_parse(filter, case_0, NULL);
|
||||
check_eventfilter(filter, UA_FALSE);
|
||||
UA_ByteString_clear(&case_0);
|
||||
break;
|
||||
}
|
||||
case 1 : {
|
||||
/*query can be found in the file example_queries/case_1 */
|
||||
UA_ByteString case_1 = UA_String_fromChars(CASE_1);
|
||||
UA_EventFilter_parse(filter, case_1, NULL);
|
||||
check_eventfilter(filter, UA_FALSE);
|
||||
UA_ByteString_clear(&case_1);
|
||||
break;
|
||||
}
|
||||
case 2 : {
|
||||
/*query can be found in the file example_queries/case_2 */
|
||||
UA_ByteString case_2 = UA_String_fromChars(CASE_2);
|
||||
UA_EventFilter_parse(filter, case_2, NULL);
|
||||
check_eventfilter(filter, UA_FALSE);
|
||||
UA_ByteString_clear(&case_2);
|
||||
break;
|
||||
}
|
||||
case 3 : {
|
||||
char *inp = "SELECT /Message, /Severity, /EventType "
|
||||
"WHERE AND(OFTYPE(ns=1;i=5001), $abc) "
|
||||
"FOR "
|
||||
"$abc := AND($xyz, $uvw) AND "
|
||||
"$xyz := ==(INT64 99, 99) AND "
|
||||
"$uvw := GREATER(i=5000/Severity, 99)";
|
||||
|
||||
UA_ByteString case_3 = UA_String_fromChars(inp);
|
||||
UA_EventFilter_parse(filter, case_3, NULL);
|
||||
check_eventfilter(filter, UA_FALSE);
|
||||
UA_ByteString_clear(&case_3);
|
||||
break;
|
||||
}
|
||||
case 4 : {
|
||||
char *inp = "SELECT /Message, /0:Severity, /EventType "
|
||||
"WHERE "
|
||||
"AND($a, GREATER(i=5000/Severity, $ref)) "
|
||||
"FOR "
|
||||
"$ref := 99 AND "
|
||||
"$a := OFTYPE(ns=1;i=5000)";
|
||||
UA_ByteString case_4 = UA_String_fromChars(inp);
|
||||
UA_EventFilter_parse(filter, case_4, NULL);
|
||||
check_eventfilter(filter, UA_FALSE);
|
||||
UA_ByteString_clear(&case_4);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
UA_EventFilter_clear(filter);
|
||||
return UA_STATUSCODE_BADCONFIGURATIONERROR;
|
||||
}
|
||||
return UA_STATUSCODE_GOOD;
|
||||
}
|
||||
|
||||
static void
|
||||
handler_events_filter(UA_Client *client, UA_UInt32 subId, void *subContext,
|
||||
UA_UInt32 monId, void *monContext,
|
||||
@ -139,14 +53,16 @@ handler_events_filter(UA_Client *client, UA_UInt32 subId, void *subContext,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static UA_StatusCode
|
||||
create_event_filter_with_monitored_item(UA_UInt16 filterSelection, UA_Client *client,
|
||||
create_event_filter_with_monitored_item(UA_Client *client,
|
||||
UA_EventFilter *filter,
|
||||
UA_CreateSubscriptionResponse *response,
|
||||
UA_MonitoredItemCreateResult *result){
|
||||
/* read the eventfilter query string and create the corresponding eventfilter */
|
||||
UA_StatusCode retval = read_queries(filterSelection, filter);
|
||||
|
||||
char *input = "SELECT /Message, /0:Severity, /EventType "
|
||||
"WHERE /Severity >= 100";
|
||||
UA_StatusCode retval = UA_EventFilter_parse(filter, UA_STRING(input), NULL);
|
||||
if(retval != UA_STATUSCODE_GOOD) {
|
||||
UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
|
||||
"Failed to parse the filter query with statuscode %s \n",
|
||||
@ -187,8 +103,8 @@ create_event_filter_with_monitored_item(UA_UInt16 filterSelection, UA_Client *cl
|
||||
return UA_STATUSCODE_BAD;
|
||||
}
|
||||
UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
|
||||
"Monitoring 'Root->Objects->Server', id %u",
|
||||
response->subscriptionId);
|
||||
"Monitoring 'Root/Objects/Server', id %u",
|
||||
response->subscriptionId);
|
||||
monId = result->monitoredItemId;
|
||||
return UA_STATUSCODE_GOOD;
|
||||
}
|
||||
@ -219,7 +135,7 @@ int main(int argc, char *argv[]) {
|
||||
UA_EventFilter_init(&filter);
|
||||
UA_CreateSubscriptionResponse *response = UA_CreateSubscriptionResponse_new();
|
||||
UA_MonitoredItemCreateResult *result = UA_MonitoredItemCreateResult_new();
|
||||
retval = create_event_filter_with_monitored_item(3, client, &filter, response, result);
|
||||
retval = create_event_filter_with_monitored_item(client, &filter, response, result);
|
||||
if(retval == UA_STATUSCODE_GOOD){
|
||||
while(running)
|
||||
UA_Client_run_iterate(client, true);
|
||||
|
@ -1,14 +0,0 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*
|
||||
* Copyright 2023-2024 (c) Fraunhofer IOSB (Author: Florian Düwel)
|
||||
*/
|
||||
#ifndef VERSION_H_IN
|
||||
#define VERSION_H_IN
|
||||
|
||||
|
||||
#define CASE_1 "@CASE_1@"
|
||||
#define CASE_2 "@CASE_2@"
|
||||
|
||||
#endif // VERSION_H_IN
|
@ -1,11 +0,0 @@
|
||||
/* This work is licensed under a Creative Commons CCZero 1.0 Universal License.
|
||||
See http://creativecommons.org/publicdomain/zero/1.0/ for more information.
|
||||
*/
|
||||
|
||||
SELECT
|
||||
PATH "/Message", PATH "/0:Severity", PATH "/EventType"
|
||||
WHERE
|
||||
OR($"ref_1", $"ref_2")
|
||||
FOR
|
||||
$"ref_2":= OFTYPE ns=1;i=5003
|
||||
$"ref_1":= OFTYPE i=3035
|
@ -1,24 +0,0 @@
|
||||
/* This work is licensed under a Creative Commons CCZero 1.0 Universal License.
|
||||
See http://creativecommons.org/publicdomain/zero/1.0/ for more information.
|
||||
*/
|
||||
|
||||
SELECT
|
||||
|
||||
PATH "/Message",
|
||||
PATH "/Severity",
|
||||
PATH "/EventType"
|
||||
|
||||
WHERE
|
||||
OFTYPE ns=1;i=5001
|
||||
|
||||
/*alternative queries to create an identical eventfilter
|
||||
|
||||
1.
|
||||
SELECT PATH "/Message", PATH "/Severity", PATH "/EventType"
|
||||
WHERE
|
||||
$4
|
||||
FOR
|
||||
$4:= OFTYPE ns=1;i=5001
|
||||
|
||||
|
||||
*/
|
@ -1,86 +0,0 @@
|
||||
/* This work is licensed under a Creative Commons CCZero 1.0 Universal License.
|
||||
See http://creativecommons.org/publicdomain/zero/1.0/ for more information.
|
||||
*/
|
||||
|
||||
SELECT
|
||||
|
||||
PATH "/Message", PATH "/Severity", PATH "/EventType"
|
||||
|
||||
WHERE
|
||||
OR(OR(OR(OFTYPE ns=1;i=5002, $4), OR($5, OFTYPE i=3035)), OR($1,$2))
|
||||
|
||||
FOR
|
||||
$1:= OFTYPE $7
|
||||
$2:= OFTYPE $8
|
||||
$4:= OFTYPE ns=1;i=5003
|
||||
$5:= OFTYPE ns=1;i=5004
|
||||
$7:= NODEID ns=1;i=5000
|
||||
$8:= ns=1;i=5001
|
||||
|
||||
|
||||
|
||||
/* alternative queries to create an identical eventfilter
|
||||
1.
|
||||
SELECT
|
||||
PATH "/Message", PATH "/Severity", PATH "/EventType"
|
||||
WHERE
|
||||
OR(OR(OR(OFTYPE ns=1;i=5002, OFTYPE ns=1;i=5003), OR(OFTYPE ns=1;i=5004, OFTYPE ns=0;i=3035)), OR(OFTYPE ns=1;i=5000, OFTYPE ns=1;i=5001))
|
||||
|
||||
2.
|
||||
SELECT
|
||||
PATH "/Message", PATH "/Severity", PATH "/EventType"
|
||||
WHERE
|
||||
OR(OR(OR($3, $4), OR($5, $6)), OR($1,$2))
|
||||
FOR
|
||||
$1:= OFTYPE ns=1;i=5000
|
||||
$2:= OFTYPE ns=1;i=5001
|
||||
$3:= OFTYPE ns=1;i=5002
|
||||
$4:= OFTYPE ns=1;i=5003
|
||||
$5:= OFTYPE ns=1;i=5004
|
||||
$6:= OFTYPE i=3035
|
||||
|
||||
3.
|
||||
SELECT
|
||||
PATH "/Message", PATH "/Severity", PATH "/EventType"
|
||||
WHERE
|
||||
$"test"
|
||||
FOR
|
||||
$"test":= OR(OR(OR(OFTYPE ns=1;i=5002, OFTYPE ns=1;i=5003), OR(OFTYPE ns=1;i=5004, OFTYPE ns=0;i=3035)), OR(OFTYPE ns=1;i=5000, OFTYPE ns=1;i=5001))
|
||||
|
||||
4.
|
||||
SELECT
|
||||
PATH "/Message", PATH "/Severity", PATH "/EventType"
|
||||
WHERE
|
||||
OR($"first branch", $"second branch")
|
||||
FOR
|
||||
$"first branch":= OR($"third branch", $"fourth branch")
|
||||
$"second branch":= OR($1, $2)
|
||||
$"third branch":= OR($3, $4)
|
||||
$"fourth branch":= OR($5, $6)
|
||||
$1:= OFTYPE ns=1;i=5000
|
||||
$2:= OFTYPE ns=1;i=5001
|
||||
$3:= OFTYPE ns=1;i=5002
|
||||
$4:= OFTYPE ns=1;i=5003
|
||||
$5:= OFTYPE $7
|
||||
$6:= OFTYPE $8
|
||||
$7:= ns=1;i=5004
|
||||
$8:= i=3035
|
||||
|
||||
|
||||
5.
|
||||
SELECT
|
||||
|
||||
PATH "/Message", PATH "/Severity", PATH "/EventType"
|
||||
|
||||
WHERE
|
||||
OR(OR(OR(OFTYPE ns=1;i=5002, $4), OR($5, OFTYPE i=3035)), OR($1,$2))
|
||||
|
||||
FOR
|
||||
$1:= OFTYPE $7
|
||||
$2:= OFTYPE $8
|
||||
$4:= OFTYPE ns=1;i=5003
|
||||
$5:= OFTYPE ns=1;i=5004
|
||||
$7:= NODEID ns=1;i=5000
|
||||
$8:= TYPEID ns=1;i=5001 PATH "." ATTRIBUTE 1
|
||||
|
||||
*/
|
@ -1,17 +0,0 @@
|
||||
/* This work is licensed under a Creative Commons CCZero 1.0 Universal License.
|
||||
See http://creativecommons.org/publicdomain/zero/1.0/ for more information.
|
||||
*/
|
||||
|
||||
SELECT
|
||||
|
||||
PATH "/Message",
|
||||
PATH "/Severity",
|
||||
PATH "/EventType"
|
||||
|
||||
WHERE
|
||||
AND((OFTYPE ns=1;i=5001), $1)
|
||||
|
||||
FOR
|
||||
$1:= AND($20, $30)
|
||||
$20:= 99 == Int64 99
|
||||
$30:= TYPEID i=5000 PATH "/Severity" > 99
|
@ -1,17 +0,0 @@
|
||||
/* This work is licensed under a Creative Commons CCZero 1.0 Universal License.
|
||||
See http://creativecommons.org/publicdomain/zero/1.0/ for more information.
|
||||
*/
|
||||
|
||||
SELECT
|
||||
|
||||
PATH "/Message",
|
||||
PATH "/0:Severity",
|
||||
PATH "/EventType"
|
||||
|
||||
WHERE
|
||||
|
||||
AND($4, TYPEID i=5000 PATH "/Severity" GREATERTHAN $"ref")
|
||||
|
||||
FOR
|
||||
$"ref":= 99
|
||||
$4:= OFTYPE ns=1;i=5000
|
@ -135,8 +135,8 @@ addSubscribedVariables (UA_Server *server, UA_NodeId dataSetReaderId) {
|
||||
* received DataSet fields and target Variables in the Subscriber AddressSpace.
|
||||
* The values subscribed from the Publisher are updated in the value field of these variables */
|
||||
/* Create the TargetVariables with respect to DataSetMetaData fields */
|
||||
UA_FieldTargetVariable *targetVars = (UA_FieldTargetVariable *)
|
||||
UA_calloc(readerConfig.dataSetMetaData.fieldsSize, sizeof(UA_FieldTargetVariable));
|
||||
UA_FieldTargetDataType *targetVars = (UA_FieldTargetDataType*)
|
||||
UA_calloc(readerConfig.dataSetMetaData.fieldsSize, sizeof(UA_FieldTargetDataType));
|
||||
for(size_t i = 0; i < readerConfig.dataSetMetaData.fieldsSize; i++) {
|
||||
/* Variable to subscribe data */
|
||||
UA_VariableAttributes vAttr = UA_VariableAttributes_default;
|
||||
@ -152,18 +152,13 @@ addSubscribedVariables (UA_Server *server, UA_NodeId dataSetReaderId) {
|
||||
UA_QUALIFIEDNAME(1, (char *)readerConfig.dataSetMetaData.fields[i].name.data),
|
||||
UA_NS0ID(BASEDATAVARIABLETYPE),
|
||||
vAttr, NULL, &newNode);
|
||||
|
||||
/* For creating Targetvariables */
|
||||
UA_FieldTargetDataType_init(&targetVars[i].targetVariable);
|
||||
targetVars[i].targetVariable.attributeId = UA_ATTRIBUTEID_VALUE;
|
||||
targetVars[i].targetVariable.targetNodeId = newNode;
|
||||
targetVars[i].attributeId = UA_ATTRIBUTEID_VALUE;
|
||||
targetVars[i].targetNodeId = newNode;
|
||||
}
|
||||
|
||||
UA_Server_DataSetReader_createTargetVariables(server, dataSetReaderId,
|
||||
readerConfig.dataSetMetaData.fieldsSize,
|
||||
targetVars);
|
||||
for(size_t i = 0; i < readerConfig.dataSetMetaData.fieldsSize; i++)
|
||||
UA_FieldTargetDataType_clear(&targetVars[i].targetVariable);
|
||||
|
||||
UA_free(targetVars);
|
||||
UA_free(readerConfig.dataSetMetaData.fields);
|
||||
|
@ -142,8 +142,8 @@ addSubscribedVariables (UA_Server *server, UA_NodeId dataSetReaderId) {
|
||||
* received DataSet fields and target Variables in the Subscriber AddressSpace.
|
||||
* The values subscribed from the Publisher are updated in the value field of these variables */
|
||||
/* Create the TargetVariables with respect to DataSetMetaData fields */
|
||||
UA_FieldTargetVariable *targetVars = (UA_FieldTargetVariable *)
|
||||
UA_calloc(readerConfig.dataSetMetaData.fieldsSize, sizeof(UA_FieldTargetVariable));
|
||||
UA_FieldTargetDataType *targetVars = (UA_FieldTargetDataType *)
|
||||
UA_calloc(readerConfig.dataSetMetaData.fieldsSize, sizeof(UA_FieldTargetDataType));
|
||||
for(size_t i = 0; i < readerConfig.dataSetMetaData.fieldsSize; i++) {
|
||||
/* Variable to subscribe data */
|
||||
UA_VariableAttributes vAttr = UA_VariableAttributes_default;
|
||||
@ -160,17 +160,13 @@ addSubscribedVariables (UA_Server *server, UA_NodeId dataSetReaderId) {
|
||||
UA_NS0ID(BASEDATAVARIABLETYPE),
|
||||
vAttr, NULL, &newNode);
|
||||
|
||||
/* For creating Targetvariables */
|
||||
UA_FieldTargetDataType_init(&targetVars[i].targetVariable);
|
||||
targetVars[i].targetVariable.attributeId = UA_ATTRIBUTEID_VALUE;
|
||||
targetVars[i].targetVariable.targetNodeId = newNode;
|
||||
targetVars[i].attributeId = UA_ATTRIBUTEID_VALUE;
|
||||
targetVars[i].targetNodeId = newNode;
|
||||
}
|
||||
|
||||
UA_Server_DataSetReader_createTargetVariables(server, dataSetReaderId,
|
||||
readerConfig.dataSetMetaData.fieldsSize,
|
||||
targetVars);
|
||||
for(size_t i = 0; i < readerConfig.dataSetMetaData.fieldsSize; i++)
|
||||
UA_FieldTargetDataType_clear(&targetVars[i].targetVariable);
|
||||
|
||||
UA_free(targetVars);
|
||||
UA_free(readerConfig.dataSetMetaData.fields);
|
||||
|
@ -178,8 +178,8 @@ addSubscribedVariables (UA_Server *server, UA_NodeId dataSetReaderId) {
|
||||
* received DataSet fields and target Variables in the Subscriber AddressSpace.
|
||||
* The values subscribed from the Publisher are updated in the value field of these variables */
|
||||
/* Create the TargetVariables with respect to DataSetMetaData fields */
|
||||
UA_FieldTargetVariable *targetVars = (UA_FieldTargetVariable *)
|
||||
UA_calloc(readerConfig.dataSetMetaData.fieldsSize, sizeof(UA_FieldTargetVariable));
|
||||
UA_FieldTargetDataType *targetVars = (UA_FieldTargetDataType*)
|
||||
UA_calloc(readerConfig.dataSetMetaData.fieldsSize, sizeof(UA_FieldTargetDataType));
|
||||
for(size_t i = 0; i < readerConfig.dataSetMetaData.fieldsSize; i++) {
|
||||
/* Variable to subscribe data */
|
||||
UA_VariableAttributes vAttr = UA_VariableAttributes_default;
|
||||
@ -196,16 +196,12 @@ addSubscribedVariables (UA_Server *server, UA_NodeId dataSetReaderId) {
|
||||
UA_NS0ID(BASEDATAVARIABLETYPE),
|
||||
vAttr, NULL, &newNode);
|
||||
|
||||
/* For creating Targetvariables */
|
||||
UA_FieldTargetDataType_init(&targetVars[i].targetVariable);
|
||||
targetVars[i].targetVariable.attributeId = UA_ATTRIBUTEID_VALUE;
|
||||
targetVars[i].targetVariable.targetNodeId = newNode;
|
||||
targetVars[i].attributeId = UA_ATTRIBUTEID_VALUE;
|
||||
targetVars[i].targetNodeId = newNode;
|
||||
}
|
||||
|
||||
retval = UA_Server_DataSetReader_createTargetVariables(server, dataSetReaderId,
|
||||
readerConfig.dataSetMetaData.fieldsSize, targetVars);
|
||||
for(size_t i = 0; i < readerConfig.dataSetMetaData.fieldsSize; i++)
|
||||
UA_FieldTargetDataType_clear(&targetVars[i].targetVariable);
|
||||
|
||||
UA_free(targetVars);
|
||||
UA_free(readerConfig.dataSetMetaData.fields);
|
||||
|
@ -6,14 +6,14 @@
|
||||
#include <open62541/server_pubsub.h>
|
||||
#include <open62541/server_config_default.h>
|
||||
|
||||
#include "ua_pubsub_internal.h"
|
||||
|
||||
#include "common.h"
|
||||
|
||||
/* Function to give user information about correct usage */
|
||||
static void usage_info(void) {
|
||||
UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "USAGE: ./server_pubsub_file_configuration [name of UA_Binary_Config_File]");
|
||||
UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "Alternatively, Bin-files can be loaded via configuration method calls.");
|
||||
UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER,
|
||||
"USAGE: ./server_pubsub_file_configuration [name of UA_Binary_Config_File]");
|
||||
UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER,
|
||||
"Alternatively, Bin-files can be loaded via configuration method calls.");
|
||||
}
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
@ -107,7 +107,8 @@ int main(int argc, char** argv) {
|
||||
statusCode |= UA_Server_enableAllPubSubComponents(server);
|
||||
statusCode |= UA_Server_runUntilInterrupt(server);
|
||||
if(statusCode != UA_STATUSCODE_GOOD) {
|
||||
UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "Server stopped. Status code: 0x%x\n", statusCode);
|
||||
UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
|
||||
"Server stopped. Status code: 0x%x\n", statusCode);
|
||||
return(-1);
|
||||
}
|
||||
|
||||
@ -120,7 +121,8 @@ int main(int argc, char** argv) {
|
||||
|
||||
if(statusCode != UA_STATUSCODE_GOOD)
|
||||
UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
|
||||
"Saving PubSub configuration to file failed. StatusCode: 0x%x\n", statusCode);
|
||||
"Saving PubSub configuration to file failed. "
|
||||
"StatusCode: 0x%x\n", statusCode);
|
||||
|
||||
UA_ByteString_clear(&buffer);
|
||||
}
|
||||
|
@ -123,8 +123,8 @@ addSubscribedVariables(UA_Server *server, UA_NodeId dataSetReaderId) {
|
||||
UA_NS0ID(BASEOBJECTTYPE), oAttr, NULL, &folderId);
|
||||
|
||||
/* Create the TargetVariables with respect to DataSetMetaData fields */
|
||||
UA_FieldTargetVariable *targetVars = (UA_FieldTargetVariable *)
|
||||
UA_calloc(readerConfig.dataSetMetaData.fieldsSize, sizeof(UA_FieldTargetVariable));
|
||||
UA_FieldTargetDataType *targetVars = (UA_FieldTargetDataType*)
|
||||
UA_calloc(readerConfig.dataSetMetaData.fieldsSize, sizeof(UA_FieldTargetDataType));
|
||||
for(size_t i = 0; i < readerConfig.dataSetMetaData.fieldsSize; i++) {
|
||||
/* Variable to subscribe data */
|
||||
UA_VariableAttributes vAttr = UA_VariableAttributes_default;
|
||||
@ -141,15 +141,14 @@ addSubscribedVariables(UA_Server *server, UA_NodeId dataSetReaderId) {
|
||||
UA_NS0ID(BASEDATAVARIABLETYPE), vAttr, NULL, &newNode);
|
||||
|
||||
/* For creating Targetvariables */
|
||||
UA_FieldTargetDataType_init(&targetVars[i].targetVariable);
|
||||
targetVars[i].targetVariable.attributeId = UA_ATTRIBUTEID_VALUE;
|
||||
targetVars[i].targetVariable.targetNodeId = newNode;
|
||||
targetVars[i].attributeId = UA_ATTRIBUTEID_VALUE;
|
||||
targetVars[i].targetNodeId = newNode;
|
||||
}
|
||||
|
||||
UA_Server_DataSetReader_createTargetVariables(server, dataSetReaderId,
|
||||
readerConfig.dataSetMetaData.fieldsSize, targetVars);
|
||||
for(size_t i = 0; i < readerConfig.dataSetMetaData.fieldsSize; i++)
|
||||
UA_FieldTargetDataType_clear(&targetVars[i].targetVariable);
|
||||
UA_FieldTargetDataType_clear(&targetVars[i]);
|
||||
|
||||
UA_free(targetVars);
|
||||
UA_free(readerConfig.dataSetMetaData.fields);
|
||||
|
@ -141,9 +141,9 @@ addSubscribedVariables(UA_Server *server, UA_NodeId dataSetReaderId) {
|
||||
* between received DataSet fields and target Variables in the Subscriber
|
||||
* AddressSpace. The values subscribed from the Publisher are updated in the value
|
||||
* field of these variables */
|
||||
/* Create the TargetVariables with respect to DataSetMetaData fields */
|
||||
UA_FieldTargetVariable *targetVars = (UA_FieldTargetVariable *)UA_calloc(
|
||||
readerConfig.dataSetMetaData.fieldsSize, sizeof(UA_FieldTargetVariable));
|
||||
|
||||
UA_FieldTargetDataType *targetVars = (UA_FieldTargetDataType*)
|
||||
UA_calloc(readerConfig.dataSetMetaData.fieldsSize, sizeof(UA_FieldTargetDataType));
|
||||
for(size_t i = 0; i < readerConfig.dataSetMetaData.fieldsSize; i++) {
|
||||
/* Variable to subscribe data */
|
||||
UA_VariableAttributes vAttr = UA_VariableAttributes_default;
|
||||
@ -160,16 +160,12 @@ addSubscribedVariables(UA_Server *server, UA_NodeId dataSetReaderId) {
|
||||
UA_QUALIFIEDNAME(1, (char *)readerConfig.dataSetMetaData.fields[i].name.data),
|
||||
UA_NS0ID(BASEDATAVARIABLETYPE), vAttr, NULL, &newNode);
|
||||
|
||||
/* For creating Targetvariables */
|
||||
UA_FieldTargetDataType_init(&targetVars[i].targetVariable);
|
||||
targetVars[i].targetVariable.attributeId = UA_ATTRIBUTEID_VALUE;
|
||||
targetVars[i].targetVariable.targetNodeId = newNode;
|
||||
targetVars[i].attributeId = UA_ATTRIBUTEID_VALUE;
|
||||
targetVars[i].targetNodeId = newNode;
|
||||
}
|
||||
|
||||
UA_Server_DataSetReader_createTargetVariables(server, dataSetReaderId,
|
||||
readerConfig.dataSetMetaData.fieldsSize, targetVars);
|
||||
for(size_t i = 0; i < readerConfig.dataSetMetaData.fieldsSize; i++)
|
||||
UA_FieldTargetDataType_clear(&targetVars[i].targetVariable);
|
||||
|
||||
UA_free(targetVars);
|
||||
UA_free(readerConfig.dataSetMetaData.fields);
|
||||
|
@ -428,11 +428,6 @@ main(int argc, char **argv) {
|
||||
if(enableTime)
|
||||
config.verifyRequestTimestamp = UA_RULEHANDLING_DEFAULT;
|
||||
|
||||
/* Override with a custom access control policy */
|
||||
UA_String_clear(&config.applicationDescription.applicationUri);
|
||||
config.applicationDescription.applicationUri =
|
||||
UA_String_fromChars("urn:open62541.server.application");
|
||||
|
||||
config.shutdownDelay = 5000.0; /* 5s */
|
||||
|
||||
/* Add supported pubsub security policies by this sks instance */
|
||||
|
@ -215,8 +215,8 @@ addSubscribedVariables (UA_Server *server, UA_NodeId dataSetReaderId) {
|
||||
* received DataSet fields and target Variables in the Subscriber AddressSpace.
|
||||
* The values subscribed from the Publisher are updated in the value field of these variables */
|
||||
/* Create the TargetVariables with respect to DataSetMetaData fields */
|
||||
UA_FieldTargetVariable *targetVars = (UA_FieldTargetVariable *)
|
||||
UA_calloc(readerConfig.dataSetMetaData.fieldsSize, sizeof(UA_FieldTargetVariable));
|
||||
UA_FieldTargetDataType *targetVars = (UA_FieldTargetDataType *)
|
||||
UA_calloc(readerConfig.dataSetMetaData.fieldsSize, sizeof(UA_FieldTargetDataType));
|
||||
for(size_t i = 0; i < readerConfig.dataSetMetaData.fieldsSize; i++) {
|
||||
/* Variable to subscribe data */
|
||||
UA_VariableAttributes vAttr = UA_VariableAttributes_default;
|
||||
@ -233,17 +233,13 @@ addSubscribedVariables (UA_Server *server, UA_NodeId dataSetReaderId) {
|
||||
UA_NS0ID(BASEDATAVARIABLETYPE),
|
||||
vAttr, NULL, &newNode);
|
||||
|
||||
/* For creating Targetvariables */
|
||||
UA_FieldTargetDataType_init(&targetVars[i].targetVariable);
|
||||
targetVars[i].targetVariable.attributeId = UA_ATTRIBUTEID_VALUE;
|
||||
targetVars[i].targetVariable.targetNodeId = newNode;
|
||||
targetVars[i].attributeId = UA_ATTRIBUTEID_VALUE;
|
||||
targetVars[i].targetNodeId = newNode;
|
||||
}
|
||||
|
||||
UA_Server_DataSetReader_createTargetVariables(server, dataSetReaderId,
|
||||
readerConfig.dataSetMetaData.fieldsSize,
|
||||
targetVars);
|
||||
for(size_t i = 0; i < readerConfig.dataSetMetaData.fieldsSize; i++)
|
||||
UA_FieldTargetDataType_clear(&targetVars[i].targetVariable);
|
||||
|
||||
UA_free(targetVars);
|
||||
UA_free(readerConfig.dataSetMetaData.fields);
|
||||
|
@ -117,15 +117,17 @@ addSubscribedVariables (UA_Server *server, UA_NodeId dataSetReaderId) {
|
||||
UA_NS0ID(OBJECTSFOLDER), UA_NS0ID(ORGANIZES),
|
||||
folderBrowseName, UA_NS0ID(BASEOBJECTTYPE), oAttr, NULL, &folderId);
|
||||
|
||||
/**
|
||||
* **TargetVariables**
|
||||
*
|
||||
* The SubscribedDataSet option TargetVariables defines a list of Variable mappings between
|
||||
* received DataSet fields and target Variables in the Subscriber AddressSpace.
|
||||
* The values subscribed from the Publisher are updated in the value field of these variables */
|
||||
/**
|
||||
* **TargetVariables**
|
||||
*
|
||||
* The SubscribedDataSet option TargetVariables defines a list of Variable
|
||||
* mappings between received DataSet fields and target Variables in the
|
||||
* Subscriber AddressSpace. The values subscribed from the Publisher are
|
||||
* updated in the value field of these variables */
|
||||
|
||||
/* Create the TargetVariables with respect to DataSetMetaData fields */
|
||||
UA_FieldTargetVariable *targetVars = (UA_FieldTargetVariable *)
|
||||
UA_calloc(readerConfig.dataSetMetaData.fieldsSize, sizeof(UA_FieldTargetVariable));
|
||||
UA_FieldTargetDataType *targetVars = (UA_FieldTargetDataType *)
|
||||
UA_calloc(readerConfig.dataSetMetaData.fieldsSize, sizeof(UA_FieldTargetDataType));
|
||||
for(size_t i = 0; i < readerConfig.dataSetMetaData.fieldsSize; i++) {
|
||||
/* Variable to subscribe data */
|
||||
UA_VariableAttributes vAttr = UA_VariableAttributes_default;
|
||||
@ -143,16 +145,13 @@ addSubscribedVariables (UA_Server *server, UA_NodeId dataSetReaderId) {
|
||||
vAttr, NULL, &newNode);
|
||||
|
||||
/* For creating Targetvariables */
|
||||
UA_FieldTargetDataType_init(&targetVars[i].targetVariable);
|
||||
targetVars[i].targetVariable.attributeId = UA_ATTRIBUTEID_VALUE;
|
||||
targetVars[i].targetVariable.targetNodeId = newNode;
|
||||
targetVars[i].attributeId = UA_ATTRIBUTEID_VALUE;
|
||||
targetVars[i].targetNodeId = newNode;
|
||||
}
|
||||
|
||||
UA_Server_DataSetReader_createTargetVariables(server, dataSetReaderId,
|
||||
readerConfig.dataSetMetaData.fieldsSize,
|
||||
targetVars);
|
||||
for(size_t i = 0; i < readerConfig.dataSetMetaData.fieldsSize; i++)
|
||||
UA_FieldTargetDataType_clear(&targetVars[i].targetVariable);
|
||||
|
||||
UA_free(targetVars);
|
||||
UA_free(readerConfig.dataSetMetaData.fields);
|
||||
|
34
examples/pubsub_realtime/README.md
Normal file
34
examples/pubsub_realtime/README.md
Normal file
@ -0,0 +1,34 @@
|
||||
# open62541 Realtime-PubSub Support
|
||||
|
||||
Two features of open625451 enable realtime Pubsub: The custom state machine for
|
||||
PubSub-Components and the generation of offset tables for the PubSub
|
||||
NetworkMessage. Both features are showcased in the respective examples for
|
||||
Publishers and Subscribers.
|
||||
|
||||
## Custom State Machine
|
||||
|
||||
The custom state machine mechanism lets user-defined code control the state of
|
||||
the PubSub-Components (PubSubConnection, ReaderGroup, Reader, ...). Hence the
|
||||
user-defined code is also responsible to register time-based events, network
|
||||
connections, and so on.
|
||||
|
||||
This allows the integration with non-POSIX networking APIs for publishers and
|
||||
subscribers. Note that the provided examples are still POSIX based, but handle
|
||||
all sockets in userland.
|
||||
|
||||
## NetworkMessage Offset Table
|
||||
|
||||
Typically realtime PubSub is used together with the periodic-fixed header layout
|
||||
where the position of the content in the NetworkMessage does not change between
|
||||
publish cycles. Then the NetworkMessage offset tables can be generated to speed
|
||||
up the updating and parsing of NetworkMessages. For the relevant fields, the
|
||||
well-known offsets are computed ahead of time. Each offset furthermore has a
|
||||
known source (the NodeId of the respective PubSub-Component in the information
|
||||
model).
|
||||
|
||||
The NetworkMessage offset table is usually generated when the
|
||||
ReaderGroup/WriterGroup changes its state to OPERATIONAL. At this time, from the
|
||||
offset table, each field of the NetworkMessage is resolved to point to a backend
|
||||
information source with fast access. Then the updating/parsing of the
|
||||
NetworkMessage in the publish interval is simply a matter of looping over the
|
||||
offset table and performing mostly memcpy steps.
|
@ -1,78 +0,0 @@
|
||||
# open62541 Realtime OPC UA PubSub Publisher
|
||||
|
||||
This example is a self-contained PubSub publisher over raw Ethernet. It
|
||||
showcases the realtime-capabilities of OPC UA PubSub. The core idea is that the
|
||||
publisher callback can be triggered from a time-triggered system interrupt and
|
||||
sends out the PubSub message within the interrupt.
|
||||
|
||||
The publisher retrieves its configuration and the payload data from the
|
||||
information model of an OPC UA server. (It could also be run in a standalone
|
||||
mode without an OPC UA server.) Since the publisher interrupt preempts the
|
||||
execution of the normal OPC UA server, the information model needs to be
|
||||
consistent at every time (reentrant). The specific techniques used to make the
|
||||
OPC UA server reentrant are described in this publication:
|
||||
|
||||
```
|
||||
@inproceedings{pfrommer2018open,
|
||||
title={Open source OPC UA PubSub over TSN for realtime industrial communication},
|
||||
author={Pfrommer, Julius and Ebner, Andreas and Ravikumar, Siddharth and Karunakaran, Bhagath},
|
||||
booktitle={2018 IEEE 23rd International Conference on Emerging Technologies and Factory Automation (ETFA)},
|
||||
pages={1087--1090},
|
||||
year={2018},
|
||||
organization={IEEE}
|
||||
}
|
||||
```
|
||||
|
||||
Please cite if you use this work.
|
||||
|
||||
OPC UA PubSub for open62541 is funded by an industry consortium in the context
|
||||
of an OSADL project (Open Source Automation Development Lab). Technical
|
||||
development is conducted by Fraunhofer IOSB and Kalycito Infotech.
|
||||
|
||||
https://www.osadl.org/OPC-UA-TSN.opcua-tsn.0.html
|
||||
|
||||
## Realtime communication with Time-Sensitive Networking (TSN)
|
||||
|
||||
OPC UA PubSub can be used together with TSN for hard-realtime Ethernet-based
|
||||
communication. Vendor-specific APIs are commonly used for TSN capabilities. This
|
||||
example only uses the standard Linux API for raw Ethernet. Vendor-specific
|
||||
examples may be added at a later time.
|
||||
|
||||
## Building the RT Publisher
|
||||
|
||||
The main open62541 library needs to be built with the following build options
|
||||
enabled for the realtime PubSub example. Note that some of the other examples
|
||||
supplied with open62541 will not link against the library with these build
|
||||
options. For good timings, ensure that the `CMAKE_BUILD_TYPE` is set to
|
||||
`Release`.
|
||||
|
||||
- UA_ENABLE_PUBSUB
|
||||
- UA_BUILD_EXAMPLES
|
||||
- UA_ENABLE_MALLOC_SINGLETON
|
||||
- UA_ENABLE_PUBSUB_BUFMALLOC
|
||||
- UA_ENABLE_IMMUTABLE_NODES
|
||||
|
||||
The publisher contains some hard-coded values that need to be adjusted to
|
||||
specific systems. Please check the top definitions in
|
||||
`pubsub_interrupt_publish.c` and `start_rt_publish.sh`.
|
||||
|
||||
## Running the RT Publisher
|
||||
|
||||
The publisher must be run as root for direct access to the Ethernet interface.
|
||||
|
||||
`# ./rt_publisher`
|
||||
|
||||
The example contains a script to be used with RT-Preempt Linux kernel. The
|
||||
following command starts the publisher, locks the process to a specific CPU, and
|
||||
sets the scheduling policy.
|
||||
|
||||
`# start_rt_publish.sh ./rt_publisher`
|
||||
|
||||
The measurements are written to a file (publisher_measurement.csv) with the
|
||||
following fields for every publish callback:
|
||||
|
||||
- Counter
|
||||
- Publication Interval
|
||||
- Nominal time for the current publish
|
||||
- Start delay from the nominal time
|
||||
- Duration of the publish callback
|
@ -1,188 +0,0 @@
|
||||
Tested Environment:
|
||||
Apollo Lake processor-1.60GHz with Intel i210 Ethernet Controller
|
||||
OS - Debian/Lubuntu
|
||||
Kernel - 4.19.37-rt19, 5.4.59-rt36
|
||||
[Note: The pubsub TSN applications have two functionalities - ETF and XDP.
|
||||
ETF will work in all RT kernels above 4.19. XDP is tested only on 5.4.59-rt36 kernel and it may work on kernels above 5.4.
|
||||
If user wishes to run these pubsub TSN applications in the kernel below 4.19, there might be minimal changes required in the applications]
|
||||
|
||||
PRE-REQUISITES:
|
||||
We recommend at least two Intel x86-based nodes with 4-cores and Intel i210 Ethernet Controllers connected in peer-peer fashion
|
||||
RT enabled kernel in nodes (say node1 and node2)
|
||||
Ensure the nodes are ptp synchronized
|
||||
PTP SYNCHRONIZATION:
|
||||
Clone and install linuxptp version 2.0
|
||||
git clone https://github.com/richardcochran/linuxptp.git
|
||||
cd linuxptp/
|
||||
git checkout -f 059269d0cc50f8543b00c3c1f52f33a6c1aa5912
|
||||
make
|
||||
make install
|
||||
cp configs/gPTP.cfg /etc/linuxptp/
|
||||
|
||||
Create VLAN with VLAN ID of 8 in peer to peer connected interface, copy paste the following lines in "/etc/network/interfaces" file and setup suitable IP address
|
||||
auto <i210 interface>.8
|
||||
iface <i210 interface>.8 inet static
|
||||
address <suitable IP address>
|
||||
netmask 255.255.255.0
|
||||
|
||||
To make node as ptp master run the following command (say in node1):
|
||||
sudo daemonize -E BUILD_ID=dontKillMe -o /var/log/ptp4l.log -e /var/log/ptp4l.err.log /usr/bin/taskset -c 1 chrt 90 /usr/local/sbin/ptp4l -i <I210 interface> -2 -mq -f /etc/linuxptp/gPTP.cfg --step_threshold=1 --fault_reset_interval=0 --announceReceiptTimeout=10 --transportSpecific=1
|
||||
To make node as ptp slave run the following command (say in node2):
|
||||
sudo daemonize -E BUILD_ID=dontKillMe -o /var/log/ptp4l.log -e /var/log/ptp4l.err.log /usr/bin/taskset -c 1 chrt 90 /usr/local/sbin/ptp4l -i <I210 interface> -2 -mq -s -f /etc/linuxptp/gPTP.cfg --step_threshold=1 --fault_reset_interval=0 --announceReceiptTimeout=10 --transportSpecific=1
|
||||
To ensure phc2sys synchronization (in both nodes, node1 and node2):
|
||||
sudo daemonize -E BUILD_ID=dontKillMe -o /var/log/phc2sys.log -e /var/log/phc2sys.err.log /usr/bin/taskset -c 1 chrt 89 /usr/local/sbin/phc2sys -s <I210 interface> -c CLOCK_REALTIME --step_threshold=1 --transportSpecific=1 -w -m
|
||||
|
||||
Check if ptp4l and phc2sys are running
|
||||
ptp4l log in /var/log/ptp4l.log
|
||||
phc2sys log in /var/log/phc2sys.log
|
||||
|
||||
Check if /etc/modules has entry for 8021q
|
||||
cat /etc/modules | grep '8021q'
|
||||
|
||||
For ETF Transmit: (In both nodes)
|
||||
#Configure ETF
|
||||
sudo tc qdisc add dev <I210 interface> parent root mqprio num_tc 3 map 2 2 1 0 2 2 2 2 2 2 2 2 2 2 2 2 queues 1@0 1@1 2@2 hw 0
|
||||
MQPRIO_NUM=`sudo tc qdisc show | grep mqprio | cut -d ':' -f1 | cut -d ' ' -f3`
|
||||
sudo tc qdisc add dev <I210 interface> parent $MQPRIO_NUM:1 etf offload clockid CLOCK_TAI delta 150000
|
||||
sudo tc qdisc add dev <I210 interface> parent $MQPRIO_NUM:2 etf offload clockid CLOCK_TAI delta 150000
|
||||
|
||||
In both nodes:
|
||||
for j in `seq 0 7`;do sudo ip link set <I210 interface>.8 type vlan egress $j:$j ; done
|
||||
for j in `seq 0 7`;do sudo ip link set <I210 interface>.8 type vlan ingress $j:$j ; done
|
||||
|
||||
TO RUN ETF APPLICATIONS:
|
||||
To run ETF applications over Ethernet in two nodes connected in peer-to-peer network
|
||||
mkdir build
|
||||
cd build
|
||||
cmake -DUA_BUILD_EXAMPLES=ON -DUA_ENABLE_PUBSUB=ON ..
|
||||
make
|
||||
|
||||
The generated binaries are generated in build/bin/ folder
|
||||
./bin/examples/pubsub_TSN_publisher -interface <I210 interface> - Run in node 1
|
||||
./bin/examples/pubsub_TSN_loopback -interface <I210 interface> - Run in node 2
|
||||
Eg: ./bin/examples/pubsub_TSN_publisher -interface enp2s0
|
||||
./bin/examples/pubsub_TSN_loopback -interface enp2s0
|
||||
|
||||
NOTE: It is always recommended to run pubsub_TSN_loopback application first to avoid missing the initial data published by pubsub_TSN_publisher
|
||||
|
||||
To know more usage
|
||||
./bin/examples/pubsub_TSN_publisher -help
|
||||
./bin/examples/pubsub_TSN_loopback -help
|
||||
|
||||
NOTE: To know more about running the OPC UA PubSub application and to evaluate performance, refer the Quick Start Guide - https://www.kalycito.com/how-to-run-opc-ua-pubsub-tsn/
|
||||
============================================================================================================
|
||||
You can also subscribe using XDP (Express Data Path) for faster processing the data, follow the below steps:
|
||||
|
||||
Pre-requisties for XDP:
|
||||
Minimum kernel version of 5.4 (Tested in RT enabled kernel - 5.4.59-rt36 with Debian OS)
|
||||
Install llvm and clang
|
||||
apt-get install llvm
|
||||
apt-get install clang
|
||||
Clone libbpf using the following steps:
|
||||
git clone https://github.com/libbpf/libbpf.git
|
||||
cd libbpf/
|
||||
git checkout ab067ed3710550c6d1b127aac6437f96f8f99447
|
||||
cd src/
|
||||
OBJDIR=/usr/lib make install
|
||||
|
||||
Check if libbpf.so is available in /usr/lib folder, and bpf/ folder is available in /usr/include folder in both nodes (node1 and node2)
|
||||
|
||||
NOTE: Make sure the header file if_xdp.h available in /usr/include/linux/ is the right header file as in kernel version 5.4.59, if not update the if_xdp.h to newer version. (Verify your if_xdp.h file with https://elixir.bootlin.com/linux/v5.4.36/source/include/uapi/linux/if_xdp.h)
|
||||
|
||||
As per the sample application, XDP listens to Rx_Queue 2; to direct the incoming ethernet packets to the second Rx_Queue, the follow the given steps
|
||||
In both nodes:
|
||||
INGRESS POLICY steps:
|
||||
#Configure equal weightage to queue 0 and 1
|
||||
ethtool -X <I210 interface> equal 2
|
||||
#Disable VLAN offload
|
||||
ethtool -K <I210 interface> rxvlan off
|
||||
ethtool -K <I210 interface> ntuple on
|
||||
ethtool --config-ntuple <I210 interface> delete 15
|
||||
ethtool --config-ntuple <I210 interface> delete 14
|
||||
#Below command forwards the VLAN tagged packets to RX_Queue 2
|
||||
ethtool --config-ntuple <I210 interface> flow-type ether proto 0x8100 dst 01:00:5E:7F:00:01 loc 15 action 2
|
||||
ethtool --config-ntuple <I210 interface> flow-type ether proto 0x8100 dst 01:00:5E:7F:00:02 loc 14 action 2
|
||||
|
||||
To run ETF in Publisher and XDP in Subscriber in two nodes connected in peer-to-peer network
|
||||
cmake -DUA_BUILD_EXAMPLES=ON -DUA_ENABLE_PUBSUB=ON ..
|
||||
make
|
||||
By default XDP will be disabled in the Subscriber, use -enableXdpSubscribe to use XDP in RX.
|
||||
./bin/examples/pubsub_TSN_publisher -interface <I210 interface> -enableXdpSubscribe - Run in node 1
|
||||
./bin/examples/pubsub_TSN_loopback -interface <I210 interface> -enableXdpSubscribe - Run in node 2
|
||||
|
||||
Optional steps:
|
||||
By default, XDP listens to RX_Queue 2 with SKB and COPY mode.
|
||||
To make XDP listen to other RX_Queue, provide -xdpQueue <num> as arguments when executing applications and modify the action value (INGRESS POLICY) to the desired queue number.
|
||||
To enable ZEROCOPY mode, provide -xdpBindFlagZeroCopy as an additional argument to the applications
|
||||
To enable XDP Driver (DRV) mode, provide -xdpFlagDrvMode as an additional argument to the applications
|
||||
|
||||
NOTE: To enable XDP socket, pass its configuration parameters through connectionProperties (KeyValuePair) in connectionConfig as mentioned below. XDP socket has support
|
||||
for both Publisher and Subscriber connection but it is recommended to use XDP only in Subscriber connection.
|
||||
|
||||
UA_KeyValuePair connectionOptions[4]; // KeyValuePair for XDP configuration
|
||||
connectionOptions[0].key = UA_QUALIFIEDNAME(0, "enableXdpSocket"); // Key for enabling the XDP socket
|
||||
UA_Boolean enableXdp = UA_TRUE; // Boolean value for enabling and disabling XDP socket
|
||||
UA_Variant_setScalar(&connectionOptions[0].value, &enableXdp, &UA_TYPES[UA_TYPES_BOOLEAN]);
|
||||
connectionOptions[1].key = UA_QUALIFIEDNAME(0, "xdpflag"); // Key for the XDP flags
|
||||
UA_UInt32 flags = xdpFlag; // Value to determine whether XDP works in SKB mode or DRV mode
|
||||
UA_Variant_setScalar(&connectionOptions[1].value, &flags, &UA_TYPES[UA_TYPES_UINT32]);
|
||||
connectionOptions[2].key = UA_QUALIFIEDNAME(0, "hwreceivequeue"); // Key for the hardware queue
|
||||
UA_UInt32 rxqueue = xdpQueue; // Value of the hardware queue to receive the packets
|
||||
UA_Variant_setScalar(&connectionOptions[2].value, &rxqueue, &UA_TYPES[UA_TYPES_UINT32]);
|
||||
connectionOptions[3].key = UA_QUALIFIEDNAME(0, "xdpbindflag"); // Key for the XDP bind flags
|
||||
UA_UInt32 bindflags = xdpBindFlag; // Value to determine whether XDP works in COPY or ZEROCOPY mode
|
||||
UA_Variant_setScalar(&connectionOptions[3].value, &bindflags, &UA_TYPES[UA_TYPES_UINT16]);
|
||||
connectionConfig.connectionProperties = connectionOptions; // Provide all the KeyValuePairs to properties of connectionConfig
|
||||
connectionConfig.connectionPropertiesSize = 4;
|
||||
|
||||
To know more usage
|
||||
./bin/examples/pubsub_TSN_publisher -help
|
||||
./bin/examples/pubsub_TSN_loopback -help
|
||||
===================================================================================================================================================================
|
||||
NOTE:
|
||||
If VLAN tag is used:
|
||||
Ensure the MAC address is given along with VLAN ID and PCP
|
||||
Eg: "opc.eth://01-00-5E-00-00-01:8.3" where 8 is the VLAN ID and 3 is the PCP
|
||||
If VLAN tag is not used:
|
||||
Ensure the MAC address is not given along with VLAN ID and PCP
|
||||
Eg: "opc.eth://01-00-5E-00-00-01"
|
||||
If MAC address is changed, follow INGRESS POLICY steps with dst address to be the same as SUBSCRIBING_MAC_ADDRESS for both nodes
|
||||
|
||||
To increase the payload size, change the REPEATED_NODECOUNTS macro in pubsub_TSN_publisher.c and pubsub_TSN_loopback.c applications (for 1 REPEATED_NODECOUNTS, 9 bytes of data will increase in payload)
|
||||
|
||||
=====================================================================================================================================================================
|
||||
TO RUN THE UNIT TEST CASES FOR ETHERNET FUNCTIONALITY:
|
||||
|
||||
Change the ethernet interface in #define ETHERNET_INTERFACE macro in
|
||||
check_pubsub_connection_ethernet.c
|
||||
check_pubsub_publish_ethernet.c
|
||||
check_pubsub_connection_ethernet_etf.c
|
||||
check_pubsub_publish_ethernet_etf.c
|
||||
|
||||
1. To test ethernet connection creation and ethernet publish unit tests(without realtime)
|
||||
cd open62541/build
|
||||
make clean
|
||||
cmake -DUA_ENABLE_PUBSUB=ON -DUA_BUILD_UNIT_TESTS=ON ..
|
||||
make
|
||||
The following binaries are generated in build/bin/tests folder
|
||||
./bin/tests/check_pubsub_connection_ethernet - To check ethernet connection creation
|
||||
./bin/tests/check_pubsub_publish_ethernet - To check ethernet send functionality
|
||||
|
||||
2. To test ethernet connection creation and ethernet publish unit tests(with realtime)
|
||||
cd open62541/build
|
||||
make clean
|
||||
cmake -DUA_ENABLE_PUBSUB=ON -DUA_BUILD_UNIT_TESTS=ON ..
|
||||
make
|
||||
The following binaries are generated in build/bin/tests folder
|
||||
./bin/tests/check_pubsub_connection_ethernet_etf - To check ethernet connection creation with etf
|
||||
./bin/tests/check_pubsub_publish_ethernet_etf - To check ethernet send functionality with etf
|
||||
|
||||
TO RUN THE UNIT TEST CASES FOR XDP FUNCTIONALITY:
|
||||
|
||||
1. To test connection creation using XDP transport layer
|
||||
cd open62541/build
|
||||
make clean
|
||||
cmake -DUA_ENABLE_PUBSUB=ON -DUA_BUILD_UNIT_TESTS=ON ..
|
||||
make
|
||||
The following binary is generated in build/bin/tests folder
|
||||
./bin/tests/check_pubsub_connection_xdp <I210 interface> - To check connection creation with XDP
|
@ -1,195 +0,0 @@
|
||||
Tested Environment:
|
||||
TTTech IP (2.3.0) - Arm architecture
|
||||
Kernel - 5.4.40 (non-RT)
|
||||
============================================================================================================
|
||||
PRE-REQUISITES:
|
||||
1) We recommend at least two TTTech TSN IP nodes with 2-cores
|
||||
|
||||
2) Ensure the nodes are ptp synchronized
|
||||
deptp_tool --get-current-dataset
|
||||
|
||||
3) Add and aditional clock domain /etc/deptp/ptp_config.xml, reboot the board after applying this change (this is a one time step)
|
||||
<Clock>
|
||||
<clock_class>187</clock_class>
|
||||
<clock_accuracy>1us</clock_accuracy>
|
||||
<clock_priority1>247</clock_priority1>
|
||||
<clock_priority2>248</clock_priority2>
|
||||
<domain>100</domain>
|
||||
<time_source>internal oscillator</time_source>
|
||||
</Clock>
|
||||
|
||||
4) Create VLAN with VLAN ID of 8 in peer to peer connected interface using the below command
|
||||
ip link add link <interfaceName> name myvlan type vlan id 8 ingress-qos-map 0:7 1:7 2:7 3:7 4:7 5:7 6:7 7:7 egress-qos-map 0:7 1:7 2:7 3:7 4:7 5:7 6:7 7:7
|
||||
ip addr add <VlanIpAddress> dev myvlan
|
||||
ip link set dev myvlan up
|
||||
bridge vlan add vid 8 dev <portName>
|
||||
bridge vlan add vid 8 dev <portName2> (optional e.g. for monitoring)
|
||||
bridge vlan add vid 8 dev <InternalPortName>
|
||||
ip route add 224.0.0.0/24 dev myvlan
|
||||
|
||||
NOTE: <portName> refers to the external physical port that has ethernet cable connected to it in each node
|
||||
|
||||
5) Create the Qbv configuration i.e setting up the gating configuration:
|
||||
1)Create a config file on both the nodes using the below command
|
||||
touch qbv.cfg
|
||||
2)At node 1 copy the below gating configuration and paste it in the qbv.cfg
|
||||
sgs 5000 0x80
|
||||
sgs 490000 0x7F
|
||||
3)At node 2 copy the below gating configuration and paste it in the qbv.cfg
|
||||
sgs 5000 0x80
|
||||
sgs 490000 0x7F
|
||||
4)To schedule the gating configuration run the below command on both the nodes
|
||||
tsntool st wrcl <portName> qbv.cfg
|
||||
tsntool st rdacl <portName> /dev/stdout
|
||||
tsntool st configure 5.0 1/2000 10000 <portName>
|
||||
tsntool st show <portName>
|
||||
|
||||
6) After these steps the following files shoud exist and have values:
|
||||
cat /run/ptp_wc_mon_offset
|
||||
cat /sys/class/net/<portName>/ieee8021ST/OperBaseTime
|
||||
============================================================================================================
|
||||
Cross Compiler options to compile:
|
||||
1) To compile the binaries create the folder using the below command:
|
||||
mkdir build
|
||||
|
||||
2) Traverse to the folder using the command
|
||||
cd build
|
||||
|
||||
3) Install the arm cross compiler - arm-linux-gnueabihf
|
||||
|
||||
4) Cross compilation for ARM architecture
|
||||
CMAKE_C_COMPILER /usr/bin/arm-linux-gnueabihf-gcc
|
||||
CMAKE_C_COMPILER_AR /usr/bin/arm-linux-gnueabihf-gcc-ar-7
|
||||
CMAKE_C_COMPILER_RANLIB /usr/bin/arm-linux-gnueabihf-gcc-ranlib-7
|
||||
CMAKE_C_FLAGS -Wno-error
|
||||
CMAKE_C_FLAGS_DEBUG -g -Wno-error
|
||||
CMAKE_C_LINKER /usr/bin/arm-linux-gnueabihf-ld
|
||||
CMAKE_NM /usr/bin/arm-linux-gnueabihf-nm
|
||||
CMAKE_OBJCOPY /usr/bin/arm-linux-gnueabihf-objcopy
|
||||
CMAKE_OBJDUMP /usr/bin/arm-linux-gnueabihf-objdump
|
||||
CMAKE_RANLIB /usr/bin/arm-linux-gnueabihf-ranlib
|
||||
CMAKE_STRIP /usr/bin/arm-linux-gnueabihf-strip
|
||||
|
||||
Note: The above mentioned options are changes for arm compiler.Ignore them if you
|
||||
are compiling for x86. The options will be available in tools/cmake/Toolchain-ARM.cmake
|
||||
for cross compilation
|
||||
|
||||
CMake options for Publisher application(pubsub_TSN_publisher_multiple_thread):
|
||||
cmake -DCMAKE_TOOLCHAIN_FILE=../tools/cmake/Toolchain-ARM.cmake -DUA_BUILD_EXAMPLES=ON -DUA_ENABLE_PUBSUB=ON ..
|
||||
make -j4 pubsub_TSN_publisher_multiple_thread
|
||||
|
||||
CMake options for loopback application(pubsub_TSN_loopback_single_thread):
|
||||
cmake -DCMAKE_TOOLCHAIN_FILE=../tools/cmake/Toolchain-ARM.cmake -DUA_BUILD_EXAMPLES=ON -DUA_ENABLE_PUBSUB=ON -DUA_ENABLE_PUBSUB_BUFMALLOC=ON -DUA_ENABLE_MALLOC_SINGLETON=ON ..
|
||||
make -j4 pubsub_TSN_loopback_single_thread
|
||||
|
||||
5) Compilation for x86 architecture
|
||||
CMake options for Publisher application(pubsub_TSN_publisher_multiple_thread):
|
||||
cmake -DUA_BUILD_EXAMPLES=ON -DUA_ENABLE_PUBSUB=ON ..
|
||||
make -j4 pubsub_TSN_publisher_multiple_thread
|
||||
|
||||
CMake options for loopback application(pubsub_TSN_loopback_single_thread):
|
||||
cmake -DUA_BUILD_EXAMPLES=ON -DUA_ENABLE_PUBSUB=ON -DUA_ENABLE_PUBSUB_BUFMALLOC=ON -DUA_ENABLE_MALLOC_SINGLETON=ON ..
|
||||
make -j4 pubsub_TSN_loopback_single_thread
|
||||
|
||||
============================================================================================================
|
||||
To RUN the APPLICATIONS:
|
||||
The generated binaries are generated in build/bin/ folder
|
||||
============================================================================================================
|
||||
TWO WAY COMMUNICATION
|
||||
============================================================================================================
|
||||
|
||||
For Ethernet(Without logs) For long run:
|
||||
./pubsub_TSN_publisher_multiple_thread -interface <interfaceName> -disableSoTxtime -operBaseTime /sys/class/net/<portName>/ieee8021ST/OperBaseTime -monotonicOffset /run/ptp_wc_mon_offset -cycleTimeInMsec 0.5 -subAppPriority 99 -pubAppPriority 98 - Run in node 1
|
||||
|
||||
./pubsub_TSN_loopback_single_thread -interface <interfaceName> -disableSoTxtime -operBaseTime /sys/class/net/<portName>/ieee8021ST/OperBaseTime -monotonicOffset /run/ptp_wc_mon_offset -cycleTimeInMsec 0.5 -pubSubAppPriority 99 - Run in node 2
|
||||
|
||||
For Ethernet:(With logs - Provide the counterdata and its time which helps to identify the roundtrip time): For Short run
|
||||
./pubsub_TSN_publisher_multiple_thread -interface <interfaceName> -disableSoTxtime -operBaseTime /sys/class/net/<portName>/ieee8021ST/OperBaseTime -monotonicOffset /run/ptp_wc_mon_offset -cycleTimeInMsec 0.5 -enableCsvLog -subAppPriority 99 -pubAppPriority 98 - Run in node 1
|
||||
|
||||
./pubsub_TSN_loopback_single_thread -interface <interfaceName> -disableSoTxtime -operBaseTime /sys/class/net/<portName>/ieee8021ST/OperBaseTime -monotonicOffset /run/ptp_wc_mon_offset -cycleTimeInMsec 0.5 -enableCsvLog -pubSubAppPriority 99 - Run in node 2
|
||||
|
||||
Note: Application will close after sending 100000 packets and you will get the logs in .csv files. To change the number of packets to capture for short run change the parameter #MAX_MEASUREMENTS define value in pubsub_TSN_publisher_multiple_thread and pubsub_TSN_loopback_single_thread
|
||||
|
||||
For UDP(Without logs) For long run:
|
||||
For UDP only one change need to be done before running that is need to provide the interface ip address as an input for -interface option
|
||||
./pubsub_TSN_publisher_multiple_thread -interface <VlanIpAddress> -disableSoTxtime -operBaseTime /sys/class/net/<portName>/ieee8021ST/OperBaseTime -monotonicOffset /run/ptp_wc_mon_offset -cycleTimeInMsec 0.5 -subAppPriority 99 -pubAppPriority 98 - Run in node 1
|
||||
|
||||
./pubsub_TSN_loopback_single_thread -interface <VlanIpAddress> -disableSoTxtime -operBaseTime /sys/class/net/<portName>/ieee8021ST/OperBaseTime -monotonicOffset /run/ptp_wc_mon_offset -cycleTimeInMsec 0.5 -pubSubAppPriority 99 - Run in node 2
|
||||
|
||||
For UDP:(With logs - Provide the counterdata and its time which helps to identify the roundtrip time): For Short run
|
||||
./pubsub_TSN_publisher_multiple_thread -interface <VlanIpAddress> -disableSoTxtime -operBaseTime /sys/class/net/<portName>/ieee8021ST/OperBaseTime -monotonicOffset /run/ptp_wc_mon_offset -cycleTimeInMsec 0.5 -enableCsvLog -subAppPriority 99 -pubAppPriority 98 - Run in node 1
|
||||
|
||||
./pubsub_TSN_loopback_single_thread -interface <VlanIpAddress> -disableSoTxtime -operBaseTime /sys/class/net/<portName>/ieee8021ST/OperBaseTime -monotonicOffset /run/ptp_wc_mon_offset -cycleTimeInMsec 0.5 -enableCsvLog -pubSubAppPriority 99 - Run in node 2
|
||||
|
||||
NOTE: Application will close after sending 100000 packets and you will get the logs in .csv files. To change the number of packets to capture for short run
|
||||
change the parameter #MAX_MEASUREMENTS define value in pubsub_TSN_publisher_multiple_thread and pubsub_TSN_loopback_single_thread
|
||||
============================================================================================================
|
||||
ONE WAY COMMUNICATION: For one way communication disable(comment) the macro TWO_WAY_COMMUNICATION in the the pubsub_TSN_publisher_multiple_thread and pubsub_TSN_loopback_single_thread
|
||||
============================================================================================================
|
||||
|
||||
Run steps for Ethernet:
|
||||
./pubsub_TSN_publisher_multiple_thread -interface <interfaceName> -disableSoTxtime -operBaseTime /sys/class/net/<portName>/ieee8021ST/OperBaseTime -monotonicOffset /run/ptp_wc_mon_offset -cycleTimeInMsec 0.5 -enableCsvLog -pubAppPriority - Run in node 1
|
||||
|
||||
./pubsub_TSN_loopback_single_thread -interface <interfaceName> -disableSoTxtime -operBaseTime /sys/class/net/<portName>/ieee8021ST/OperBaseTime -monotonicOffset /run/ptp_wc_mon_offset -cycleTimeInMsec 0.5 -enableCsvLog -pubSubAppPriority 99 - Run in node 2
|
||||
|
||||
Run steps for UDP:
|
||||
./pubsub_TSN_publisher_multiple_thread -interface <VlanIpAddress> -disableSoTxtime -operBaseTime /sys/class/net/<portName>/ieee8021ST/OperBaseTime -monotonicOffset /run/ptp_wc_mon_offset -cycleTimeInMsec 0.5 -enableCsvLog -pubAppPriority - Run in node 1
|
||||
|
||||
./pubsub_TSN_loopback_single_thread -interface <VlanIpAddress> -disableSoTxtime -operBaseTime /sys/class/net/<portName>/ieee8021ST/OperBaseTime -monotonicOffset /run/ptp_wc_mon_offset -cycleTimeInMsec 0.5 -enableCsvLog -pubSubAppPriority 99 - Run in node 2
|
||||
|
||||
NOTE: As we have mentioned previously for long run remove the option -enableCsvLog
|
||||
============================================================================================================
|
||||
NOTE: It is always recommended to run pubsub_TSN_loopback_single_thread application first to avoid missing the initial data published by pubsub_TSN_publisher_multiple_thread
|
||||
|
||||
To know more usage
|
||||
./bin/examples/pubsub_TSN_publisher_multiple_thread -help
|
||||
./bin/examples/pubsub_TSN_loopback_single_thread -help
|
||||
============================================================================================================
|
||||
NOTE:
|
||||
For Ethernet
|
||||
If VLAN tag is used:
|
||||
Ensure the MAC address is given along with VLAN ID and PCP
|
||||
Eg: "opc.eth://01-00-5E-00-00-01:8.3" where 8 is the VLAN ID and 3 is the PCP
|
||||
If VLAN tag is not used:
|
||||
Ensure the MAC address is not given along with VLAN ID and PCP
|
||||
Eg: "opc.eth://01-00-5E-00-00-01"
|
||||
If MAC address is changed, follow INGRESS POLICY steps with dst address to be the same as SUBSCRIBING_MAC_ADDRESS for both nodes
|
||||
|
||||
To increase the payload size, change the REPEATED_NODECOUNTS macro in pubsub_TSN_publisher_multiple_thread.c and pubsub_TSN_loopback_single_thread.c applications (for 1 REPEATED_NODECOUNTS, 9 bytes of data will increase in payload)
|
||||
|
||||
============================================================================================================
|
||||
Output Logs: If application runs with option enableCsvLog
|
||||
/**
|
||||
* Trace point setup
|
||||
*
|
||||
* +--------------+ +----------------+
|
||||
* T1 | OPCUA PubSub | T8 T5 | OPCUA loopback | T4
|
||||
* | | Application | ^ | | Application | ^
|
||||
* | +--------------+ | | +----------------+ |
|
||||
* User | | | | | | | |
|
||||
* Space | | | | | | | |
|
||||
* | | | | | | | |
|
||||
* ----------|--------------|------------------------|----------------|-------
|
||||
* | | Node 1 | | | | Node 2 | |
|
||||
* Kernel| | | | | | | |
|
||||
* Space | | | | | | | |
|
||||
* | | | | | | | |
|
||||
* v +--------------+ | v +----------------+ |
|
||||
* T2 | TX tcpdump | T7<----------------T6 | RX tcpdump | T3
|
||||
* | +--------------+ +----------------+ ^
|
||||
* | |
|
||||
* ----------------------------------------------------------------
|
||||
*/
|
||||
For publisher application (pubsub_TSN_publisher_multiple_thread): publisher_T1.csv, subscriber_T8.csv
|
||||
For loopback application: publisher_T5.csv, subscriber_T4.csv
|
||||
|
||||
To Compute the round trip time: Subtract T8-T1 (subtract the time in the .csv files of subscriber_T8.csv - publisher_T1.csv)
|
||||
============================================================================================================
|
||||
To close the running application press ctrl+c during the application exit the packet loss count of the entire run will be displayed in the console print.
|
||||
|
||||
============================================================================================================
|
||||
|
||||
For the TTTech TSN IP version 2.3.0 the following are applicable:
|
||||
<interfaceName> sw0ep
|
||||
<portName> available external physical ports: sw0p2, sw0p3, sw0p5, sw0p3
|
||||
<InternalPortName> sw0p1
|
@ -1,49 +0,0 @@
|
||||
####################
|
||||
# Nodeset Examples PubSub#
|
||||
####################
|
||||
|
||||
###################
|
||||
# Custom XML #
|
||||
###################
|
||||
|
||||
set(FILE_CSV_DIRPREFIX ${PROJECT_SOURCE_DIR}/pubsub_realtime/nodeset)
|
||||
set(FILE_BSD_DIRPREFIX ${PROJECT_SOURCE_DIR}/pubsub_realtime/nodeset)
|
||||
set(FILE_NS_DIRPREFIX ${PROJECT_SOURCE_DIR}/pubsub_realtime/nodeset)
|
||||
|
||||
get_target_property(OPEN62541_BIN_DIR open62541::open62541 BINARY_DIR)
|
||||
set(PROJECT_BINARY_DIR ${OPEN62541_BIN_DIR})
|
||||
|
||||
# generate namespace from XML file
|
||||
ua_generate_nodeset_and_datatypes(
|
||||
NAME "example_publisher"
|
||||
FILE_NS "${FILE_NS_DIRPREFIX}/pubDataModel.xml"
|
||||
DEPENDS "${CMAKE_CURRENT_LIST_DIR}/../../../tools/schema/Opc.Ua.NodeSet2.Reduced.xml"
|
||||
)
|
||||
ua_generate_nodeset_and_datatypes(
|
||||
NAME "example_subscriber"
|
||||
FILE_NS "${FILE_NS_DIRPREFIX}/subDataModel.xml"
|
||||
DEPENDS "${CMAKE_CURRENT_LIST_DIR}/../../../tools/schema/Opc.Ua.NodeSet2.Reduced.xml"
|
||||
)
|
||||
# The .csv file can be created from within UaModeler or manually
|
||||
ua_generate_nodeid_header(
|
||||
NAME "example_nodeids_publisher"
|
||||
ID_PREFIX "EXAMPLE_NS_PUBLISHER"
|
||||
TARGET_SUFFIX "ids_example_publisher"
|
||||
FILE_CSV "${FILE_CSV_DIRPREFIX}/pubDataModel.csv"
|
||||
)
|
||||
ua_generate_nodeid_header(
|
||||
NAME "example_nodeids_subscriber"
|
||||
ID_PREFIX "EXAMPLE_NS_SUBSCRIBER"
|
||||
TARGET_SUFFIX "ids_example_subscriber"
|
||||
FILE_CSV "${FILE_CSV_DIRPREFIX}/subDataModel.csv"
|
||||
)
|
||||
|
||||
add_example(pubsub_nodeset_rt_publisher pubsub_nodeset_rt_publisher.c
|
||||
${UA_NODESET_EXAMPLE_PUBLISHER_SOURCES}
|
||||
${PROJECT_BINARY_DIR}/src_generated/open62541/example_nodeids_publisher.h)
|
||||
add_example(pubsub_nodeset_rt_subscriber pubsub_nodeset_rt_subscriber.c
|
||||
${UA_NODESET_EXAMPLE_SUBSCRIBER_SOURCES}
|
||||
${PROJECT_BINARY_DIR}/src_generated/open62541/example_nodeids_subscriber.h)
|
||||
target_link_libraries(pubsub_nodeset_rt_publisher rt pthread)
|
||||
target_link_libraries(pubsub_nodeset_rt_subscriber rt pthread)
|
||||
|
@ -1,4 +0,0 @@
|
||||
PublisherInfo,2001,ObjectType
|
||||
Publisher,2004,Object
|
||||
PublisherCounterValue,2005,Variable
|
||||
Pressure,2006,Variable
|
|
@ -1,80 +0,0 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<UANodeSet xmlns="http://opcfoundation.org/UA/2011/03/UANodeSet.xsd" xmlns:uax="http://opcfoundation.org/UA/2008/02/Types.xsd" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||
<NamespaceUris>
|
||||
<Uri>http://yourorganisation.org/test/</Uri>
|
||||
</NamespaceUris>
|
||||
<Aliases>
|
||||
<Alias Alias="HasModellingRule">i=37</Alias>
|
||||
<Alias Alias="UInt64">i=9</Alias>
|
||||
<Alias Alias="Double">i=11</Alias>
|
||||
<Alias Alias="Organizes">i=35</Alias>
|
||||
<Alias Alias="HasTypeDefinition">i=40</Alias>
|
||||
<Alias Alias="HasSubtype">i=45</Alias>
|
||||
<Alias Alias="HasComponent">i=47</Alias>
|
||||
</Aliases>
|
||||
<UAObjectType BrowseName="1:PublisherInfo" NodeId="ns=1;i=2001">
|
||||
<DisplayName>PublisherInfo</DisplayName>
|
||||
<Description>PublisherInfo</Description>
|
||||
<References>
|
||||
<Reference IsForward="false" ReferenceType="HasSubtype">i=58</Reference>
|
||||
<Reference ReferenceType="HasComponent">ns=1;i=2002</Reference>
|
||||
<Reference ReferenceType="HasComponent">ns=1;i=2003</Reference>
|
||||
</References>
|
||||
</UAObjectType>
|
||||
<UAVariable BrowseName="1:PublisherCounterVariable" DataType="UInt64" NodeId="ns=1;i=2002" ParentNodeId="ns=1;i=2001">
|
||||
<DisplayName>PublisherCounterVariable</DisplayName>
|
||||
<Description>PublisherCounterVariable</Description>
|
||||
<References>
|
||||
<Reference IsForward="false" ReferenceType="HasComponent">ns=1;i=2001</Reference>
|
||||
<Reference ReferenceType="HasTypeDefinition">i=63</Reference>
|
||||
<Reference ReferenceType="HasModellingRule">i=78</Reference>
|
||||
</References>
|
||||
<Value>
|
||||
<uax:UInt64>0</uax:UInt64>
|
||||
</Value>
|
||||
</UAVariable>
|
||||
<UAVariable BrowseName="1:Pressure" DataType="Double" NodeId="ns=1;i=2003" ParentNodeId="ns=1;i=2001">
|
||||
<DisplayName>Pressure</DisplayName>
|
||||
<Description>Pressure</Description>
|
||||
<References>
|
||||
<Reference IsForward="false" ReferenceType="HasComponent">ns=1;i=2001</Reference>
|
||||
<Reference ReferenceType="HasTypeDefinition">i=63</Reference>
|
||||
<Reference ReferenceType="HasModellingRule">i=78</Reference>
|
||||
</References>
|
||||
<Value>
|
||||
<uax:Double>0.0</uax:Double>
|
||||
</Value>
|
||||
</UAVariable>
|
||||
<UAObject BrowseName="1:Publisher" NodeId="ns=1;i=2004" ParentNodeId="i=85">
|
||||
<DisplayName>Publisher</DisplayName>
|
||||
<Description>PublisherInfo</Description>
|
||||
<References>
|
||||
<Reference IsForward="false" ReferenceType="Organizes">i=85</Reference>
|
||||
<Reference ReferenceType="HasTypeDefinition">ns=1;i=2001</Reference>
|
||||
<Reference ReferenceType="HasComponent">ns=1;i=2005</Reference>
|
||||
<Reference ReferenceType="HasComponent">ns=1;i=2006</Reference>
|
||||
</References>
|
||||
</UAObject>
|
||||
<UAVariable BrowseName="1:PublisherCounterVariable" DataType="UInt64" NodeId="ns=1;i=2005" ParentNodeId="ns=1;i=2004">
|
||||
<DisplayName>PublisherCounterVariable</DisplayName>
|
||||
<Description>PublisherCounterVariable</Description>
|
||||
<References>
|
||||
<Reference IsForward="false" ReferenceType="HasComponent">ns=1;i=2004</Reference>
|
||||
<Reference ReferenceType="HasTypeDefinition">i=63</Reference>
|
||||
</References>
|
||||
<Value>
|
||||
<uax:UInt64>0</uax:UInt64>
|
||||
</Value>
|
||||
</UAVariable>
|
||||
<UAVariable BrowseName="1:Pressure" DataType="Double" NodeId="ns=1;i=2006" ParentNodeId="ns=1;i=2004">
|
||||
<DisplayName>Pressure</DisplayName>
|
||||
<Description>Pressure</Description>
|
||||
<References>
|
||||
<Reference IsForward="false" ReferenceType="HasComponent">ns=1;i=2004</Reference>
|
||||
<Reference ReferenceType="HasTypeDefinition">i=63</Reference>
|
||||
</References>
|
||||
<Value>
|
||||
<uax:Double>0.0</uax:Double>
|
||||
</Value>
|
||||
</UAVariable>
|
||||
</UANodeSet>
|
@ -1,736 +0,0 @@
|
||||
/* This work is licensed under a Creative Commons CCZero 1.0 Universal License.
|
||||
* See http://creativecommons.org/publicdomain/zero/1.0/ for more information. */
|
||||
|
||||
/**
|
||||
* .. _pubsub-nodeset-tutorial:
|
||||
*
|
||||
* Publisher Realtime example using custom nodes
|
||||
* ---------------------------------------------
|
||||
*
|
||||
* The purpose of this example file is to use the custom nodes of the XML
|
||||
* file(pubDataModel.xml) for publisher. This Publisher example uses the two
|
||||
* custom nodes (PublisherCounterVariable and Pressure) created using the XML
|
||||
* file(pubDataModel.xml) for publishing the packet. The pubDataModel.csv will
|
||||
* contain the nodeids of custom nodes(object and variables) and the nodeids of
|
||||
* the custom nodes are harcoded inside the addDataSetField API. This example
|
||||
* uses two threads namely the Publisher and UserApplication. The Publisher
|
||||
* thread is used to publish data at every cycle. The UserApplication thread
|
||||
* serves the functionality of the Control loop, which increments the
|
||||
* counterdata to be published by the Publisher and also writes the published
|
||||
* data in a csv along with transmission timestamp.
|
||||
*
|
||||
* Run steps of the Publisher application as mentioned below:
|
||||
*
|
||||
* ``./bin/examples/pubsub_nodeset_rt_publisher -i <iface>``
|
||||
*
|
||||
* For more information run ``./bin/examples/pubsub_nodeset_rt_publisher -h``. */
|
||||
|
||||
#define _GNU_SOURCE
|
||||
|
||||
/* For thread operations */
|
||||
#include <pthread.h>
|
||||
|
||||
#include <open62541/server.h>
|
||||
#include <open62541/server_config_default.h>
|
||||
#include <open62541/plugin/log_stdout.h>
|
||||
#include <open62541/types_generated.h>
|
||||
|
||||
#include "ua_pubsub.h"
|
||||
#include "open62541/namespace_example_publisher_generated.h"
|
||||
|
||||
/* to find load of each thread
|
||||
* ps -L -o pid,pri,%cpu -C pubsub_nodeset_rt_publisher */
|
||||
|
||||
/* Configurable Parameters */
|
||||
/* Cycle time in milliseconds */
|
||||
#define DEFAULT_CYCLE_TIME 0.25
|
||||
/* Qbv offset */
|
||||
#define QBV_OFFSET 25 * 1000
|
||||
#define DEFAULT_SOCKET_PRIORITY 3
|
||||
#define PUBLISHER_ID 2234
|
||||
#define WRITER_GROUP_ID 101
|
||||
#define DATA_SET_WRITER_ID 62541
|
||||
#define PUBLISHING_MAC_ADDRESS "opc.eth://01-00-5E-7F-00-01:8.3"
|
||||
#define PORT_NUMBER 62541
|
||||
|
||||
/* Non-Configurable Parameters */
|
||||
/* Milli sec and sec conversion to nano sec */
|
||||
#define MILLI_SECONDS 1000 * 1000
|
||||
#define SECONDS 1000 * 1000 * 1000
|
||||
#define SECONDS_SLEEP 5
|
||||
#define DEFAULT_PUB_SCHED_PRIORITY 78
|
||||
#define DEFAULT_PUBSUB_CORE 2
|
||||
#define DEFAULT_USER_APP_CORE 3
|
||||
#define MAX_MEASUREMENTS 30000000
|
||||
#define SECONDS_INCREMENT 1
|
||||
#define CLOCKID CLOCK_TAI
|
||||
#define ETH_TRANSPORT_PROFILE "http://opcfoundation.org/UA-Profile/Transport/pubsub-eth-uadp"
|
||||
#define DEFAULT_USERAPPLICATION_SCHED_PRIORITY 75
|
||||
|
||||
/* Below mentioned parameters can be provided as input using command line arguments
|
||||
* If user did not provide the below mentioned parameters as input through command line
|
||||
* argument then default value will be used */
|
||||
static UA_Double cycleTimeMsec = DEFAULT_CYCLE_TIME;
|
||||
static UA_Boolean consolePrint = UA_FALSE;
|
||||
static UA_Int32 socketPriority = DEFAULT_SOCKET_PRIORITY;
|
||||
static UA_Int32 pubPriority = DEFAULT_PUB_SCHED_PRIORITY;
|
||||
static UA_Int32 userAppPriority = DEFAULT_USERAPPLICATION_SCHED_PRIORITY;
|
||||
static UA_Int32 pubSubCore = DEFAULT_PUBSUB_CORE;
|
||||
static UA_Int32 userAppCore = DEFAULT_USER_APP_CORE;
|
||||
static UA_Boolean useSoTxtime = UA_TRUE;
|
||||
|
||||
/* User application Pub will wakeup at the 30% of cycle time and handles the */
|
||||
/* user data write in Information model */
|
||||
/* First 30% is left for subscriber for future use*/
|
||||
static UA_Double userAppWakeupPercentage = 0.3;
|
||||
/* Publisher will sleep for 60% of cycle time and then prepares the */
|
||||
/* transmission packet within 40% */
|
||||
/* after some prototyping and analyzing it */
|
||||
static UA_Double pubWakeupPercentage = 0.6;
|
||||
static UA_Boolean fileWrite = UA_FALSE;
|
||||
|
||||
/* If the Hardcoded publisher MAC addresses need to be changed,
|
||||
* change PUBLISHING_MAC_ADDRESS
|
||||
*/
|
||||
|
||||
/* Set server running as true */
|
||||
UA_Boolean running = UA_TRUE;
|
||||
UA_UInt16 nsIdx = 0;
|
||||
/* Variables corresponding to PubSub connection creation,
|
||||
* published data set and writer group */
|
||||
UA_NodeId connectionIdent;
|
||||
UA_NodeId publishedDataSetIdent;
|
||||
UA_NodeId writerGroupIdent;
|
||||
/* Variables for counter data handling in address space */
|
||||
UA_UInt64 *pubCounterData;
|
||||
UA_DataValue *pubDataValueRT;
|
||||
/* Variables for counter data handling in address space */
|
||||
UA_Double *pressureData;
|
||||
UA_DataValue *pressureValueRT;
|
||||
|
||||
/* File to store the data and timestamps for different traffic */
|
||||
FILE *fpPublisher;
|
||||
char *fileName = "publisher_T1.csv";
|
||||
/* Array to store published counter data */
|
||||
UA_UInt64 publishCounterValue[MAX_MEASUREMENTS];
|
||||
UA_Double pressureValues[MAX_MEASUREMENTS];
|
||||
size_t measurementsPublisher = 0;
|
||||
/* Array to store timestamp */
|
||||
struct timespec publishTimestamp[MAX_MEASUREMENTS];
|
||||
|
||||
/* Thread for publisher */
|
||||
pthread_t pubthreadID;
|
||||
struct timespec dataModificationTime;
|
||||
|
||||
/* Thread for user application*/
|
||||
pthread_t userApplicationThreadID;
|
||||
|
||||
typedef struct {
|
||||
UA_Server* ServerRun;
|
||||
} serverConfigStruct;
|
||||
|
||||
/* Structure to define thread parameters */
|
||||
typedef struct {
|
||||
UA_Server* server;
|
||||
void* data;
|
||||
UA_ServerCallback callback;
|
||||
UA_Duration interval_ms;
|
||||
UA_UInt64* callbackId;
|
||||
} threadArg;
|
||||
|
||||
/* Publisher thread routine for ETF */
|
||||
void *publisherETF(void *arg);
|
||||
/* User application thread routine */
|
||||
void *userApplicationPub(void *arg);
|
||||
/* To create multi-threads */
|
||||
static pthread_t threadCreation(UA_Int32 threadPriority, UA_Int32 coreAffinity, void *(*thread) (void *),
|
||||
char *applicationName, void *serverConfig);
|
||||
|
||||
/* Stop signal */
|
||||
static void stopHandler(int sign) {
|
||||
UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "received ctrl-c");
|
||||
running = UA_FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* **Nanosecond field handling**
|
||||
*
|
||||
* Nanosecond field in timespec is checked for overflowing and one second
|
||||
* is added to seconds field and nanosecond field is set to zero
|
||||
*/
|
||||
static void nanoSecondFieldConversion(struct timespec *timeSpecValue) {
|
||||
/* Check if ns field is greater than '1 ns less than 1sec' */
|
||||
while (timeSpecValue->tv_nsec > (SECONDS -1)) {
|
||||
/* Move to next second and remove it from ns field */
|
||||
timeSpecValue->tv_sec += SECONDS_INCREMENT;
|
||||
timeSpecValue->tv_nsec -= SECONDS;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* **Custom callback handling**
|
||||
*
|
||||
* Custom callback thread handling overwrites the default timer based
|
||||
* callback function with the custom (user-specified) callback interval. */
|
||||
/* Add a callback for cyclic repetition */
|
||||
static UA_StatusCode
|
||||
addPubSubApplicationCallback(UA_Server *server, UA_NodeId identifier,
|
||||
UA_ServerCallback callback,
|
||||
void *data, UA_Double interval_ms,
|
||||
UA_DateTime *baseTime, UA_TimerPolicy timerPolicy,
|
||||
UA_UInt64 *callbackId) {
|
||||
/* Initialize arguments required for the thread to run */
|
||||
threadArg *threadArguments = (threadArg *) UA_malloc(sizeof(threadArg));
|
||||
|
||||
/* Pass the value required for the threads */
|
||||
threadArguments->server = server;
|
||||
threadArguments->data = data;
|
||||
threadArguments->callback = callback;
|
||||
threadArguments->interval_ms = interval_ms;
|
||||
threadArguments->callbackId = callbackId;
|
||||
/* Create the publisher thread with the required priority and core affinity */
|
||||
char threadNamePub[10] = "Publisher";
|
||||
pubthreadID = threadCreation(pubPriority, pubSubCore, publisherETF, threadNamePub, threadArguments);
|
||||
return UA_STATUSCODE_GOOD;
|
||||
}
|
||||
|
||||
static UA_StatusCode
|
||||
changePubSubApplicationCallback(UA_Server *server, UA_NodeId identifier,
|
||||
UA_UInt64 callbackId, UA_Double interval_ms,
|
||||
UA_DateTime *baseTime, UA_TimerPolicy timerPolicy) {
|
||||
/* Callback interval need not be modified as it is thread based implementation.
|
||||
* The thread uses nanosleep for calculating cycle time and modification in
|
||||
* nanosleep value changes cycle time */
|
||||
return UA_STATUSCODE_GOOD;
|
||||
}
|
||||
|
||||
/* Remove the callback added for cyclic repetition */
|
||||
static void
|
||||
removePubSubApplicationCallback(UA_Server *server, UA_NodeId identifier, UA_UInt64 callbackId){
|
||||
if(callbackId && (pthread_join((pthread_t)callbackId, NULL) != 0))
|
||||
UA_LOG_WARNING(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
|
||||
"Pthread Join Failed thread: %lu\n", (long unsigned)callbackId);
|
||||
}
|
||||
|
||||
/**
|
||||
* **External data source handling**
|
||||
*
|
||||
* If the external data source is written over the information model, the
|
||||
* externalDataWriteCallback will be triggered. The user has to take care and assure
|
||||
* that the write leads not to synchronization issues and race conditions. */
|
||||
static UA_StatusCode
|
||||
externalDataWriteCallback(UA_Server *server, const UA_NodeId *sessionId,
|
||||
void *sessionContext, const UA_NodeId *nodeId,
|
||||
void *nodeContext, const UA_NumericRange *range,
|
||||
const UA_DataValue *data){
|
||||
//node values are updated by using variables in the memory
|
||||
//UA_Server_write is not used for updating node values.
|
||||
return UA_STATUSCODE_GOOD;
|
||||
}
|
||||
|
||||
static UA_StatusCode
|
||||
externalDataReadNotificationCallback(UA_Server *server, const UA_NodeId *sessionId,
|
||||
void *sessionContext, const UA_NodeId *nodeid,
|
||||
void *nodeContext, const UA_NumericRange *range){
|
||||
//allow read without any preparation
|
||||
return UA_STATUSCODE_GOOD;
|
||||
}
|
||||
|
||||
/**
|
||||
* **PubSub connection handling**
|
||||
*
|
||||
* Create a new ConnectionConfig. The addPubSubConnection function takes the
|
||||
* config and creates a new connection. The Connection identifier is
|
||||
* copied to the NodeId parameter.
|
||||
*/
|
||||
static void
|
||||
addPubSubConnection(UA_Server *server, UA_NetworkAddressUrlDataType *networkAddressUrlPub){
|
||||
/* Details about the connection configuration and handling are located
|
||||
* in the pubsub connection tutorial */
|
||||
UA_PubSubConnectionConfig connectionConfig;
|
||||
memset(&connectionConfig, 0, sizeof(connectionConfig));
|
||||
connectionConfig.name = UA_STRING("Publisher Connection");
|
||||
connectionConfig.enabled = UA_TRUE;
|
||||
UA_NetworkAddressUrlDataType networkAddressUrl = *networkAddressUrlPub;
|
||||
connectionConfig.transportProfileUri = UA_STRING(ETH_TRANSPORT_PROFILE);
|
||||
UA_Variant_setScalar(&connectionConfig.address, &networkAddressUrl,
|
||||
&UA_TYPES[UA_TYPES_NETWORKADDRESSURLDATATYPE]);
|
||||
connectionConfig.publisherIdType = UA_PUBLISHERIDTYPE_UINT16;
|
||||
connectionConfig.publisherId.uint16 = PUBLISHER_ID;
|
||||
/* Connection options are given as Key/Value Pairs - Sockprio and Txtime */
|
||||
UA_KeyValuePair connectionOptions[2];
|
||||
connectionOptions[0].key = UA_QUALIFIEDNAME(0, "sockpriority");
|
||||
UA_UInt32 sockPriority = (UA_UInt32)socketPriority;
|
||||
UA_Variant_setScalar(&connectionOptions[0].value, &sockPriority, &UA_TYPES[UA_TYPES_UINT32]);
|
||||
connectionOptions[1].key = UA_QUALIFIEDNAME(0, "enablesotxtime");
|
||||
UA_Boolean enableTxTime = UA_TRUE;
|
||||
UA_Variant_setScalar(&connectionOptions[1].value, &enableTxTime, &UA_TYPES[UA_TYPES_BOOLEAN]);
|
||||
connectionConfig.connectionProperties.map = connectionOptions;
|
||||
connectionConfig.connectionProperties.mapSize = 2;
|
||||
UA_Server_addPubSubConnection(server, &connectionConfig, &connectionIdent);
|
||||
}
|
||||
|
||||
/**
|
||||
* **PublishedDataSet handling**
|
||||
*
|
||||
* Details about the connection configuration and handling are located
|
||||
* in the pubsub connection tutorial
|
||||
*/
|
||||
static void
|
||||
addPublishedDataSet(UA_Server *server) {
|
||||
UA_PublishedDataSetConfig publishedDataSetConfig;
|
||||
memset(&publishedDataSetConfig, 0, sizeof(UA_PublishedDataSetConfig));
|
||||
publishedDataSetConfig.publishedDataSetType = UA_PUBSUB_DATASET_PUBLISHEDITEMS;
|
||||
publishedDataSetConfig.name = UA_STRING("Demo PDS");
|
||||
UA_Server_addPublishedDataSet(server, &publishedDataSetConfig, &publishedDataSetIdent);
|
||||
}
|
||||
|
||||
/**
|
||||
* **DataSetField handling**
|
||||
*
|
||||
* The DataSetField (DSF) is part of the PDS and describes exactly one
|
||||
* published field.
|
||||
*/
|
||||
/* This example only uses two addDataSetField which uses the custom nodes of the XML file
|
||||
* (pubDataModel.xml) */
|
||||
static void
|
||||
_addDataSetField(UA_Server *server) {
|
||||
UA_NodeId dataSetFieldIdent;
|
||||
UA_DataSetFieldConfig dsfConfig;
|
||||
memset(&dsfConfig, 0, sizeof(UA_DataSetFieldConfig));
|
||||
pubCounterData = UA_UInt64_new();
|
||||
*pubCounterData = 0;
|
||||
pubDataValueRT = UA_DataValue_new();
|
||||
UA_Variant_setScalar(&pubDataValueRT->value, pubCounterData, &UA_TYPES[UA_TYPES_UINT64]);
|
||||
pubDataValueRT->hasValue = UA_TRUE;
|
||||
/* Set the value backend of the above create node to 'external value source' */
|
||||
UA_ValueBackend valueBackend;
|
||||
valueBackend.backendType = UA_VALUEBACKENDTYPE_EXTERNAL;
|
||||
valueBackend.backend.external.value = &pubDataValueRT;
|
||||
valueBackend.backend.external.callback.userWrite = externalDataWriteCallback;
|
||||
valueBackend.backend.external.callback.notificationRead = externalDataReadNotificationCallback;
|
||||
/* If user need to change the nodeid of the custom nodes in the application then it must be
|
||||
* changed inside the xml and .csv file inside examples\pubsub_realtime\nodeset\*/
|
||||
/* The nodeid of the Custom node PublisherCounterVariable is 2005 which is used below */
|
||||
UA_Server_setVariableNode_valueBackend(server, UA_NODEID_NUMERIC(nsIdx, 2005), valueBackend);
|
||||
/* setup RT DataSetField config */
|
||||
dsfConfig.field.variable.rtValueSource.rtInformationModelNode = UA_TRUE;
|
||||
dsfConfig.field.variable.publishParameters.publishedVariable = UA_NODEID_NUMERIC(nsIdx, 2005);
|
||||
UA_Server_addDataSetField(server, publishedDataSetIdent, &dsfConfig, &dataSetFieldIdent);
|
||||
UA_NodeId dataSetFieldIdent1;
|
||||
UA_DataSetFieldConfig dsfConfig1;
|
||||
memset(&dsfConfig1, 0, sizeof(UA_DataSetFieldConfig));
|
||||
pressureData = UA_Double_new();
|
||||
*pressureData = 17.07;
|
||||
pressureValueRT = UA_DataValue_new();
|
||||
UA_Variant_setScalar(&pressureValueRT->value, pressureData, &UA_TYPES[UA_TYPES_DOUBLE]);
|
||||
pressureValueRT->hasValue = UA_TRUE;
|
||||
/* Set the value backend of the above create node to 'external value source' */
|
||||
UA_ValueBackend valueBackend1;
|
||||
valueBackend1.backendType = UA_VALUEBACKENDTYPE_EXTERNAL;
|
||||
valueBackend1.backend.external.value = &pressureValueRT;
|
||||
valueBackend1.backend.external.callback.userWrite = externalDataWriteCallback;
|
||||
valueBackend1.backend.external.callback.notificationRead = externalDataReadNotificationCallback;
|
||||
/* The nodeid of the Custom node Pressure is 2006 which is used below */
|
||||
UA_Server_setVariableNode_valueBackend(server, UA_NODEID_NUMERIC(nsIdx, 2006), valueBackend1);
|
||||
/* setup RT DataSetField config */
|
||||
dsfConfig1.field.variable.rtValueSource.rtInformationModelNode = UA_TRUE;
|
||||
dsfConfig1.field.variable.publishParameters.publishedVariable = UA_NODEID_NUMERIC(nsIdx, 2006);
|
||||
UA_Server_addDataSetField(server, publishedDataSetIdent, &dsfConfig1, &dataSetFieldIdent1);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* **WriterGroup handling**
|
||||
*
|
||||
* The WriterGroup (WG) is part of the connection and contains the primary
|
||||
* configuration parameters for the message creation.
|
||||
*/
|
||||
static void
|
||||
addWriterGroup(UA_Server *server) {
|
||||
UA_WriterGroupConfig writerGroupConfig;
|
||||
memset(&writerGroupConfig, 0, sizeof(UA_WriterGroupConfig));
|
||||
writerGroupConfig.name = UA_STRING("Demo WriterGroup");
|
||||
writerGroupConfig.publishingInterval = cycleTimeMsec;
|
||||
writerGroupConfig.enabled = UA_FALSE;
|
||||
writerGroupConfig.encodingMimeType = UA_PUBSUB_ENCODING_UADP;
|
||||
writerGroupConfig.writerGroupId = WRITER_GROUP_ID;
|
||||
writerGroupConfig.rtLevel = UA_PUBSUB_RT_FIXED_SIZE;
|
||||
writerGroupConfig.pubsubManagerCallback.addCustomCallback = addPubSubApplicationCallback;
|
||||
writerGroupConfig.pubsubManagerCallback.changeCustomCallback = changePubSubApplicationCallback;
|
||||
writerGroupConfig.pubsubManagerCallback.removeCustomCallback = removePubSubApplicationCallback;
|
||||
|
||||
writerGroupConfig.messageSettings.encoding = UA_EXTENSIONOBJECT_DECODED;
|
||||
writerGroupConfig.messageSettings.content.decoded.type = &UA_TYPES[UA_TYPES_UADPWRITERGROUPMESSAGEDATATYPE];
|
||||
/* The configuration flags for the messages are encapsulated inside the
|
||||
* message- and transport settings extension objects. These extension
|
||||
* objects are defined by the standard. e.g.
|
||||
* UadpWriterGroupMessageDataType */
|
||||
UA_UadpWriterGroupMessageDataType *writerGroupMessage = UA_UadpWriterGroupMessageDataType_new();
|
||||
/* Change message settings of writerGroup to send PublisherId,
|
||||
* WriterGroupId in GroupHeader and DataSetWriterId in PayloadHeader
|
||||
* of NetworkMessage */
|
||||
writerGroupMessage->networkMessageContentMask = (UA_UadpNetworkMessageContentMask)(UA_UADPNETWORKMESSAGECONTENTMASK_PUBLISHERID |
|
||||
(UA_UadpNetworkMessageContentMask)UA_UADPNETWORKMESSAGECONTENTMASK_GROUPHEADER |
|
||||
(UA_UadpNetworkMessageContentMask)UA_UADPNETWORKMESSAGECONTENTMASK_WRITERGROUPID |
|
||||
(UA_UadpNetworkMessageContentMask)UA_UADPNETWORKMESSAGECONTENTMASK_PAYLOADHEADER);
|
||||
writerGroupConfig.messageSettings.content.decoded.data = writerGroupMessage;
|
||||
UA_Server_addWriterGroup(server, connectionIdent, &writerGroupConfig, &writerGroupIdent);
|
||||
UA_Server_enableWriterGroup(server, writerGroupIdent);
|
||||
UA_UadpWriterGroupMessageDataType_delete(writerGroupMessage);
|
||||
}
|
||||
|
||||
/**
|
||||
* **DataSetWriter handling**
|
||||
*
|
||||
* A DataSetWriter (DSW) is the glue between the WG and the PDS. The DSW is
|
||||
* linked to exactly one PDS and contains additional information for the
|
||||
* message generation.
|
||||
*/
|
||||
static void
|
||||
addDataSetWriter(UA_Server *server) {
|
||||
UA_NodeId dataSetWriterIdent;
|
||||
UA_DataSetWriterConfig dataSetWriterConfig;
|
||||
memset(&dataSetWriterConfig, 0, sizeof(UA_DataSetWriterConfig));
|
||||
dataSetWriterConfig.name = UA_STRING("Demo DataSetWriter");
|
||||
dataSetWriterConfig.dataSetWriterId = DATA_SET_WRITER_ID;
|
||||
dataSetWriterConfig.keyFrameCount = 10;
|
||||
UA_Server_addDataSetWriter(server, writerGroupIdent, publishedDataSetIdent,
|
||||
&dataSetWriterConfig, &dataSetWriterIdent);
|
||||
}
|
||||
|
||||
/**
|
||||
* **Published data handling**
|
||||
*
|
||||
* The published data is updated in the array using this function
|
||||
*/
|
||||
static void
|
||||
updateMeasurementsPublisher(struct timespec start_time,
|
||||
UA_UInt64 counterValue, UA_Double pressureValue) {
|
||||
publishTimestamp[measurementsPublisher] = start_time;
|
||||
publishCounterValue[measurementsPublisher] = counterValue;
|
||||
pressureValues[measurementsPublisher] = pressureValue;
|
||||
measurementsPublisher++;
|
||||
}
|
||||
|
||||
/**
|
||||
* **Publisher thread routine**
|
||||
*
|
||||
* The Publisher thread sleeps for 60% of the cycletime (250us) and prepares the tranmission packet within 40% of
|
||||
* cycletime. The data published by this thread in one cycle is subscribed by the subscriber thread of pubsub_nodeset_rt_subscriber in the
|
||||
* next cycle (two cycle timing model).
|
||||
*
|
||||
* The publisherETF function is the routine used by the publisher thread.
|
||||
*/
|
||||
void *publisherETF(void *arg) {
|
||||
struct timespec nextnanosleeptime;
|
||||
UA_ServerCallback pubCallback;
|
||||
UA_Server* server;
|
||||
UA_WriterGroup* currentWriterGroup;
|
||||
UA_UInt64 interval_ns;
|
||||
UA_UInt64 transmission_time;
|
||||
|
||||
/* Initialise value for nextnanosleeptime timespec */
|
||||
nextnanosleeptime.tv_nsec = 0;
|
||||
|
||||
threadArg *threadArgumentsPublisher = (threadArg *)arg;
|
||||
server = threadArgumentsPublisher->server;
|
||||
pubCallback = threadArgumentsPublisher->callback;
|
||||
currentWriterGroup = (UA_WriterGroup *)threadArgumentsPublisher->data;
|
||||
interval_ns = (UA_UInt64)(threadArgumentsPublisher->interval_ms * MILLI_SECONDS);
|
||||
|
||||
/* Get current time and compute the next nanosleeptime */
|
||||
clock_gettime(CLOCKID, &nextnanosleeptime);
|
||||
/* Variable to nano Sleep until 1ms before a 1 second boundary */
|
||||
nextnanosleeptime.tv_sec += SECONDS_SLEEP;
|
||||
nextnanosleeptime.tv_nsec = (__syscall_slong_t)(cycleTimeMsec * pubWakeupPercentage * MILLI_SECONDS);
|
||||
nanoSecondFieldConversion(&nextnanosleeptime);
|
||||
|
||||
/* Define Ethernet ETF transport settings */
|
||||
UA_EthernetWriterGroupTransportDataType ethernettransportSettings;
|
||||
memset(ðernettransportSettings, 0, sizeof(UA_EthernetWriterGroupTransportDataType));
|
||||
ethernettransportSettings.transmission_time = 0;
|
||||
|
||||
/* Encapsulate ETF config in transportSettings */
|
||||
UA_ExtensionObject transportSettings;
|
||||
memset(&transportSettings, 0, sizeof(UA_ExtensionObject));
|
||||
/* TODO: transportSettings encoding and type to be defined */
|
||||
transportSettings.content.decoded.data = ðernettransportSettings;
|
||||
currentWriterGroup->config.transportSettings = transportSettings;
|
||||
UA_UInt64 roundOffCycleTime = (UA_UInt64)((cycleTimeMsec * MILLI_SECONDS) - (cycleTimeMsec * pubWakeupPercentage * MILLI_SECONDS));
|
||||
|
||||
while (running) {
|
||||
clock_nanosleep(CLOCKID, TIMER_ABSTIME, &nextnanosleeptime, NULL);
|
||||
transmission_time = ((UA_UInt64)nextnanosleeptime.tv_sec * SECONDS + (UA_UInt64)nextnanosleeptime.tv_nsec) + roundOffCycleTime + QBV_OFFSET;
|
||||
ethernettransportSettings.transmission_time = transmission_time;
|
||||
pubCallback(server, currentWriterGroup);
|
||||
nextnanosleeptime.tv_nsec += (__syscall_slong_t)interval_ns;
|
||||
nanoSecondFieldConversion(&nextnanosleeptime);
|
||||
}
|
||||
|
||||
UA_free(threadArgumentsPublisher);
|
||||
|
||||
return (void*)NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* **UserApplication thread routine**
|
||||
*
|
||||
* The userapplication thread will wakeup at 30% of cycle time and handles the userdata in the Information Model.
|
||||
* This thread is used to increment the counterdata that will be published by the Publisher thread and also writes the published data in a csv.
|
||||
*/
|
||||
void *userApplicationPub(void *arg) {
|
||||
struct timespec nextnanosleeptimeUserApplication;
|
||||
/* Get current time and compute the next nanosleeptime */
|
||||
clock_gettime(CLOCKID, &nextnanosleeptimeUserApplication);
|
||||
/* Variable to nano Sleep until 1ms before a 1 second boundary */
|
||||
nextnanosleeptimeUserApplication.tv_sec += SECONDS_SLEEP;
|
||||
nextnanosleeptimeUserApplication.tv_nsec = (__syscall_slong_t)(cycleTimeMsec * userAppWakeupPercentage * MILLI_SECONDS);
|
||||
nanoSecondFieldConversion(&nextnanosleeptimeUserApplication);
|
||||
*pubCounterData = 0;
|
||||
while (running) {
|
||||
clock_nanosleep(CLOCKID, TIMER_ABSTIME, &nextnanosleeptimeUserApplication, NULL);
|
||||
*pubCounterData = *pubCounterData + 1;
|
||||
*pressureData = *pressureData + 1;
|
||||
clock_gettime(CLOCKID, &dataModificationTime);
|
||||
if ((fileWrite == UA_TRUE) || (consolePrint == UA_TRUE))
|
||||
updateMeasurementsPublisher(dataModificationTime, *pubCounterData, *pressureData);
|
||||
nextnanosleeptimeUserApplication.tv_nsec += (__syscall_slong_t)(cycleTimeMsec * MILLI_SECONDS);
|
||||
nanoSecondFieldConversion(&nextnanosleeptimeUserApplication);
|
||||
}
|
||||
|
||||
return (void*)NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* **Thread creation**
|
||||
*
|
||||
* The threadcreation functionality creates thread with given threadpriority, coreaffinity. The function returns the threadID of the newly
|
||||
* created thread.
|
||||
*/
|
||||
static pthread_t threadCreation(UA_Int32 threadPriority, UA_Int32 coreAffinity, void *(*thread) (void *), char *applicationName, void *serverConfig){
|
||||
|
||||
/* Core affinity set */
|
||||
cpu_set_t cpuset;
|
||||
pthread_t threadID;
|
||||
struct sched_param schedParam;
|
||||
UA_Int32 returnValue = 0;
|
||||
UA_Int32 errorSetAffinity = 0;
|
||||
/* Return the ID for thread */
|
||||
threadID = pthread_self();
|
||||
schedParam.sched_priority = threadPriority;
|
||||
returnValue = pthread_setschedparam(threadID, SCHED_FIFO, &schedParam);
|
||||
if (returnValue != 0) {
|
||||
UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,"pthread_setschedparam: failed\n");
|
||||
exit(1);
|
||||
}
|
||||
UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,\
|
||||
"\npthread_setschedparam:%s Thread priority is %d \n", \
|
||||
applicationName, schedParam.sched_priority);
|
||||
CPU_ZERO(&cpuset);
|
||||
CPU_SET((size_t)coreAffinity, &cpuset);
|
||||
errorSetAffinity = pthread_setaffinity_np(threadID, sizeof(cpu_set_t), &cpuset);
|
||||
if (errorSetAffinity) {
|
||||
fprintf(stderr, "pthread_setaffinity_np: %s\n", strerror(errorSetAffinity));
|
||||
exit(1);
|
||||
}
|
||||
|
||||
returnValue = pthread_create(&threadID, NULL, thread, serverConfig);
|
||||
if (returnValue != 0) {
|
||||
UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,":%s Cannot create thread\n", applicationName);
|
||||
}
|
||||
|
||||
if (CPU_ISSET((size_t)coreAffinity, &cpuset)) {
|
||||
UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,"%s CPU CORE: %d\n", applicationName, coreAffinity);
|
||||
}
|
||||
|
||||
return threadID;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* **Usage function**
|
||||
*
|
||||
* The usage function gives the list of options that can be configured in the application.
|
||||
*
|
||||
* ./bin/examples/pubsub_nodeset_rt_publisher -h gives the list of options for running the application.
|
||||
*/
|
||||
static void usage(char *appname)
|
||||
{
|
||||
fprintf(stderr,
|
||||
"\n"
|
||||
"usage: %s [options]\n"
|
||||
"\n"
|
||||
" -i [name] use network interface 'name'\n"
|
||||
" -C [num] cycle time in milli seconds (default %lf)\n"
|
||||
" -p Do you need to print the data in console output\n"
|
||||
" -s [num] set SO_PRIORITY to 'num' (default %d)\n"
|
||||
" -P [num] Publisher priority value (default %d)\n"
|
||||
" -U [num] User application priority value (default %d)\n"
|
||||
" -c [num] run on CPU for publisher'num'(default %d)\n"
|
||||
" -u [num] run on CPU for userApplication'num'(default %d)\n"
|
||||
" -t do not use SO_TXTIME\n"
|
||||
" -m [mac_addr] ToDO:dst MAC address\n"
|
||||
" -h prints this message and exits\n"
|
||||
"\n",
|
||||
appname, DEFAULT_CYCLE_TIME, DEFAULT_SOCKET_PRIORITY, DEFAULT_PUB_SCHED_PRIORITY, \
|
||||
DEFAULT_USERAPPLICATION_SCHED_PRIORITY, DEFAULT_PUBSUB_CORE, DEFAULT_USER_APP_CORE);
|
||||
}
|
||||
|
||||
/**
|
||||
* **Main Server code**
|
||||
*
|
||||
* The main function contains publisher threads running
|
||||
*/
|
||||
int main(int argc, char **argv) {
|
||||
signal(SIGINT, stopHandler);
|
||||
signal(SIGTERM, stopHandler);
|
||||
|
||||
UA_Int32 returnValue = 0;
|
||||
char *interface = NULL;
|
||||
char *progname;
|
||||
UA_Int32 argInputs = -1;
|
||||
UA_StatusCode retval = UA_STATUSCODE_GOOD;
|
||||
UA_Server *server = UA_Server_new();
|
||||
UA_ServerConfig *config = UA_Server_getConfig(server);
|
||||
pthread_t userThreadID;
|
||||
UA_ServerConfig_setMinimal(config, PORT_NUMBER, NULL);
|
||||
|
||||
/* Files namespace_example_publisher_generated.h and namespace_example_publisher_generated.c are created from
|
||||
* pubDataModel.xml in the /src_generated directory by CMake */
|
||||
/* Loading the user created variables into the information model from the generated .c and .h files */
|
||||
if(namespace_example_publisher_generated(server) != UA_STATUSCODE_GOOD) {
|
||||
UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "Could not add the example nodeset. "
|
||||
"Check previous output for any error.");
|
||||
}
|
||||
else
|
||||
{
|
||||
nsIdx = UA_Server_addNamespace(server, "http://yourorganisation.org/test/");
|
||||
}
|
||||
|
||||
UA_NetworkAddressUrlDataType networkAddressUrlPub;
|
||||
|
||||
/* Process the command line arguments */
|
||||
/* For more information run ./bin/examples/pubsub_nodeset_rt_publisher -h */
|
||||
progname = strrchr(argv[0], '/');
|
||||
progname = progname ? 1 + progname : argv[0];
|
||||
while (EOF != (argInputs = getopt(argc, argv, "i:C:f:ps:P:U:c:u:tm:h:"))) {
|
||||
switch (argInputs) {
|
||||
case 'i':
|
||||
interface = optarg;
|
||||
break;
|
||||
case 'C':
|
||||
cycleTimeMsec = atof(optarg);
|
||||
break;
|
||||
case 'f':
|
||||
fileName = optarg;
|
||||
fileWrite = UA_TRUE;
|
||||
fpPublisher = fopen(fileName, "w");
|
||||
break;
|
||||
case 'p':
|
||||
consolePrint = UA_TRUE;
|
||||
break;
|
||||
case 's':
|
||||
socketPriority = atoi(optarg);
|
||||
break;
|
||||
case 'P':
|
||||
pubPriority = atoi(optarg);
|
||||
break;
|
||||
case 'U':
|
||||
userAppPriority = atoi(optarg);
|
||||
break;
|
||||
case 'c':
|
||||
pubSubCore = atoi(optarg);
|
||||
break;
|
||||
case 'u':
|
||||
userAppCore = atoi(optarg);
|
||||
break;
|
||||
case 't':
|
||||
useSoTxtime = UA_FALSE;
|
||||
break;
|
||||
case 'm':
|
||||
/*ToDo:Need to handle for mac address*/
|
||||
break;
|
||||
case 'h':
|
||||
usage(progname);
|
||||
return -1;
|
||||
case '?':
|
||||
usage(progname);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
if (cycleTimeMsec < 0.125) {
|
||||
UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "%f Bad cycle time", cycleTimeMsec);
|
||||
usage(progname);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!interface) {
|
||||
UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "Need a network interface to run");
|
||||
usage(progname);
|
||||
UA_Server_delete(server);
|
||||
return 0;
|
||||
}
|
||||
|
||||
networkAddressUrlPub.networkInterface = UA_STRING(interface);
|
||||
networkAddressUrlPub.url = UA_STRING(PUBLISHING_MAC_ADDRESS);
|
||||
|
||||
addPubSubConnection(server, &networkAddressUrlPub);
|
||||
addPublishedDataSet(server);
|
||||
_addDataSetField(server);
|
||||
addWriterGroup(server);
|
||||
addDataSetWriter(server);
|
||||
UA_Server_freezeWriterGroupConfiguration(server, writerGroupIdent);
|
||||
|
||||
serverConfigStruct *serverConfig;
|
||||
serverConfig = (serverConfigStruct*)UA_malloc(sizeof(serverConfigStruct));
|
||||
serverConfig->ServerRun = server;
|
||||
char threadNameUserApplication[22] = "UserApplicationPub";
|
||||
userThreadID = threadCreation(userAppPriority, userAppCore, userApplicationPub, threadNameUserApplication, serverConfig);
|
||||
retval |= UA_Server_run(server, &running);
|
||||
returnValue = pthread_join(pubthreadID, NULL);
|
||||
if (returnValue != 0) {
|
||||
UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,"\nPthread Join Failed for publisher thread:%d\n", returnValue);
|
||||
}
|
||||
returnValue = pthread_join(userThreadID, NULL);
|
||||
if (returnValue != 0) {
|
||||
UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,"\nPthread Join Failed for User thread:%d\n", returnValue);
|
||||
}
|
||||
|
||||
if (fileWrite == UA_TRUE) {
|
||||
/* Write the published data in a file */
|
||||
size_t pubLoopVariable = 0;
|
||||
for (pubLoopVariable = 0; pubLoopVariable < measurementsPublisher;
|
||||
pubLoopVariable++) {
|
||||
fprintf(fpPublisher, "%lu,%ld.%09ld,%lf\n",
|
||||
(long unsigned)publishCounterValue[pubLoopVariable],
|
||||
publishTimestamp[pubLoopVariable].tv_sec,
|
||||
publishTimestamp[pubLoopVariable].tv_nsec,
|
||||
pressureValues[pubLoopVariable]);
|
||||
}
|
||||
fclose(fpPublisher);
|
||||
}
|
||||
if (consolePrint == UA_TRUE) {
|
||||
size_t pubLoopVariable = 0;
|
||||
for (pubLoopVariable = 0; pubLoopVariable < measurementsPublisher;
|
||||
pubLoopVariable++) {
|
||||
printf("%lu,%ld.%09ld,%lf\n",
|
||||
(long unsigned)publishCounterValue[pubLoopVariable],
|
||||
publishTimestamp[pubLoopVariable].tv_sec,
|
||||
publishTimestamp[pubLoopVariable].tv_nsec,
|
||||
pressureValues[pubLoopVariable]);
|
||||
}
|
||||
}
|
||||
|
||||
UA_Server_delete(server);
|
||||
UA_free(serverConfig);
|
||||
UA_free(pubCounterData);
|
||||
/* Free external data source */
|
||||
UA_free(pubDataValueRT);
|
||||
UA_free(pressureData);
|
||||
/* Free external data source */
|
||||
UA_free(pressureValueRT);
|
||||
return (int)retval;
|
||||
}
|
@ -1,656 +0,0 @@
|
||||
/* This work is licensed under a Creative Commons CCZero 1.0 Universal License.
|
||||
* See http://creativecommons.org/publicdomain/zero/1.0/ for more information. */
|
||||
|
||||
/**
|
||||
* .. _pubsub-nodeset-subscriber-tutorial:
|
||||
*
|
||||
* Subscriber Realtime example using custom nodes
|
||||
* ----------------------------------------------
|
||||
*
|
||||
* The purpose of this example file is to use the custom nodes of the XML
|
||||
* file(subDataModel.xml) for subscriber. This Subscriber example uses the two
|
||||
* custom nodes (SubscriberCounterVariable and Pressure) created using the XML
|
||||
* file(subDataModel.xml) for subscribing the packet. The subDataModel.csv will
|
||||
* contain the nodeids of custom nodes(object and variables) and the nodeids of
|
||||
* the custom nodes are harcoded inside the addSubscribedVariables API
|
||||
*
|
||||
* This example uses two threads namely the Subscriber and UserApplication. The
|
||||
* Subscriber thread is used to subscribe to data at every cycle. The
|
||||
* UserApplication thread serves the functionality of the Control loop, which
|
||||
* reads the Information Model of the Subscriber and the new counterdata will be
|
||||
* written in the csv along with received timestamp.
|
||||
*
|
||||
* Run steps of the Subscriber application as mentioned below:
|
||||
*
|
||||
* ``./bin/examples/pubsub_nodeset_rt_subscriber -i <iface>``
|
||||
*
|
||||
* For more information run ``./bin/examples/pubsub_nodeset_rt_subscriber -h``. */
|
||||
|
||||
#define _GNU_SOURCE
|
||||
|
||||
/* For thread operations */
|
||||
#include <pthread.h>
|
||||
|
||||
#include <open62541/server.h>
|
||||
#include <open62541/server_config_default.h>
|
||||
#include <open62541/plugin/log_stdout.h>
|
||||
#include <open62541/types_generated.h>
|
||||
|
||||
#include "ua_pubsub.h"
|
||||
#include "open62541/namespace_example_subscriber_generated.h"
|
||||
|
||||
UA_NodeId readerGroupIdentifier;
|
||||
UA_NodeId readerIdentifier;
|
||||
UA_DataSetReaderConfig readerConfig;
|
||||
|
||||
/* to find load of each thread
|
||||
* ps -L -o pid,pri,%cpu -C pubsub_nodeset_rt_subscriber*/
|
||||
|
||||
/* Configurable Parameters */
|
||||
/* Cycle time in milliseconds */
|
||||
#define DEFAULT_CYCLE_TIME 0.25
|
||||
/* Qbv offset */
|
||||
#define QBV_OFFSET 25 * 1000
|
||||
#define DEFAULT_SOCKET_PRIORITY 3
|
||||
#define PUBLISHER_ID_SUB 2234
|
||||
#define WRITER_GROUP_ID_SUB 101
|
||||
#define DATA_SET_WRITER_ID_SUB 62541
|
||||
#define SUBSCRIBING_MAC_ADDRESS "opc.eth://01-00-5E-7F-00-01:8.3"
|
||||
#define PORT_NUMBER 62541
|
||||
|
||||
/* Non-Configurable Parameters */
|
||||
/* Milli sec and sec conversion to nano sec */
|
||||
#define MILLI_SECONDS 1000 * 1000
|
||||
#define SECONDS 1000 * 1000 * 1000
|
||||
#define SECONDS_SLEEP 5
|
||||
#define DEFAULT_SUB_SCHED_PRIORITY 81
|
||||
#define MAX_MEASUREMENTS 30000000
|
||||
#define DEFAULT_PUBSUB_CORE 2
|
||||
#define DEFAULT_USER_APP_CORE 3
|
||||
#define SECONDS_INCREMENT 1
|
||||
#define CLOCKID CLOCK_TAI
|
||||
#define ETH_TRANSPORT_PROFILE "http://opcfoundation.org/UA-Profile/Transport/pubsub-eth-uadp"
|
||||
#define DEFAULT_USERAPPLICATION_SCHED_PRIORITY 75
|
||||
|
||||
/* Below mentioned parameters can be provided as input using command line arguments
|
||||
* If user did not provide the below mentioned parameters as input through command line
|
||||
* argument then default value will be used */
|
||||
static UA_Double cycleTimeMsec = DEFAULT_CYCLE_TIME;
|
||||
static UA_Boolean consolePrint = UA_FALSE;
|
||||
static UA_Int32 subPriority = DEFAULT_SUB_SCHED_PRIORITY;
|
||||
static UA_Int32 userAppPriority = DEFAULT_USERAPPLICATION_SCHED_PRIORITY;
|
||||
static UA_Int32 pubSubCore = DEFAULT_PUBSUB_CORE;
|
||||
static UA_Int32 userAppCore = DEFAULT_USER_APP_CORE;
|
||||
/* User application Pub will wakeup at the 30% of cycle time and handles the */
|
||||
/* user data write in Information model */
|
||||
/* After 60% is left for publisher */
|
||||
static UA_Double userAppWakeupPercentage = 0.3;
|
||||
/* Subscriber will wake up at the start of cycle time and then receives the packet */
|
||||
static UA_Double subWakeupPercentage = 0;
|
||||
static UA_Boolean fileWrite = UA_FALSE;
|
||||
|
||||
/* Set server running as true */
|
||||
UA_Boolean running = UA_TRUE;
|
||||
UA_UInt16 nsIdx = 0;
|
||||
|
||||
UA_UInt64 *subCounterData;
|
||||
UA_DataValue *subDataValueRT;
|
||||
UA_Double *pressureData;
|
||||
UA_DataValue *pressureValueRT;
|
||||
|
||||
/* File to store the data and timestamps for different traffic */
|
||||
FILE *fpSubscriber;
|
||||
char *fileName = "subscriber_T4.csv";
|
||||
/* Array to store subscribed counter data */
|
||||
UA_UInt64 subscribeCounterValue[MAX_MEASUREMENTS];
|
||||
UA_Double pressureValues[MAX_MEASUREMENTS];
|
||||
size_t measurementsSubscriber = 0;
|
||||
/* Array to store timestamp */
|
||||
struct timespec subscribeTimestamp[MAX_MEASUREMENTS];
|
||||
|
||||
/* Thread for subscriber */
|
||||
pthread_t subthreadID;
|
||||
/* Variable for PubSub connection creation */
|
||||
UA_NodeId connectionIdentSubscriber;
|
||||
struct timespec dataReceiveTime;
|
||||
|
||||
/* Thread for user application*/
|
||||
pthread_t userApplicationThreadID;
|
||||
|
||||
typedef struct {
|
||||
UA_Server* ServerRun;
|
||||
} serverConfigStruct;
|
||||
|
||||
/* Structure to define thread parameters */
|
||||
typedef struct {
|
||||
UA_Server* server;
|
||||
void* data;
|
||||
UA_ServerCallback callback;
|
||||
UA_Duration interval_ms;
|
||||
UA_UInt64* callbackId;
|
||||
} threadArg;
|
||||
|
||||
/* Subscriber thread routine */
|
||||
void *subscriber(void *arg);
|
||||
/* User application thread routine */
|
||||
void *userApplicationSub(void *arg);
|
||||
/* To create multi-threads */
|
||||
static pthread_t threadCreation(UA_Int32 threadPriority, UA_Int32 coreAffinity, void *(*thread) (void *),
|
||||
char *applicationName, void *serverConfig);
|
||||
|
||||
/* Stop signal */
|
||||
static void stopHandler(int sign) {
|
||||
UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "received ctrl-c");
|
||||
running = UA_FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* **Nanosecond field handling**
|
||||
*
|
||||
* Nanosecond field in timespec is checked for overflowing and one second
|
||||
* is added to seconds field and nanosecond field is set to zero
|
||||
*/
|
||||
static void nanoSecondFieldConversion(struct timespec *timeSpecValue) {
|
||||
/* Check if ns field is greater than '1 ns less than 1sec' */
|
||||
while (timeSpecValue->tv_nsec > (SECONDS -1)) {
|
||||
/* Move to next second and remove it from ns field */
|
||||
timeSpecValue->tv_sec += SECONDS_INCREMENT;
|
||||
timeSpecValue->tv_nsec -= SECONDS;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* **External data source handling**
|
||||
*
|
||||
* If the external data source is written over the information model, the
|
||||
* externalDataWriteCallback will be triggered. The user has to take care and assure
|
||||
* that the write leads not to synchronization issues and race conditions. */
|
||||
static UA_StatusCode
|
||||
externalDataWriteCallback(UA_Server *server, const UA_NodeId *sessionId,
|
||||
void *sessionContext, const UA_NodeId *nodeId,
|
||||
void *nodeContext, const UA_NumericRange *range,
|
||||
const UA_DataValue *data){
|
||||
//node values are updated by using variables in the memory
|
||||
//UA_Server_write is not used for updating node values.
|
||||
return UA_STATUSCODE_GOOD;
|
||||
}
|
||||
|
||||
static UA_StatusCode
|
||||
externalDataReadNotificationCallback(UA_Server *server, const UA_NodeId *sessionId,
|
||||
void *sessionContext, const UA_NodeId *nodeid,
|
||||
void *nodeContext, const UA_NumericRange *range){
|
||||
//allow read without any preparation
|
||||
return UA_STATUSCODE_GOOD;
|
||||
}
|
||||
|
||||
/**
|
||||
* **Subscriber Connection Creation**
|
||||
*
|
||||
* Create Subscriber connection for the Subscriber thread
|
||||
*/
|
||||
static void
|
||||
addPubSubConnectionSubscriber(UA_Server *server, UA_NetworkAddressUrlDataType *networkAddressUrlSubscriber){
|
||||
UA_StatusCode retval = UA_STATUSCODE_GOOD;
|
||||
/* Details about the connection configuration and handling are located
|
||||
* in the pubsub connection tutorial */
|
||||
UA_PubSubConnectionConfig connectionConfig;
|
||||
memset(&connectionConfig, 0, sizeof(connectionConfig));
|
||||
connectionConfig.name = UA_STRING("Subscriber Connection");
|
||||
connectionConfig.enabled = UA_TRUE;
|
||||
UA_NetworkAddressUrlDataType networkAddressUrlsubscribe = *networkAddressUrlSubscriber;
|
||||
connectionConfig.transportProfileUri = UA_STRING(ETH_TRANSPORT_PROFILE);
|
||||
UA_Variant_setScalar(&connectionConfig.address, &networkAddressUrlsubscribe, &UA_TYPES[UA_TYPES_NETWORKADDRESSURLDATATYPE]);
|
||||
connectionConfig.publisherIdType = UA_PUBLISHERIDTYPE_UINT32;
|
||||
connectionConfig.publisherId.uint32 = UA_UInt32_random();
|
||||
retval |= UA_Server_addPubSubConnection(server, &connectionConfig, &connectionIdentSubscriber);
|
||||
if (retval == UA_STATUSCODE_GOOD)
|
||||
UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER,"The PubSub Connection was created successfully!");
|
||||
}
|
||||
|
||||
/**
|
||||
* **ReaderGroup**
|
||||
*
|
||||
* ReaderGroup is used to group a list of DataSetReaders. All ReaderGroups are
|
||||
* created within a PubSubConnection and automatically deleted if the connection
|
||||
* is removed. */
|
||||
/* Add ReaderGroup to the created connection */
|
||||
static void
|
||||
addReaderGroup(UA_Server *server) {
|
||||
if (server == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
UA_ReaderGroupConfig readerGroupConfig;
|
||||
memset (&readerGroupConfig, 0, sizeof(UA_ReaderGroupConfig));
|
||||
readerGroupConfig.name = UA_STRING("ReaderGroup1");
|
||||
readerGroupConfig.rtLevel = UA_PUBSUB_RT_FIXED_SIZE;
|
||||
|
||||
UA_Server_addReaderGroup(server, connectionIdentSubscriber, &readerGroupConfig,
|
||||
&readerGroupIdentifier);
|
||||
}
|
||||
|
||||
/**
|
||||
* **SubscribedDataSet**
|
||||
*
|
||||
* Set SubscribedDataSet type to TargetVariables data type
|
||||
* Add SubscriberCounter variable to the DataSetReader */
|
||||
static void addSubscribedVariables (UA_Server *server) {
|
||||
if (server == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
UA_FieldTargetVariable *targetVars = (UA_FieldTargetVariable*)
|
||||
UA_calloc(2, sizeof(UA_FieldTargetVariable));
|
||||
|
||||
subCounterData = UA_UInt64_new();
|
||||
*subCounterData = 0;
|
||||
subDataValueRT = UA_DataValue_new();
|
||||
UA_Variant_setScalar(&subDataValueRT->value, subCounterData, &UA_TYPES[UA_TYPES_UINT64]);
|
||||
subDataValueRT->hasValue = UA_TRUE;
|
||||
/* Set the value backend of the above create node to 'external value source' */
|
||||
UA_ValueBackend valueBackend;
|
||||
valueBackend.backendType = UA_VALUEBACKENDTYPE_EXTERNAL;
|
||||
valueBackend.backend.external.value = &subDataValueRT;
|
||||
valueBackend.backend.external.callback.userWrite = externalDataWriteCallback;
|
||||
valueBackend.backend.external.callback.notificationRead = externalDataReadNotificationCallback;
|
||||
/* If user need to change the nodeid of the custom nodes in the application then it must be
|
||||
* changed inside the xml and .csv file inside examples\pubsub_realtime\nodeset\*/
|
||||
/* The nodeid of the Custom node SubscriberCounterVariable is 2005 which is used below */
|
||||
UA_Server_setVariableNode_valueBackend(server, UA_NODEID_NUMERIC(nsIdx, 2005), valueBackend);
|
||||
UA_FieldTargetDataType_init(&targetVars[0].targetVariable);
|
||||
targetVars[0].targetVariable.attributeId = UA_ATTRIBUTEID_VALUE;
|
||||
targetVars[0].targetVariable.targetNodeId = UA_NODEID_NUMERIC(nsIdx, 2005);
|
||||
|
||||
pressureData = UA_Double_new();
|
||||
*pressureData = 0;
|
||||
pressureValueRT = UA_DataValue_new();
|
||||
UA_Variant_setScalar(&pressureValueRT->value, pressureData, &UA_TYPES[UA_TYPES_DOUBLE]);
|
||||
pressureValueRT->hasValue = UA_TRUE;
|
||||
/* Set the value backend of the above create node to 'external value source' */
|
||||
UA_ValueBackend valueBackend1;
|
||||
valueBackend1.backendType = UA_VALUEBACKENDTYPE_EXTERNAL;
|
||||
valueBackend1.backend.external.value = &pressureValueRT;
|
||||
valueBackend1.backend.external.callback.userWrite = externalDataWriteCallback;
|
||||
valueBackend1.backend.external.callback.notificationRead = externalDataReadNotificationCallback;
|
||||
/* The nodeid of the Custom node Pressure is 2006 which is used below */
|
||||
UA_Server_setVariableNode_valueBackend(server, UA_NODEID_NUMERIC(nsIdx, 2006), valueBackend1);
|
||||
UA_FieldTargetDataType_init(&targetVars[1].targetVariable);
|
||||
targetVars[1].targetVariable.attributeId = UA_ATTRIBUTEID_VALUE;
|
||||
targetVars[1].targetVariable.targetNodeId = UA_NODEID_NUMERIC(nsIdx, 2006);
|
||||
|
||||
/* Set the subscribed data to TargetVariable type */
|
||||
readerConfig.subscribedDataSetType = UA_PUBSUB_SDS_TARGET;
|
||||
readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariables = targetVars;
|
||||
readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariablesSize = 2;
|
||||
}
|
||||
|
||||
/**
|
||||
* **DataSetReader**
|
||||
*
|
||||
* DataSetReader can receive NetworkMessages with the DataSetMessage
|
||||
* of interest sent by the Publisher. DataSetReader provides
|
||||
* the configuration necessary to receive and process DataSetMessages
|
||||
* on the Subscriber side. DataSetReader must be linked with a
|
||||
* SubscribedDataSet and be contained within a ReaderGroup. */
|
||||
/* Add DataSetReader to the ReaderGroup */
|
||||
static void
|
||||
addDataSetReader(UA_Server *server) {
|
||||
if (server == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
memset (&readerConfig, 0, sizeof(UA_DataSetReaderConfig));
|
||||
readerConfig.name = UA_STRING("DataSet Reader 1");
|
||||
UA_UInt16 publisherIdentifier = PUBLISHER_ID_SUB;
|
||||
readerConfig.publisherId.type = &UA_TYPES[UA_TYPES_UINT16];
|
||||
readerConfig.publisherId.data = &publisherIdentifier;
|
||||
readerConfig.writerGroupId = WRITER_GROUP_ID_SUB;
|
||||
readerConfig.dataSetWriterId = DATA_SET_WRITER_ID_SUB;
|
||||
|
||||
readerConfig.messageSettings.encoding = UA_EXTENSIONOBJECT_DECODED;
|
||||
readerConfig.messageSettings.content.decoded.type = &UA_TYPES[UA_TYPES_UADPDATASETREADERMESSAGEDATATYPE];
|
||||
UA_UadpDataSetReaderMessageDataType *dataSetReaderMessage = UA_UadpDataSetReaderMessageDataType_new();
|
||||
dataSetReaderMessage->networkMessageContentMask = (UA_UadpNetworkMessageContentMask)(UA_UADPNETWORKMESSAGECONTENTMASK_PUBLISHERID |
|
||||
(UA_UadpNetworkMessageContentMask)UA_UADPNETWORKMESSAGECONTENTMASK_GROUPHEADER |
|
||||
(UA_UadpNetworkMessageContentMask)UA_UADPNETWORKMESSAGECONTENTMASK_WRITERGROUPID |
|
||||
(UA_UadpNetworkMessageContentMask)UA_UADPNETWORKMESSAGECONTENTMASK_PAYLOADHEADER);
|
||||
readerConfig.messageSettings.content.decoded.data = dataSetReaderMessage;
|
||||
|
||||
/* Setting up Meta data configuration in DataSetReader */
|
||||
UA_DataSetMetaDataType *pMetaData = &readerConfig.dataSetMetaData;
|
||||
/* FilltestMetadata function in subscriber implementation */
|
||||
UA_DataSetMetaDataType_init(pMetaData);
|
||||
pMetaData->name = UA_STRING ("DataSet Test");
|
||||
/* Static definition of number of fields size to 1 to create one
|
||||
targetVariable */
|
||||
pMetaData->fieldsSize = 2;
|
||||
pMetaData->fields = (UA_FieldMetaData*)UA_Array_new (pMetaData->fieldsSize,
|
||||
&UA_TYPES[UA_TYPES_FIELDMETADATA]);
|
||||
|
||||
/* Unsigned Integer DataType */
|
||||
UA_FieldMetaData_init (&pMetaData->fields[0]);
|
||||
UA_NodeId_copy (&UA_TYPES[UA_TYPES_UINT64].typeId,
|
||||
&pMetaData->fields[0].dataType);
|
||||
pMetaData->fields[0].builtInType = UA_NS0ID_UINT64;
|
||||
pMetaData->fields[0].valueRank = -1; /* scalar */
|
||||
|
||||
/* Double DataType */
|
||||
UA_FieldMetaData_init (&pMetaData->fields[1]);
|
||||
UA_NodeId_copy (&UA_TYPES[UA_TYPES_DOUBLE].typeId,
|
||||
&pMetaData->fields[1].dataType);
|
||||
pMetaData->fields[1].builtInType = UA_NS0ID_DOUBLE;
|
||||
pMetaData->fields[1].valueRank = -1; /* scalar */
|
||||
|
||||
/* Setup Target Variables in DSR config */
|
||||
addSubscribedVariables(server);
|
||||
|
||||
/* Setting up Meta data configuration in DataSetReader */
|
||||
UA_Server_addDataSetReader(server, readerGroupIdentifier, &readerConfig,
|
||||
&readerIdentifier);
|
||||
|
||||
UA_free(readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariables);
|
||||
UA_free(readerConfig.dataSetMetaData.fields);
|
||||
UA_UadpDataSetReaderMessageDataType_delete(dataSetReaderMessage);
|
||||
}
|
||||
|
||||
/**
|
||||
* **Subscribed data handling**
|
||||
*
|
||||
* The subscribed data is updated in the array using this function Subscribed data handling**
|
||||
*/
|
||||
static void
|
||||
updateMeasurementsSubscriber(struct timespec receive_time, UA_UInt64 counterValue, UA_Double pressureValue) {
|
||||
subscribeTimestamp[measurementsSubscriber] = receive_time;
|
||||
subscribeCounterValue[measurementsSubscriber] = counterValue;
|
||||
pressureValues[measurementsSubscriber] = pressureValue;
|
||||
measurementsSubscriber++;
|
||||
}
|
||||
|
||||
/**
|
||||
* **Subscriber thread routine**
|
||||
*
|
||||
* Subscriber thread will wakeup during the start of cycle at 250us interval and check if the packets are received.
|
||||
* The subscriber function is the routine used by the subscriber thread.
|
||||
*/
|
||||
void *subscriber(void *arg) {
|
||||
UA_Server* server;
|
||||
UA_ReaderGroup* currentReaderGroup;
|
||||
UA_ServerCallback subCallback;
|
||||
struct timespec nextnanosleeptimeSub;
|
||||
|
||||
threadArg *threadArgumentsSubscriber = (threadArg *)arg;
|
||||
server = threadArgumentsSubscriber->server;
|
||||
subCallback = threadArgumentsSubscriber->callback;
|
||||
currentReaderGroup = (UA_ReaderGroup *)threadArgumentsSubscriber->data;
|
||||
|
||||
/* Get current time and compute the next nanosleeptime */
|
||||
clock_gettime(CLOCKID, &nextnanosleeptimeSub);
|
||||
/* Variable to nano Sleep until 1ms before a 1 second boundary */
|
||||
nextnanosleeptimeSub.tv_sec += SECONDS_SLEEP;
|
||||
nextnanosleeptimeSub.tv_nsec = (__syscall_slong_t)(cycleTimeMsec * subWakeupPercentage * MILLI_SECONDS);
|
||||
nanoSecondFieldConversion(&nextnanosleeptimeSub);
|
||||
while (running) {
|
||||
clock_nanosleep(CLOCKID, TIMER_ABSTIME, &nextnanosleeptimeSub, NULL);
|
||||
/* Read subscribed data from the SubscriberCounter variable */
|
||||
subCallback(server, currentReaderGroup);
|
||||
nextnanosleeptimeSub.tv_nsec += (__syscall_slong_t)(cycleTimeMsec * MILLI_SECONDS);
|
||||
nanoSecondFieldConversion(&nextnanosleeptimeSub);
|
||||
}
|
||||
|
||||
UA_free(threadArgumentsSubscriber);
|
||||
|
||||
return (void*)NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* **UserApplication thread routine**
|
||||
*
|
||||
* The userapplication thread will wakeup at 30% of cycle time and handles the userdata in the Information Model.
|
||||
* This thread is used to write the counterdata that is subscribed by the Subscriber thread in a csv.
|
||||
*/
|
||||
void *userApplicationSub(void *arg) {
|
||||
struct timespec nextnanosleeptimeUserApplication;
|
||||
/* Get current time and compute the next nanosleeptime */
|
||||
clock_gettime(CLOCKID, &nextnanosleeptimeUserApplication);
|
||||
/* Variable to nano Sleep until 1ms before a 1 second boundary */
|
||||
nextnanosleeptimeUserApplication.tv_sec += SECONDS_SLEEP;
|
||||
nextnanosleeptimeUserApplication.tv_nsec = (__syscall_slong_t)(cycleTimeMsec * userAppWakeupPercentage * MILLI_SECONDS);
|
||||
nanoSecondFieldConversion(&nextnanosleeptimeUserApplication);
|
||||
|
||||
while (running) {
|
||||
clock_nanosleep(CLOCKID, TIMER_ABSTIME, &nextnanosleeptimeUserApplication, NULL);
|
||||
clock_gettime(CLOCKID, &dataReceiveTime);
|
||||
if ((fileWrite == UA_TRUE) || (consolePrint == UA_TRUE)) {
|
||||
if (*subCounterData > 0)
|
||||
updateMeasurementsSubscriber(dataReceiveTime, *subCounterData, *pressureData);
|
||||
}
|
||||
nextnanosleeptimeUserApplication.tv_nsec += (__syscall_slong_t)(cycleTimeMsec * MILLI_SECONDS);
|
||||
nanoSecondFieldConversion(&nextnanosleeptimeUserApplication);
|
||||
}
|
||||
|
||||
return (void*)NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* **Thread creation**
|
||||
*
|
||||
* The threadcreation functionality creates thread with given threadpriority, coreaffinity. The function returns the threadID of the newly
|
||||
* created thread.
|
||||
*/
|
||||
static pthread_t threadCreation(UA_Int32 threadPriority, UA_Int32 coreAffinity, void *(*thread) (void *), \
|
||||
char *applicationName, void *serverConfig){
|
||||
|
||||
/* Core affinity set */
|
||||
cpu_set_t cpuset;
|
||||
pthread_t threadID;
|
||||
struct sched_param schedParam;
|
||||
UA_Int32 returnValue = 0;
|
||||
UA_Int32 errorSetAffinity = 0;
|
||||
/* Return the ID for thread */
|
||||
threadID = pthread_self();
|
||||
schedParam.sched_priority = threadPriority;
|
||||
returnValue = pthread_setschedparam(threadID, SCHED_FIFO, &schedParam);
|
||||
if (returnValue != 0) {
|
||||
UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,"pthread_setschedparam: failed\n");
|
||||
exit(1);
|
||||
}
|
||||
UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,\
|
||||
"\npthread_setschedparam:%s Thread priority is %d \n", \
|
||||
applicationName, schedParam.sched_priority);
|
||||
CPU_ZERO(&cpuset);
|
||||
CPU_SET((size_t)coreAffinity, &cpuset);
|
||||
errorSetAffinity = pthread_setaffinity_np(threadID, sizeof(cpu_set_t), &cpuset);
|
||||
if (errorSetAffinity) {
|
||||
fprintf(stderr, "pthread_setaffinity_np: %s\n", strerror(errorSetAffinity));
|
||||
exit(1);
|
||||
}
|
||||
|
||||
returnValue = pthread_create(&threadID, NULL, thread, serverConfig);
|
||||
if (returnValue != 0) {
|
||||
UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,":%s Cannot create thread\n", applicationName);
|
||||
}
|
||||
|
||||
if (CPU_ISSET((size_t)coreAffinity, &cpuset)) {
|
||||
UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,"%s CPU CORE: %d\n", applicationName, coreAffinity);
|
||||
}
|
||||
|
||||
return threadID;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* **Usage function**
|
||||
*
|
||||
* The usage function gives the list of options that can be configured in the application.
|
||||
*
|
||||
* ./bin/examples/pubsub_nodeset_rt_subscriber -h gives the list of options for running the application.
|
||||
*/
|
||||
static void usage(char *appname)
|
||||
{
|
||||
fprintf(stderr,
|
||||
"\n"
|
||||
"usage: %s [options]\n"
|
||||
"\n"
|
||||
" -i [name] use network interface 'name'\n"
|
||||
" -C [num] cycle time in milli seconds (default %lf)\n"
|
||||
" -p Do you need to print the data in console output\n"
|
||||
" -P [num] Publisher priority value (default %d)\n"
|
||||
" -U [num] User application priority value (default %d)\n"
|
||||
" -c [num] run on CPU for publisher'num'(default %d)\n"
|
||||
" -u [num] run on CPU for userApplication'num'(default %d)\n"
|
||||
" -m [mac_addr] ToDO:dst MAC address\n"
|
||||
" -h prints this message and exits\n"
|
||||
"\n",
|
||||
appname, DEFAULT_CYCLE_TIME, DEFAULT_SUB_SCHED_PRIORITY, \
|
||||
DEFAULT_USERAPPLICATION_SCHED_PRIORITY, DEFAULT_PUBSUB_CORE, DEFAULT_USER_APP_CORE);
|
||||
}
|
||||
|
||||
/**
|
||||
* **Main Server code**
|
||||
*
|
||||
* The main function contains subscriber threads running
|
||||
*/
|
||||
int main(int argc, char **argv) {
|
||||
signal(SIGINT, stopHandler);
|
||||
signal(SIGTERM, stopHandler);
|
||||
|
||||
UA_Int32 returnValue = 0;
|
||||
char *interface = NULL;
|
||||
char *progname;
|
||||
UA_Int32 argInputs = -1;
|
||||
UA_StatusCode retval = UA_STATUSCODE_GOOD;
|
||||
UA_Server *server = UA_Server_new();
|
||||
UA_ServerConfig *config = UA_Server_getConfig(server);
|
||||
pthread_t userThreadID;
|
||||
UA_ServerConfig_setMinimal(config, PORT_NUMBER, NULL);
|
||||
|
||||
/* Files namespace_example_subscriber_generated.h and namespace_example_subscriber_generated.c are created from
|
||||
* subDataModel.xml in the /src_generated directory by CMake */
|
||||
/* Loading the user created variables into the information model from the generated .c and .h files */
|
||||
if(namespace_example_subscriber_generated(server) != UA_STATUSCODE_GOOD) {
|
||||
UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "Could not add the example nodeset. "
|
||||
"Check previous output for any error.");
|
||||
}
|
||||
else
|
||||
{
|
||||
nsIdx = UA_Server_addNamespace(server, "http://yourorganisation.org/test/");
|
||||
}
|
||||
UA_NetworkAddressUrlDataType networkAddressUrlSub;
|
||||
/* For more information run ./bin/examples/pubsub_nodeset_rt_subscriber -h */
|
||||
/* Process the command line arguments */
|
||||
progname = strrchr(argv[0], '/');
|
||||
progname = progname ? 1 + progname : argv[0];
|
||||
while (EOF != (argInputs = getopt(argc, argv, "i:C:f:ps:P:U:c:u:tm:h:"))) {
|
||||
switch (argInputs) {
|
||||
case 'i':
|
||||
interface = optarg;
|
||||
break;
|
||||
case 'C':
|
||||
cycleTimeMsec = atof(optarg);
|
||||
break;
|
||||
case 'f':
|
||||
fileName = optarg;
|
||||
fileWrite = UA_TRUE;
|
||||
fpSubscriber = fopen(fileName, "w");
|
||||
break;
|
||||
case 'p':
|
||||
consolePrint = UA_TRUE;
|
||||
break;
|
||||
case 'P':
|
||||
subPriority = atoi(optarg);
|
||||
break;
|
||||
case 'U':
|
||||
userAppPriority = atoi(optarg);
|
||||
break;
|
||||
case 'c':
|
||||
pubSubCore = atoi(optarg);
|
||||
break;
|
||||
case 'u':
|
||||
userAppCore = atoi(optarg);
|
||||
break;
|
||||
case 'm':
|
||||
/*ToDo:Need to handle for mac address*/
|
||||
break;
|
||||
case 'h':
|
||||
usage(progname);
|
||||
return -1;
|
||||
case '?':
|
||||
usage(progname);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
if (cycleTimeMsec < 0.125) {
|
||||
UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "%f Bad cycle time", cycleTimeMsec);
|
||||
usage(progname);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!interface) {
|
||||
UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "Need a network interface to run");
|
||||
usage(progname);
|
||||
UA_Server_delete(server);
|
||||
return 0;
|
||||
}
|
||||
networkAddressUrlSub.networkInterface = UA_STRING(interface);
|
||||
networkAddressUrlSub.url = UA_STRING(SUBSCRIBING_MAC_ADDRESS);
|
||||
|
||||
addPubSubConnectionSubscriber(server, &networkAddressUrlSub);
|
||||
addReaderGroup(server);
|
||||
addDataSetReader(server);
|
||||
UA_Server_freezeReaderGroupConfiguration(server, readerGroupIdentifier);
|
||||
UA_Server_enableReaderGroup(server, readerGroupIdentifier);
|
||||
serverConfigStruct *serverConfig;
|
||||
serverConfig = (serverConfigStruct*)UA_malloc(sizeof(serverConfigStruct));
|
||||
serverConfig->ServerRun = server;
|
||||
|
||||
char threadNameUserApplication[22] = "UserApplicationSub";
|
||||
userThreadID = threadCreation(userAppPriority, userAppCore, userApplicationSub, threadNameUserApplication, serverConfig);
|
||||
|
||||
retval |= UA_Server_run(server, &running);
|
||||
|
||||
UA_Server_unfreezeReaderGroupConfiguration(server, readerGroupIdentifier);
|
||||
returnValue = pthread_join(subthreadID, NULL);
|
||||
if (returnValue != 0) {
|
||||
UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,"\nPthread Join Failed for subscriber thread:%d\n", returnValue);
|
||||
}
|
||||
returnValue = pthread_join(userThreadID, NULL);
|
||||
if (returnValue != 0) {
|
||||
UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,"\nPthread Join Failed for User thread:%d\n", returnValue);
|
||||
}
|
||||
if (fileWrite == UA_TRUE) {
|
||||
/* Write the subscribed data in the file */
|
||||
size_t subLoopVariable = 0;
|
||||
for (subLoopVariable = 0; subLoopVariable < measurementsSubscriber;
|
||||
subLoopVariable++) {
|
||||
fprintf(fpSubscriber, "%lu,%ld.%09ld,%lf\n",
|
||||
(long unsigned)subscribeCounterValue[subLoopVariable],
|
||||
subscribeTimestamp[subLoopVariable].tv_sec,
|
||||
subscribeTimestamp[subLoopVariable].tv_nsec,
|
||||
pressureValues[subLoopVariable]);
|
||||
}
|
||||
fclose(fpSubscriber);
|
||||
}
|
||||
if (consolePrint == UA_TRUE) {
|
||||
size_t subLoopVariable = 0;
|
||||
for (subLoopVariable = 0; subLoopVariable < measurementsSubscriber;
|
||||
subLoopVariable++) {
|
||||
fprintf(fpSubscriber, "%lu,%ld.%09ld,%lf\n",
|
||||
(long unsigned)subscribeCounterValue[subLoopVariable],
|
||||
subscribeTimestamp[subLoopVariable].tv_sec,
|
||||
subscribeTimestamp[subLoopVariable].tv_nsec,
|
||||
pressureValues[subLoopVariable]);
|
||||
}
|
||||
}
|
||||
UA_Server_delete(server);
|
||||
UA_free(serverConfig);
|
||||
UA_free(subCounterData);
|
||||
/* Free external data source */
|
||||
UA_free(subDataValueRT);
|
||||
UA_free(pressureData);
|
||||
/* Free external data source */
|
||||
UA_free(pressureValueRT);
|
||||
return (int)retval;
|
||||
}
|
||||
|
@ -1,4 +0,0 @@
|
||||
SubscriberInfo,2001,ObjectType
|
||||
Subscriber,2004,Object
|
||||
PublisherCounterValue,2005,Variable
|
||||
Pressure,2006,Variable
|
|
@ -1,80 +0,0 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<UANodeSet xmlns="http://opcfoundation.org/UA/2011/03/UANodeSet.xsd" xmlns:uax="http://opcfoundation.org/UA/2008/02/Types.xsd" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||
<NamespaceUris>
|
||||
<Uri>http://yourorganisation.org/test/</Uri>
|
||||
</NamespaceUris>
|
||||
<Aliases>
|
||||
<Alias Alias="HasModellingRule">i=37</Alias>
|
||||
<Alias Alias="UInt64">i=9</Alias>
|
||||
<Alias Alias="Double">i=11</Alias>
|
||||
<Alias Alias="Organizes">i=35</Alias>
|
||||
<Alias Alias="HasTypeDefinition">i=40</Alias>
|
||||
<Alias Alias="HasSubtype">i=45</Alias>
|
||||
<Alias Alias="HasComponent">i=47</Alias>
|
||||
</Aliases>
|
||||
<UAObjectType BrowseName="1:SubscriberInfo" NodeId="ns=1;i=2001">
|
||||
<DisplayName>SubscriberInfo</DisplayName>
|
||||
<Description>SubscriberInfo</Description>
|
||||
<References>
|
||||
<Reference IsForward="false" ReferenceType="HasSubtype">i=58</Reference>
|
||||
<Reference ReferenceType="HasComponent">ns=1;i=2002</Reference>
|
||||
<Reference ReferenceType="HasComponent">ns=1;i=2003</Reference>
|
||||
</References>
|
||||
</UAObjectType>
|
||||
<UAVariable BrowseName="1:SubscriberCounterVariable" DataType="UInt64" NodeId="ns=1;i=2002" ParentNodeId="ns=1;i=2001">
|
||||
<DisplayName>SubscriberCounterVariable</DisplayName>
|
||||
<Description>SubscriberCounterVariable</Description>
|
||||
<References>
|
||||
<Reference IsForward="false" ReferenceType="HasComponent">ns=1;i=2001</Reference>
|
||||
<Reference ReferenceType="HasTypeDefinition">i=63</Reference>
|
||||
<Reference ReferenceType="HasModellingRule">i=78</Reference>
|
||||
</References>
|
||||
<Value>
|
||||
<uax:UInt64>0</uax:UInt64>
|
||||
</Value>
|
||||
</UAVariable>
|
||||
<UAVariable BrowseName="1:Pressure" DataType="Double" NodeId="ns=1;i=2003" ParentNodeId="ns=1;i=2001">
|
||||
<DisplayName>Pressure</DisplayName>
|
||||
<Description>Pressure</Description>
|
||||
<References>
|
||||
<Reference IsForward="false" ReferenceType="HasComponent">ns=1;i=2001</Reference>
|
||||
<Reference ReferenceType="HasTypeDefinition">i=63</Reference>
|
||||
<Reference ReferenceType="HasModellingRule">i=78</Reference>
|
||||
</References>
|
||||
<Value>
|
||||
<uax:Double>0.0</uax:Double>
|
||||
</Value>
|
||||
</UAVariable>
|
||||
<UAObject BrowseName="1:Subscriber" NodeId="ns=1;i=2004" ParentNodeId="i=85">
|
||||
<DisplayName>Subscriber</DisplayName>
|
||||
<Description>SubscriberInfo</Description>
|
||||
<References>
|
||||
<Reference IsForward="false" ReferenceType="Organizes">i=85</Reference>
|
||||
<Reference ReferenceType="HasTypeDefinition">ns=1;i=2001</Reference>
|
||||
<Reference ReferenceType="HasComponent">ns=1;i=2005</Reference>
|
||||
<Reference ReferenceType="HasComponent">ns=1;i=2006</Reference>
|
||||
</References>
|
||||
</UAObject>
|
||||
<UAVariable BrowseName="1:SubscriberCounterVariable" DataType="UInt64" NodeId="ns=1;i=2005" ParentNodeId="ns=1;i=2004">
|
||||
<DisplayName>SubscriberCounterVariable</DisplayName>
|
||||
<Description>SubscriberCounterVariable</Description>
|
||||
<References>
|
||||
<Reference IsForward="false" ReferenceType="HasComponent">ns=1;i=2004</Reference>
|
||||
<Reference ReferenceType="HasTypeDefinition">i=63</Reference>
|
||||
</References>
|
||||
<Value>
|
||||
<uax:UInt64>0</uax:UInt64>
|
||||
</Value>
|
||||
</UAVariable>
|
||||
<UAVariable BrowseName="1:Pressure" DataType="Double" NodeId="ns=1;i=2006" ParentNodeId="ns=1;i=2004">
|
||||
<DisplayName>Pressure</DisplayName>
|
||||
<Description>Pressure</Description>
|
||||
<References>
|
||||
<Reference IsForward="false" ReferenceType="HasComponent">ns=1;i=2004</Reference>
|
||||
<Reference ReferenceType="HasTypeDefinition">i=63</Reference>
|
||||
</References>
|
||||
<Value>
|
||||
<uax:Double>0.0</uax:Double>
|
||||
</Value>
|
||||
</UAVariable>
|
||||
</UANodeSet>
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -1,380 +0,0 @@
|
||||
/* This work is licensed under a Creative Commons CCZero 1.0 Universal License.
|
||||
* See http://creativecommons.org/publicdomain/zero/1.0/ for more information.
|
||||
*
|
||||
* Copyright 2018-2019 (c) Kalycito Infotech
|
||||
* Copyright 2019 (c) Fraunhofer IOSB (Author: Andreas Ebner)
|
||||
* Copyright 2019 (c) Fraunhofer IOSB (Author: Julius Pfrommer)
|
||||
* Copyright (c) 2020 Wind River Systems, Inc.
|
||||
*/
|
||||
|
||||
#if __STDC_VERSION__ >= 199901L
|
||||
#define _XOPEN_SOURCE 600
|
||||
#else
|
||||
#define _XOPEN_SOURCE 500
|
||||
#endif /* __STDC_VERSION__ */
|
||||
|
||||
#include <errno.h>
|
||||
#include <signal.h>
|
||||
#include <stdio.h>
|
||||
#include <time.h>
|
||||
#include <open62541/server.h>
|
||||
#include <open62541/server_config_default.h>
|
||||
#include <open62541/plugin/log_stdout.h>
|
||||
#include <open62541/server_pubsub.h>
|
||||
|
||||
#define ETH_PUBLISH_ADDRESS "opc.eth://0a-00-27-00-00-08"
|
||||
#define ETH_INTERFACE "enp0s8"
|
||||
#define MAX_MEASUREMENTS 10000
|
||||
#define MILLI_AS_NANO_SECONDS (1000 * 1000)
|
||||
#define SECONDS_AS_NANO_SECONDS (1000 * 1000 * 1000)
|
||||
#define CLOCKID CLOCK_MONOTONIC_RAW
|
||||
#define SIG SIGUSR1
|
||||
#define PUB_INTERVAL 0.25 /* Publish interval in milliseconds */
|
||||
#define DATA_SET_WRITER_ID 62541
|
||||
#define MEASUREMENT_OUTPUT "publisher_measurement.csv"
|
||||
|
||||
/* The RT level of the publisher */
|
||||
/* possible options: PUBSUB_CONFIG_FASTPATH_NONE, PUBSUB_CONFIG_FASTPATH_FIXED_OFFSETS, PUBSUB_CONFIG_FASTPATH_STATIC_VALUES */
|
||||
#define PUBSUB_CONFIG_FASTPATH_FIXED_OFFSETS
|
||||
|
||||
UA_NodeId counterNodePublisher = {1, UA_NODEIDTYPE_NUMERIC, {1234}};
|
||||
UA_Int64 pubIntervalNs;
|
||||
UA_ServerCallback pubCallback = NULL; /* Sentinel if a timer is active */
|
||||
UA_Server *pubServer;
|
||||
UA_Boolean running = true;
|
||||
void *pubData;
|
||||
timer_t pubEventTimer;
|
||||
struct sigevent pubEvent;
|
||||
struct sigaction signalAction;
|
||||
|
||||
#if defined(PUBSUB_CONFIG_FASTPATH_FIXED_OFFSETS) || (PUBSUB_CONFIG_FASTPATH_STATIC_VALUES)
|
||||
UA_DataValue *staticValueSource = NULL;
|
||||
#endif
|
||||
|
||||
/* Arrays to store measurement data */
|
||||
UA_Int32 currentPublishCycleTime[MAX_MEASUREMENTS+1];
|
||||
struct timespec calculatedCycleStartTime[MAX_MEASUREMENTS+1];
|
||||
struct timespec cycleStartDelay[MAX_MEASUREMENTS+1];
|
||||
struct timespec cycleDuration[MAX_MEASUREMENTS+1];
|
||||
size_t publisherMeasurementsCounter = 0;
|
||||
|
||||
/* The value to published */
|
||||
static UA_UInt64 publishValue = 62541;
|
||||
|
||||
static UA_StatusCode
|
||||
readPublishValue(UA_Server *server,
|
||||
const UA_NodeId *sessionId, void *sessionContext,
|
||||
const UA_NodeId *nodeId, void *nodeContext,
|
||||
UA_Boolean sourceTimeStamp, const UA_NumericRange *range,
|
||||
UA_DataValue *dataValue) {
|
||||
UA_Variant_setScalarCopy(&dataValue->value, &publishValue,
|
||||
&UA_TYPES[UA_TYPES_UINT64]);
|
||||
dataValue->hasValue = true;
|
||||
return UA_STATUSCODE_GOOD;
|
||||
}
|
||||
|
||||
static void
|
||||
timespec_diff(struct timespec *start, struct timespec *stop,
|
||||
struct timespec *result) {
|
||||
if((stop->tv_nsec - start->tv_nsec) < 0) {
|
||||
result->tv_sec = stop->tv_sec - start->tv_sec - 1;
|
||||
result->tv_nsec = stop->tv_nsec - start->tv_nsec + 1000000000;
|
||||
} else {
|
||||
result->tv_sec = stop->tv_sec - start->tv_sec;
|
||||
result->tv_nsec = stop->tv_nsec - start->tv_nsec;
|
||||
}
|
||||
}
|
||||
|
||||
/* Used to adjust the nanosecond > 1s field value */
|
||||
static void
|
||||
nanoSecondFieldConversion(struct timespec *timeSpecValue) {
|
||||
while(timeSpecValue->tv_nsec > (SECONDS_AS_NANO_SECONDS - 1)) {
|
||||
timeSpecValue->tv_sec += 1;
|
||||
timeSpecValue->tv_nsec -= SECONDS_AS_NANO_SECONDS;
|
||||
}
|
||||
}
|
||||
|
||||
/* Signal handler */
|
||||
static void
|
||||
publishInterrupt(int sig, siginfo_t* si, void* uc) {
|
||||
if(si->si_value.sival_ptr != &pubEventTimer) {
|
||||
UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "stray signal");
|
||||
return;
|
||||
}
|
||||
|
||||
/* Execute the publish callback in the interrupt */
|
||||
struct timespec begin, end;
|
||||
clock_gettime(CLOCKID, &begin);
|
||||
pubCallback(pubServer, pubData);
|
||||
clock_gettime(CLOCKID, &end);
|
||||
|
||||
if(publisherMeasurementsCounter >= MAX_MEASUREMENTS)
|
||||
return;
|
||||
|
||||
/* Save current configured publish interval */
|
||||
currentPublishCycleTime[publisherMeasurementsCounter] = (UA_Int32)pubIntervalNs;
|
||||
|
||||
/* Save the difference to the calculated time */
|
||||
timespec_diff(&calculatedCycleStartTime[publisherMeasurementsCounter],
|
||||
&begin, &cycleStartDelay[publisherMeasurementsCounter]);
|
||||
|
||||
/* Save the duration of the publish callback */
|
||||
timespec_diff(&begin, &end, &cycleDuration[publisherMeasurementsCounter]);
|
||||
|
||||
publisherMeasurementsCounter++;
|
||||
|
||||
/* Save the calculated starting time for the next cycle */
|
||||
calculatedCycleStartTime[publisherMeasurementsCounter].tv_nsec =
|
||||
calculatedCycleStartTime[publisherMeasurementsCounter - 1].tv_nsec + pubIntervalNs;
|
||||
calculatedCycleStartTime[publisherMeasurementsCounter].tv_sec =
|
||||
calculatedCycleStartTime[publisherMeasurementsCounter - 1].tv_sec;
|
||||
nanoSecondFieldConversion(&calculatedCycleStartTime[publisherMeasurementsCounter]);
|
||||
|
||||
/* Write the pubsub measurement data */
|
||||
if(publisherMeasurementsCounter == MAX_MEASUREMENTS) {
|
||||
UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
|
||||
"Logging the measurements to %s", MEASUREMENT_OUTPUT);
|
||||
|
||||
FILE *fpPublisher = fopen(MEASUREMENT_OUTPUT, "w");
|
||||
for(UA_UInt32 i = 0; i < publisherMeasurementsCounter; i++) {
|
||||
fprintf(fpPublisher, "%u, %u, %ld.%09ld, %ld.%09ld, %ld.%09ld\n",
|
||||
i,
|
||||
currentPublishCycleTime[i],
|
||||
calculatedCycleStartTime[i].tv_sec,
|
||||
calculatedCycleStartTime[i].tv_nsec,
|
||||
cycleStartDelay[i].tv_sec,
|
||||
cycleStartDelay[i].tv_nsec,
|
||||
cycleDuration[i].tv_sec,
|
||||
cycleDuration[i].tv_nsec);
|
||||
}
|
||||
fclose(fpPublisher);
|
||||
}
|
||||
}
|
||||
|
||||
/* The following three methods are originally defined in
|
||||
* /src/pubsub/ua_pubsub_manager.c. We provide a custom implementation here to
|
||||
* use system interrupts instead if time-triggered callbacks in the OPC UA
|
||||
* server control flow. */
|
||||
|
||||
static UA_StatusCode
|
||||
addPubSubApplicationCallback(UA_Server *server, UA_NodeId identifier,
|
||||
UA_ServerCallback callback,
|
||||
void *data, UA_Double interval_ms,
|
||||
UA_DateTime *baseTime, UA_TimerPolicy timerPolicy,
|
||||
UA_UInt64 *callbackId) {
|
||||
if(pubCallback) {
|
||||
UA_LOG_WARNING(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
|
||||
"At most one publisher can be registered for interrupt callbacks");
|
||||
return UA_STATUSCODE_BADINTERNALERROR;
|
||||
}
|
||||
|
||||
UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
|
||||
"Adding a publisher with a cycle time of %lf milliseconds", interval_ms);
|
||||
|
||||
/* Set global values for the publish callback */
|
||||
int resultTimerCreate = 0;
|
||||
pubIntervalNs = (UA_Int64) (interval_ms * MILLI_AS_NANO_SECONDS);
|
||||
|
||||
/* Handle the signal */
|
||||
memset(&signalAction, 0, sizeof(signalAction));
|
||||
signalAction.sa_flags = SA_SIGINFO;
|
||||
signalAction.sa_sigaction = publishInterrupt;
|
||||
sigemptyset(&signalAction.sa_mask);
|
||||
sigaction(SIG, &signalAction, NULL);
|
||||
|
||||
/* Create the timer */
|
||||
memset(&pubEventTimer, 0, sizeof(pubEventTimer));
|
||||
pubEvent.sigev_notify = SIGEV_SIGNAL;
|
||||
pubEvent.sigev_signo = SIG;
|
||||
pubEvent.sigev_value.sival_ptr = &pubEventTimer;
|
||||
resultTimerCreate = timer_create(CLOCKID, &pubEvent, &pubEventTimer);
|
||||
if(resultTimerCreate != 0) {
|
||||
UA_LOG_WARNING(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
|
||||
"Failed to create a system event with code %s",
|
||||
strerror(errno));
|
||||
return UA_STATUSCODE_BADINTERNALERROR;
|
||||
}
|
||||
|
||||
/* Arm the timer */
|
||||
struct itimerspec timerspec;
|
||||
timerspec.it_interval.tv_sec = (long int) (pubIntervalNs / (SECONDS_AS_NANO_SECONDS));
|
||||
timerspec.it_interval.tv_nsec = (long int) (pubIntervalNs % SECONDS_AS_NANO_SECONDS);
|
||||
timerspec.it_value.tv_sec = (long int) (pubIntervalNs / (SECONDS_AS_NANO_SECONDS));
|
||||
timerspec.it_value.tv_nsec = (long int) (pubIntervalNs % SECONDS_AS_NANO_SECONDS);
|
||||
resultTimerCreate = timer_settime(pubEventTimer, 0, &timerspec, NULL);
|
||||
if(resultTimerCreate != 0) {
|
||||
UA_LOG_WARNING(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
|
||||
"Failed to arm the system timer with code %i", resultTimerCreate);
|
||||
timer_delete(pubEventTimer);
|
||||
return UA_STATUSCODE_BADINTERNALERROR;
|
||||
}
|
||||
|
||||
/* Start taking measurements */
|
||||
publisherMeasurementsCounter = 0;
|
||||
clock_gettime(CLOCKID, &calculatedCycleStartTime[0]);
|
||||
calculatedCycleStartTime[0].tv_nsec += pubIntervalNs;
|
||||
nanoSecondFieldConversion(&calculatedCycleStartTime[0]);
|
||||
|
||||
/* Set the callback -- used as a sentinel to detect an operational publisher */
|
||||
pubServer = server;
|
||||
pubCallback = callback;
|
||||
pubData = data;
|
||||
|
||||
return UA_STATUSCODE_GOOD;
|
||||
}
|
||||
|
||||
static UA_StatusCode
|
||||
changePubSubApplicationCallback(UA_Server *server, UA_NodeId identifier,
|
||||
UA_UInt64 callbackId, UA_Double interval_ms,
|
||||
UA_DateTime *baseTime, UA_TimerPolicy timerPolicy) {
|
||||
UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
|
||||
"Switching the publisher cycle to %lf milliseconds", interval_ms);
|
||||
|
||||
struct itimerspec timerspec;
|
||||
int resultTimerCreate = 0;
|
||||
pubIntervalNs = (UA_Int64) (interval_ms * MILLI_AS_NANO_SECONDS);
|
||||
timerspec.it_interval.tv_sec = (long int) (pubIntervalNs / SECONDS_AS_NANO_SECONDS);
|
||||
timerspec.it_interval.tv_nsec = (long int) (pubIntervalNs % SECONDS_AS_NANO_SECONDS);
|
||||
timerspec.it_value.tv_sec = (long int) (pubIntervalNs / (SECONDS_AS_NANO_SECONDS));
|
||||
timerspec.it_value.tv_nsec = (long int) (pubIntervalNs % SECONDS_AS_NANO_SECONDS);
|
||||
resultTimerCreate = timer_settime(pubEventTimer, 0, &timerspec, NULL);
|
||||
if(resultTimerCreate != 0) {
|
||||
UA_LOG_WARNING(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
|
||||
"Failed to arm the system timer");
|
||||
timer_delete(pubEventTimer);
|
||||
return UA_STATUSCODE_BADINTERNALERROR;
|
||||
}
|
||||
|
||||
clock_gettime(CLOCKID, &calculatedCycleStartTime[publisherMeasurementsCounter]);
|
||||
calculatedCycleStartTime[publisherMeasurementsCounter].tv_nsec += pubIntervalNs;
|
||||
nanoSecondFieldConversion(&calculatedCycleStartTime[publisherMeasurementsCounter]);
|
||||
|
||||
return UA_STATUSCODE_GOOD;
|
||||
}
|
||||
|
||||
static void
|
||||
removePubSubApplicationCallback(UA_Server *server, UA_NodeId identifier, UA_UInt64 callbackId) {
|
||||
if(!pubCallback)
|
||||
return;
|
||||
timer_delete(pubEventTimer);
|
||||
pubCallback = NULL; /* So that a new callback can be registered */
|
||||
}
|
||||
|
||||
static void
|
||||
addPubSubConfiguration(UA_Server* server) {
|
||||
UA_NodeId connectionIdent;
|
||||
UA_NodeId publishedDataSetIdent;
|
||||
UA_NodeId writerGroupIdent;
|
||||
|
||||
UA_PubSubConnectionConfig connectionConfig;
|
||||
memset(&connectionConfig, 0, sizeof(connectionConfig));
|
||||
connectionConfig.name = UA_STRING("UDP-UADP Connection 1");
|
||||
connectionConfig.transportProfileUri =
|
||||
UA_STRING("http://opcfoundation.org/UA-Profile/Transport/pubsub-eth-uadp");
|
||||
connectionConfig.enabled = true;
|
||||
UA_NetworkAddressUrlDataType networkAddressUrl =
|
||||
{UA_STRING(ETH_INTERFACE), UA_STRING(ETH_PUBLISH_ADDRESS)};
|
||||
UA_Variant_setScalar(&connectionConfig.address, &networkAddressUrl,
|
||||
&UA_TYPES[UA_TYPES_NETWORKADDRESSURLDATATYPE]);
|
||||
connectionConfig.publisherIdType = UA_PUBLISHERIDTYPE_UINT32;
|
||||
connectionConfig.publisherId.uint32 = UA_UInt32_random();
|
||||
|
||||
UA_Server_addPubSubConnection(server, &connectionConfig, &connectionIdent);
|
||||
|
||||
UA_PublishedDataSetConfig publishedDataSetConfig;
|
||||
memset(&publishedDataSetConfig, 0, sizeof(UA_PublishedDataSetConfig));
|
||||
publishedDataSetConfig.publishedDataSetType = UA_PUBSUB_DATASET_PUBLISHEDITEMS;
|
||||
publishedDataSetConfig.name = UA_STRING("Demo PDS");
|
||||
UA_Server_addPublishedDataSet(server, &publishedDataSetConfig,
|
||||
&publishedDataSetIdent);
|
||||
|
||||
UA_NodeId dataSetFieldIdentCounter;
|
||||
UA_DataSetFieldConfig counterValue;
|
||||
memset(&counterValue, 0, sizeof(UA_DataSetFieldConfig));
|
||||
counterValue.dataSetFieldType = UA_PUBSUB_DATASETFIELD_VARIABLE;
|
||||
counterValue.field.variable.fieldNameAlias = UA_STRING ("Counter Variable 1");
|
||||
counterValue.field.variable.promotedField = UA_FALSE;
|
||||
counterValue.field.variable.publishParameters.publishedVariable = counterNodePublisher;
|
||||
counterValue.field.variable.publishParameters.attributeId = UA_ATTRIBUTEID_VALUE;
|
||||
|
||||
#if defined(PUBSUB_CONFIG_FASTPATH_FIXED_OFFSETS) || (PUBSUB_CONFIG_FASTPATH_STATIC_VALUES)
|
||||
staticValueSource = UA_DataValue_new();
|
||||
UA_Variant_setScalar(&staticValueSource->value, &publishValue, &UA_TYPES[UA_TYPES_UINT64]);
|
||||
counterValue.field.variable.rtValueSource.rtFieldSourceEnabled = UA_TRUE;
|
||||
counterValue.field.variable.rtValueSource.staticValueSource = &staticValueSource;
|
||||
#endif
|
||||
UA_Server_addDataSetField(server, publishedDataSetIdent, &counterValue,
|
||||
&dataSetFieldIdentCounter);
|
||||
UA_WriterGroupConfig writerGroupConfig;
|
||||
memset(&writerGroupConfig, 0, sizeof(UA_WriterGroupConfig));
|
||||
writerGroupConfig.name = UA_STRING("Demo WriterGroup");
|
||||
writerGroupConfig.publishingInterval = PUB_INTERVAL;
|
||||
writerGroupConfig.enabled = UA_FALSE;
|
||||
writerGroupConfig.encodingMimeType = UA_PUBSUB_ENCODING_UADP;
|
||||
writerGroupConfig.rtLevel = UA_PUBSUB_RT_FIXED_SIZE;
|
||||
writerGroupConfig.pubsubManagerCallback.addCustomCallback = addPubSubApplicationCallback;
|
||||
writerGroupConfig.pubsubManagerCallback.changeCustomCallback = changePubSubApplicationCallback;
|
||||
writerGroupConfig.pubsubManagerCallback.removeCustomCallback = removePubSubApplicationCallback;
|
||||
UA_Server_addWriterGroup(server, connectionIdent,
|
||||
&writerGroupConfig, &writerGroupIdent);
|
||||
|
||||
UA_NodeId dataSetWriterIdent;
|
||||
UA_DataSetWriterConfig dataSetWriterConfig;
|
||||
memset(&dataSetWriterConfig, 0, sizeof(UA_DataSetWriterConfig));
|
||||
dataSetWriterConfig.name = UA_STRING("Demo DataSetWriter");
|
||||
dataSetWriterConfig.dataSetWriterId = DATA_SET_WRITER_ID;
|
||||
dataSetWriterConfig.keyFrameCount = 10;
|
||||
UA_Server_addDataSetWriter(server, writerGroupIdent, publishedDataSetIdent,
|
||||
&dataSetWriterConfig, &dataSetWriterIdent);
|
||||
|
||||
UA_Server_freezeWriterGroupConfiguration(server, writerGroupIdent);
|
||||
UA_Server_enableWriterGroup(server, writerGroupIdent);
|
||||
}
|
||||
|
||||
static void
|
||||
addServerNodes(UA_Server* server) {
|
||||
UA_UInt64 value = 0;
|
||||
UA_VariableAttributes publisherAttr = UA_VariableAttributes_default;
|
||||
UA_Variant_setScalar(&publisherAttr.value, &value, &UA_TYPES[UA_TYPES_UINT64]);
|
||||
publisherAttr.displayName = UA_LOCALIZEDTEXT("en-US", "Publisher Counter");
|
||||
publisherAttr.accessLevel = UA_ACCESSLEVELMASK_READ | UA_ACCESSLEVELMASK_WRITE;
|
||||
UA_DataSource dataSource;
|
||||
dataSource.read = readPublishValue;
|
||||
dataSource.write = NULL;
|
||||
UA_Server_addDataSourceVariableNode(server, counterNodePublisher,
|
||||
UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER),
|
||||
UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES),
|
||||
UA_QUALIFIEDNAME(1, "Publisher Counter"),
|
||||
UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE),
|
||||
publisherAttr, dataSource, NULL, NULL);
|
||||
}
|
||||
|
||||
/* Stop signal */
|
||||
static void stopHandler(int sign) {
|
||||
UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "received ctrl-c");
|
||||
running = UA_FALSE;
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
signal(SIGINT, stopHandler);
|
||||
signal(SIGTERM, stopHandler);
|
||||
|
||||
UA_Server *server = UA_Server_new();
|
||||
UA_ServerConfig *config = UA_Server_getConfig(server);
|
||||
UA_ServerConfig_setDefault(config);
|
||||
|
||||
addServerNodes(server);
|
||||
addPubSubConfiguration(server);
|
||||
|
||||
/* Run the server */
|
||||
UA_StatusCode retval = UA_Server_run(server, &running);
|
||||
#if defined(PUBSUB_CONFIG_FASTPATH_FIXED_OFFSETS) || (PUBSUB_CONFIG_FASTPATH_STATIC_VALUES)
|
||||
if(staticValueSource != NULL) {
|
||||
UA_DataValue_init(staticValueSource);
|
||||
UA_DataValue_delete(staticValueSource);
|
||||
}
|
||||
#endif
|
||||
UA_Server_delete(server);
|
||||
|
||||
return (int)retval;
|
||||
}
|
@ -1,51 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
#program path or install dir
|
||||
IF=enp0s8
|
||||
CPU_NR=3
|
||||
PRIO=90
|
||||
OLDDIR=$(pwd)
|
||||
|
||||
reset() {
|
||||
#standard on most systems. ondemand -> Dynamic CPU-Freq.
|
||||
echo ondemand >/sys/devices/system/cpu/cpu$CPU_NR/cpufreq/scaling_governor
|
||||
cd /sys/devices/system/cpu/cpu$CPU_NR/cpuidle
|
||||
for i in *
|
||||
do
|
||||
#~disable sleep state
|
||||
echo 0 >$i/disable
|
||||
done
|
||||
|
||||
phy=$IF #get interface name
|
||||
#get pid's from interface irq
|
||||
for i in `ps ax | grep -v grep | grep $phy | sed "s/^ //" | cut -d" " -f1`
|
||||
do
|
||||
#retrive or set a process's CPU affinity
|
||||
taskset -pc 0-$CPU_NR $i >/dev/null
|
||||
#manipulate the real-time attributes of a process -p priority -f scheduling policy to SCHED_FIFO
|
||||
chrt -pf 50 $i
|
||||
done
|
||||
#distribute hardware interrupts across processsors on a muliprocessor system
|
||||
systemctl start irqbalance
|
||||
}
|
||||
|
||||
trap reset ERR
|
||||
systemctl stop irqbalance
|
||||
|
||||
phy=$IF
|
||||
for i in `ps ax | grep -v grep | grep $phy | sed "s/^ //" | cut -d" " -f1`
|
||||
do
|
||||
taskset -pc $CPU_NR $i >/dev/null
|
||||
chrt -pf $PRIO $i
|
||||
done
|
||||
|
||||
cd /sys/devices/system/cpu/cpu$CPU_NR/cpuidle
|
||||
for i in `ls -1r`
|
||||
do
|
||||
echo 1 >$i/disable
|
||||
done
|
||||
|
||||
echo performance >/sys/devices/system/cpu/cpu$CPU_NR/cpufreq/scaling_governor
|
||||
|
||||
cd $OLDDIR
|
||||
taskset -c $CPU_NR chrt -f $PRIO $1
|
@ -1,103 +0,0 @@
|
||||
# Open62541 pubsub VxWorks TSN publisher
|
||||
|
||||
This TSN publisher is a complement to [pubsub_interrupt_publish.c](../pubsub_interrupt_publish.c)(For more information, please refer to its [README](../README.md)). It is a VxWorks specific implementation with VxWorks-native TSN features like TSN Clock, TSN Stream, etc, which can be applied to hard realtime scenarios.
|
||||
|
||||
In this publisher, TSN cycle is triggered by TSN Clock -- A high-precision hardware interrupt generated by a 1588 device. The ISR will wake up a task to publish OPC-UA packets, one packet per cycle.
|
||||
|
||||
Considering in a typical VxWorks user scenario -- an embedded device with limited compute resources and probably without local storage, the performance will not be measured directly by this publisher. Instead, the publisher will collect all the necessary data, pack the data into an OPC-UA packet and publish it.
|
||||
|
||||
The measurement data includes:
|
||||
|
||||
1. The trigger time(1588 time) of each cycle.
|
||||
2. Each wake-up time(1588 time) of the publisher task.
|
||||
3. Each end time(1588 time) of the publisher task.
|
||||
|
||||
## User interfaces
|
||||
|
||||
This publisher provides 2 APIs to users:
|
||||
|
||||
1. `open62541PubTSNStart`
|
||||
|
||||
```C
|
||||
/**
|
||||
* Create a publisher with/without TSN.
|
||||
* eName: Ethernet interface name like "gei", "gem", etc
|
||||
* eNameSize: The length of eName including "\0"
|
||||
* unit: Unit NO of the ethernet interface
|
||||
* stkIdx: Network stack index
|
||||
* withTsn: true: Enable TSN; false: Disable TSN
|
||||
*
|
||||
* @return OK on success, otherwise ERROR
|
||||
*/
|
||||
STATUS open62541PubTSNStart(char *eName, size_t eNameSize, int unit, uint32_t stkIdx, bool withTsn);
|
||||
```
|
||||
|
||||
In order to get the best performance, this API supports to associate a dedicated CPU core with TSN related interrupt and tasks. The stkIdx argument is used for this purpose. Its value is the CPU core number: 0 means Core 0, 1 means Core 1, etc.
|
||||
|
||||
2. `open62541PubTSNStop`. This API is used to stop the publisher.
|
||||
|
||||
```C
|
||||
void open62541PubTSNStop();
|
||||
```
|
||||
|
||||
## Building the VxWorks TSN Publisher
|
||||
|
||||
This publisher should be built under VxWorks' building environment. For specific building instructions, please refer to VxWorks' user manual.
|
||||
|
||||
## Running the VxWorks TSN Publisher
|
||||
|
||||
1. Before starting the publisher, you should make sure that PTP is well synchronized. For the usage of PTP, please refer to VxWorks' user manual.
|
||||
2. Do TSN configuration by tsnConfig as below:
|
||||
|
||||
```sh
|
||||
-> tsnConfig("gei", 4, 1, 0, "/romfs/62541.json")
|
||||
```
|
||||
|
||||
`62541.json` is a TSN configuration file. Below is one example configuration using 250 usecs cycle time. Adjust cycle_time and tx_time according to your system performance.
|
||||
|
||||
```json
|
||||
{
|
||||
"schedule": {
|
||||
"cycle_time": 250000,
|
||||
"start_sec": 0,
|
||||
"start_nsec": 0
|
||||
},
|
||||
"stream_objects": [
|
||||
{
|
||||
"stream": {
|
||||
"name": "flow1",
|
||||
"dst_mac": "01:00:5E:00:00:01",
|
||||
"vid": 3000,
|
||||
"pcp": 7,
|
||||
"tclass": 7,
|
||||
"tx_time": {
|
||||
"offset": 200000
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
3. Then run `open62541PubTSNStart`. Below is an example.
|
||||
|
||||
```sh
|
||||
-> open62541PubTSNStart("gei", 4, 1, 3)
|
||||
value = 0 = 0x0
|
||||
-> [1970-01-01 00:47:33.100 (UTC+0000)] warn/server Username/Password configured, but no encrypting SecurityPolicy. This can leak credentials on the ne.
|
||||
[1970-01-01 00:47:33.100 (UTC+0000)] warn/userland AcceptAll Certificate Verification. Any remote certificate will be accepted.
|
||||
[1970-01-01 00:47:33.100 (UTC+0000)] info/userland PubSub channel requested
|
||||
[1970-01-01 00:47:33.100 (UTC+0000)] info/server Open PubSub ethernet connection.
|
||||
[1970-01-01 00:47:33.100 (UTC+0000)] info/userland Adding a publisher with a cycle time of 0.250000 milliseconds
|
||||
[1970-01-01 00:47:33.200 (UTC+0000)] info/network TCP network layer listening on opc.tcp://vxWorks:4840/
|
||||
```
|
||||
|
||||
4. Run `open62541PubTSNStop` to stop the publisher.
|
||||
|
||||
```sh
|
||||
-> open62541PubTSNStop
|
||||
[1970-01-01 00:48:16.500 (UTC+0000)] info/server Stop TSN publisher
|
||||
[1970-01-01 00:48:16.516 (UTC+0000)] info/network Shutting down the TCP network layer
|
||||
[1970-01-01 00:48:16.516 (UTC+0000)] info/server PubSub cleanup was called.
|
||||
value = 0 = 0x0
|
||||
```
|
@ -1,630 +0,0 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*
|
||||
* Copyright (c) 2020, 2022, 2024 Wind River Systems, Inc.
|
||||
*/
|
||||
|
||||
#include <vxWorks.h>
|
||||
#include <stdio.h>
|
||||
#include <signal.h>
|
||||
#include <time.h>
|
||||
|
||||
#include <endCommon.h>
|
||||
#include <tsnClkLib.h>
|
||||
#include <endian.h>
|
||||
#include <semLib.h>
|
||||
#include <taskLib.h>
|
||||
|
||||
#include <tsnConfigLib.h>
|
||||
|
||||
#include <open62541/server.h>
|
||||
#include <open62541/server_pubsub.h>
|
||||
#include <open62541/server_config_default.h>
|
||||
#include <open62541/plugin/log_stdout.h>
|
||||
|
||||
#define ETH_PUBLISH_ADDRESS "opc.eth://01-00-5E-00-00-01"
|
||||
#define MILLI_AS_NANO_SECONDS (1000 * 1000)
|
||||
#define SECONDS_AS_NANO_SECONDS (1000 * 1000 * 1000)
|
||||
|
||||
#define DATA_SET_WRITER_ID 62541
|
||||
#define TSN_TIMER_NO 0
|
||||
#define NS_PER_SEC 1000000000 /* nanoseconds per one second */
|
||||
#define NS_PER_MS 1000000 /* nanoseconds per 1 millisecond */
|
||||
#define TSN_TASK_PRIO 30
|
||||
#define TSN_TASK_STACKSZ 8192
|
||||
|
||||
#define KEY_STREAM_NAME "streamName"
|
||||
#define KEY_STACK_IDX "stackIdx"
|
||||
|
||||
static UA_NodeId seqNumNodeId;
|
||||
static UA_NodeId cycleTriggerTimeNodeId;
|
||||
static UA_NodeId taskBeginTimeNodeId;
|
||||
static UA_NodeId taskEndTimeNodeId;
|
||||
static UA_ServerCallback pubCallback = NULL; /* Sentinel if a timer is active */
|
||||
static UA_Server *pubServer;
|
||||
static UA_Boolean running = true;
|
||||
static void *pubData;
|
||||
static TASK_ID tsnTask = TASK_ID_NULL;
|
||||
static TASK_ID serverTask = TASK_ID_NULL;
|
||||
static TSN_STREAM_CFG *streamCfg = NULL;
|
||||
|
||||
/* The value to published */
|
||||
static UA_UInt32 sequenceNumber = 0;
|
||||
static UA_UInt64 cycleTriggerTime = 0;
|
||||
static UA_UInt64 lastCycleTriggerTime = 0;
|
||||
static UA_UInt64 lastTaskBeginTime = 0;
|
||||
static UA_UInt64 lastTaskEndTime = 0;
|
||||
static UA_DataValue *staticValueSeqNum = NULL;
|
||||
static UA_DataValue *staticValueCycTrig = NULL;
|
||||
static UA_DataValue *staticValueCycBegin = NULL;
|
||||
static UA_DataValue *staticValueCycEnd = NULL;
|
||||
static clockid_t tsnClockId = 0; /* TSN clock ID */
|
||||
static char *ethName = NULL;
|
||||
static int ethUnit = 0;
|
||||
static char ethInterface[END_NAME_MAX];
|
||||
static SEM_ID msgSendSem = SEM_ID_NULL;
|
||||
static UA_String streamName = {0, NULL};
|
||||
static uint32_t stackIndex = 0;
|
||||
static UA_Double pubInterval = 0;
|
||||
static bool withTSN = false;
|
||||
|
||||
static UA_UInt64 ieee1588TimeGet() {
|
||||
struct timespec ts;
|
||||
(void)tsnClockTimeGet(tsnClockId, &ts);
|
||||
return ((UA_UInt64)ts.tv_sec * NS_PER_SEC + (UA_UInt64)ts.tv_nsec);
|
||||
}
|
||||
|
||||
/* Signal handler */
|
||||
static void
|
||||
publishInterrupt(_Vx_usr_arg_t arg) {
|
||||
cycleTriggerTime = ieee1588TimeGet();
|
||||
if(running) {
|
||||
(void)semGive(msgSendSem);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* **initTsnTimer**
|
||||
*
|
||||
* This function initializes a TSN timer. It connects a user defined routine
|
||||
* to the interrupt handler and sets timer expiration rate.
|
||||
*
|
||||
* period is the period of the timer in nanoseconds.
|
||||
* RETURNS: Clock Id or 0 if anything fails*/
|
||||
static clockid_t
|
||||
initTsnTimer(uint32_t period) {
|
||||
clockid_t cid;
|
||||
uint32_t tickRate = 0;
|
||||
|
||||
cid = tsnClockIdGet(ethName, ethUnit, TSN_TIMER_NO);
|
||||
if(cid != 0) {
|
||||
tickRate = NS_PER_SEC / period;
|
||||
if(tsnTimerAllocate(cid) == ERROR) {
|
||||
return 0;
|
||||
}
|
||||
if((tsnClockConnect(cid, (FUNCPTR)publishInterrupt, NULL) == ERROR) ||
|
||||
(tsnClockRateSet(cid, tickRate) == ERROR)) {
|
||||
(void)tsnTimerRelease(cid);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* Reroute TSN timer interrupt to a specific CPU core */
|
||||
if(tsnClockIntReroute(ethName, ethUnit, stackIndex) != OK) {
|
||||
cid = 0;
|
||||
}
|
||||
return cid;
|
||||
}
|
||||
|
||||
/* The following three methods are originally defined in
|
||||
* /src/pubsub/ua_pubsub_manager.c. We provide a custom implementation here to
|
||||
* use system interrupts instead of time-triggered callbacks in the OPC UA
|
||||
* server control flow. */
|
||||
|
||||
static UA_StatusCode
|
||||
addApplicationCallback(UA_Server *server, UA_NodeId identifier,
|
||||
UA_ServerCallback callback,
|
||||
void *data,
|
||||
UA_Double interval_ms,
|
||||
UA_DateTime *baseTime, UA_TimerPolicy timerPolicy,
|
||||
UA_UInt64 *callbackId) {
|
||||
if(pubCallback) {
|
||||
UA_LOG_WARNING(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
|
||||
"At most one publisher can be registered for interrupt callbacks");
|
||||
return UA_STATUSCODE_BADINTERNALERROR;
|
||||
}
|
||||
|
||||
UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
|
||||
"Adding a publisher with a cycle time of %lf milliseconds", interval_ms);
|
||||
|
||||
/* Convert a double float value milliseconds into an integer value in nanoseconds */
|
||||
uint32_t interval = (uint32_t)(interval_ms * NS_PER_MS);
|
||||
tsnClockId = initTsnTimer(interval);
|
||||
if(tsnClockId == 0) {
|
||||
UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "Cannot allocate a TSN timer");
|
||||
return UA_STATUSCODE_BADINTERNALERROR;
|
||||
}
|
||||
/* Set the callback -- used as a sentinel to detect an operational publisher */
|
||||
pubServer = server;
|
||||
pubCallback = callback;
|
||||
pubData = data;
|
||||
|
||||
if(tsnClockEnable (tsnClockId, NULL) == ERROR) {
|
||||
UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "Cannot enable a TSN timer");
|
||||
(void)tsnTimerRelease(tsnClockId);
|
||||
tsnClockId = 0;
|
||||
return UA_STATUSCODE_BADINTERNALERROR;
|
||||
}
|
||||
|
||||
return UA_STATUSCODE_GOOD;
|
||||
}
|
||||
|
||||
static UA_StatusCode
|
||||
changeApplicationCallbackInterval(UA_Server *server, UA_NodeId identifier,
|
||||
UA_UInt64 callbackId,
|
||||
UA_Double interval_ms,
|
||||
UA_DateTime *baseTime, UA_TimerPolicy timerPolicy) {
|
||||
UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
|
||||
"Switching the publisher cycle to %lf milliseconds", interval_ms);
|
||||
|
||||
/* We are not going to change the timer interval for this case */
|
||||
|
||||
return UA_STATUSCODE_GOOD;
|
||||
}
|
||||
|
||||
static void
|
||||
removeApplicationPubSubCallback(UA_Server *server, UA_NodeId identifier, UA_UInt64 callbackId) {
|
||||
if(!pubCallback) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Before release timer resource, wait for TSN task stopping first. */
|
||||
(void) semGive(msgSendSem);
|
||||
if(tsnTask != TASK_ID_NULL) {
|
||||
(void) taskWait(tsnTask, WAIT_FOREVER);
|
||||
tsnTask = TASK_ID_NULL;
|
||||
}
|
||||
|
||||
/* It is safe to disable and release the timer first, then clear callback */
|
||||
if(tsnClockId != 0) {
|
||||
(void)tsnClockDisable(tsnClockId);
|
||||
(void)tsnTimerRelease(tsnClockId);
|
||||
tsnClockId = 0;
|
||||
}
|
||||
|
||||
pubCallback = NULL;
|
||||
pubServer = NULL;
|
||||
pubData = NULL;
|
||||
}
|
||||
|
||||
static void
|
||||
addPubSubConfiguration(UA_Server* server) {
|
||||
UA_NodeId connectionIdent;
|
||||
UA_NodeId publishedDataSetIdent;
|
||||
UA_NodeId writerGroupIdent;
|
||||
|
||||
UA_PubSubConnectionConfig connectionConfig;
|
||||
memset(&connectionConfig, 0, sizeof(connectionConfig));
|
||||
connectionConfig.name = UA_STRING("UDP-UADP Connection 1");
|
||||
connectionConfig.transportProfileUri =
|
||||
UA_STRING("http://opcfoundation.org/UA-Profile/Transport/pubsub-eth-uadp");
|
||||
connectionConfig.enabled = true;
|
||||
UA_NetworkAddressUrlDataType networkAddressUrl =
|
||||
{UA_STRING(ethInterface), UA_STRING(ETH_PUBLISH_ADDRESS)};
|
||||
UA_Variant_setScalar(&connectionConfig.address, &networkAddressUrl,
|
||||
&UA_TYPES[UA_TYPES_NETWORKADDRESSURLDATATYPE]);
|
||||
connectionConfig.publisherId.numeric = UA_UInt32_random();
|
||||
|
||||
UA_KeyValuePair connectionOptions[2];
|
||||
|
||||
connectionOptions[0].key = UA_QUALIFIEDNAME(0, KEY_STREAM_NAME);
|
||||
UA_Variant_setScalar(&connectionOptions[0].value, &streamName, &UA_TYPES[UA_TYPES_STRING]);
|
||||
connectionOptions[1].key = UA_QUALIFIEDNAME(0, KEY_STACK_IDX);
|
||||
UA_Variant_setScalar(&connectionOptions[1].value, &stackIndex, &UA_TYPES[UA_TYPES_UINT32]);
|
||||
|
||||
connectionConfig.connectionPropertiesSize = 2;
|
||||
connectionConfig.connectionProperties = connectionOptions;
|
||||
|
||||
UA_Server_addPubSubConnection(server, &connectionConfig, &connectionIdent);
|
||||
|
||||
UA_PublishedDataSetConfig publishedDataSetConfig;
|
||||
memset(&publishedDataSetConfig, 0, sizeof(UA_PublishedDataSetConfig));
|
||||
publishedDataSetConfig.publishedDataSetType = UA_PUBSUB_DATASET_PUBLISHEDITEMS;
|
||||
publishedDataSetConfig.name = UA_STRING("Demo PDS");
|
||||
UA_Server_addPublishedDataSet(server, &publishedDataSetConfig,
|
||||
&publishedDataSetIdent);
|
||||
|
||||
UA_DataSetFieldConfig dataSetFieldCfg;
|
||||
UA_NodeId f4;
|
||||
memset(&dataSetFieldCfg, 0, sizeof(UA_DataSetFieldConfig));
|
||||
dataSetFieldCfg.dataSetFieldType = UA_PUBSUB_DATASETFIELD_VARIABLE;
|
||||
dataSetFieldCfg.field.variable.fieldNameAlias = UA_STRING ("Sequence Number");
|
||||
dataSetFieldCfg.field.variable.promotedField = UA_FALSE;
|
||||
dataSetFieldCfg.field.variable.publishParameters.publishedVariable = seqNumNodeId;
|
||||
dataSetFieldCfg.field.variable.publishParameters.attributeId = UA_ATTRIBUTEID_VALUE;
|
||||
|
||||
dataSetFieldCfg.field.variable.rtValueSource.rtFieldSourceEnabled = UA_TRUE;
|
||||
staticValueSeqNum = UA_DataValue_new();
|
||||
UA_Variant_setScalar(&staticValueSeqNum->value, &sequenceNumber, &UA_TYPES[UA_TYPES_UINT32]);
|
||||
staticValueSeqNum->value.storageType = UA_VARIANT_DATA_NODELETE;
|
||||
dataSetFieldCfg.field.variable.rtValueSource.staticValueSource = &staticValueSeqNum;
|
||||
|
||||
UA_Server_addDataSetField(server, publishedDataSetIdent, &dataSetFieldCfg, &f4);
|
||||
|
||||
UA_NodeId f3;
|
||||
memset(&dataSetFieldCfg, 0, sizeof(UA_DataSetFieldConfig));
|
||||
dataSetFieldCfg.dataSetFieldType = UA_PUBSUB_DATASETFIELD_VARIABLE;
|
||||
dataSetFieldCfg.field.variable.fieldNameAlias = UA_STRING ("Cycle Trigger Time");
|
||||
dataSetFieldCfg.field.variable.promotedField = UA_FALSE;
|
||||
dataSetFieldCfg.field.variable.publishParameters.publishedVariable = cycleTriggerTimeNodeId;
|
||||
dataSetFieldCfg.field.variable.publishParameters.attributeId = UA_ATTRIBUTEID_VALUE;
|
||||
|
||||
dataSetFieldCfg.field.variable.rtValueSource.rtFieldSourceEnabled = UA_TRUE;
|
||||
staticValueCycTrig = UA_DataValue_new();
|
||||
UA_Variant_setScalar(&staticValueCycTrig->value, &lastCycleTriggerTime, &UA_TYPES[UA_TYPES_UINT64]);
|
||||
staticValueCycTrig->value.storageType = UA_VARIANT_DATA_NODELETE;
|
||||
dataSetFieldCfg.field.variable.rtValueSource.staticValueSource = &staticValueCycTrig;
|
||||
|
||||
UA_Server_addDataSetField(server, publishedDataSetIdent, &dataSetFieldCfg, &f3);
|
||||
|
||||
UA_NodeId f2;
|
||||
memset(&dataSetFieldCfg, 0, sizeof(UA_DataSetFieldConfig));
|
||||
dataSetFieldCfg.dataSetFieldType = UA_PUBSUB_DATASETFIELD_VARIABLE;
|
||||
dataSetFieldCfg.field.variable.fieldNameAlias = UA_STRING ("Task Begin Time");
|
||||
dataSetFieldCfg.field.variable.promotedField = UA_FALSE;
|
||||
dataSetFieldCfg.field.variable.publishParameters.publishedVariable = taskBeginTimeNodeId;
|
||||
dataSetFieldCfg.field.variable.publishParameters.attributeId = UA_ATTRIBUTEID_VALUE;
|
||||
|
||||
dataSetFieldCfg.field.variable.rtValueSource.rtFieldSourceEnabled = UA_TRUE;
|
||||
staticValueCycBegin = UA_DataValue_new();
|
||||
UA_Variant_setScalar(&staticValueCycBegin->value, &lastTaskBeginTime, &UA_TYPES[UA_TYPES_UINT64]);
|
||||
staticValueCycBegin->value.storageType = UA_VARIANT_DATA_NODELETE;
|
||||
dataSetFieldCfg.field.variable.rtValueSource.staticValueSource = &staticValueCycBegin;
|
||||
|
||||
UA_Server_addDataSetField(server, publishedDataSetIdent, &dataSetFieldCfg, &f2);
|
||||
|
||||
|
||||
UA_NodeId f1;
|
||||
memset(&dataSetFieldCfg, 0, sizeof(UA_DataSetFieldConfig));
|
||||
dataSetFieldCfg.dataSetFieldType = UA_PUBSUB_DATASETFIELD_VARIABLE;
|
||||
dataSetFieldCfg.field.variable.fieldNameAlias = UA_STRING ("Task End Time");
|
||||
dataSetFieldCfg.field.variable.promotedField = UA_FALSE;
|
||||
dataSetFieldCfg.field.variable.publishParameters.publishedVariable = taskEndTimeNodeId;
|
||||
dataSetFieldCfg.field.variable.publishParameters.attributeId = UA_ATTRIBUTEID_VALUE;
|
||||
|
||||
dataSetFieldCfg.field.variable.rtValueSource.rtFieldSourceEnabled = UA_TRUE;
|
||||
staticValueCycEnd = UA_DataValue_new();
|
||||
UA_Variant_setScalar(&staticValueCycEnd->value, &lastTaskEndTime, &UA_TYPES[UA_TYPES_UINT64]);
|
||||
staticValueCycEnd->value.storageType = UA_VARIANT_DATA_NODELETE;
|
||||
dataSetFieldCfg.field.variable.rtValueSource.staticValueSource = &staticValueCycEnd;
|
||||
|
||||
UA_Server_addDataSetField(server, publishedDataSetIdent, &dataSetFieldCfg, &f1);
|
||||
|
||||
UA_WriterGroupConfig writerGroupConfig;
|
||||
memset(&writerGroupConfig, 0, sizeof(UA_WriterGroupConfig));
|
||||
writerGroupConfig.name = UA_STRING("Demo WriterGroup");
|
||||
writerGroupConfig.publishingInterval = pubInterval;
|
||||
writerGroupConfig.enabled = UA_FALSE;
|
||||
writerGroupConfig.encodingMimeType = UA_PUBSUB_ENCODING_UADP;
|
||||
writerGroupConfig.rtLevel = UA_PUBSUB_RT_FIXED_SIZE;
|
||||
writerGroupConfig.pubsubManagerCallback.addCustomCallback = addApplicationCallback;
|
||||
writerGroupConfig.pubsubManagerCallback.changeCustomCallback = changeApplicationCallbackInterval;
|
||||
writerGroupConfig.pubsubManagerCallback.removeCustomCallback = removeApplicationPubSubCallback;
|
||||
UA_Server_addWriterGroup(server, connectionIdent,
|
||||
&writerGroupConfig, &writerGroupIdent);
|
||||
|
||||
UA_NodeId dataSetWriterIdent;
|
||||
UA_DataSetWriterConfig dataSetWriterConfig;
|
||||
memset(&dataSetWriterConfig, 0, sizeof(UA_DataSetWriterConfig));
|
||||
dataSetWriterConfig.name = UA_STRING("Demo DataSetWriter");
|
||||
dataSetWriterConfig.dataSetWriterId = DATA_SET_WRITER_ID;
|
||||
dataSetWriterConfig.keyFrameCount = 10;
|
||||
UA_Server_addDataSetWriter(server, writerGroupIdent, publishedDataSetIdent,
|
||||
&dataSetWriterConfig, &dataSetWriterIdent);
|
||||
|
||||
UA_Server_freezeWriterGroupConfiguration(server, writerGroupIdent);
|
||||
UA_Server_enableWriterGroup(server, writerGroupIdent);
|
||||
}
|
||||
|
||||
static void
|
||||
addServerNodes(UA_Server* server) {
|
||||
UA_UInt64 initVal64 = 0;
|
||||
UA_UInt32 initVal32 = 0;
|
||||
|
||||
UA_NodeId folderId;
|
||||
UA_NodeId_init(&folderId);
|
||||
UA_ObjectAttributes oAttr = UA_ObjectAttributes_default;
|
||||
oAttr.displayName = UA_LOCALIZEDTEXT("en-US", "Publisher TSN");
|
||||
UA_Server_addObjectNode(server, UA_NODEID_NULL,
|
||||
UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER),
|
||||
UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES),
|
||||
UA_QUALIFIEDNAME(1, "Publisher TSN"), UA_NODEID_NUMERIC(0, UA_NS0ID_BASEOBJECTTYPE),
|
||||
oAttr, NULL, &folderId);
|
||||
|
||||
UA_NodeId_init(&seqNumNodeId);
|
||||
seqNumNodeId = UA_NODEID_STRING(1, "sequence.number");
|
||||
UA_VariableAttributes publisherAttr = UA_VariableAttributes_default;
|
||||
publisherAttr.dataType = UA_TYPES[UA_TYPES_UINT32].typeId;
|
||||
UA_Variant_setScalar(&publisherAttr.value, &initVal32, &UA_TYPES[UA_TYPES_UINT32]);
|
||||
publisherAttr.displayName = UA_LOCALIZEDTEXT("en-US", "Sequence Number");
|
||||
publisherAttr.accessLevel = UA_ACCESSLEVELMASK_READ | UA_ACCESSLEVELMASK_WRITE;
|
||||
UA_Server_addVariableNode(server, seqNumNodeId,
|
||||
folderId,
|
||||
UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
|
||||
UA_QUALIFIEDNAME(1, "Sequence Number"),
|
||||
UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE),
|
||||
publisherAttr, NULL, NULL);
|
||||
UA_ValueBackend valueBackend;
|
||||
valueBackend.backendType = UA_VALUEBACKENDTYPE_EXTERNAL;
|
||||
valueBackend.backend.external.value = &staticValueSeqNum;
|
||||
UA_Server_setVariableNode_valueBackend(server, seqNumNodeId, valueBackend);
|
||||
|
||||
UA_NodeId_init(&cycleTriggerTimeNodeId);
|
||||
cycleTriggerTimeNodeId = UA_NODEID_STRING(1, "cycle.trigger.time");
|
||||
publisherAttr = UA_VariableAttributes_default;
|
||||
publisherAttr.dataType = UA_TYPES[UA_TYPES_UINT64].typeId;
|
||||
UA_Variant_setScalar(&publisherAttr.value, &initVal64, &UA_TYPES[UA_TYPES_UINT64]);
|
||||
publisherAttr.displayName = UA_LOCALIZEDTEXT("en-US", "Cycle Trigger Time");
|
||||
publisherAttr.accessLevel = UA_ACCESSLEVELMASK_READ | UA_ACCESSLEVELMASK_WRITE;
|
||||
UA_Server_addVariableNode(server, cycleTriggerTimeNodeId,
|
||||
folderId,
|
||||
UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
|
||||
UA_QUALIFIEDNAME(1, "Cycle Trigger Time"),
|
||||
UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE),
|
||||
publisherAttr, NULL, NULL);
|
||||
valueBackend.backend.external.value = &staticValueCycTrig;
|
||||
UA_Server_setVariableNode_valueBackend(server, cycleTriggerTimeNodeId, valueBackend);
|
||||
|
||||
UA_NodeId_init(&taskBeginTimeNodeId);
|
||||
taskBeginTimeNodeId = UA_NODEID_STRING(1, "task.begin.time");
|
||||
publisherAttr = UA_VariableAttributes_default;
|
||||
publisherAttr.dataType = UA_TYPES[UA_TYPES_UINT64].typeId;
|
||||
UA_Variant_setScalar(&publisherAttr.value, &initVal64, &UA_TYPES[UA_TYPES_UINT64]);
|
||||
publisherAttr.displayName = UA_LOCALIZEDTEXT("en-US", "Task Begin Time");
|
||||
publisherAttr.accessLevel = UA_ACCESSLEVELMASK_READ | UA_ACCESSLEVELMASK_WRITE;
|
||||
UA_Server_addVariableNode(server, taskBeginTimeNodeId,
|
||||
folderId,
|
||||
UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
|
||||
UA_QUALIFIEDNAME(1, "Task Begin Time"),
|
||||
UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE),
|
||||
publisherAttr, NULL, NULL);
|
||||
valueBackend.backend.external.value = &staticValueCycBegin;
|
||||
UA_Server_setVariableNode_valueBackend(server, taskBeginTimeNodeId, valueBackend);
|
||||
|
||||
UA_NodeId_init(&taskEndTimeNodeId);
|
||||
taskEndTimeNodeId = UA_NODEID_STRING(1, "task.end.time");
|
||||
publisherAttr = UA_VariableAttributes_default;
|
||||
publisherAttr.dataType = UA_TYPES[UA_TYPES_UINT64].typeId;
|
||||
UA_Variant_setScalar(&publisherAttr.value, &initVal64, &UA_TYPES[UA_TYPES_UINT64]);
|
||||
publisherAttr.displayName = UA_LOCALIZEDTEXT("en-US", "Task End Time");
|
||||
publisherAttr.accessLevel = UA_ACCESSLEVELMASK_READ | UA_ACCESSLEVELMASK_WRITE;
|
||||
UA_Server_addVariableNode(server, taskEndTimeNodeId,
|
||||
folderId,
|
||||
UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
|
||||
UA_QUALIFIEDNAME(1, "Task End Time"),
|
||||
UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE),
|
||||
publisherAttr, NULL, NULL);
|
||||
valueBackend.backend.external.value = &staticValueCycEnd;
|
||||
UA_Server_setVariableNode_valueBackend(server, taskEndTimeNodeId, valueBackend);
|
||||
}
|
||||
|
||||
static void open62541EthTSNTask(void) {
|
||||
uint64_t t = 0;
|
||||
while(running) {
|
||||
(void) semTake(msgSendSem, WAIT_FOREVER);
|
||||
if(!running) {
|
||||
break;
|
||||
}
|
||||
t = ieee1588TimeGet();
|
||||
|
||||
/*
|
||||
* Because we cannot get the task end time of one packet before it is
|
||||
* sent, we let one packet take its previous packet's cycleTriggerTime,
|
||||
* taskBeginTime and taskEndTime.
|
||||
*/
|
||||
if(sequenceNumber == 0) {
|
||||
lastCycleTriggerTime = 0;
|
||||
lastTaskBeginTime = 0;
|
||||
lastTaskEndTime = 0;
|
||||
}
|
||||
pubCallback(pubServer, pubData);
|
||||
|
||||
sequenceNumber++;
|
||||
lastCycleTriggerTime = cycleTriggerTime;
|
||||
lastTaskBeginTime = t;
|
||||
lastTaskEndTime = ieee1588TimeGet();
|
||||
}
|
||||
}
|
||||
|
||||
static void open62541ServerTask(void) {
|
||||
UA_Server *server = UA_Server_new();
|
||||
if(server == NULL) {
|
||||
UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "Cannot allocate a server object");
|
||||
goto serverCleanup;
|
||||
}
|
||||
|
||||
UA_ServerConfig *config = UA_Server_getConfig(server);
|
||||
UA_ServerConfig_setDefault(config);
|
||||
|
||||
addServerNodes(server);
|
||||
addPubSubConfiguration(server);
|
||||
|
||||
/* Run the server */
|
||||
(void) UA_Server_run(server, &running);
|
||||
|
||||
serverCleanup:
|
||||
if(server != NULL) {
|
||||
UA_Server_delete(server);
|
||||
server = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static bool initTSNStream(char *eName, size_t eNameSize, int unit) {
|
||||
streamCfg = tsnConfigFind (eName, eNameSize, unit);
|
||||
if(streamCfg == NULL) {
|
||||
UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "Cannot find TSN configuration for %s%d", eName, unit);
|
||||
return false;
|
||||
}
|
||||
if(streamCfg->streamCount == 0) {
|
||||
UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "Cannot find stream defined for %s%d", eName, unit);
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
if(withTSN) {
|
||||
streamName = UA_STRING(streamCfg->streamObjs[0].stream.name);
|
||||
} else {
|
||||
UA_LOG_WARNING(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "No TSN stream associated.");
|
||||
streamName = UA_STRING_NULL;
|
||||
}
|
||||
|
||||
}
|
||||
pubInterval = (UA_Double)((streamCfg->schedule.cycleTime * 1.0) / NS_PER_MS);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool initTSNTask() {
|
||||
tsnTask = taskSpawn ((char *)"tTsnPub", TSN_TASK_PRIO, 0, TSN_TASK_STACKSZ, (FUNCPTR)open62541EthTSNTask,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
|
||||
if(tsnTask == TASK_ID_ERROR) {
|
||||
UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "Cannot spawn a TSN task");
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Reroute TSN task to a specific CPU core */
|
||||
#ifdef _WRS_CONFIG_SMP
|
||||
unsigned int ncpus = vxCpuConfiguredGet ();
|
||||
cpuset_t cpus;
|
||||
|
||||
if(stackIndex < ncpus) {
|
||||
CPUSET_ZERO (cpus);
|
||||
CPUSET_SET (cpus, stackIndex);
|
||||
if(taskCpuAffinitySet (tsnTask, cpus) != OK) {
|
||||
UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "Cannot move TSN task to core %d", stackIndex);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool initServerTask() {
|
||||
serverTask = taskSpawn((char *)"tPubServer", TSN_TASK_PRIO + 5, 0, TSN_TASK_STACKSZ*2, (FUNCPTR)open62541ServerTask,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
|
||||
if(serverTask == TASK_ID_ERROR) {
|
||||
UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "Cannot spawn a server task");
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Reroute TSN task to a specific CPU core */
|
||||
#ifdef _WRS_CONFIG_SMP
|
||||
unsigned int ncpus = vxCpuConfiguredGet();
|
||||
cpuset_t cpus;
|
||||
|
||||
if(stackIndex < ncpus) {
|
||||
CPUSET_ZERO(cpus);
|
||||
CPUSET_SET(cpus, stackIndex);
|
||||
if(taskCpuAffinitySet(serverTask, cpus) != OK) {
|
||||
UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "Cannot move TSN task to core %d", stackIndex);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
void open62541PubTSNStop() {
|
||||
UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "Stop TSN publisher");
|
||||
running = UA_FALSE;
|
||||
if(serverTask != TASK_ID_NULL) {
|
||||
(void) taskWait(serverTask, WAIT_FOREVER);
|
||||
serverTask = TASK_ID_NULL;
|
||||
}
|
||||
(void) semGive(msgSendSem);
|
||||
if(tsnTask != TASK_ID_NULL) {
|
||||
(void) taskWait(tsnTask, WAIT_FOREVER);
|
||||
tsnTask = TASK_ID_NULL;
|
||||
}
|
||||
if(msgSendSem != NULL) {
|
||||
(void)semDelete(msgSendSem);
|
||||
msgSendSem = SEM_ID_NULL;
|
||||
}
|
||||
|
||||
ethName = NULL;
|
||||
ethUnit = 0;
|
||||
streamName = UA_STRING_NULL;
|
||||
stackIndex = 0;
|
||||
withTSN = false;
|
||||
pubInterval = 0;
|
||||
streamCfg = NULL;
|
||||
|
||||
if(staticValueSeqNum != NULL) {
|
||||
UA_DataValue_delete(staticValueSeqNum);
|
||||
staticValueSeqNum = NULL;
|
||||
}
|
||||
if(staticValueCycTrig != NULL) {
|
||||
UA_DataValue_delete(staticValueCycTrig);
|
||||
staticValueCycTrig = NULL;
|
||||
}
|
||||
if(staticValueCycBegin != NULL) {
|
||||
UA_DataValue_delete(staticValueCycBegin);
|
||||
staticValueCycBegin = NULL;
|
||||
}
|
||||
if(staticValueCycEnd != NULL) {
|
||||
UA_DataValue_delete(staticValueCycEnd);
|
||||
staticValueCycEnd = NULL;
|
||||
}
|
||||
sequenceNumber = 0;
|
||||
cycleTriggerTime = 0;
|
||||
lastCycleTriggerTime = 0;
|
||||
lastTaskBeginTime = 0;
|
||||
lastTaskEndTime = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a publisher with/without TSN.
|
||||
* eName: Ethernet interface name like "gei", "gem", etc
|
||||
* eNameSize: The length of eName including "\0"
|
||||
* unit: Unit NO of the ethernet interface
|
||||
* stkIdx: Network stack index
|
||||
* withTsn: true: Enable TSN; false: Disable TSN
|
||||
*
|
||||
* @return OK on success, otherwise ERROR
|
||||
*/
|
||||
STATUS open62541PubTSNStart(char *eName, size_t eNameSize, int unit, uint32_t stkIdx, bool withTsn) {
|
||||
if((eName == NULL) || (eNameSize == 0)) {
|
||||
UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "Ethernet interface name is invalid");
|
||||
return ERROR;
|
||||
}
|
||||
|
||||
ethName = eName;
|
||||
ethUnit = unit;
|
||||
stackIndex = stkIdx;
|
||||
withTSN = withTsn;
|
||||
snprintf(ethInterface, sizeof(ethInterface), "%s%d", eName, unit);
|
||||
|
||||
if(!initTSNStream(eName, eNameSize, unit)) {
|
||||
goto startCleanup;
|
||||
}
|
||||
|
||||
/* Create a binary semaphore which is used by the TSN timer to wake up the sender task */
|
||||
msgSendSem = semBCreate(SEM_Q_FIFO, SEM_EMPTY);
|
||||
if(msgSendSem == SEM_ID_NULL) {
|
||||
UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "Cannot create a semaphore");
|
||||
goto startCleanup;
|
||||
}
|
||||
running = true;
|
||||
if(!initTSNTask()) {
|
||||
goto startCleanup;
|
||||
}
|
||||
|
||||
if(!initServerTask()) {
|
||||
goto startCleanup;
|
||||
}
|
||||
|
||||
|
||||
return OK;
|
||||
startCleanup:
|
||||
open62541PubTSNStop();
|
||||
return ERROR;
|
||||
}
|
0
examples/pubsub_realtime/opc-ua-tsn-wrs.pdf
Executable file → Normal file
0
examples/pubsub_realtime/opc-ua-tsn-wrs.pdf
Executable file → Normal file
151
examples/pubsub_realtime/server_pubsub_publish_rt_offsets.c
Normal file
151
examples/pubsub_realtime/server_pubsub_publish_rt_offsets.c
Normal file
@ -0,0 +1,151 @@
|
||||
/* This work is licensed under a Creative Commons CCZero 1.0 Universal License.
|
||||
* See http://creativecommons.org/publicdomain/zero/1.0/ for more information. */
|
||||
|
||||
#include <open62541/server.h>
|
||||
#include <open62541/server_pubsub.h>
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#define PUBSUB_CONFIG_PUBLISH_CYCLE_MS 100
|
||||
#define PUBSUB_CONFIG_FIELD_COUNT 10
|
||||
|
||||
static UA_NodeId publishedDataSetIdent, dataSetFieldIdent, writerGroupIdent, connectionIdentifier;
|
||||
|
||||
/* Values in static locations. We cycle the dvPointers double-pointer to the
|
||||
* next with atomic operations. */
|
||||
UA_UInt32 valueStore[PUBSUB_CONFIG_FIELD_COUNT];
|
||||
UA_DataValue dvStore[PUBSUB_CONFIG_FIELD_COUNT];
|
||||
UA_DataValue *dvPointers[PUBSUB_CONFIG_FIELD_COUNT];
|
||||
UA_NodeId publishVariables[PUBSUB_CONFIG_FIELD_COUNT];
|
||||
|
||||
int main(void) {
|
||||
/* Prepare the values */
|
||||
for(size_t i = 0; i < PUBSUB_CONFIG_FIELD_COUNT; i++) {
|
||||
valueStore[i] = (UA_UInt32) i + 1;
|
||||
UA_Variant_setScalar(&dvStore[i].value, &valueStore[i], &UA_TYPES[UA_TYPES_UINT32]);
|
||||
dvStore[i].hasValue = true;
|
||||
dvPointers[i] = &dvStore[i];
|
||||
}
|
||||
|
||||
/* Initialize the server */
|
||||
UA_Server *server = UA_Server_new();
|
||||
|
||||
/* Add a PubSubConnection */
|
||||
UA_PubSubConnectionConfig connectionConfig;
|
||||
memset(&connectionConfig, 0, sizeof(connectionConfig));
|
||||
connectionConfig.name = UA_STRING("UDP-UADP Connection 1");
|
||||
connectionConfig.transportProfileUri =
|
||||
UA_STRING("http://opcfoundation.org/UA-Profile/Transport/pubsub-udp-uadp");
|
||||
UA_NetworkAddressUrlDataType networkAddressUrl =
|
||||
{UA_STRING_NULL , UA_STRING("opc.udp://224.0.0.22:4840/")};
|
||||
UA_Variant_setScalar(&connectionConfig.address, &networkAddressUrl,
|
||||
&UA_TYPES[UA_TYPES_NETWORKADDRESSURLDATATYPE]);
|
||||
connectionConfig.publisherId.idType = UA_PUBLISHERIDTYPE_UINT16;
|
||||
connectionConfig.publisherId.id.uint16 = 2234;
|
||||
UA_Server_addPubSubConnection(server, &connectionConfig, &connectionIdentifier);
|
||||
|
||||
/* Add a PublishedDataSet */
|
||||
UA_PublishedDataSetConfig publishedDataSetConfig;
|
||||
memset(&publishedDataSetConfig, 0, sizeof(UA_PublishedDataSetConfig));
|
||||
publishedDataSetConfig.publishedDataSetType = UA_PUBSUB_DATASET_PUBLISHEDITEMS;
|
||||
publishedDataSetConfig.name = UA_STRING("Demo PDS");
|
||||
UA_Server_addPublishedDataSet(server, &publishedDataSetConfig, &publishedDataSetIdent);
|
||||
|
||||
/* Add DataSetFields with static value source to PDS */
|
||||
UA_DataSetFieldConfig dsfConfig;
|
||||
for(size_t i = 0; i < PUBSUB_CONFIG_FIELD_COUNT; i++) {
|
||||
/* Create the variable */
|
||||
UA_VariableAttributes vAttr = UA_VariableAttributes_default;
|
||||
vAttr.displayName = UA_LOCALIZEDTEXT ("en-US", "Subscribed UInt32");
|
||||
vAttr.dataType = UA_TYPES[UA_TYPES_UINT32].typeId;
|
||||
UA_Server_addVariableNode(server, UA_NODEID_NUMERIC(1, (UA_UInt32)i + 50000),
|
||||
UA_NS0ID(OBJECTSFOLDER), UA_NS0ID(HASCOMPONENT),
|
||||
UA_QUALIFIEDNAME(1, "Subscribed UInt32"),
|
||||
UA_NS0ID(BASEDATAVARIABLETYPE),
|
||||
vAttr, NULL, &publishVariables[i]);
|
||||
|
||||
/* Set the value backend of the above create node to 'external value source' */
|
||||
UA_ValueBackend valueBackend;
|
||||
memset(&valueBackend, 0, sizeof(UA_ValueBackend));
|
||||
valueBackend.backendType = UA_VALUEBACKENDTYPE_EXTERNAL;
|
||||
valueBackend.backend.external.value = &dvPointers[i];
|
||||
UA_Server_setVariableNode_valueBackend(server, publishVariables[i], valueBackend);
|
||||
|
||||
/* Add the DataSetField */
|
||||
memset(&dsfConfig, 0, sizeof(UA_DataSetFieldConfig));
|
||||
dsfConfig.field.variable.publishParameters.publishedVariable = publishVariables[i];
|
||||
UA_Server_addDataSetField(server, publishedDataSetIdent, &dsfConfig, &dataSetFieldIdent);
|
||||
}
|
||||
|
||||
/* Add a WriterGroup */
|
||||
UA_WriterGroupConfig writerGroupConfig;
|
||||
memset(&writerGroupConfig, 0, sizeof(UA_WriterGroupConfig));
|
||||
writerGroupConfig.name = UA_STRING("Demo WriterGroup");
|
||||
writerGroupConfig.publishingInterval = PUBSUB_CONFIG_PUBLISH_CYCLE_MS;
|
||||
writerGroupConfig.writerGroupId = 100;
|
||||
writerGroupConfig.encodingMimeType = UA_PUBSUB_ENCODING_UADP;
|
||||
|
||||
/* Change message settings of writerGroup to send PublisherId, WriterGroupId
|
||||
* in GroupHeader and DataSetWriterId in PayloadHeader of NetworkMessage */
|
||||
UA_UadpWriterGroupMessageDataType writerGroupMessage;
|
||||
UA_UadpWriterGroupMessageDataType_init(&writerGroupMessage);
|
||||
writerGroupMessage.networkMessageContentMask = (UA_UadpNetworkMessageContentMask)
|
||||
(UA_UADPNETWORKMESSAGECONTENTMASK_PUBLISHERID |
|
||||
UA_UADPNETWORKMESSAGECONTENTMASK_GROUPHEADER |
|
||||
UA_UADPNETWORKMESSAGECONTENTMASK_GROUPVERSION |
|
||||
UA_UADPNETWORKMESSAGECONTENTMASK_WRITERGROUPID |
|
||||
UA_UADPNETWORKMESSAGECONTENTMASK_SEQUENCENUMBER |
|
||||
UA_UADPNETWORKMESSAGECONTENTMASK_PAYLOADHEADER);
|
||||
UA_ExtensionObject_setValue(&writerGroupConfig.messageSettings, &writerGroupMessage,
|
||||
&UA_TYPES[UA_TYPES_UADPWRITERGROUPMESSAGEDATATYPE]);
|
||||
|
||||
UA_Server_addWriterGroup(server, connectionIdentifier, &writerGroupConfig, &writerGroupIdent);
|
||||
|
||||
/* Add a DataSetWriter to the WriterGroup */
|
||||
UA_NodeId dataSetWriterIdent;
|
||||
UA_DataSetWriterConfig dataSetWriterConfig;
|
||||
memset(&dataSetWriterConfig, 0, sizeof(UA_DataSetWriterConfig));
|
||||
dataSetWriterConfig.name = UA_STRING("Demo DataSetWriter");
|
||||
dataSetWriterConfig.dataSetWriterId = 62541;
|
||||
dataSetWriterConfig.keyFrameCount = 10;
|
||||
dataSetWriterConfig.dataSetFieldContentMask = UA_DATASETFIELDCONTENTMASK_RAWDATA;
|
||||
|
||||
UA_UadpDataSetWriterMessageDataType uadpDataSetWriterMessageDataType;
|
||||
UA_UadpDataSetWriterMessageDataType_init(&uadpDataSetWriterMessageDataType);
|
||||
uadpDataSetWriterMessageDataType.dataSetMessageContentMask =
|
||||
UA_UADPDATASETMESSAGECONTENTMASK_SEQUENCENUMBER;
|
||||
UA_ExtensionObject_setValue(&dataSetWriterConfig.messageSettings,
|
||||
&uadpDataSetWriterMessageDataType,
|
||||
&UA_TYPES[UA_TYPES_UADPDATASETWRITERMESSAGEDATATYPE]);
|
||||
|
||||
UA_Server_addDataSetWriter(server, writerGroupIdent, publishedDataSetIdent,
|
||||
&dataSetWriterConfig, &dataSetWriterIdent);
|
||||
|
||||
/* Print the Offset Table */
|
||||
UA_PubSubOffsetTable ot;
|
||||
UA_Server_computeWriterGroupOffsetTable(server, writerGroupIdent, &ot);
|
||||
for(size_t i = 0; i < ot.offsetsSize; i++) {
|
||||
UA_String out = UA_STRING_NULL;
|
||||
if(ot.offsets[i].offsetType >= UA_PUBSUBOFFSETTYPE_DATASETFIELD_DATAVALUE) {
|
||||
/* For writers the component is the NodeId is the DataSetField.
|
||||
* Instead print the source node that contains the data */
|
||||
UA_DataSetFieldConfig dsfc;
|
||||
UA_Server_getDataSetFieldConfig(server, ot.offsets[i].component, &dsfc);
|
||||
UA_NodeId_print(&dsfc.field.variable.publishParameters.publishedVariable, &out);
|
||||
UA_DataSetFieldConfig_clear(&dsfc);
|
||||
} else {
|
||||
UA_NodeId_print(&ot.offsets[i].component, &out);
|
||||
}
|
||||
printf("%u:\tOffset %u\tOffsetType %u\tComponent %.*s\n",
|
||||
(unsigned)i, (unsigned)ot.offsets[i].offset,
|
||||
(unsigned)ot.offsets[i].offsetType,
|
||||
(int)out.length, out.data);
|
||||
UA_String_clear(&out);
|
||||
}
|
||||
|
||||
/* Cleanup */
|
||||
UA_PubSubOffsetTable_clear(&ot);
|
||||
UA_Server_delete(server);
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
@ -8,11 +8,15 @@
|
||||
#include <open62541/types.h>
|
||||
|
||||
#include <signal.h>
|
||||
#include <stdio.h>
|
||||
#include <time.h>
|
||||
|
||||
#define PUBSUB_CONFIG_PUBLISH_CYCLE_MS 100
|
||||
#define PUBSUB_CONFIG_FIELD_COUNT 10
|
||||
|
||||
UA_NodeId publishedDataSetIdent, dataSetFieldIdent, writerGroupIdent, connectionIdentifier;
|
||||
static UA_Server *server;
|
||||
static timer_t writerGroupTimer;
|
||||
static UA_NodeId publishedDataSetIdent, dataSetFieldIdent, writerGroupIdent, connectionIdentifier;
|
||||
|
||||
/**
|
||||
* For realtime publishing the following is configured:
|
||||
@ -47,36 +51,65 @@ valueUpdateCallback(UA_Server *server, void *data) {
|
||||
}
|
||||
}
|
||||
|
||||
/* Dedicated EventLoop for PubSub */
|
||||
volatile UA_Boolean pubSubELRunning = true;
|
||||
UA_EventLoop *pubSubEL;
|
||||
/* WriterGroup timer managed by a custom state machine. This uses
|
||||
* UA_Server_triggerWriterGroupPublish. The server can block its internal mutex,
|
||||
* so this can have some jitter. For hard realtime the publish callback has to
|
||||
* send out the packet without going through the server. */
|
||||
|
||||
static void *
|
||||
runPubSubEL(void *_) {
|
||||
sigset_t set;
|
||||
sigemptyset(&set);
|
||||
sigaddset(&set, SIGINT);
|
||||
pthread_sigmask(SIG_BLOCK, &set, NULL);
|
||||
while(pubSubELRunning)
|
||||
pubSubEL->run(pubSubEL, 100);
|
||||
return NULL;
|
||||
static void
|
||||
writerGroupPublishTrigger(union sigval signal) {
|
||||
printf("XXX Publish Callback\n");
|
||||
UA_Server_triggerWriterGroupPublish(server, writerGroupIdent);
|
||||
}
|
||||
|
||||
static UA_StatusCode
|
||||
writerGroupStateMachine(UA_Server *server, const UA_NodeId componentId,
|
||||
void *componentContext, UA_PubSubState *state,
|
||||
UA_PubSubState targetState) {
|
||||
UA_WriterGroupConfig config;
|
||||
struct itimerspec interval;
|
||||
memset(&interval, 0, sizeof(interval));
|
||||
|
||||
if(targetState == *state)
|
||||
return UA_STATUSCODE_GOOD;
|
||||
|
||||
switch(targetState) {
|
||||
/* Disabled or Error */
|
||||
case UA_PUBSUBSTATE_ERROR:
|
||||
case UA_PUBSUBSTATE_DISABLED:
|
||||
case UA_PUBSUBSTATE_PAUSED:
|
||||
printf("XXX Disabling the WriterGroup\n");
|
||||
timer_settime(writerGroupTimer, 0, &interval, NULL);
|
||||
*state = targetState;
|
||||
break;
|
||||
|
||||
/* Operational */
|
||||
case UA_PUBSUBSTATE_PREOPERATIONAL:
|
||||
case UA_PUBSUBSTATE_OPERATIONAL:
|
||||
if(*state == UA_PUBSUBSTATE_OPERATIONAL)
|
||||
break;
|
||||
printf("XXX Enabling the WriterGroup\n");
|
||||
UA_Server_getWriterGroupConfig(server, writerGroupIdent, &config);
|
||||
interval.it_interval.tv_sec = config.publishingInterval / 1000;
|
||||
interval.it_interval.tv_nsec =
|
||||
((long long)(config.publishingInterval * 1000 * 1000)) % (1000 * 1000 * 1000);
|
||||
interval.it_value = interval.it_interval;
|
||||
UA_WriterGroupConfig_clear(&config);
|
||||
int res = timer_settime(writerGroupTimer, 0, &interval, NULL);
|
||||
if(res != 0)
|
||||
return UA_STATUSCODE_BADINTERNALERROR;
|
||||
*state = UA_PUBSUBSTATE_OPERATIONAL;
|
||||
break;
|
||||
|
||||
/* Unknown state */
|
||||
default:
|
||||
return UA_STATUSCODE_BADINTERNALERROR;
|
||||
}
|
||||
|
||||
return UA_STATUSCODE_GOOD;
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
UA_Server *server = UA_Server_new();
|
||||
|
||||
/* Instantiate the custom EventLoop.
|
||||
* Will be attached to the PubSubConnection and gets used for everything "above".
|
||||
* This should be bound to a dedicated core for RT. */
|
||||
pubSubEL = UA_EventLoop_new_POSIX(UA_Log_Stdout);
|
||||
UA_ConnectionManager *udpCM =
|
||||
UA_ConnectionManager_new_POSIX_UDP(UA_STRING("udp connection manager"));
|
||||
pubSubEL->registerEventSource(pubSubEL, (UA_EventSource *)udpCM);
|
||||
pubSubEL->start(pubSubEL);
|
||||
|
||||
pthread_t pubSubELThread;
|
||||
pthread_create(&pubSubELThread, NULL, runPubSubEL, NULL);
|
||||
|
||||
/* Prepare the values */
|
||||
for(size_t i = 0; i < PUBSUB_CONFIG_FIELD_COUNT; i++) {
|
||||
valueStore[i] = (UA_UInt32) i + 1;
|
||||
@ -85,14 +118,26 @@ int main(void) {
|
||||
dvPointers[i] = &dvStore[i];
|
||||
}
|
||||
|
||||
/* Initialize the timer */
|
||||
struct sigevent sigev;
|
||||
memset(&sigev, 0, sizeof(sigev));
|
||||
sigev.sigev_notify = SIGEV_THREAD;
|
||||
sigev.sigev_notify_function = writerGroupPublishTrigger;
|
||||
timer_create(CLOCK_REALTIME, &sigev, &writerGroupTimer);
|
||||
|
||||
/* Initialize the server */
|
||||
server = UA_Server_new();
|
||||
|
||||
/* Add a PubSubConnection */
|
||||
UA_PubSubConnectionConfig connectionConfig;
|
||||
memset(&connectionConfig, 0, sizeof(connectionConfig));
|
||||
connectionConfig.name = UA_STRING("UDP-UADP Connection 1");
|
||||
connectionConfig.transportProfileUri = UA_STRING("http://opcfoundation.org/UA-Profile/Transport/pubsub-udp-uadp");
|
||||
connectionConfig.eventLoop = pubSubEL;
|
||||
UA_NetworkAddressUrlDataType networkAddressUrl = {UA_STRING_NULL , UA_STRING("opc.udp://224.0.0.22:4840/")};
|
||||
UA_Variant_setScalar(&connectionConfig.address, &networkAddressUrl, &UA_TYPES[UA_TYPES_NETWORKADDRESSURLDATATYPE]);
|
||||
connectionConfig.transportProfileUri =
|
||||
UA_STRING("http://opcfoundation.org/UA-Profile/Transport/pubsub-udp-uadp");
|
||||
UA_NetworkAddressUrlDataType networkAddressUrl =
|
||||
{UA_STRING_NULL , UA_STRING("opc.udp://224.0.0.22:4840/")};
|
||||
UA_Variant_setScalar(&connectionConfig.address, &networkAddressUrl,
|
||||
&UA_TYPES[UA_TYPES_NETWORKADDRESSURLDATATYPE]);
|
||||
connectionConfig.publisherId.idType = UA_PUBLISHERIDTYPE_UINT16;
|
||||
connectionConfig.publisherId.id.uint16 = 2234;
|
||||
UA_Server_addPubSubConnection(server, &connectionConfig, &connectionIdentifier);
|
||||
@ -109,8 +154,6 @@ int main(void) {
|
||||
for(size_t i = 0; i < PUBSUB_CONFIG_FIELD_COUNT; i++) {
|
||||
/* TODO: Point to a variable in the information model */
|
||||
memset(&dsfConfig, 0, sizeof(UA_DataSetFieldConfig));
|
||||
dsfConfig.field.variable.rtValueSource.rtFieldSourceEnabled = true;
|
||||
dsfConfig.field.variable.rtValueSource.staticValueSource = &dvPointers[i];
|
||||
UA_Server_addDataSetField(server, publishedDataSetIdent, &dsfConfig, &dataSetFieldIdent);
|
||||
}
|
||||
|
||||
@ -121,15 +164,17 @@ int main(void) {
|
||||
writerGroupConfig.publishingInterval = PUBSUB_CONFIG_PUBLISH_CYCLE_MS;
|
||||
writerGroupConfig.writerGroupId = 100;
|
||||
writerGroupConfig.encodingMimeType = UA_PUBSUB_ENCODING_UADP;
|
||||
writerGroupConfig.rtLevel = UA_PUBSUB_RT_FIXED_SIZE;
|
||||
writerGroupConfig.customStateMachine = writerGroupStateMachine;
|
||||
|
||||
/* Change message settings of writerGroup to send PublisherId, WriterGroupId
|
||||
* in GroupHeader and DataSetWriterId in PayloadHeader of NetworkMessage */
|
||||
UA_UadpWriterGroupMessageDataType writerGroupMessage;
|
||||
UA_UadpWriterGroupMessageDataType_init(&writerGroupMessage);
|
||||
writerGroupMessage.networkMessageContentMask = (UA_UadpNetworkMessageContentMask)
|
||||
(UA_UADPNETWORKMESSAGECONTENTMASK_PUBLISHERID | UA_UADPNETWORKMESSAGECONTENTMASK_GROUPHEADER |
|
||||
UA_UADPNETWORKMESSAGECONTENTMASK_WRITERGROUPID | UA_UADPNETWORKMESSAGECONTENTMASK_SEQUENCENUMBER |
|
||||
(UA_UADPNETWORKMESSAGECONTENTMASK_PUBLISHERID |
|
||||
UA_UADPNETWORKMESSAGECONTENTMASK_GROUPHEADER |
|
||||
UA_UADPNETWORKMESSAGECONTENTMASK_WRITERGROUPID |
|
||||
UA_UADPNETWORKMESSAGECONTENTMASK_SEQUENCENUMBER |
|
||||
UA_UADPNETWORKMESSAGECONTENTMASK_PAYLOADHEADER);
|
||||
UA_ExtensionObject_setValue(&writerGroupConfig.messageSettings, &writerGroupMessage,
|
||||
&UA_TYPES[UA_TYPES_UADPWRITERGROUPMESSAGEDATATYPE]);
|
||||
@ -163,17 +208,10 @@ int main(void) {
|
||||
UA_Server_addRepeatedCallback(server, valueUpdateCallback, NULL,
|
||||
PUBSUB_CONFIG_PUBLISH_CYCLE_MS, &callbackId);
|
||||
|
||||
UA_StatusCode retval = UA_Server_runUntilInterrupt(server);
|
||||
|
||||
pubSubELRunning = false;
|
||||
pthread_join(pubSubELThread, NULL);
|
||||
|
||||
pubSubEL->stop(pubSubEL);
|
||||
while(pubSubEL->state != UA_EVENTLOOPSTATE_STOPPED)
|
||||
pubSubEL->run(pubSubEL, 0);
|
||||
pubSubEL->free(pubSubEL);
|
||||
UA_Server_runUntilInterrupt(server);
|
||||
|
||||
/* Cleanup */
|
||||
UA_Server_delete(server);
|
||||
|
||||
return retval == UA_STATUSCODE_GOOD ? EXIT_SUCCESS : EXIT_FAILURE;
|
||||
timer_delete(writerGroupTimer);
|
||||
return EXIT_SUCCESS;
|
||||
}
|
@ -1,194 +0,0 @@
|
||||
/* This work is licensed under a Creative Commons CCZero 1.0 Universal License.
|
||||
* See http://creativecommons.org/publicdomain/zero/1.0/ for more information. */
|
||||
|
||||
#include <open62541/server.h>
|
||||
#include <open62541/server_pubsub.h>
|
||||
#include <open62541/plugin/log.h>
|
||||
#include <open62541/plugin/log_stdout.h>
|
||||
#include <open62541/server_config_default.h>
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <signal.h>
|
||||
|
||||
UA_NodeId publishedDataSetIdent, dataSetFieldIdent, writerGroupIdent, connectionIdentifier;
|
||||
UA_UInt32 *integerRTValue, *integerRTValue2;
|
||||
UA_NodeId rtNodeId1, rtNodeId2;
|
||||
|
||||
/* Info: It is still possible to create a RT-PubSub configuration without an
|
||||
* information model node. Just set the DSF flags to 'rtInformationModelNode' ->
|
||||
* true and 'rtInformationModelNode' -> false and provide the PTR to your self
|
||||
* managed value source. */
|
||||
|
||||
static UA_NodeId
|
||||
addVariable(UA_Server *server, char *name) {
|
||||
/* Define the attribute of the myInteger variable node */
|
||||
UA_VariableAttributes attr = UA_VariableAttributes_default;
|
||||
UA_UInt32 myInteger = 42;
|
||||
UA_Variant_setScalar(&attr.value, &myInteger, &UA_TYPES[UA_TYPES_UINT32]);
|
||||
attr.description = UA_LOCALIZEDTEXT("en-US", name);
|
||||
attr.displayName = UA_LOCALIZEDTEXT("en-US", name);
|
||||
attr.dataType = UA_TYPES[UA_TYPES_UINT32].typeId;
|
||||
attr.accessLevel = UA_ACCESSLEVELMASK_READ | UA_ACCESSLEVELMASK_WRITE;
|
||||
|
||||
/* Add the variable node to the information model */
|
||||
UA_NodeId outNodeId;
|
||||
UA_QualifiedName myIntegerName = UA_QUALIFIEDNAME(1, name);
|
||||
UA_NodeId parentNodeId = UA_NS0ID(OBJECTSFOLDER);
|
||||
UA_NodeId parentReferenceNodeId = UA_NS0ID(ORGANIZES);
|
||||
UA_Server_addVariableNode(server, UA_NODEID_NULL, parentNodeId, parentReferenceNodeId,
|
||||
myIntegerName, UA_NS0ID(BASEDATAVARIABLETYPE),
|
||||
attr, NULL, &outNodeId);
|
||||
return outNodeId;
|
||||
}
|
||||
|
||||
/* If the external data source is written over the information model, the
|
||||
* externalDataWriteCallback will be triggered. The user has to take care and assure
|
||||
* that the write leads not to synchronization issues and race conditions. */
|
||||
static UA_StatusCode
|
||||
externalDataWriteCallback(UA_Server *server, const UA_NodeId *sessionId,
|
||||
void *sessionContext, const UA_NodeId *nodeId,
|
||||
void *nodeContext, const UA_NumericRange *range,
|
||||
const UA_DataValue *data) {
|
||||
/* It's possible to create a new DataValue here and use an atomic ptr switch
|
||||
* to update the value without the need for locks e.g. UA_atomic_cmpxchg(); */
|
||||
if(UA_NodeId_equal(nodeId, &rtNodeId1)){
|
||||
memcpy(integerRTValue, data->value.data, sizeof(UA_UInt32));
|
||||
} else if(UA_NodeId_equal(nodeId, &rtNodeId2)){
|
||||
memcpy(integerRTValue2, data->value.data, sizeof(UA_UInt32));
|
||||
}
|
||||
return UA_STATUSCODE_GOOD;
|
||||
}
|
||||
|
||||
static void
|
||||
cyclicValueUpdateCallback_UpdateToMemory(UA_Server *server, void *data) {
|
||||
*integerRTValue = (*integerRTValue)+1;
|
||||
*integerRTValue2 = (*integerRTValue2)+1;
|
||||
}
|
||||
|
||||
static void
|
||||
cyclicValueUpdateCallback_UpdateToStack(UA_Server *server, void *data) {
|
||||
UA_Variant valueToWrite;
|
||||
UA_UInt32 newValue = (*integerRTValue)+10;
|
||||
UA_Variant_setScalar(&valueToWrite, &newValue, &UA_TYPES[UA_TYPES_UINT32]);
|
||||
UA_Server_writeValue(server, rtNodeId1, valueToWrite);
|
||||
|
||||
UA_Variant valueToWrite2;
|
||||
UA_UInt32 newValue2 = (*integerRTValue2)+10;
|
||||
UA_Variant_setScalar(&valueToWrite2, &newValue2, &UA_TYPES[UA_TYPES_UINT32]);
|
||||
UA_Server_writeValue(server, rtNodeId2, valueToWrite2);
|
||||
}
|
||||
|
||||
int main(void){
|
||||
UA_Server *server = UA_Server_new();
|
||||
|
||||
/* Add one PubSubConnection */
|
||||
UA_PubSubConnectionConfig connectionConfig;
|
||||
memset(&connectionConfig, 0, sizeof(connectionConfig));
|
||||
connectionConfig.name = UA_STRING("UDP-UADP Connection 1");
|
||||
connectionConfig.transportProfileUri =
|
||||
UA_STRING("http://opcfoundation.org/UA-Profile/Transport/pubsub-udp-uadp");
|
||||
UA_NetworkAddressUrlDataType networkAddressUrl =
|
||||
{UA_STRING_NULL , UA_STRING("opc.udp://224.0.0.22:4840/")};
|
||||
UA_Variant_setScalar(&connectionConfig.address, &networkAddressUrl,
|
||||
&UA_TYPES[UA_TYPES_NETWORKADDRESSURLDATATYPE]);
|
||||
connectionConfig.publisherId.idType = UA_PUBLISHERIDTYPE_UINT16;
|
||||
connectionConfig.publisherId.id.uint16 = 2234;
|
||||
UA_Server_addPubSubConnection(server, &connectionConfig, &connectionIdentifier);
|
||||
|
||||
/* Add one PublishedDataSet */
|
||||
UA_PublishedDataSetConfig publishedDataSetConfig;
|
||||
memset(&publishedDataSetConfig, 0, sizeof(UA_PublishedDataSetConfig));
|
||||
publishedDataSetConfig.publishedDataSetType = UA_PUBSUB_DATASET_PUBLISHEDITEMS;
|
||||
publishedDataSetConfig.name = UA_STRING("Demo PDS");
|
||||
|
||||
/* Add one DataSetField to the PDS */
|
||||
UA_Server_addPublishedDataSet(server, &publishedDataSetConfig, &publishedDataSetIdent);
|
||||
|
||||
/* Add RT configuration */
|
||||
UA_WriterGroupConfig writerGroupConfig;
|
||||
memset(&writerGroupConfig, 0, sizeof(UA_WriterGroupConfig));
|
||||
writerGroupConfig.name = UA_STRING("Demo WriterGroup");
|
||||
writerGroupConfig.publishingInterval = 1000;
|
||||
writerGroupConfig.writerGroupId = 100;
|
||||
writerGroupConfig.encodingMimeType = UA_PUBSUB_ENCODING_UADP;
|
||||
UA_UadpWriterGroupMessageDataType writerGroupMessage;
|
||||
UA_UadpWriterGroupMessageDataType_init(&writerGroupMessage);
|
||||
writerGroupMessage.networkMessageContentMask = (UA_UadpNetworkMessageContentMask)
|
||||
(UA_UADPNETWORKMESSAGECONTENTMASK_PUBLISHERID | UA_UADPNETWORKMESSAGECONTENTMASK_GROUPHEADER |
|
||||
UA_UADPNETWORKMESSAGECONTENTMASK_WRITERGROUPID | UA_UADPNETWORKMESSAGECONTENTMASK_PAYLOADHEADER);
|
||||
UA_ExtensionObject_setValue(&writerGroupConfig.messageSettings, &writerGroupMessage,
|
||||
&UA_TYPES[UA_TYPES_UADPWRITERGROUPMESSAGEDATATYPE]);
|
||||
writerGroupConfig.rtLevel = UA_PUBSUB_RT_FIXED_SIZE;
|
||||
UA_Server_addWriterGroup(server, connectionIdentifier, &writerGroupConfig, &writerGroupIdent);
|
||||
|
||||
/* Add one DataSetWriter */
|
||||
UA_NodeId dataSetWriterIdent;
|
||||
UA_DataSetWriterConfig dataSetWriterConfig;
|
||||
memset(&dataSetWriterConfig, 0, sizeof(UA_DataSetWriterConfig));
|
||||
dataSetWriterConfig.name = UA_STRING("Demo DataSetWriter");
|
||||
dataSetWriterConfig.dataSetWriterId = 62541;
|
||||
dataSetWriterConfig.keyFrameCount = 10;
|
||||
/* Encode fields as RAW-Encoded */
|
||||
dataSetWriterConfig.dataSetFieldContentMask = UA_DATASETFIELDCONTENTMASK_RAWDATA;
|
||||
UA_Server_addDataSetWriter(server, writerGroupIdent, publishedDataSetIdent,
|
||||
&dataSetWriterConfig, &dataSetWriterIdent);
|
||||
|
||||
/* Add new nodes */
|
||||
rtNodeId1 = addVariable(server, "RT value source 1");
|
||||
rtNodeId2 = addVariable(server, "RT value source 2");
|
||||
|
||||
/* Set the value backend to 'external value source' */
|
||||
integerRTValue = UA_UInt32_new();
|
||||
UA_DataValue *dataValueRT = UA_DataValue_new();
|
||||
dataValueRT->hasValue = UA_TRUE;
|
||||
UA_Variant_setScalar(&dataValueRT->value, integerRTValue, &UA_TYPES[UA_TYPES_UINT32]);
|
||||
UA_ValueBackend valueBackend;
|
||||
valueBackend.backendType = UA_VALUEBACKENDTYPE_EXTERNAL;
|
||||
valueBackend.backend.external.value = &dataValueRT;
|
||||
valueBackend.backend.external.callback.userWrite = externalDataWriteCallback;
|
||||
UA_Server_setVariableNode_valueBackend(server, rtNodeId1, valueBackend);
|
||||
|
||||
/* Setup RT DataSetField config */
|
||||
UA_NodeId dsfNodeId;
|
||||
UA_DataSetFieldConfig dsfConfig;
|
||||
memset(&dsfConfig, 0, sizeof(UA_DataSetFieldConfig));
|
||||
dsfConfig.field.variable.rtValueSource.rtInformationModelNode = UA_TRUE;
|
||||
dsfConfig.field.variable.publishParameters.publishedVariable = rtNodeId1;
|
||||
dsfConfig.field.variable.fieldNameAlias = UA_STRING("Field 1");
|
||||
UA_Server_addDataSetField(server, publishedDataSetIdent, &dsfConfig, &dsfNodeId);
|
||||
|
||||
/* Set the value backend of the above create node to 'external value source' */
|
||||
integerRTValue2 = UA_UInt32_new();
|
||||
*integerRTValue2 = 1000;
|
||||
UA_DataValue *dataValue2RT = UA_DataValue_new();
|
||||
dataValue2RT->hasValue = true;
|
||||
UA_Variant_setScalar(&dataValue2RT->value, integerRTValue2, &UA_TYPES[UA_TYPES_UINT32]);
|
||||
UA_ValueBackend valueBackend2;
|
||||
valueBackend2.backendType = UA_VALUEBACKENDTYPE_EXTERNAL;
|
||||
valueBackend2.backend.external.value = &dataValue2RT;
|
||||
valueBackend2.backend.external.callback.userWrite = externalDataWriteCallback;
|
||||
UA_Server_setVariableNode_valueBackend(server, rtNodeId2, valueBackend2);
|
||||
|
||||
/* Setup second DataSetField config */
|
||||
UA_DataSetFieldConfig dsfConfig2;
|
||||
memset(&dsfConfig2, 0, sizeof(UA_DataSetFieldConfig));
|
||||
dsfConfig2.field.variable.rtValueSource.rtInformationModelNode = UA_TRUE;
|
||||
dsfConfig2.field.variable.publishParameters.publishedVariable = rtNodeId2;
|
||||
dsfConfig2.field.variable.fieldNameAlias = UA_STRING("Field 2");
|
||||
UA_Server_addDataSetField(server, publishedDataSetIdent, &dsfConfig2, NULL);
|
||||
|
||||
UA_Server_enableAllPubSubComponents(server);
|
||||
|
||||
UA_Server_addRepeatedCallback(server, cyclicValueUpdateCallback_UpdateToMemory,
|
||||
NULL, 1000, NULL);
|
||||
|
||||
UA_Server_addRepeatedCallback(server, cyclicValueUpdateCallback_UpdateToStack,
|
||||
NULL, 5000, NULL);
|
||||
|
||||
UA_StatusCode retval = UA_Server_runUntilInterrupt(server);
|
||||
|
||||
UA_Server_delete(server);
|
||||
UA_DataValue_delete(dataValueRT);
|
||||
UA_DataValue_delete(dataValue2RT);
|
||||
return retval == UA_STATUSCODE_GOOD ? EXIT_SUCCESS : EXIT_FAILURE;
|
||||
}
|
@ -1,19 +1,19 @@
|
||||
/* This work is licensed under a Creative Commons CCZero 1.0 Universal License.
|
||||
* See http://creativecommons.org/publicdomain/zero/1.0/ for more information. */
|
||||
|
||||
#include <open62541/plugin/log_stdout.h>
|
||||
#include <open62541/server.h>
|
||||
#include <open62541/server_pubsub.h>
|
||||
#include <open62541/server_config_default.h>
|
||||
#include <open62541/types.h>
|
||||
#include <open62541/types_generated.h>
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <assert.h>
|
||||
|
||||
#define PUBSUB_CONFIG_FIELD_COUNT 10
|
||||
|
||||
static UA_NetworkAddressUrlDataType networkAddressUrl =
|
||||
{{0, NULL}, UA_STRING_STATIC("opc.udp://224.0.0.22:4840/")};
|
||||
static UA_String transportProfile =
|
||||
UA_STRING_STATIC("http://opcfoundation.org/UA-Profile/Transport/pubsub-udp-uadp");
|
||||
|
||||
/**
|
||||
* The main target of this example is to reduce the time spread and effort
|
||||
* during the subscribe cycle. This RT level example is based on buffered
|
||||
@ -27,83 +27,9 @@
|
||||
* the buffered NetworkMessage will only be updated.
|
||||
*/
|
||||
|
||||
UA_NodeId connectionIdentifier;
|
||||
UA_NodeId readerGroupIdentifier;
|
||||
UA_NodeId readerIdentifier;
|
||||
|
||||
UA_Server *server;
|
||||
UA_DataSetReaderConfig readerConfig;
|
||||
|
||||
/* Simulate a custom data sink (e.g. shared memory) */
|
||||
UA_UInt32 repeatedFieldValues[PUBSUB_CONFIG_FIELD_COUNT];
|
||||
UA_DataValue *repeatedDataValueRT[PUBSUB_CONFIG_FIELD_COUNT];
|
||||
|
||||
/* If the external data source is written over the information model, the
|
||||
* externalDataWriteCallback will be triggered. The user has to take care and assure
|
||||
* that the write leads not to synchronization issues and race conditions. */
|
||||
static UA_StatusCode
|
||||
externalDataWriteCallback(UA_Server *server, const UA_NodeId *sessionId,
|
||||
void *sessionContext, const UA_NodeId *nodeId,
|
||||
void *nodeContext, const UA_NumericRange *range,
|
||||
const UA_DataValue *data){
|
||||
//node values are updated by using variables in the memory
|
||||
//UA_Server_write is not used for updating node values.
|
||||
return UA_STATUSCODE_GOOD;
|
||||
}
|
||||
|
||||
static UA_StatusCode
|
||||
externalDataReadNotificationCallback(UA_Server *server, const UA_NodeId *sessionId,
|
||||
void *sessionContext, const UA_NodeId *nodeid,
|
||||
void *nodeContext, const UA_NumericRange *range){
|
||||
//allow read without any preparation
|
||||
return UA_STATUSCODE_GOOD;
|
||||
}
|
||||
|
||||
static void
|
||||
subscribeAfterWriteCallback(UA_Server *server, const UA_NodeId *dataSetReaderId,
|
||||
const UA_NodeId *readerGroupId,
|
||||
const UA_NodeId *targetVariableId,
|
||||
void *targetVariableContext,
|
||||
UA_DataValue **externalDataValue) {
|
||||
(void) server;
|
||||
(void) dataSetReaderId;
|
||||
(void) readerGroupId;
|
||||
(void) targetVariableContext;
|
||||
|
||||
assert(targetVariableId != 0);
|
||||
assert(externalDataValue != 0);
|
||||
|
||||
UA_String strId;
|
||||
UA_String_init(&strId);
|
||||
UA_NodeId_print(targetVariableId, &strId);
|
||||
UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "subscribeAfterWriteCallback(): "
|
||||
"WriteUpdate() for node Id = '%.*s'. New Value = %u", (UA_Int32) strId.length, strId.data,
|
||||
*((UA_UInt32*) (**externalDataValue).value.data));
|
||||
UA_String_clear(&strId);
|
||||
}
|
||||
|
||||
/* Callback gets triggered before subscriber has received data received data
|
||||
* hasn't been copied/handled yet */
|
||||
static void
|
||||
subscribeBeforeWriteCallback(UA_Server *server, const UA_NodeId *dataSetReaderId,
|
||||
const UA_NodeId *readerGroupId, const UA_NodeId *targetVariableId,
|
||||
void *targetVariableContext, UA_DataValue **externalDataValue) {
|
||||
(void) server;
|
||||
(void) dataSetReaderId;
|
||||
(void) readerGroupId;
|
||||
(void) targetVariableContext;
|
||||
|
||||
assert(targetVariableId != 0);
|
||||
assert(externalDataValue != 0);
|
||||
|
||||
UA_String strId;
|
||||
UA_String_init(&strId);
|
||||
UA_NodeId_print(targetVariableId, &strId);
|
||||
UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER,
|
||||
"subscribeBeforeWriteCallback(): "
|
||||
"WriteUpdate() for node Id = '%.*s'",
|
||||
(UA_Int32) strId.length, strId.data);
|
||||
UA_String_clear(&strId);
|
||||
}
|
||||
UA_NodeId connectionIdentifier, readerGroupIdentifier, readerIdentifier;
|
||||
|
||||
/* Define MetaData for TargetVariables */
|
||||
static void
|
||||
@ -133,14 +59,13 @@ fillTestDataSetMetaData(UA_DataSetMetaDataType *pMetaData) {
|
||||
|
||||
/* Add new connection to the server */
|
||||
static void
|
||||
addPubSubConnection(UA_Server *server, UA_String *transportProfile,
|
||||
UA_NetworkAddressUrlDataType *networkAddressUrl) {
|
||||
addPubSubConnection(UA_Server *server) {
|
||||
/* Configuration creation for the connection */
|
||||
UA_PubSubConnectionConfig connectionConfig;
|
||||
memset (&connectionConfig, 0, sizeof(UA_PubSubConnectionConfig));
|
||||
connectionConfig.name = UA_STRING("UDPMC Connection 1");
|
||||
connectionConfig.transportProfileUri = *transportProfile;
|
||||
UA_Variant_setScalar(&connectionConfig.address, networkAddressUrl,
|
||||
connectionConfig.transportProfileUri = transportProfile;
|
||||
UA_Variant_setScalar(&connectionConfig.address, &networkAddressUrl,
|
||||
&UA_TYPES[UA_TYPES_NETWORKADDRESSURLDATATYPE]);
|
||||
connectionConfig.publisherId.idType = UA_PUBLISHERIDTYPE_UINT32;
|
||||
connectionConfig.publisherId.id.uint32 = UA_UInt32_random();
|
||||
@ -153,7 +78,6 @@ addReaderGroup(UA_Server *server) {
|
||||
UA_ReaderGroupConfig readerGroupConfig;
|
||||
memset (&readerGroupConfig, 0, sizeof(UA_ReaderGroupConfig));
|
||||
readerGroupConfig.name = UA_STRING("ReaderGroup1");
|
||||
readerGroupConfig.rtLevel = UA_PUBSUB_RT_DETERMINISTIC;
|
||||
UA_Server_addReaderGroup(server, connectionIdentifier, &readerGroupConfig,
|
||||
&readerGroupIdentifier);
|
||||
}
|
||||
@ -185,12 +109,11 @@ addSubscribedVariables (UA_Server *server) {
|
||||
/* Set the subscribed data to TargetVariable type */
|
||||
readerConfig.subscribedDataSetType = UA_PUBSUB_SDS_TARGET;
|
||||
/* Create the TargetVariables with respect to DataSetMetaData fields */
|
||||
readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariablesSize =
|
||||
readerConfig.subscribedDataSet.target.targetVariablesSize =
|
||||
readerConfig.dataSetMetaData.fieldsSize;
|
||||
readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariables =
|
||||
(UA_FieldTargetVariable *)UA_calloc(
|
||||
readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariablesSize,
|
||||
sizeof(UA_FieldTargetVariable));
|
||||
readerConfig.subscribedDataSet.target.targetVariables = (UA_FieldTargetDataType*)
|
||||
UA_calloc(readerConfig.subscribedDataSet.target.targetVariablesSize,
|
||||
sizeof(UA_FieldTargetDataType));
|
||||
for(size_t i = 0; i < readerConfig.dataSetMetaData.fieldsSize; i++) {
|
||||
/* Variable to subscribe data */
|
||||
UA_VariableAttributes vAttr = UA_VariableAttributes_default;
|
||||
@ -209,33 +132,10 @@ addSubscribedVariables (UA_Server *server) {
|
||||
UA_QUALIFIEDNAME(1, "Subscribed UInt32"),
|
||||
UA_NS0ID(BASEDATAVARIABLETYPE),
|
||||
vAttr, NULL, &newnodeId);
|
||||
repeatedFieldValues[i] = 0;
|
||||
repeatedDataValueRT[i] = UA_DataValue_new();
|
||||
UA_Variant_setScalar(&repeatedDataValueRT[i]->value, &repeatedFieldValues[i],
|
||||
&UA_TYPES[UA_TYPES_UINT32]);
|
||||
repeatedDataValueRT[i]->value.storageType = UA_VARIANT_DATA_NODELETE;
|
||||
repeatedDataValueRT[i]->hasValue = true;
|
||||
|
||||
/* Set the value backend of the above create node to 'external value source' */
|
||||
UA_ValueBackend valueBackend;
|
||||
valueBackend.backendType = UA_VALUEBACKENDTYPE_EXTERNAL;
|
||||
valueBackend.backend.external.value = &repeatedDataValueRT[i];
|
||||
valueBackend.backend.external.callback.userWrite = externalDataWriteCallback;
|
||||
valueBackend.backend.external.callback.notificationRead = externalDataReadNotificationCallback;
|
||||
UA_Server_setVariableNode_valueBackend(server, newnodeId, valueBackend);
|
||||
|
||||
UA_FieldTargetVariable *tv =
|
||||
&readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariables[i];
|
||||
UA_FieldTargetDataType *ftdt = &tv->targetVariable;
|
||||
|
||||
/* For creating Targetvariables */
|
||||
UA_FieldTargetDataType_init(ftdt);
|
||||
ftdt->attributeId = UA_ATTRIBUTEID_VALUE;
|
||||
ftdt->targetNodeId = newnodeId;
|
||||
/* set both before and after write callback to show the usage */
|
||||
tv->beforeWrite = subscribeBeforeWriteCallback;
|
||||
tv->externalDataValue = &repeatedDataValueRT[i];
|
||||
tv->afterWrite = subscribeAfterWriteCallback;
|
||||
UA_FieldTargetDataType *tv = &readerConfig.subscribedDataSet.target.targetVariables[i];
|
||||
tv->attributeId = UA_ATTRIBUTEID_VALUE;
|
||||
tv->targetNodeId = newnodeId;
|
||||
}
|
||||
}
|
||||
|
||||
@ -273,43 +173,12 @@ addDataSetReader(UA_Server *server) {
|
||||
addSubscribedVariables(server);
|
||||
UA_Server_addDataSetReader(server, readerGroupIdentifier, &readerConfig, &readerIdentifier);
|
||||
|
||||
for(size_t i = 0; i < readerConfig.dataSetMetaData.fieldsSize; i++) {
|
||||
UA_FieldTargetVariable *tv =
|
||||
&readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariables[i];
|
||||
UA_FieldTargetDataType *ftdt = &tv->targetVariable;
|
||||
UA_FieldTargetDataType_clear(ftdt);
|
||||
}
|
||||
|
||||
UA_free(readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariables);
|
||||
UA_DataSetReaderConfig_clear(&readerConfig);
|
||||
UA_free(readerConfig.dataSetMetaData.fields);
|
||||
UA_UadpDataSetReaderMessageDataType_delete(dataSetReaderMessage);
|
||||
}
|
||||
|
||||
static int
|
||||
run(UA_String *transportProfile, UA_NetworkAddressUrlDataType *networkAddressUrl) {
|
||||
/* Return value initialized to Status Good */
|
||||
UA_StatusCode retval = UA_STATUSCODE_GOOD;
|
||||
UA_Server *server = UA_Server_new();
|
||||
|
||||
addPubSubConnection(server, transportProfile, networkAddressUrl);
|
||||
addReaderGroup(server);
|
||||
addDataSetReader(server);
|
||||
|
||||
UA_Server_enableAllPubSubComponents(server);
|
||||
retval = UA_Server_runUntilInterrupt(server);
|
||||
|
||||
UA_Server_delete(server);
|
||||
|
||||
for(UA_Int32 i = 0; i < PUBSUB_CONFIG_FIELD_COUNT; i++) {
|
||||
UA_DataValue_delete(repeatedDataValueRT[i]);
|
||||
}
|
||||
|
||||
return retval == UA_STATUSCODE_GOOD ? EXIT_SUCCESS : EXIT_FAILURE;
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
UA_String transportProfile = UA_STRING("http://opcfoundation.org/UA-Profile/Transport/pubsub-udp-uadp");
|
||||
UA_NetworkAddressUrlDataType networkAddressUrl = {UA_STRING_NULL , UA_STRING("opc.udp://224.0.0.22:4840/")};
|
||||
if(argc > 1) {
|
||||
if(strcmp(argv[1], "-h") == 0) {
|
||||
printf("usage: %s <uri> [device]\n", argv[0]);
|
||||
@ -330,9 +199,32 @@ int main(int argc, char **argv) {
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
}
|
||||
if (argc > 2) {
|
||||
if(argc > 2)
|
||||
networkAddressUrl.networkInterface = UA_STRING(argv[2]);
|
||||
|
||||
/* Return value initialized to Status Good */
|
||||
UA_StatusCode retval = UA_STATUSCODE_GOOD;
|
||||
server = UA_Server_new();
|
||||
|
||||
addPubSubConnection(server);
|
||||
addReaderGroup(server);
|
||||
addDataSetReader(server);
|
||||
|
||||
/* Print the Offset Table */
|
||||
UA_PubSubOffsetTable ot;
|
||||
UA_Server_computeReaderGroupOffsetTable(server, readerGroupIdentifier, &ot);
|
||||
for(size_t i = 0; i < ot.offsetsSize; i++) {
|
||||
UA_String out = UA_STRING_NULL;
|
||||
UA_NodeId_print(&ot.offsets[i].component, &out);
|
||||
printf("%u:\tOffset %u\tOffsetType %u\tComponent %.*s\n",
|
||||
(unsigned)i, (unsigned)ot.offsets[i].offset,
|
||||
(unsigned)ot.offsets[i].offsetType,
|
||||
(int)out.length, out.data);
|
||||
UA_String_clear(&out);
|
||||
}
|
||||
|
||||
return run(&transportProfile, &networkAddressUrl);
|
||||
UA_Server_delete(server);
|
||||
UA_PubSubOffsetTable_clear(&ot);
|
||||
|
||||
return retval == UA_STATUSCODE_GOOD ? EXIT_SUCCESS : EXIT_FAILURE;
|
||||
}
|
@ -0,0 +1,399 @@
|
||||
/* This work is licensed under a Creative Commons CCZero 1.0 Universal License.
|
||||
* See http://creativecommons.org/publicdomain/zero/1.0/ for more information. */
|
||||
|
||||
#include <open62541/plugin/log_stdout.h>
|
||||
#include <open62541/server.h>
|
||||
#include <open62541/server_pubsub.h>
|
||||
#include <open62541/server_config_default.h>
|
||||
#include <open62541/types.h>
|
||||
#include <open62541/types_generated.h>
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <assert.h>
|
||||
#include <signal.h>
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/socket.h>
|
||||
#include <netdb.h>
|
||||
#include <poll.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
|
||||
#define PUBSUB_CONFIG_FIELD_COUNT 10
|
||||
|
||||
static UA_NetworkAddressUrlDataType networkAddressUrl =
|
||||
{{0, NULL}, UA_STRING_STATIC("opc.udp://224.0.0.22:4840/")};
|
||||
static UA_String transportProfile =
|
||||
UA_STRING_STATIC("http://opcfoundation.org/UA-Profile/Transport/pubsub-udp-uadp");
|
||||
|
||||
/**
|
||||
* The main target of this example is to reduce the time spread and effort
|
||||
* during the subscribe cycle. This RT level example is based on buffered
|
||||
* DataSetMessages and NetworkMessages. Since changes in the
|
||||
* PubSub-configuration will invalidate the buffered frames, the PubSub
|
||||
* configuration must be frozen after the configuration phase.
|
||||
*
|
||||
* After enabling the subscriber (and when the first message is received), the
|
||||
* NetworkMessages and DataSetMessages will be calculated and buffered. During
|
||||
* the subscribe cycle, decoding will happen only to the necessary offsets and
|
||||
* the buffered NetworkMessage will only be updated.
|
||||
*/
|
||||
|
||||
UA_NodeId connectionIdentifier;
|
||||
UA_NodeId readerGroupIdentifier;
|
||||
UA_NodeId readerIdentifier;
|
||||
|
||||
UA_Server *server;
|
||||
pthread_t listenThread;
|
||||
int listenSocket;
|
||||
|
||||
UA_DataSetReaderConfig readerConfig;
|
||||
|
||||
static void *
|
||||
listenUDP(void *_) {
|
||||
(void)_;
|
||||
|
||||
/* Block SIGINT for correct shutdown via the main thread */
|
||||
sigset_t blockset;
|
||||
sigemptyset(&blockset);
|
||||
sigaddset(&blockset, SIGINT);
|
||||
sigprocmask(SIG_BLOCK, &blockset, NULL);
|
||||
|
||||
/* Extract the hostname */
|
||||
UA_UInt16 port = 0;
|
||||
UA_String hostname = UA_STRING_NULL;
|
||||
UA_parseEndpointUrl(&networkAddressUrl.url, &hostname, &port, NULL);
|
||||
|
||||
/* Get all the interface and IPv4/6 combinations for the configured hostname */
|
||||
struct addrinfo hints, *info;
|
||||
memset(&hints, 0, sizeof hints);
|
||||
hints.ai_family = AF_UNSPEC; /* Allow IPv4 and IPv6 */
|
||||
hints.ai_socktype = SOCK_DGRAM;
|
||||
hints.ai_protocol = IPPROTO_UDP;
|
||||
hints.ai_flags = AI_PASSIVE;
|
||||
|
||||
/* getaddrinfo */
|
||||
char portstr[6];
|
||||
char hostnamebuf[256];
|
||||
snprintf(portstr, 6, "%d", port);
|
||||
memcpy(hostnamebuf, hostname.data, hostname.length);
|
||||
hostnamebuf[hostname.length] = 0;
|
||||
int result = getaddrinfo(hostnamebuf, portstr, &hints, &info);
|
||||
if(result != 0) {
|
||||
printf("XXX getaddrinfo failed\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Open the socket */
|
||||
listenSocket = socket(info->ai_family, info->ai_socktype, info->ai_protocol);
|
||||
if(listenSocket <= 0) {
|
||||
printf("XXX Cannot create the socket\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Set socket options */
|
||||
int opts = fcntl(listenSocket, F_GETFL);
|
||||
result |= fcntl(listenSocket, F_SETFL, opts | O_NONBLOCK);
|
||||
int optval = 1;
|
||||
result |= setsockopt(listenSocket, SOL_SOCKET, SO_REUSEADDR,
|
||||
(const char*)&optval, sizeof(optval));
|
||||
if(result < 0) {
|
||||
printf("XXX Cannot set the socket options\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Bind the socket */
|
||||
result = bind(listenSocket, info->ai_addr, (socklen_t)info->ai_addrlen);
|
||||
if(result < 0) {
|
||||
printf("XXX Cannot bind the socket\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Join the multicast group */
|
||||
if(info->ai_family == AF_INET) {
|
||||
struct ip_mreqn ipv4;
|
||||
struct sockaddr_in *sin = (struct sockaddr_in *)info->ai_addr;
|
||||
ipv4.imr_multiaddr = sin->sin_addr;
|
||||
ipv4.imr_address.s_addr = htonl(INADDR_ANY); /* default ANY */
|
||||
ipv4.imr_ifindex = 0;
|
||||
result = setsockopt(listenSocket, IPPROTO_IP, IP_ADD_MEMBERSHIP, &ipv4, sizeof(ipv4));
|
||||
} else if(info->ai_family == AF_INET6) {
|
||||
struct ipv6_mreq ipv6;
|
||||
struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)info->ai_addr;
|
||||
ipv6.ipv6mr_multiaddr = sin6->sin6_addr;
|
||||
ipv6.ipv6mr_interface = 0; /* default ANY interface */
|
||||
result = setsockopt(listenSocket, IPPROTO_IPV6, IPV6_JOIN_GROUP, &ipv6, sizeof(ipv6));
|
||||
}
|
||||
if(result < 0) {
|
||||
printf("XXX Cannot join the multicast group\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
freeaddrinfo(info);
|
||||
|
||||
/* The connection is open, change the state to OPERATIONAL.
|
||||
* The state machine checks whether listenSocket != 0. */
|
||||
printf("XXX Listening on UDP multicast (%s, port %u)\n",
|
||||
hostnamebuf, (unsigned)port);
|
||||
UA_Server_enablePubSubConnection(server, connectionIdentifier);
|
||||
|
||||
/* Poll and process in a loop.
|
||||
* The socket is closed in the state machine and */
|
||||
struct pollfd pfd;
|
||||
pfd.fd = listenSocket;
|
||||
pfd.events = POLLIN;
|
||||
while(true) {
|
||||
result = poll(&pfd, 1, -1); /* infinite timeout */
|
||||
if(pfd.revents & POLLERR || pfd.revents & POLLHUP || pfd.revents & POLLNVAL)
|
||||
break;
|
||||
|
||||
if(pfd.revents & POLLIN) {
|
||||
static char buf[1024];
|
||||
ssize_t size = read(listenSocket, buf, sizeof(buf));
|
||||
if(size > 0) {
|
||||
printf("XXX Received a packet\n");
|
||||
UA_ByteString packet = {(size_t)size, (UA_Byte*)buf};
|
||||
UA_Server_processPubSubConnectionReceive(server, connectionIdentifier, packet);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
printf("XXX The UDP multicast connection is closed\n");
|
||||
|
||||
/* Clean up and notify the state machine */
|
||||
close(listenSocket);
|
||||
listenSocket = 0;
|
||||
UA_Server_disablePubSubConnection(server, connectionIdentifier);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static UA_StatusCode
|
||||
connectionStateMachine(UA_Server *server, const UA_NodeId componentId,
|
||||
void *componentContext, UA_PubSubState *state,
|
||||
UA_PubSubState targetState) {
|
||||
if(targetState == *state)
|
||||
return UA_STATUSCODE_GOOD;
|
||||
|
||||
switch(targetState) {
|
||||
/* Disabled or Error */
|
||||
case UA_PUBSUBSTATE_ERROR:
|
||||
case UA_PUBSUBSTATE_DISABLED:
|
||||
case UA_PUBSUBSTATE_PAUSED:
|
||||
printf("XXX Closing the UDP multicast connection\n");
|
||||
if(listenSocket != 0)
|
||||
shutdown(listenSocket, SHUT_RDWR);
|
||||
*state = targetState;
|
||||
break;
|
||||
|
||||
/* Operational */
|
||||
case UA_PUBSUBSTATE_PREOPERATIONAL:
|
||||
case UA_PUBSUBSTATE_OPERATIONAL:
|
||||
if(listenSocket != 0) {
|
||||
*state = UA_PUBSUBSTATE_OPERATIONAL;
|
||||
break;
|
||||
}
|
||||
printf("XXX Opening the UDP multicast connection\n");
|
||||
*state = UA_PUBSUBSTATE_PREOPERATIONAL;
|
||||
int res = pthread_create(&listenThread, NULL, listenUDP, NULL);
|
||||
if(res != 0)
|
||||
return UA_STATUSCODE_BADINTERNALERROR;
|
||||
break;
|
||||
|
||||
/* Unknown state */
|
||||
default:
|
||||
return UA_STATUSCODE_BADINTERNALERROR;
|
||||
}
|
||||
|
||||
return UA_STATUSCODE_GOOD;
|
||||
}
|
||||
|
||||
/* Define MetaData for TargetVariables */
|
||||
static void
|
||||
fillTestDataSetMetaData(UA_DataSetMetaDataType *pMetaData) {
|
||||
if(pMetaData == NULL)
|
||||
return;
|
||||
|
||||
UA_DataSetMetaDataType_init (pMetaData);
|
||||
pMetaData->name = UA_STRING ("DataSet 1");
|
||||
|
||||
/* Static definition of number of fields size to PUBSUB_CONFIG_FIELD_COUNT
|
||||
* to create targetVariables */
|
||||
pMetaData->fieldsSize = PUBSUB_CONFIG_FIELD_COUNT;
|
||||
pMetaData->fields = (UA_FieldMetaData*)UA_Array_new (pMetaData->fieldsSize,
|
||||
&UA_TYPES[UA_TYPES_FIELDMETADATA]);
|
||||
|
||||
for(size_t i = 0; i < pMetaData->fieldsSize; i++) {
|
||||
/* UInt32 DataType */
|
||||
UA_FieldMetaData_init (&pMetaData->fields[i]);
|
||||
UA_NodeId_copy(&UA_TYPES[UA_TYPES_UINT32].typeId,
|
||||
&pMetaData->fields[i].dataType);
|
||||
pMetaData->fields[i].builtInType = UA_NS0ID_UINT32;
|
||||
pMetaData->fields[i].name = UA_STRING ("UInt32 varibale");
|
||||
pMetaData->fields[i].valueRank = -1; /* scalar */
|
||||
}
|
||||
}
|
||||
|
||||
/* Add new connection to the server */
|
||||
static void
|
||||
addPubSubConnection(UA_Server *server) {
|
||||
/* Configuration creation for the connection */
|
||||
UA_PubSubConnectionConfig connectionConfig;
|
||||
memset (&connectionConfig, 0, sizeof(UA_PubSubConnectionConfig));
|
||||
connectionConfig.name = UA_STRING("UDPMC Connection 1");
|
||||
connectionConfig.transportProfileUri = transportProfile;
|
||||
UA_Variant_setScalar(&connectionConfig.address, &networkAddressUrl,
|
||||
&UA_TYPES[UA_TYPES_NETWORKADDRESSURLDATATYPE]);
|
||||
connectionConfig.publisherId.idType = UA_PUBLISHERIDTYPE_UINT32;
|
||||
connectionConfig.publisherId.id.uint32 = UA_UInt32_random();
|
||||
connectionConfig.customStateMachine = connectionStateMachine;
|
||||
UA_Server_addPubSubConnection(server, &connectionConfig, &connectionIdentifier);
|
||||
}
|
||||
|
||||
/* Add ReaderGroup to the created connection */
|
||||
static void
|
||||
addReaderGroup(UA_Server *server) {
|
||||
UA_ReaderGroupConfig readerGroupConfig;
|
||||
memset (&readerGroupConfig, 0, sizeof(UA_ReaderGroupConfig));
|
||||
readerGroupConfig.name = UA_STRING("ReaderGroup1");
|
||||
UA_Server_addReaderGroup(server, connectionIdentifier, &readerGroupConfig,
|
||||
&readerGroupIdentifier);
|
||||
}
|
||||
|
||||
/* Set SubscribedDataSet type to TargetVariables data type
|
||||
* Add subscribedvariables to the DataSetReader */
|
||||
static void
|
||||
addSubscribedVariables (UA_Server *server) {
|
||||
UA_NodeId folderId;
|
||||
UA_NodeId newnodeId;
|
||||
UA_String folderName = readerConfig.dataSetMetaData.name;
|
||||
UA_ObjectAttributes oAttr = UA_ObjectAttributes_default;
|
||||
UA_QualifiedName folderBrowseName;
|
||||
if(folderName.length > 0) {
|
||||
oAttr.displayName.locale = UA_STRING ("en-US");
|
||||
oAttr.displayName.text = folderName;
|
||||
folderBrowseName.namespaceIndex = 1;
|
||||
folderBrowseName.name = folderName;
|
||||
} else {
|
||||
oAttr.displayName = UA_LOCALIZEDTEXT ("en-US", "Subscribed Variables");
|
||||
folderBrowseName = UA_QUALIFIEDNAME (1, "Subscribed Variables");
|
||||
}
|
||||
|
||||
UA_Server_addObjectNode(server, UA_NODEID_NULL, UA_NS0ID(OBJECTSFOLDER),
|
||||
UA_NS0ID(ORGANIZES), folderBrowseName,
|
||||
UA_NS0ID(BASEOBJECTTYPE), oAttr,
|
||||
NULL, &folderId);
|
||||
|
||||
/* Set the subscribed data to TargetVariable type */
|
||||
readerConfig.subscribedDataSetType = UA_PUBSUB_SDS_TARGET;
|
||||
/* Create the TargetVariables with respect to DataSetMetaData fields */
|
||||
readerConfig.subscribedDataSet.target.targetVariablesSize =
|
||||
readerConfig.dataSetMetaData.fieldsSize;
|
||||
readerConfig.subscribedDataSet.target.targetVariables = (UA_FieldTargetDataType*)
|
||||
UA_calloc(readerConfig.subscribedDataSet.target.targetVariablesSize,
|
||||
sizeof(UA_FieldTargetDataType));
|
||||
for(size_t i = 0; i < readerConfig.dataSetMetaData.fieldsSize; i++) {
|
||||
/* Variable to subscribe data */
|
||||
UA_VariableAttributes vAttr = UA_VariableAttributes_default;
|
||||
vAttr.description = UA_LOCALIZEDTEXT ("en-US", "Subscribed UInt32");
|
||||
vAttr.displayName = UA_LOCALIZEDTEXT ("en-US", "Subscribed UInt32");
|
||||
vAttr.dataType = UA_TYPES[UA_TYPES_UINT32].typeId;
|
||||
// Initialize the values at first to create the buffered NetworkMessage
|
||||
// with correct size and offsets
|
||||
UA_Variant value;
|
||||
UA_Variant_init(&value);
|
||||
UA_UInt32 intValue = 0;
|
||||
UA_Variant_setScalar(&value, &intValue, &UA_TYPES[UA_TYPES_UINT32]);
|
||||
vAttr.value = value;
|
||||
UA_Server_addVariableNode(server, UA_NODEID_NUMERIC(1, (UA_UInt32)i + 50000),
|
||||
folderId, UA_NS0ID(HASCOMPONENT),
|
||||
UA_QUALIFIEDNAME(1, "Subscribed UInt32"),
|
||||
UA_NS0ID(BASEDATAVARIABLETYPE),
|
||||
vAttr, NULL, &newnodeId);
|
||||
|
||||
UA_FieldTargetDataType *tv =
|
||||
&readerConfig.subscribedDataSet.target.targetVariables[i];
|
||||
tv->attributeId = UA_ATTRIBUTEID_VALUE;
|
||||
tv->targetNodeId = newnodeId;
|
||||
}
|
||||
}
|
||||
|
||||
/* Add DataSetReader to the ReaderGroup */
|
||||
static void
|
||||
addDataSetReader(UA_Server *server) {
|
||||
memset(&readerConfig, 0, sizeof(UA_DataSetReaderConfig));
|
||||
readerConfig.name = UA_STRING("DataSet Reader 1");
|
||||
/* Parameters to filter which DataSetMessage has to be processed
|
||||
* by the DataSetReader */
|
||||
/* The following parameters are used to show that the data published by
|
||||
* tutorial_pubsub_publish.c is being subscribed and is being updated in
|
||||
* the information model */
|
||||
UA_UInt16 publisherIdentifier = 2234;
|
||||
readerConfig.publisherId.idType = UA_PUBLISHERIDTYPE_UINT16;
|
||||
readerConfig.publisherId.id.uint16 = publisherIdentifier;
|
||||
readerConfig.writerGroupId = 100;
|
||||
readerConfig.dataSetWriterId = 62541;
|
||||
readerConfig.messageSettings.encoding = UA_EXTENSIONOBJECT_DECODED;
|
||||
readerConfig.expectedEncoding = UA_PUBSUB_RT_RAW;
|
||||
readerConfig.messageSettings.content.decoded.type =
|
||||
&UA_TYPES[UA_TYPES_UADPDATASETREADERMESSAGEDATATYPE];
|
||||
UA_UadpDataSetReaderMessageDataType *dataSetReaderMessage =
|
||||
UA_UadpDataSetReaderMessageDataType_new();
|
||||
dataSetReaderMessage->networkMessageContentMask = (UA_UadpNetworkMessageContentMask)
|
||||
(UA_UADPNETWORKMESSAGECONTENTMASK_PUBLISHERID | UA_UADPNETWORKMESSAGECONTENTMASK_GROUPHEADER |
|
||||
UA_UADPNETWORKMESSAGECONTENTMASK_SEQUENCENUMBER | UA_UADPNETWORKMESSAGECONTENTMASK_WRITERGROUPID |
|
||||
UA_UADPNETWORKMESSAGECONTENTMASK_PAYLOADHEADER);
|
||||
dataSetReaderMessage->dataSetMessageContentMask = UA_UADPDATASETMESSAGECONTENTMASK_SEQUENCENUMBER;
|
||||
readerConfig.messageSettings.content.decoded.data = dataSetReaderMessage;
|
||||
|
||||
readerConfig.dataSetFieldContentMask = UA_DATASETFIELDCONTENTMASK_RAWDATA;
|
||||
|
||||
/* Setting up Meta data configuration in DataSetReader */
|
||||
fillTestDataSetMetaData(&readerConfig.dataSetMetaData);
|
||||
|
||||
addSubscribedVariables(server);
|
||||
UA_Server_addDataSetReader(server, readerGroupIdentifier, &readerConfig, &readerIdentifier);
|
||||
|
||||
UA_DataSetReaderConfig_clear(&readerConfig);
|
||||
UA_UadpDataSetReaderMessageDataType_delete(dataSetReaderMessage);
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
if(argc > 1) {
|
||||
if(strcmp(argv[1], "-h") == 0) {
|
||||
printf("usage: %s <uri> [device]\n", argv[0]);
|
||||
return EXIT_SUCCESS;
|
||||
} else if(strncmp(argv[1], "opc.udp://", 10) == 0) {
|
||||
networkAddressUrl.url = UA_STRING(argv[1]);
|
||||
} else if(strncmp(argv[1], "opc.eth://", 10) == 0) {
|
||||
transportProfile =
|
||||
UA_STRING("http://opcfoundation.org/UA-Profile/Transport/pubsub-eth-uadp");
|
||||
if(argc < 3) {
|
||||
printf("Error: UADP/ETH needs an interface name\n");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
networkAddressUrl.networkInterface = UA_STRING(argv[2]);
|
||||
networkAddressUrl.url = UA_STRING(argv[1]);
|
||||
} else {
|
||||
printf ("Error: unknown URI\n");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
}
|
||||
if(argc > 2)
|
||||
networkAddressUrl.networkInterface = UA_STRING(argv[2]);
|
||||
|
||||
/* Return value initialized to Status Good */
|
||||
UA_StatusCode retval = UA_STATUSCODE_GOOD;
|
||||
server = UA_Server_new();
|
||||
|
||||
addPubSubConnection(server);
|
||||
addReaderGroup(server);
|
||||
addDataSetReader(server);
|
||||
|
||||
UA_Server_enableAllPubSubComponents(server);
|
||||
retval = UA_Server_runUntilInterrupt(server);
|
||||
|
||||
UA_Server_delete(server);
|
||||
pthread_join(listenThread, NULL);
|
||||
return retval == UA_STATUSCODE_GOOD ? EXIT_SUCCESS : EXIT_FAILURE;
|
||||
}
|
@ -145,6 +145,32 @@ struct UA_ClientConfig {
|
||||
* data types are provided in ``/examples/custom_datatype/``. */
|
||||
const UA_DataTypeArray *customDataTypes;
|
||||
|
||||
/**
|
||||
* Namespace Mapping
|
||||
* ~~~~~~~~~~~~~~~~~
|
||||
* The namespaces index is "just" a mapping to the Uris in the namespace
|
||||
* array of the server. In order to have stable NodeIds across servers, the
|
||||
* client keeps a list of predefined namespaces. Use
|
||||
* ``UA_Client_addNamespaceUri``, ``UA_Client_getNamespaceUri`` and
|
||||
* ``UA_Client_getNamespaceIndex`` to interact with the local namespace
|
||||
* mapping.
|
||||
*
|
||||
* The namespace indices are assigned internally in the client as follows:
|
||||
*
|
||||
* - Ns0 and Ns1 are pre-defined by the standard. Ns0 is always
|
||||
* ```http://opcfoundation.org/UA/``` and used for standard-defined
|
||||
* NodeIds. Ns1 corresponds to the application uri of the individual
|
||||
* server.
|
||||
* - The next namespaces are added in-order from the list below at startup
|
||||
* (starting at index 2).
|
||||
* - The local API ``UA_Client_addNamespaceUri`` can be used to add more
|
||||
* namespaces.
|
||||
* - When the client connects, the namespace array of the server is read.
|
||||
* All previously unknown namespaces are added from this to the internal
|
||||
* array of the client. */
|
||||
UA_String *namespaces;
|
||||
size_t namespacesSize;
|
||||
|
||||
/**
|
||||
* Advanced Client Configuration
|
||||
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
|
||||
@ -953,6 +979,20 @@ UA_Client_removeCallback(UA_Client *client, UA_UInt64 callbackId);
|
||||
UA_EXPORT const UA_DataType *
|
||||
UA_Client_findDataType(UA_Client *client, const UA_NodeId *typeId);
|
||||
|
||||
/* The string is allocated and needs to be cleared */
|
||||
UA_EXPORT UA_StatusCode UA_THREADSAFE
|
||||
UA_Client_getNamespaceUri(UA_Client *client, UA_UInt16 index,
|
||||
UA_String *nsUri);
|
||||
|
||||
UA_EXPORT UA_StatusCode UA_THREADSAFE
|
||||
UA_Client_getNamespaceIndex(UA_Client *client, const UA_String nsUri,
|
||||
UA_UInt16 *outIndex);
|
||||
|
||||
/* Returns the old index of the namespace already exists */
|
||||
UA_EXPORT UA_StatusCode UA_THREADSAFE
|
||||
UA_Client_addNamespace(UA_Client *client, const UA_String nsUri,
|
||||
UA_UInt16 *outIndex);
|
||||
|
||||
/**
|
||||
* .. toctree::
|
||||
*
|
||||
|
@ -53,6 +53,7 @@
|
||||
#cmakedefine UA_ENABLE_PARSING
|
||||
#cmakedefine UA_ENABLE_SUBSCRIPTIONS_EVENTS
|
||||
#cmakedefine UA_ENABLE_JSON_ENCODING
|
||||
#cmakedefine UA_ENABLE_JSON_ENCODING_LEGACY
|
||||
#cmakedefine UA_ENABLE_XML_ENCODING
|
||||
#cmakedefine UA_ENABLE_MQTT
|
||||
#cmakedefine UA_ENABLE_NODESET_INJECTOR
|
||||
@ -77,7 +78,11 @@
|
||||
#cmakedefine UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS
|
||||
#cmakedefine UA_ENABLE_DETERMINISTIC_RNG
|
||||
#cmakedefine UA_ENABLE_DISCOVERY
|
||||
#cmakedefine UA_ENABLE_DISCOVERY_MULTICAST
|
||||
#cmakedefine UA_ENABLE_DISCOVERY_MULTICAST_MDNSD
|
||||
#cmakedefine UA_ENABLE_DISCOVERY_MULTICAST_AVAHI
|
||||
#if defined(UA_ENABLE_DISCOVERY_MULTICAST_MDNSD) || defined(UA_ENABLE_DISCOVERY_MULTICAST_AVAHI)
|
||||
#define UA_ENABLE_DISCOVERY_MULTICAST
|
||||
#endif
|
||||
#cmakedefine UA_ENABLE_QUERY
|
||||
#cmakedefine UA_ENABLE_MALLOC_SINGLETON
|
||||
#cmakedefine UA_ENABLE_DISCOVERY_SEMAPHORE
|
||||
|
@ -63,7 +63,8 @@ struct UA_CertificateGroup {
|
||||
UA_EXPORT UA_StatusCode
|
||||
UA_CertificateUtils_verifyApplicationURI(UA_RuleHandling ruleHandling,
|
||||
const UA_ByteString *certificate,
|
||||
const UA_String *applicationURI);
|
||||
const UA_String *applicationURI,
|
||||
UA_Logger *logger);
|
||||
|
||||
/* Get the expire date from certificate */
|
||||
UA_EXPORT UA_StatusCode
|
||||
|
@ -43,15 +43,16 @@ typedef enum {
|
||||
|
||||
typedef enum {
|
||||
UA_LOGCATEGORY_NETWORK = 0,
|
||||
UA_LOGCATEGORY_SECURECHANNEL,
|
||||
UA_LOGCATEGORY_SESSION,
|
||||
UA_LOGCATEGORY_SERVER,
|
||||
UA_LOGCATEGORY_CLIENT,
|
||||
UA_LOGCATEGORY_USERLAND,
|
||||
UA_LOGCATEGORY_SECURITYPOLICY,
|
||||
UA_LOGCATEGORY_EVENTLOOP,
|
||||
UA_LOGCATEGORY_PUBSUB,
|
||||
UA_LOGCATEGORY_DISCOVERY
|
||||
UA_LOGCATEGORY_SECURECHANNEL = 1,
|
||||
UA_LOGCATEGORY_SESSION = 2,
|
||||
UA_LOGCATEGORY_SERVER = 3,
|
||||
UA_LOGCATEGORY_CLIENT = 4,
|
||||
UA_LOGCATEGORY_USERLAND = 5,
|
||||
UA_LOGCATEGORY_SECURITYPOLICY = 6,
|
||||
UA_LOGCATEGORY_SECURITY = 6, /* == SECURITYPOLICY */
|
||||
UA_LOGCATEGORY_EVENTLOOP = 7,
|
||||
UA_LOGCATEGORY_PUBSUB = 8,
|
||||
UA_LOGCATEGORY_DISCOVERY = 9
|
||||
} UA_LogCategory;
|
||||
|
||||
typedef struct UA_Logger {
|
||||
|
@ -29,11 +29,6 @@ _UA_BEGIN_DECLS
|
||||
* DataSet Message
|
||||
* ^^^^^^^^^^^^^^^ */
|
||||
|
||||
typedef struct {
|
||||
UA_Byte count;
|
||||
UA_UInt16* dataSetWriterIds;
|
||||
} UA_DataSetPayloadHeader;
|
||||
|
||||
typedef enum {
|
||||
UA_FIELDENCODING_VARIANT = 0,
|
||||
UA_FIELDENCODING_RAWDATA = 1,
|
||||
@ -42,28 +37,42 @@ typedef enum {
|
||||
} UA_FieldEncoding;
|
||||
|
||||
typedef enum {
|
||||
UA_DATASETMESSAGE_DATAKEYFRAME = 0,
|
||||
UA_DATASETMESSAGE_DATADELTAFRAME = 1,
|
||||
UA_DATASETMESSAGE_EVENT = 2,
|
||||
UA_DATASETMESSAGE_KEEPALIVE = 3
|
||||
UA_DATASETMESSAGETYPE_DATAKEYFRAME = 0,
|
||||
UA_DATASETMESSAGE_DATAKEYFRAME = 0,
|
||||
UA_DATASETMESSAGETYPE_DATADELTAFRAME = 1,
|
||||
UA_DATASETMESSAGE_DATADELTAFRAME = 1,
|
||||
UA_DATASETMESSAGETYPE_EVENT = 2,
|
||||
UA_DATASETMESSAGE_EVENT = 2,
|
||||
UA_DATASETMESSAGETYPE_KEEPALIVE = 3,
|
||||
UA_DATASETMESSAGE_KEEPALIVE = 3
|
||||
} UA_DataSetMessageType;
|
||||
|
||||
typedef struct {
|
||||
/* Settings and message fields enabled with the DataSetFlags1 */
|
||||
UA_Boolean dataSetMessageValid;
|
||||
|
||||
UA_FieldEncoding fieldEncoding;
|
||||
|
||||
UA_Boolean dataSetMessageSequenceNrEnabled;
|
||||
UA_Boolean timestampEnabled;
|
||||
UA_Boolean statusEnabled;
|
||||
UA_Boolean configVersionMajorVersionEnabled;
|
||||
UA_Boolean configVersionMinorVersionEnabled;
|
||||
UA_DataSetMessageType dataSetMessageType;
|
||||
UA_Boolean picoSecondsIncluded;
|
||||
UA_UInt16 dataSetMessageSequenceNr;
|
||||
UA_UtcTime timestamp;
|
||||
UA_UInt16 picoSeconds;
|
||||
|
||||
UA_Boolean statusEnabled;
|
||||
UA_UInt16 status;
|
||||
|
||||
UA_Boolean configVersionMajorVersionEnabled;
|
||||
UA_UInt32 configVersionMajorVersion;
|
||||
|
||||
UA_Boolean configVersionMinorVersionEnabled;
|
||||
UA_UInt32 configVersionMinorVersion;
|
||||
|
||||
/* Settings and message fields enabled with the DataSetFlags2 */
|
||||
UA_DataSetMessageType dataSetMessageType;
|
||||
|
||||
UA_Boolean timestampEnabled;
|
||||
UA_UtcTime timestamp;
|
||||
|
||||
UA_Boolean picoSecondsIncluded;
|
||||
UA_UInt16 picoSeconds;
|
||||
} UA_DataSetMessageHeader;
|
||||
|
||||
typedef struct {
|
||||
@ -87,6 +96,8 @@ typedef struct {
|
||||
} UA_DataSetMessage_DataDeltaFrameData;
|
||||
|
||||
typedef struct {
|
||||
UA_UInt16 dataSetWriterId; /* Goes into the payload header */
|
||||
|
||||
UA_DataSetMessageHeader header;
|
||||
union {
|
||||
UA_DataSetMessage_DataKeyFrameData keyFrameData;
|
||||
@ -105,19 +116,17 @@ typedef enum {
|
||||
UA_NETWORKMESSAGE_DISCOVERY_RESPONSE = 2
|
||||
} UA_NetworkMessageType;
|
||||
|
||||
typedef struct {
|
||||
UA_UInt16* sizes;
|
||||
UA_DataSetMessage* dataSetMessages;
|
||||
} UA_DataSetPayload;
|
||||
|
||||
typedef struct {
|
||||
UA_Boolean writerGroupIdEnabled;
|
||||
UA_Boolean groupVersionEnabled;
|
||||
UA_Boolean networkMessageNumberEnabled;
|
||||
UA_Boolean sequenceNumberEnabled;
|
||||
UA_UInt16 writerGroupId;
|
||||
|
||||
UA_Boolean groupVersionEnabled;
|
||||
UA_UInt32 groupVersion;
|
||||
|
||||
UA_Boolean networkMessageNumberEnabled;
|
||||
UA_UInt16 networkMessageNumber;
|
||||
|
||||
UA_Boolean sequenceNumberEnabled;
|
||||
UA_UInt16 sequenceNumber;
|
||||
} UA_NetworkMessageGroupHeader;
|
||||
|
||||
@ -125,47 +134,64 @@ typedef struct {
|
||||
|
||||
typedef struct {
|
||||
UA_Boolean networkMessageSigned;
|
||||
|
||||
UA_Boolean networkMessageEncrypted;
|
||||
|
||||
UA_Boolean securityFooterEnabled;
|
||||
UA_Boolean forceKeyReset;
|
||||
UA_UInt32 securityTokenId;
|
||||
UA_Byte messageNonce[UA_NETWORKMESSAGE_MAX_NONCE_LENGTH];
|
||||
UA_UInt16 messageNonceSize;
|
||||
UA_UInt16 securityFooterSize;
|
||||
|
||||
UA_Boolean forceKeyReset;
|
||||
|
||||
UA_UInt32 securityTokenId;
|
||||
|
||||
UA_UInt16 messageNonceSize;
|
||||
UA_Byte messageNonce[UA_NETWORKMESSAGE_MAX_NONCE_LENGTH];
|
||||
} UA_NetworkMessageSecurityHeader;
|
||||
|
||||
typedef struct {
|
||||
UA_Byte version;
|
||||
UA_Boolean messageIdEnabled;
|
||||
UA_String messageId; /* For Json NetworkMessage */
|
||||
UA_Boolean publisherIdEnabled;
|
||||
UA_Boolean groupHeaderEnabled;
|
||||
UA_Boolean payloadHeaderEnabled;
|
||||
UA_Boolean dataSetClassIdEnabled;
|
||||
UA_Boolean securityEnabled;
|
||||
UA_Boolean timestampEnabled;
|
||||
UA_Boolean picosecondsEnabled;
|
||||
UA_Boolean chunkMessage;
|
||||
UA_Boolean promotedFieldsEnabled;
|
||||
UA_NetworkMessageType networkMessageType;
|
||||
UA_PublisherId publisherId;
|
||||
UA_Guid dataSetClassId;
|
||||
|
||||
/* Fields defined via the UADPFlags */
|
||||
UA_Boolean publisherIdEnabled;
|
||||
UA_PublisherId publisherId;
|
||||
|
||||
UA_Boolean groupHeaderEnabled;
|
||||
UA_NetworkMessageGroupHeader groupHeader;
|
||||
|
||||
union {
|
||||
UA_DataSetPayloadHeader dataSetPayloadHeader;
|
||||
} payloadHeader;
|
||||
UA_Boolean payloadHeaderEnabled;
|
||||
|
||||
UA_DateTime timestamp;
|
||||
UA_UInt16 picoseconds;
|
||||
UA_UInt16 promotedFieldsSize;
|
||||
UA_Variant* promotedFields; /* BaseDataType */
|
||||
/* Fields defined via the Extended1Flags */
|
||||
UA_Boolean dataSetClassIdEnabled;
|
||||
UA_Guid dataSetClassId;
|
||||
|
||||
UA_Boolean securityEnabled;
|
||||
UA_NetworkMessageSecurityHeader securityHeader;
|
||||
|
||||
UA_Boolean timestampEnabled;
|
||||
UA_DateTime timestamp;
|
||||
|
||||
UA_Boolean picosecondsEnabled;
|
||||
UA_UInt16 picoseconds;
|
||||
|
||||
/* Fields defined via the Extended2Flags */
|
||||
UA_Boolean chunkMessage;
|
||||
|
||||
UA_Boolean promotedFieldsEnabled;
|
||||
UA_UInt16 promotedFieldsSize;
|
||||
UA_Variant *promotedFields; /* BaseDataType */
|
||||
|
||||
UA_NetworkMessageType networkMessageType;
|
||||
|
||||
/* For Json NetworkMessage */
|
||||
UA_Boolean messageIdEnabled;
|
||||
UA_String messageId;
|
||||
|
||||
union {
|
||||
UA_DataSetPayload dataSetPayload;
|
||||
struct {
|
||||
UA_DataSetMessage *dataSetMessages;
|
||||
size_t dataSetMessagesSize; /* Goes into the payload header */
|
||||
} dataSetPayload;
|
||||
/* Extended with other payload types in the future */
|
||||
} payload;
|
||||
|
||||
UA_ByteString securityFooter;
|
||||
|
@ -288,10 +288,12 @@ struct UA_ServerConfig {
|
||||
# ifdef UA_ENABLE_DISCOVERY_MULTICAST
|
||||
UA_Boolean mdnsEnabled;
|
||||
UA_MdnsDiscoveryConfiguration mdnsConfig;
|
||||
# ifdef UA_ENABLE_DISCOVERY_MULTICAST_MDNSD
|
||||
UA_String mdnsInterfaceIP;
|
||||
# if !defined(UA_HAS_GETIFADDR)
|
||||
# if !defined(UA_HAS_GETIFADDR)
|
||||
size_t mdnsIpAddressListSize;
|
||||
UA_UInt32 *mdnsIpAddressList;
|
||||
# endif
|
||||
# endif
|
||||
# endif
|
||||
#endif
|
||||
|
@ -181,6 +181,30 @@ typedef struct {
|
||||
UA_PubSubSecurityPolicy *securityPolicies;
|
||||
} UA_PubSubConfiguration;
|
||||
|
||||
/**
|
||||
* PubSub Components
|
||||
* -----------------
|
||||
* All PubSubComponents (Connection, Reader, ReaderGroup, ...) have a two
|
||||
* configuration items in common: A void context-pointer and a callback to
|
||||
* override the default state machine with a custom implementation.
|
||||
*
|
||||
* When a custom state machine is set, then internally no sockets are opened and
|
||||
* no periodic callbacks are registered. All "active behavior" has to be
|
||||
* managed/configured entirely in the custom state machine. */
|
||||
|
||||
/* The custom state machine callback is optional (can be NULL). It gets called
|
||||
* with a request to change the state targetState. The state pointer contains
|
||||
* the old (and afterwards the new) state. The notification stateChangeCallback
|
||||
* is called afterwards. When a bad statuscode is returned, the component must
|
||||
* be set to an ERROR state. */
|
||||
#define UA_PUBSUB_COMPONENT_CONTEXT \
|
||||
void *context; \
|
||||
UA_StatusCode (*customStateMachine)(UA_Server *server, \
|
||||
const UA_NodeId componentId, \
|
||||
void *componentContext, \
|
||||
UA_PubSubState *state, \
|
||||
UA_PubSubState targetState); \
|
||||
|
||||
/* Enable all PubSubComponents. Returns the ORed statuscodes for enabling each
|
||||
* component individually. */
|
||||
UA_EXPORT UA_StatusCode
|
||||
@ -199,6 +223,7 @@ UA_Server_disableAllPubSubComponents(UA_Server *server);
|
||||
* runtime. */
|
||||
|
||||
typedef struct {
|
||||
/* Configuration parameters from PubSubConnectionDataType */
|
||||
UA_String name;
|
||||
UA_PublisherId publisherId;
|
||||
UA_String transportProfileUri;
|
||||
@ -206,10 +231,7 @@ typedef struct {
|
||||
UA_KeyValueMap connectionProperties;
|
||||
UA_Variant connectionTransportSettings;
|
||||
|
||||
UA_EventLoop *eventLoop; /* Use an external EventLoop (use the EventLoop of
|
||||
* the server if this is NULL). Propagates to the
|
||||
* ReaderGroup/WriterGroup attached to the
|
||||
* Connection. */
|
||||
UA_PUBSUB_COMPONENT_CONTEXT /* Context Configuration */
|
||||
} UA_PubSubConnectionConfig;
|
||||
|
||||
/* Add a new PubSub connection to the given server and open it.
|
||||
@ -232,6 +254,14 @@ UA_EXPORT UA_StatusCode UA_THREADSAFE
|
||||
UA_Server_disablePubSubConnection(UA_Server *server,
|
||||
const UA_NodeId connectionId);
|
||||
|
||||
/* Manually "inject" a packet as if it had been received by the
|
||||
* PubSubConnection. This is intended to be used in combination with a custom
|
||||
* state machine where sockets (connections) are handled by user code. */
|
||||
UA_EXPORT UA_StatusCode UA_THREADSAFE
|
||||
UA_Server_processPubSubConnectionReceive(UA_Server *server,
|
||||
const UA_NodeId connectionId,
|
||||
const UA_ByteString packet);
|
||||
|
||||
/* Returns a deep copy of the config */
|
||||
UA_StatusCode UA_EXPORT UA_THREADSAFE
|
||||
UA_Server_getPubSubConnectionConfig(UA_Server *server,
|
||||
@ -291,6 +321,9 @@ typedef struct {
|
||||
UA_PublishedEventConfig event;
|
||||
UA_PublishedEventTemplateConfig eventTemplate;
|
||||
} config;
|
||||
|
||||
void *context; /* Context Configuration (PublishedDataSet has no state
|
||||
* machine) */
|
||||
} UA_PublishedDataSetConfig;
|
||||
|
||||
void UA_EXPORT
|
||||
@ -338,20 +371,10 @@ typedef struct {
|
||||
UA_Boolean promotedField;
|
||||
UA_PublishedVariableDataType publishParameters;
|
||||
|
||||
/* non std. field */
|
||||
struct {
|
||||
UA_Boolean rtFieldSourceEnabled;
|
||||
/* If the rtInformationModelNode is set, the nodeid in publishParameter
|
||||
* must point to a node with external data source backend defined */
|
||||
UA_Boolean rtInformationModelNode;
|
||||
/* TODO: Decide if suppress C++ warnings and use 'UA_DataValue * * const
|
||||
* staticValueSource;' */
|
||||
UA_DataValue ** staticValueSource;
|
||||
} rtValueSource;
|
||||
UA_UInt32 maxStringLength;
|
||||
UA_LocalizedText description;
|
||||
/* If dataSetFieldId is not set, the GUID will be generated on adding the
|
||||
* field*/
|
||||
* field */
|
||||
UA_Guid dataSetFieldId;
|
||||
} UA_DataSetVariableConfig;
|
||||
|
||||
@ -398,70 +421,13 @@ UA_Server_removeDataSetField(UA_Server *server, const UA_NodeId dsfId);
|
||||
* container for :ref:`dsw` and network message settings. The WriterGroup can be
|
||||
* imagined as producer of the network messages. The creation of network
|
||||
* messages is controlled by parameters like the publish interval, which is e.g.
|
||||
* contained in the WriterGroup.
|
||||
*
|
||||
* The message publishing can be configured for realtime requirements. The RT-levels
|
||||
* go along with different requirements. The below listed levels can be configured:
|
||||
*
|
||||
* UA_PUBSUB_RT_NONE
|
||||
* No realtime-specific configuration.
|
||||
*
|
||||
* UA_PUBSUB_RT_DIRECT_VALUE_ACCESS
|
||||
* All PublishedDataSets need to point to a variable with a
|
||||
* ``UA_VALUEBACKENDTYPE_EXTERNAL`` value backend. The value backend gets
|
||||
* cached when the configuration is frozen. No lookup of the variable from
|
||||
* the information is performed afterwards. This enables also big data
|
||||
* structures to be updated atomically with a compare-and-switch operation on
|
||||
* the ``UA_DataValue`` double-pointer in the backend.
|
||||
*
|
||||
* UA_PUBSUB_RT_FIXED_SIZE
|
||||
* Validate that the message constains only fields with a known size.
|
||||
* Then the message fields have fixed offsets that are known ahead of time.
|
||||
*
|
||||
* UA_PUBSUB_RT_DETERMINISTIC
|
||||
* Both direct-access and fixed-size is being used. The server pre-allocates
|
||||
* buffers when the configuration is frozen and uses only memcpy operations
|
||||
* to update the PubSub network messages for sending.
|
||||
*
|
||||
* WARNING! For hard real time requirements the underlying system must be
|
||||
* RT-capable. Also note that each PubSubConnection can have a dedicated
|
||||
* EventLoop. That way normal client/server operations can run independently
|
||||
* from PubSub. The double-pointer in the ``UA_VALUEBACKENDTYPE_EXTERNAL`` value
|
||||
* backend allows avoid race-condition with non-blocking atomic operations. */
|
||||
|
||||
typedef enum {
|
||||
UA_PUBSUB_RT_NONE = 0,
|
||||
UA_PUBSUB_RT_DIRECT_VALUE_ACCESS = 1,
|
||||
UA_PUBSUB_RT_FIXED_SIZE = 2,
|
||||
UA_PUBSUB_RT_DETERMINISTIC = 3,
|
||||
} UA_PubSubRTLevel;
|
||||
* contained in the WriterGroup. */
|
||||
|
||||
typedef enum {
|
||||
UA_PUBSUB_ENCODING_UADP = 0,
|
||||
UA_PUBSUB_ENCODING_JSON
|
||||
} UA_PubSubEncodingType;
|
||||
|
||||
/**
|
||||
* The user can define his own callback implementation for publishing and
|
||||
* subscribing. The user must take care of the callback to call for every
|
||||
* publishing or subscibing interval. The configured base time and timer policy
|
||||
* are provided as an argument so that the user can implement his callback
|
||||
* (thread) considering base time and timer policies */
|
||||
|
||||
typedef struct {
|
||||
UA_StatusCode (*addCustomCallback)(UA_Server *server, UA_NodeId identifier,
|
||||
UA_ServerCallback callback, void *data,
|
||||
UA_Double interval_ms, UA_DateTime *baseTime,
|
||||
UA_TimerPolicy timerPolicy,
|
||||
UA_UInt64 *callbackId);
|
||||
UA_StatusCode (*changeCustomCallback)(UA_Server *server, UA_NodeId identifier,
|
||||
UA_UInt64 callbackId, UA_Double interval_ms,
|
||||
UA_DateTime *baseTime,
|
||||
UA_TimerPolicy timerPolicy);
|
||||
void (*removeCustomCallback)(UA_Server *server, UA_NodeId identifier,
|
||||
UA_UInt64 callbackId);
|
||||
} UA_PubSub_CallbackLifecycle;
|
||||
|
||||
typedef struct {
|
||||
UA_String name;
|
||||
UA_UInt16 writerGroupId;
|
||||
@ -472,20 +438,20 @@ typedef struct {
|
||||
UA_ExtensionObject messageSettings;
|
||||
UA_KeyValueMap groupProperties;
|
||||
UA_PubSubEncodingType encodingMimeType;
|
||||
/* PubSub Manager Callback */
|
||||
UA_PubSub_CallbackLifecycle pubsubManagerCallback;
|
||||
|
||||
/* non std. config parameter. maximum count of embedded DataSetMessage in
|
||||
* one NetworkMessage */
|
||||
UA_UInt16 maxEncapsulatedDataSetMessageCount;
|
||||
/* non std. field */
|
||||
UA_PubSubRTLevel rtLevel;
|
||||
|
||||
/* Message are encrypted if a SecurityPolicy is configured and the
|
||||
/* Security Configuration
|
||||
* Message are encrypted if a SecurityPolicy is configured and the
|
||||
* securityMode set accordingly. The symmetric key is a runtime information
|
||||
* and has to be set via UA_Server_setWriterGroupEncryptionKey. */
|
||||
UA_MessageSecurityMode securityMode; /* via the UA_WriterGroupDataType */
|
||||
UA_PubSubSecurityPolicy *securityPolicy;
|
||||
UA_String securityGroupId;
|
||||
|
||||
UA_PUBSUB_COMPONENT_CONTEXT /* Context Configuration */
|
||||
} UA_WriterGroupConfig;
|
||||
|
||||
void UA_EXPORT
|
||||
@ -563,6 +529,8 @@ typedef struct {
|
||||
UA_ExtensionObject transportSettings;
|
||||
UA_String dataSetName;
|
||||
UA_KeyValueMap dataSetWriterProperties;
|
||||
|
||||
UA_PUBSUB_COMPONENT_CONTEXT /* Context Configuration */
|
||||
} UA_DataSetWriterConfig;
|
||||
|
||||
void UA_EXPORT
|
||||
@ -625,45 +593,17 @@ typedef enum {
|
||||
UA_PUBSUB_SDS_MIRROR
|
||||
} UA_SubscribedDataSetType;
|
||||
|
||||
typedef struct {
|
||||
/* Standard-defined FieldTargetDataType */
|
||||
UA_FieldTargetDataType targetVariable;
|
||||
|
||||
/* If realtime-handling is required, set this pointer non-NULL and it will be used
|
||||
* to memcpy the value instead of using the Write service.
|
||||
* If the beforeWrite method pointer is set, it will be called before a memcpy update
|
||||
* to the value. But param externalDataValue already contains the new value.
|
||||
* If the afterWrite method pointer is set, it will be called after a memcpy update
|
||||
* to the value. */
|
||||
UA_DataValue **externalDataValue;
|
||||
void *targetVariableContext; /* user-defined pointer */
|
||||
void (*beforeWrite)(UA_Server *server,
|
||||
const UA_NodeId *readerIdentifier,
|
||||
const UA_NodeId *readerGroupIdentifier,
|
||||
const UA_NodeId *targetVariableIdentifier,
|
||||
void *targetVariableContext,
|
||||
UA_DataValue **externalDataValue);
|
||||
void (*afterWrite)(UA_Server *server,
|
||||
const UA_NodeId *readerIdentifier,
|
||||
const UA_NodeId *readerGroupIdentifier,
|
||||
const UA_NodeId *targetVariableIdentifier,
|
||||
void *targetVariableContext,
|
||||
UA_DataValue **externalDataValue);
|
||||
} UA_FieldTargetVariable;
|
||||
|
||||
typedef struct {
|
||||
size_t targetVariablesSize;
|
||||
UA_FieldTargetVariable *targetVariables;
|
||||
} UA_TargetVariables;
|
||||
|
||||
typedef struct {
|
||||
UA_String name;
|
||||
UA_SubscribedDataSetType subscribedDataSetType;
|
||||
union {
|
||||
/* datasetmirror is currently not implemented */
|
||||
/* DataSetMirror is currently not implemented */
|
||||
UA_TargetVariablesDataType target;
|
||||
} subscribedDataSet;
|
||||
UA_DataSetMetaDataType dataSetMetaData;
|
||||
|
||||
void *context; /* Context Configuration (SubscribedDataSet has no state
|
||||
* machine) */
|
||||
} UA_SubscribedDataSetConfig;
|
||||
|
||||
UA_EXPORT void
|
||||
@ -711,14 +651,15 @@ typedef struct {
|
||||
UA_ExtensionObject messageSettings;
|
||||
UA_ExtensionObject transportSettings;
|
||||
UA_SubscribedDataSetType subscribedDataSetType;
|
||||
/* TODO UA_SubscribedDataSetMirrorDataType subscribedDataSetMirror */
|
||||
union {
|
||||
UA_TargetVariables subscribedDataSetTarget;
|
||||
// UA_SubscribedDataSetMirrorDataType subscribedDataSetMirror;
|
||||
/* TODO: UA_SubscribedDataSetMirrorDataType subscribedDataSetMirror */
|
||||
UA_TargetVariablesDataType target;
|
||||
} subscribedDataSet;
|
||||
/* non std. fields */
|
||||
UA_String linkedStandaloneSubscribedDataSetName;
|
||||
UA_PubSubRtEncoding expectedEncoding;
|
||||
|
||||
UA_PUBSUB_COMPONENT_CONTEXT /* Context Configuration */
|
||||
} UA_DataSetReaderConfig;
|
||||
|
||||
UA_EXPORT UA_StatusCode
|
||||
@ -753,10 +694,8 @@ UA_EXPORT UA_StatusCode UA_THREADSAFE
|
||||
UA_Server_disableDataSetReader(UA_Server *server, const UA_NodeId dsrId);
|
||||
|
||||
UA_EXPORT UA_StatusCode UA_THREADSAFE
|
||||
UA_Server_setDataSetReaderTargetVariables(UA_Server *server,
|
||||
const UA_NodeId dsrId,
|
||||
size_t tvsSize,
|
||||
const UA_FieldTargetVariable *tvs);
|
||||
UA_Server_setDataSetReaderTargetVariables(UA_Server *server, const UA_NodeId dsrId,
|
||||
size_t targetVariablesSize, const UA_FieldTargetDataType *targetVariables);
|
||||
|
||||
/* Legacy API */
|
||||
#define UA_Server_DataSetReader_getConfig(server, dsrId, config) \
|
||||
@ -776,19 +715,12 @@ UA_Server_setDataSetReaderTargetVariables(UA_Server *server,
|
||||
* DataSetReader.
|
||||
*
|
||||
* The RT-levels go along with different requirements. The below listed levels
|
||||
* can be configured for a ReaderGroup.
|
||||
*
|
||||
* - UA_PUBSUB_RT_NONE: RT applied to this level
|
||||
* - UA_PUBSUB_RT_FIXED_SIZE: Extends PubSub RT functionality and implements
|
||||
* fast path message decoding in the Subscriber. Uses a buffered network
|
||||
* message and only decodes the necessary offsets stored in an offset
|
||||
* buffer. */
|
||||
* can be configured for a ReaderGroup. */
|
||||
|
||||
typedef struct {
|
||||
UA_String name;
|
||||
|
||||
/* non std. field */
|
||||
UA_PubSubRTLevel rtLevel;
|
||||
UA_KeyValueMap groupProperties;
|
||||
UA_PubSubEncodingType encodingMimeType;
|
||||
UA_ExtensionObject transportSettings;
|
||||
@ -799,6 +731,8 @@ typedef struct {
|
||||
UA_MessageSecurityMode securityMode;
|
||||
UA_PubSubSecurityPolicy *securityPolicy;
|
||||
UA_String securityGroupId;
|
||||
|
||||
UA_PUBSUB_COMPONENT_CONTEXT /* Context Configuration */
|
||||
} UA_ReaderGroupConfig;
|
||||
|
||||
void UA_EXPORT
|
||||
@ -971,6 +905,67 @@ UA_Server_setWriterGroupActivateKey(UA_Server *server,
|
||||
|
||||
#endif /* UA_ENABLE_PUBSUB_SKS */
|
||||
|
||||
/**
|
||||
* Offset Table
|
||||
* ------------
|
||||
* When the content of a PubSub Networkmessage has a fixed length, then only a
|
||||
* few "content bytes" at known locations within the NetworkMessage change
|
||||
* between publish cycles. The so-called offset table exposes this to enable
|
||||
* fast-path implementations for realtime applications. */
|
||||
|
||||
typedef enum {
|
||||
UA_PUBSUBOFFSETTYPE_NETWORKMESSAGE_GROUPVERSION, /* UInt32 */
|
||||
UA_PUBSUBOFFSETTYPE_NETWORKMESSAGE_SEQUENCENUMBER, /* UInt16 */
|
||||
UA_PUBSUBOFFSETTYPE_NETWORKMESSAGE_TIMESTAMP, /* DateTime */
|
||||
UA_PUBSUBOFFSETTYPE_NETWORKMESSAGE_PICOSECONDS, /* UInt16 */
|
||||
UA_PUBSUBOFFSETTYPE_DATASETMESSAGE, /* no content, marks the DSM beginning */
|
||||
UA_PUBSUBOFFSETTYPE_DATASETMESSAGE_SEQUENCENUMBER, /* UInt16 */
|
||||
UA_PUBSUBOFFSETTYPE_DATASETMESSAGE_STATUS, /* UInt16 */
|
||||
UA_PUBSUBOFFSETTYPE_DATASETMESSAGE_TIMESTAMP, /* DateTime */
|
||||
UA_PUBSUBOFFSETTYPE_DATASETMESSAGE_PICOSECONDS, /* UInt16 */
|
||||
UA_PUBSUBOFFSETTYPE_DATASETFIELD_DATAVALUE,
|
||||
UA_PUBSUBOFFSETTYPE_DATASETFIELD_VARIANT,
|
||||
UA_PUBSUBOFFSETTYPE_DATASETFIELD_RAW
|
||||
} UA_PubSubOffsetType;
|
||||
|
||||
typedef struct {
|
||||
UA_PubSubOffsetType offsetType; /* Content type at the offset */
|
||||
size_t offset; /* Offset in the NetworkMessage */
|
||||
|
||||
/* The PubSub component that originates / receives the offset content.
|
||||
* - For NetworkMessage-offsets this is the ReaderGroup / WriterGroup.
|
||||
* - For DataSetMessage-offsets this is DataSetReader / DataSetWriter.
|
||||
* - For DataSetFields this is the NodeId associated with the field:
|
||||
* - For Writers the NodeId of the DataSetField (in a PublishedDataSet).
|
||||
* - For Readers the TargetNodeId of the FieldTargetDataType (this can
|
||||
* come from a SubscribedDataSet or a StandaloneSubscribedDataSets).
|
||||
* Access more metadata from the FieldTargetVariable by counting the
|
||||
* index of the current DataSetField-offset within the DataSetMessage
|
||||
* and use that index for the lookup in the DataSetReader configuration. */
|
||||
UA_NodeId component;
|
||||
} UA_PubSubOffset;
|
||||
|
||||
typedef struct {
|
||||
UA_PubSubOffset *offsets; /* Array of offset entries */
|
||||
size_t offsetsSize; /* Number of entries */
|
||||
UA_ByteString networkMessage; /* Current NetworkMessage in binary encoding */
|
||||
} UA_PubSubOffsetTable;
|
||||
|
||||
UA_EXPORT void
|
||||
UA_PubSubOffsetTable_clear(UA_PubSubOffsetTable *ot);
|
||||
|
||||
/* Compute the offset table for a WriterGroup */
|
||||
UA_EXPORT UA_StatusCode UA_THREADSAFE
|
||||
UA_Server_computeWriterGroupOffsetTable(UA_Server *server,
|
||||
const UA_NodeId writerGroupId,
|
||||
UA_PubSubOffsetTable *ot);
|
||||
|
||||
/* Compute the offset table for a ReaderGroup */
|
||||
UA_EXPORT UA_StatusCode UA_THREADSAFE
|
||||
UA_Server_computeReaderGroupOffsetTable(UA_Server *server,
|
||||
const UA_NodeId readerGroupId,
|
||||
UA_PubSubOffsetTable *ot);
|
||||
|
||||
#endif /* UA_ENABLE_PUBSUB */
|
||||
|
||||
_UA_END_DECLS
|
||||
|
@ -21,6 +21,9 @@
|
||||
#include <open62541/common.h>
|
||||
#include <open62541/statuscodes.h>
|
||||
|
||||
struct UA_NamespaceMapping;
|
||||
typedef struct UA_NamespaceMapping UA_NamespaceMapping;
|
||||
|
||||
_UA_BEGIN_DECLS
|
||||
|
||||
/**
|
||||
@ -200,7 +203,7 @@ UA_String_fromChars(const char *src) UA_FUNC_ATTR_WARN_UNUSED_RESULT;
|
||||
UA_Boolean UA_EXPORT
|
||||
UA_String_isEmpty(const UA_String *s);
|
||||
|
||||
UA_StatusCode
|
||||
UA_StatusCode UA_EXPORT
|
||||
UA_String_append(UA_String *s, const UA_String s2);
|
||||
|
||||
UA_EXPORT extern const UA_String UA_STRING_NULL;
|
||||
@ -417,8 +420,7 @@ UA_EXPORT extern const UA_NodeId UA_NODEID_NULL;
|
||||
|
||||
UA_Boolean UA_EXPORT UA_NodeId_isNull(const UA_NodeId *p);
|
||||
|
||||
/* Print the NodeId in the human-readable format defined in Part 6,
|
||||
* 5.3.1.10.
|
||||
/* Print the NodeId in the human-readable format defined in Part 6.
|
||||
*
|
||||
* Examples:
|
||||
* UA_NODEID("i=13")
|
||||
@ -432,13 +434,39 @@ UA_Boolean UA_EXPORT UA_NodeId_isNull(const UA_NodeId *p);
|
||||
UA_StatusCode UA_EXPORT
|
||||
UA_NodeId_print(const UA_NodeId *id, UA_String *output);
|
||||
|
||||
/* Extended NodeId printing. If nsMapping argument is non-NULL, then the
|
||||
* NamespaceIndex is translated to the NamespaceUri. If that is not successful,
|
||||
* the numerical NamespaceIndex is used instead.
|
||||
*
|
||||
* Examples:
|
||||
* nsu=http://widgets.com/schemas/hello;s=Hello World
|
||||
*/
|
||||
UA_StatusCode UA_EXPORT
|
||||
UA_NodeId_printEx(const UA_NodeId *id, UA_String *output,
|
||||
const UA_NamespaceMapping *nsMapping);
|
||||
|
||||
#ifdef UA_ENABLE_PARSING
|
||||
/* Parse the human-readable NodeId format. Attention! String and
|
||||
* ByteString NodeIds have their identifier malloc'ed and need to be
|
||||
* cleaned up. */
|
||||
#ifdef UA_ENABLE_PARSING
|
||||
UA_StatusCode UA_EXPORT
|
||||
UA_NodeId_parse(UA_NodeId *id, const UA_String str);
|
||||
|
||||
/* Extended parsing that uses the provided namespace mapping to find the
|
||||
* NamespaceIndex for a provided NamespaceUri.
|
||||
*
|
||||
* If the NodeId uses an unknown NamespaceUri, then a String-NodeId is returned
|
||||
* that uses NamespaceIndex 0 and the full original encoding for the string
|
||||
* part.
|
||||
*
|
||||
* Example:
|
||||
* nsu=my_uri;i=5 => s="nsu=my_uri;i=5" (The quotation marks are for
|
||||
* illustration purposes and not actually included)
|
||||
*/
|
||||
UA_StatusCode UA_EXPORT
|
||||
UA_NodeId_parseEx(UA_NodeId *id, const UA_String str,
|
||||
const UA_NamespaceMapping *nsMapping);
|
||||
|
||||
UA_INLINABLE(UA_NodeId
|
||||
UA_NODEID(const char *chars), {
|
||||
UA_NodeId id;
|
||||
@ -550,13 +578,29 @@ UA_EXPORT extern const UA_ExpandedNodeId UA_EXPANDEDNODEID_NULL;
|
||||
UA_StatusCode UA_EXPORT
|
||||
UA_ExpandedNodeId_print(const UA_ExpandedNodeId *id, UA_String *output);
|
||||
|
||||
/* Extended printing of ExpandedNodeId. It tries to map NamespaceIndex and
|
||||
* ServerIndex to a Uri using the provided mapping.
|
||||
*
|
||||
* Examples:
|
||||
* svu=http://smith.com/west/factory;nsu=tag:acme.com,2023;i=1234
|
||||
*/
|
||||
UA_StatusCode UA_EXPORT
|
||||
UA_ExpandedNodeId_printEx(const UA_ExpandedNodeId *id, UA_String *output,
|
||||
const UA_NamespaceMapping *nsMapping,
|
||||
size_t serverUrisSize, const UA_String *serverUris);
|
||||
|
||||
#ifdef UA_ENABLE_PARSING
|
||||
/* Parse the human-readable NodeId format. Attention! String and
|
||||
* ByteString NodeIds have their identifier malloc'ed and need to be
|
||||
* cleaned up. */
|
||||
#ifdef UA_ENABLE_PARSING
|
||||
UA_StatusCode UA_EXPORT
|
||||
UA_ExpandedNodeId_parse(UA_ExpandedNodeId *id, const UA_String str);
|
||||
|
||||
UA_StatusCode UA_EXPORT
|
||||
UA_ExpandedNodeId_parseEx(UA_ExpandedNodeId *id, const UA_String str,
|
||||
const UA_NamespaceMapping *nsMapping,
|
||||
size_t serverUrisSize, const UA_String *serverUris);
|
||||
|
||||
UA_INLINABLE(UA_ExpandedNodeId
|
||||
UA_EXPANDEDNODEID(const char *chars), {
|
||||
UA_ExpandedNodeId id;
|
||||
@ -665,6 +709,42 @@ UA_INLINABLE(UA_QualifiedName
|
||||
return qn;
|
||||
})
|
||||
|
||||
/* Print the human-readable QualifiedName format. QualifiedNames can be printed
|
||||
* with either the integer NamespaceIndex or using the NamespaceUri.
|
||||
* The Namespace 0 is always omitted.
|
||||
*
|
||||
* The extended printing tries to translate the NamespaceIndex to the
|
||||
* NamespaceUri from the mapping table. When the mapping fails, the integer
|
||||
* NamespaceIndex from is used.
|
||||
*
|
||||
* Examples:
|
||||
* Namespace Zero: HelloWorld
|
||||
* NamespaceIndex Form: 3:HelloWorld
|
||||
* NamespaceUri Form: nsu=http://widgets.com/schemas/hello;HelloWorld
|
||||
*
|
||||
* The method can either use a pre-allocated string buffer or allocates memory
|
||||
* internally if called with an empty output string. */
|
||||
UA_StatusCode UA_EXPORT
|
||||
UA_QualifiedName_print(const UA_QualifiedName *qn, UA_String *output);
|
||||
|
||||
UA_StatusCode UA_EXPORT
|
||||
UA_QualifiedName_printEx(const UA_QualifiedName *qn, UA_String *output,
|
||||
const UA_NamespaceMapping *nsMapping);
|
||||
|
||||
#ifdef UA_ENABLE_PARSING
|
||||
/* Parse the human-readable QualifiedName format.
|
||||
*
|
||||
* The extended parsing tries to translate the NamespaceIndex to a NamespaceUri
|
||||
* from the mapping table. When the mapping fails, the name component gets the
|
||||
* entire string. */
|
||||
UA_StatusCode UA_EXPORT
|
||||
UA_QualifiedName_parse(UA_QualifiedName *qn, const UA_String str);
|
||||
|
||||
UA_StatusCode UA_EXPORT
|
||||
UA_QualifiedName_parseEx(UA_QualifiedName *qn, const UA_String str,
|
||||
const UA_NamespaceMapping *nsMapping);
|
||||
#endif
|
||||
|
||||
/**
|
||||
* LocalizedText
|
||||
* ^^^^^^^^^^^^^
|
||||
@ -1245,6 +1325,66 @@ UA_INLINABLE(UA_Boolean
|
||||
return (UA_order(p1, p2, type) == UA_ORDER_EQ);
|
||||
})
|
||||
|
||||
/**
|
||||
* Namespace Mapping
|
||||
* -----------------
|
||||
*
|
||||
* Every :ref:`nodeid` references a namespace index. Actually the namespace is
|
||||
* identified by its URI. The namespace-array of the server maps the URI to the
|
||||
* namespace index in the array. Namespace zero always has the URI
|
||||
* ```http://opcfoundation.org/UA/```. Namespace one has the application URI of
|
||||
* the server. All namespaces beyond get a custom assignment.
|
||||
*
|
||||
* In order to have predictable NodeIds, a client might predefined its own
|
||||
* namespace array that is different from the server's. When a NodeId is decoded
|
||||
* from a network message (binary or JSON), a mapping-table can be used to
|
||||
* automatically translate between the remote and local namespace index. The
|
||||
* mapping is typically done by the client who can generate the mapping table
|
||||
* after reading the namespace-array of the server. The reverse mapping is done
|
||||
* in the encoding if the mapping table is set in the options.
|
||||
*
|
||||
* The mapping table also contains the full URI names. It is also used to
|
||||
* translate the ``NamespaceUri`` field of an ExpandedNodeId into the namespace
|
||||
* index of the NodeId embedded in the ExpandedNodeId. */
|
||||
|
||||
struct UA_NamespaceMapping {
|
||||
/* Namespaces with their local index */
|
||||
UA_String *namespaceUris;
|
||||
size_t namespaceUrisSize;
|
||||
|
||||
/* Map from local to remote indices */
|
||||
UA_UInt16 *local2remote;
|
||||
size_t local2remoteSize;
|
||||
|
||||
/* Map from remote to local indices */
|
||||
UA_UInt16 *remote2local;
|
||||
size_t remote2localSize;
|
||||
};
|
||||
|
||||
/* If the index is unknown, returns (UINT16_MAX - index) */
|
||||
UA_UInt16
|
||||
UA_NamespaceMapping_local2Remote(const UA_NamespaceMapping *nm,
|
||||
UA_UInt16 localIndex);
|
||||
|
||||
UA_UInt16
|
||||
UA_NamespaceMapping_remote2Local(const UA_NamespaceMapping *nm,
|
||||
UA_UInt16 remoteIndex);
|
||||
|
||||
/* Returns an error if the namespace uri was not found.
|
||||
* The pointer to the index argument needs to be non-NULL. */
|
||||
UA_StatusCode
|
||||
UA_NamespaceMapping_uri2Index(const UA_NamespaceMapping *nm,
|
||||
UA_String uri, UA_UInt16 *index);
|
||||
|
||||
/* Upon success, the uri string gets set. The string is not copied and must not
|
||||
* outlive the namespace mapping structure. */
|
||||
UA_StatusCode
|
||||
UA_NamespaceMapping_index2Uri(const UA_NamespaceMapping *nm,
|
||||
UA_UInt16 index, UA_String *uri);
|
||||
|
||||
void
|
||||
UA_NamespaceMapping_delete(UA_NamespaceMapping *nm);
|
||||
|
||||
/**
|
||||
* Binary Encoding/Decoding
|
||||
* ------------------------
|
||||
@ -1252,10 +1392,17 @@ UA_INLINABLE(UA_Boolean
|
||||
* Encoding and decoding routines for the binary format. For the binary decoding
|
||||
* additional data types can be forwarded. */
|
||||
|
||||
typedef struct {
|
||||
/* Mapping of namespace indices in NodeIds and of NamespaceUris in
|
||||
* ExpandedNodeIds. */
|
||||
UA_NamespaceMapping *namespaceMapping;
|
||||
} UA_EncodeBinaryOptions;
|
||||
|
||||
/* Returns the number of bytes the value p takes in binary encoding. Returns
|
||||
* zero if an error occurs. */
|
||||
UA_EXPORT size_t
|
||||
UA_calcSizeBinary(const void *p, const UA_DataType *type);
|
||||
UA_calcSizeBinary(const void *p, const UA_DataType *type,
|
||||
UA_EncodeBinaryOptions *options);
|
||||
|
||||
/* Encodes a data-structure in the binary format. If outBuf has a length of
|
||||
* zero, a buffer of the required size is allocated. Otherwise, encoding into
|
||||
@ -1263,7 +1410,7 @@ UA_calcSizeBinary(const void *p, const UA_DataType *type);
|
||||
* small). */
|
||||
UA_EXPORT UA_StatusCode
|
||||
UA_encodeBinary(const void *p, const UA_DataType *type,
|
||||
UA_ByteString *outBuf);
|
||||
UA_ByteString *outBuf, UA_EncodeBinaryOptions *options);
|
||||
|
||||
/* The structure with the decoding options may be extended in the future.
|
||||
* Zero-out the entire structure initially to ensure code-compatibility when
|
||||
@ -1272,6 +1419,10 @@ typedef struct {
|
||||
/* Begin of a linked list with custom datatype definitions */
|
||||
const UA_DataTypeArray *customTypes;
|
||||
|
||||
/* Mapping of namespace indices in NodeIds and of NamespaceUris in
|
||||
* ExpandedNodeIds. */
|
||||
UA_NamespaceMapping *namespaceMapping;
|
||||
|
||||
/* Override calloc for arena-based memory allocation. Note that allocated
|
||||
* memory is not freed if decoding fails afterwards. */
|
||||
void *callocContext;
|
||||
@ -1310,8 +1461,10 @@ UA_decodeBinary(const UA_ByteString *inBuf,
|
||||
#ifdef UA_ENABLE_JSON_ENCODING
|
||||
|
||||
typedef struct {
|
||||
const UA_String *namespaces;
|
||||
size_t namespacesSize;
|
||||
/* Mapping of namespace indices in NodeIds and of NamespaceUris in
|
||||
* ExpandedNodeIds. */
|
||||
UA_NamespaceMapping *namespaceMapping;
|
||||
|
||||
const UA_String *serverUris;
|
||||
size_t serverUrisSize;
|
||||
UA_Boolean useReversible;
|
||||
@ -1347,12 +1500,16 @@ UA_encodeJson(const void *src, const UA_DataType *type, UA_ByteString *outBuf,
|
||||
* Zero-out the entire structure initially to ensure code-compatibility when
|
||||
* more fields are added in a later release. */
|
||||
typedef struct {
|
||||
const UA_String *namespaces;
|
||||
size_t namespacesSize;
|
||||
/* Mapping of namespace indices in NodeIds and of NamespaceUris in
|
||||
* ExpandedNodeIds. */
|
||||
UA_NamespaceMapping *namespaceMapping;
|
||||
|
||||
const UA_String *serverUris;
|
||||
size_t serverUrisSize;
|
||||
|
||||
const UA_DataTypeArray *customTypes; /* Begin of a linked list with custom
|
||||
* datatype definitions */
|
||||
|
||||
size_t *decodedLength; /* If non-NULL, the length of the decoded input is
|
||||
* stored to the pointer. When this is set, decoding
|
||||
* succeeds also if there is more content after the
|
||||
|
@ -439,6 +439,10 @@ UA_ByteString_memZero(UA_ByteString *bs);
|
||||
UA_EXPORT UA_StatusCode
|
||||
UA_TrustListDataType_add(const UA_TrustListDataType *src, UA_TrustListDataType *dst);
|
||||
|
||||
/* Replaces the contents of the destination trusted list with the certificates from the source trusted list. */
|
||||
UA_EXPORT UA_StatusCode
|
||||
UA_TrustListDataType_set(const UA_TrustListDataType *src, UA_TrustListDataType *dst);
|
||||
|
||||
/* Removes all of the certificates from the dst trust list that are specified
|
||||
* in the src trust list. */
|
||||
UA_EXPORT UA_StatusCode
|
||||
|
@ -9,7 +9,6 @@
|
||||
|
||||
#include <open62541/util.h>
|
||||
#include <open62541/plugin/certificategroup_default.h>
|
||||
#include <open62541/plugin/log_stdout.h>
|
||||
|
||||
#ifdef UA_ENABLE_ENCRYPTION_MBEDTLS
|
||||
|
||||
@ -95,8 +94,8 @@ MemoryCertStore_setTrustList(UA_CertificateGroup *certGroup, const UA_TrustListD
|
||||
return UA_STATUSCODE_BADINTERNALERROR;
|
||||
}
|
||||
context->reloadRequired = true;
|
||||
UA_TrustListDataType_clear(&context->trustList);
|
||||
return UA_TrustListDataType_add(trustList, &context->trustList);
|
||||
/* Remove the section of the trust list that needs to be reset, while keeping the remaining parts intact */
|
||||
return UA_TrustListDataType_set(trustList, &context->trustList);
|
||||
}
|
||||
|
||||
static UA_StatusCode
|
||||
@ -153,7 +152,7 @@ mbedtlsFindCrls(UA_CertificateGroup *certGroup, const UA_ByteString *certificate
|
||||
mbedtls_x509_crt_init(&cert);
|
||||
UA_StatusCode retval = UA_mbedTLS_LoadCertificate(certificate, &cert);
|
||||
if(retval != UA_STATUSCODE_GOOD) {
|
||||
UA_LOG_WARNING(certGroup->logging, UA_LOGCATEGORY_SERVER,
|
||||
UA_LOG_WARNING(certGroup->logging, UA_LOGCATEGORY_SECURITYPOLICY,
|
||||
"An error occurred while parsing the certificate.");
|
||||
return retval;
|
||||
}
|
||||
@ -161,7 +160,7 @@ mbedtlsFindCrls(UA_CertificateGroup *certGroup, const UA_ByteString *certificate
|
||||
/* Check if the certificate is a CA certificate.
|
||||
* Only a CA certificate can have a CRL. */
|
||||
if(!mbedtlsCheckCA(&cert)) {
|
||||
UA_LOG_WARNING(certGroup->logging, UA_LOGCATEGORY_SERVER,
|
||||
UA_LOG_WARNING(certGroup->logging, UA_LOGCATEGORY_SECURITYPOLICY,
|
||||
"The certificate is not a CA certificate and therefore does not have a CRL.");
|
||||
mbedtls_x509_crt_free(&cert);
|
||||
return UA_STATUSCODE_GOOD;
|
||||
@ -173,7 +172,7 @@ mbedtlsFindCrls(UA_CertificateGroup *certGroup, const UA_ByteString *certificate
|
||||
mbedtls_x509_crl_init(&crl);
|
||||
retval = UA_mbedTLS_LoadCrl(&crlList[i], &crl);
|
||||
if(retval != UA_STATUSCODE_GOOD) {
|
||||
UA_LOG_WARNING(certGroup->logging, UA_LOGCATEGORY_SERVER,
|
||||
UA_LOG_WARNING(certGroup->logging, UA_LOGCATEGORY_SECURITYPOLICY,
|
||||
"An error occurred while parsing the crl.");
|
||||
mbedtls_x509_crt_free(&cert);
|
||||
return retval;
|
||||
@ -697,6 +696,8 @@ cleanup:
|
||||
return retval;
|
||||
}
|
||||
|
||||
#if !defined(mbedtls_x509_subject_alternative_name)
|
||||
|
||||
/* Find binary substring. Taken and adjusted from
|
||||
* http://tungchingkai.blogspot.com/2011/07/binary-strstr.html */
|
||||
|
||||
@ -736,10 +737,13 @@ UA_Bstrstr(const unsigned char *s1, size_t l1, const unsigned char *s2, size_t l
|
||||
return NULL;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
UA_StatusCode
|
||||
UA_CertificateUtils_verifyApplicationURI(UA_RuleHandling ruleHandling,
|
||||
const UA_ByteString *certificate,
|
||||
const UA_String *applicationURI) {
|
||||
const UA_String *applicationURI,
|
||||
UA_Logger *logger) {
|
||||
/* Parse the certificate */
|
||||
mbedtls_x509_crt remoteCertificate;
|
||||
mbedtls_x509_crt_init(&remoteCertificate);
|
||||
@ -748,21 +752,56 @@ UA_CertificateUtils_verifyApplicationURI(UA_RuleHandling ruleHandling,
|
||||
if(retval != UA_STATUSCODE_GOOD)
|
||||
return retval;
|
||||
|
||||
#if defined(mbedtls_x509_subject_alternative_name)
|
||||
/* Get the Subject Alternative Name and compate */
|
||||
mbedtls_x509_subject_alternative_name san;
|
||||
mbedtls_x509_sequence *cur = &remoteCertificate.subject_alt_names;
|
||||
retval = UA_STATUSCODE_BADCERTIFICATEURIINVALID;
|
||||
for(; cur; cur = cur->next) {
|
||||
int res = mbedtls_x509_parse_subject_alt_name(&cur->buf, &san);
|
||||
if(res != 0)
|
||||
continue;
|
||||
if(san.type != MBEDTLS_X509_SAN_UNIFORM_RESOURCE_IDENTIFIER) {
|
||||
mbedtls_x509_free_subject_alt_name(&san);
|
||||
continue;
|
||||
}
|
||||
|
||||
UA_String uri = {san.san.unstructured_name.len, san.san.unstructured_name.p};
|
||||
UA_Boolean found = UA_String_equal(&uri, applicationURI);
|
||||
if(found) {
|
||||
retval = UA_STATUSCODE_GOOD;
|
||||
} else if(ruleHandling != UA_RULEHANDLING_ACCEPT) {
|
||||
UA_LOG_WARNING(logger, UA_LOGCATEGORY_SECURITYPOLICY,
|
||||
"The certificate's Subject Alternative Name URI (%S) "
|
||||
"does not match the ApplicationURI (%S)",
|
||||
uri, *applicationURI);
|
||||
}
|
||||
mbedtls_x509_free_subject_alt_name(&san);
|
||||
break;
|
||||
}
|
||||
|
||||
if(!cur && ruleHandling != UA_RULEHANDLING_ACCEPT) {
|
||||
UA_LOG_WARNING(logger, UA_LOGCATEGORY_SECURITYPOLICY,
|
||||
"The certificate has no Subject Alternative Name URI defined");
|
||||
}
|
||||
#else
|
||||
/* Poor man's ApplicationUri verification. mbedTLS does not parse all fields
|
||||
* of the Alternative Subject Name. Instead test whether the URI-string is
|
||||
* present in the v3_ext field in general.
|
||||
*
|
||||
* TODO: Improve parsing of the Alternative Subject Name */
|
||||
* present in the v3_ext field in general. */
|
||||
if(UA_Bstrstr(remoteCertificate.v3_ext.p, remoteCertificate.v3_ext.len,
|
||||
applicationURI->data, applicationURI->length) == NULL)
|
||||
retval = UA_STATUSCODE_BADCERTIFICATEURIINVALID;
|
||||
|
||||
if(retval != UA_STATUSCODE_GOOD && ruleHandling == UA_RULEHANDLING_DEFAULT) {
|
||||
UA_LOG_WARNING(UA_Log_Stdout, UA_LOGCATEGORY_SERVER,
|
||||
if(retval != UA_STATUSCODE_GOOD && ruleHandling != UA_RULEHANDLING_ACCEPT) {
|
||||
UA_LOG_WARNING(logger, UA_LOGCATEGORY_SECURITYPOLICY,
|
||||
"The certificate's application URI could not be verified. StatusCode %s",
|
||||
UA_StatusCode_name(retval));
|
||||
retval = UA_STATUSCODE_GOOD;
|
||||
}
|
||||
#endif
|
||||
|
||||
if(ruleHandling != UA_RULEHANDLING_ABORT)
|
||||
retval = UA_STATUSCODE_GOOD;
|
||||
|
||||
mbedtls_x509_crt_free(&remoteCertificate);
|
||||
return retval;
|
||||
}
|
||||
@ -798,13 +837,23 @@ UA_CertificateUtils_getSubjectName(UA_ByteString *certificate,
|
||||
mbedtls_x509_crt publicKey;
|
||||
mbedtls_x509_crt_init(&publicKey);
|
||||
|
||||
UA_StatusCode retval = UA_mbedTLS_LoadCertificate(certificate, &publicKey);
|
||||
if(retval != UA_STATUSCODE_GOOD)
|
||||
return retval;
|
||||
mbedtls_x509_crl crl;
|
||||
mbedtls_x509_crl_init(&crl);
|
||||
|
||||
char buf[1024];
|
||||
int res = mbedtls_x509_dn_gets(buf, 1024, &publicKey.subject);
|
||||
mbedtls_x509_crt_free(&publicKey);
|
||||
int res = 0;
|
||||
UA_StatusCode retval = UA_mbedTLS_LoadCertificate(certificate, &publicKey);
|
||||
if(retval == UA_STATUSCODE_GOOD) {
|
||||
res = mbedtls_x509_dn_gets(buf, 1024, &publicKey.subject);
|
||||
mbedtls_x509_crt_free(&publicKey);
|
||||
} else {
|
||||
retval = UA_mbedTLS_LoadCrl(certificate, &crl);
|
||||
if(retval != UA_STATUSCODE_GOOD)
|
||||
return retval;
|
||||
res = mbedtls_x509_dn_gets(buf, 1024, &crl.issuer);
|
||||
mbedtls_x509_crl_free(&crl);
|
||||
}
|
||||
|
||||
if(res < 0)
|
||||
return UA_STATUSCODE_BADINTERNALERROR;
|
||||
UA_String tmp = {(size_t)res, (UA_Byte*)buf};
|
||||
|
@ -9,7 +9,6 @@
|
||||
|
||||
#include <open62541/util.h>
|
||||
#include <open62541/plugin/certificategroup_default.h>
|
||||
#include <open62541/plugin/log_stdout.h>
|
||||
|
||||
#if defined(UA_ENABLE_ENCRYPTION_OPENSSL) || defined(UA_ENABLE_ENCRYPTION_LIBRESSL)
|
||||
#include <openssl/x509.h>
|
||||
@ -93,8 +92,8 @@ MemoryCertStore_setTrustList(UA_CertificateGroup *certGroup, const UA_TrustListD
|
||||
return UA_STATUSCODE_BADINTERNALERROR;
|
||||
}
|
||||
context->reloadRequired = true;
|
||||
UA_TrustListDataType_clear(&context->trustList);
|
||||
return UA_TrustListDataType_add(trustList, &context->trustList);
|
||||
/* Remove the section of the trust list that needs to be reset, while keeping the remaining parts intact */
|
||||
return UA_TrustListDataType_set(trustList, &context->trustList);
|
||||
}
|
||||
|
||||
static UA_StatusCode
|
||||
@ -159,8 +158,8 @@ openSSLFindCrls(UA_CertificateGroup *certGroup, const UA_ByteString *certificate
|
||||
/* Check if the certificate is a CA certificate.
|
||||
* Only a CA certificate can have a CRL. */
|
||||
if(!openSSLCheckCA(cert)) {
|
||||
UA_LOG_WARNING(certGroup->logging, UA_LOGCATEGORY_SERVER,
|
||||
"The certificate is not a CA certificate and therefore does not have a CRL.");
|
||||
UA_LOG_WARNING(certGroup->logging, UA_LOGCATEGORY_SECURITYPOLICY,
|
||||
"The certificate is not a CA certificate and therefore does not have a CRL.");
|
||||
X509_free(cert);
|
||||
return UA_STATUSCODE_GOOD;
|
||||
}
|
||||
@ -761,7 +760,8 @@ cleanup:
|
||||
UA_StatusCode
|
||||
UA_CertificateUtils_verifyApplicationURI(UA_RuleHandling ruleHandling,
|
||||
const UA_ByteString *certificate,
|
||||
const UA_String *applicationURI) {
|
||||
const UA_String *applicationURI,
|
||||
UA_Logger *logger) {
|
||||
const unsigned char * pData;
|
||||
X509 * certificateX509;
|
||||
UA_String subjectURI = UA_STRING_NULL;
|
||||
@ -810,13 +810,16 @@ UA_CertificateUtils_verifyApplicationURI(UA_RuleHandling ruleHandling,
|
||||
ret = UA_STATUSCODE_BADCERTIFICATEURIINVALID;
|
||||
}
|
||||
|
||||
if(ret != UA_STATUSCODE_GOOD && ruleHandling == UA_RULEHANDLING_DEFAULT) {
|
||||
UA_LOG_WARNING(UA_Log_Stdout, UA_LOGCATEGORY_SERVER,
|
||||
"The certificate's application URI could not be verified. StatusCode %s",
|
||||
UA_StatusCode_name(ret));
|
||||
ret = UA_STATUSCODE_GOOD;
|
||||
if(ret != UA_STATUSCODE_GOOD && ruleHandling != UA_RULEHANDLING_ACCEPT) {
|
||||
UA_LOG_WARNING(logger, UA_LOGCATEGORY_SECURITYPOLICY,
|
||||
"The certificate's Subject Alternative Name URI (%S) "
|
||||
"does not match the ApplicationURI (%S)",
|
||||
subjectURI, *applicationURI);
|
||||
}
|
||||
|
||||
if(ruleHandling != UA_RULEHANDLING_ABORT)
|
||||
ret = UA_STATUSCODE_GOOD;
|
||||
|
||||
X509_free (certificateX509);
|
||||
sk_GENERAL_NAME_pop_free(pNames, GENERAL_NAME_free);
|
||||
UA_String_clear (&subjectURI);
|
||||
@ -855,14 +858,24 @@ UA_CertificateUtils_getExpirationDate(UA_ByteString *certificate,
|
||||
UA_StatusCode
|
||||
UA_CertificateUtils_getSubjectName(UA_ByteString *certificate,
|
||||
UA_String *subjectName) {
|
||||
X509_NAME *sn = NULL;
|
||||
X509 *x509 = UA_OpenSSL_LoadCertificate(certificate);
|
||||
if(!x509)
|
||||
return UA_STATUSCODE_BADSECURITYCHECKSFAILED;
|
||||
X509_CRL *x509_crl = NULL;
|
||||
|
||||
if(x509) {
|
||||
sn = X509_get_subject_name(x509);
|
||||
} else {
|
||||
x509_crl = UA_OpenSSL_LoadCrl(certificate);
|
||||
if(!x509_crl)
|
||||
return UA_STATUSCODE_BADSECURITYCHECKSFAILED;
|
||||
sn = X509_CRL_get_issuer(x509_crl);
|
||||
}
|
||||
|
||||
X509_NAME *sn = X509_get_subject_name(x509);
|
||||
char buf[1024];
|
||||
*subjectName = UA_STRING_ALLOC(X509_NAME_oneline(sn, buf, 1024));
|
||||
X509_free(x509);
|
||||
|
||||
if (x509) X509_free(x509);
|
||||
if (x509_crl) X509_CRL_free(x509_crl);
|
||||
return UA_STATUSCODE_GOOD;
|
||||
}
|
||||
|
||||
@ -872,15 +885,25 @@ UA_CertificateUtils_getThumbprint(UA_ByteString *certificate,
|
||||
if(certificate == NULL || thumbprint->length != (SHA1_DIGEST_LENGTH * 2))
|
||||
return UA_STATUSCODE_BADINTERNALERROR;
|
||||
|
||||
X509 *cert = UA_OpenSSL_LoadCertificate(certificate);
|
||||
if(!cert)
|
||||
return UA_STATUSCODE_BADSECURITYCHECKSFAILED;
|
||||
|
||||
unsigned char digest[SHA1_DIGEST_LENGTH];
|
||||
unsigned int digestLen;
|
||||
if(X509_digest(cert, EVP_sha1(), digest, &digestLen) != 1) {
|
||||
|
||||
X509 *cert = UA_OpenSSL_LoadCertificate(certificate);
|
||||
if(cert) {
|
||||
if(X509_digest(cert, EVP_sha1(), digest, &digestLen) != 1) {
|
||||
X509_free(cert);
|
||||
return UA_STATUSCODE_BADINTERNALERROR;
|
||||
}
|
||||
X509_free(cert);
|
||||
return UA_STATUSCODE_BADINTERNALERROR;
|
||||
} else {
|
||||
X509_CRL *crl = UA_OpenSSL_LoadCrl(certificate);
|
||||
if(!crl)
|
||||
return UA_STATUSCODE_BADSECURITYCHECKSFAILED;
|
||||
if(X509_CRL_digest(crl, EVP_sha1(), digest, &digestLen) != 1) {
|
||||
X509_CRL_free(crl);
|
||||
return UA_STATUSCODE_BADINTERNALERROR;
|
||||
}
|
||||
X509_CRL_free(crl);
|
||||
}
|
||||
|
||||
UA_String thumb = UA_STRING_NULL;
|
||||
@ -894,8 +917,6 @@ UA_CertificateUtils_getThumbprint(UA_ByteString *certificate,
|
||||
}
|
||||
|
||||
memcpy(thumbprint->data, thumb.data, thumbprint->length);
|
||||
|
||||
X509_free(cert);
|
||||
free(thumb.data);
|
||||
|
||||
return UA_STATUSCODE_GOOD;
|
||||
|
@ -9,34 +9,26 @@
|
||||
|
||||
#include <open62541/util.h>
|
||||
#include <open62541/plugin/certificategroup_default.h>
|
||||
#include <open62541/plugin/log_stdout.h>
|
||||
|
||||
#include "ua_filestore_common.h"
|
||||
#include "mp_printf.h"
|
||||
|
||||
#ifdef UA_ENABLE_ENCRYPTION
|
||||
|
||||
#ifdef __linux__ /* Linux only so far */
|
||||
|
||||
#include <stdio.h>
|
||||
#include <linux/limits.h>
|
||||
#include <libgen.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/inotify.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <dirent.h>
|
||||
#include <bits/stdio_lim.h>
|
||||
#include <errno.h>
|
||||
#if defined(__linux__) || defined(UA_ARCHITECTURE_WIN32)
|
||||
|
||||
#ifdef __linux__
|
||||
#define EVENT_SIZE (sizeof(struct inotify_event))
|
||||
#define BUF_LEN (1024 * ( EVENT_SIZE + 16 ))
|
||||
#endif /* __linux__ */
|
||||
|
||||
typedef struct {
|
||||
/* Memory cert store as a base */
|
||||
UA_CertificateGroup *store;
|
||||
|
||||
#ifdef __linux__
|
||||
int inotifyFd;
|
||||
#endif /* __linux__ */
|
||||
|
||||
UA_String trustedCertFolder;
|
||||
UA_String trustedCrlFolder;
|
||||
@ -49,13 +41,13 @@ typedef struct {
|
||||
} FileCertStore;
|
||||
|
||||
static int
|
||||
mkpath(char *dir, mode_t mode) {
|
||||
struct stat sb;
|
||||
mkpath(char *dir, UA_MODE mode) {
|
||||
struct UA_STAT sb;
|
||||
|
||||
if(dir == NULL)
|
||||
return 1;
|
||||
|
||||
if(!stat(dir, &sb))
|
||||
if(!UA_stat(dir, &sb))
|
||||
return 0; /* Directory already exist */
|
||||
|
||||
size_t len = strlen(dir) + 1;
|
||||
@ -66,10 +58,10 @@ mkpath(char *dir, mode_t mode) {
|
||||
|
||||
/* Before the actual target directory is created, the recursive call ensures
|
||||
* that all parent directories are created or already exist. */
|
||||
mkpath(dirname(tmp_dir), mode);
|
||||
mkpath(UA_dirname(tmp_dir), mode);
|
||||
UA_free(tmp_dir);
|
||||
|
||||
return mkdir(dir, mode);
|
||||
return UA_mkdir(dir, mode);
|
||||
}
|
||||
|
||||
static UA_StatusCode
|
||||
@ -81,22 +73,23 @@ removeAllFilesFromDir(const char *const path, bool removeSubDirs) {
|
||||
return UA_STATUSCODE_BADINTERNALERROR;
|
||||
|
||||
/* remove all regular files from directory */
|
||||
DIR *dir = opendir(path);
|
||||
UA_DIR *dir = UA_opendir(path);
|
||||
if(!dir)
|
||||
return UA_STATUSCODE_BADINTERNALERROR;
|
||||
|
||||
struct dirent *dirent;
|
||||
while((dirent = readdir(dir)) != NULL) {
|
||||
if(dirent->d_type == DT_REG) {
|
||||
char file_name[FILENAME_MAX];
|
||||
mp_snprintf(file_name, FILENAME_MAX, "%s/%s", path, (char*)dirent->d_name);
|
||||
remove(file_name);
|
||||
struct UA_DIRENT *dirent;
|
||||
while((dirent = UA_readdir(dir)) != NULL) {
|
||||
if(dirent->d_type == UA_DT_REG) {
|
||||
char file_name[UA_FILENAME_MAX];
|
||||
mp_snprintf(file_name, UA_FILENAME_MAX, "%s/%s", path,
|
||||
(char *)dirent->d_name);
|
||||
UA_remove(file_name);
|
||||
}
|
||||
if(dirent->d_type == DT_DIR && removeSubDirs == true) {
|
||||
if(dirent->d_type == UA_DT_DIR && removeSubDirs == true) {
|
||||
char *directory = (char*)dirent->d_name;
|
||||
|
||||
char dir_name[FILENAME_MAX];
|
||||
mp_snprintf(dir_name, FILENAME_MAX, "%s/%s", path, (char*)dirent->d_name);
|
||||
char dir_name[UA_FILENAME_MAX];
|
||||
mp_snprintf(dir_name, UA_FILENAME_MAX, "%s/%s", path, (char *)dirent->d_name);
|
||||
|
||||
if(strlen(directory) == 1 && directory[0] == '.')
|
||||
continue;
|
||||
@ -106,7 +99,7 @@ removeAllFilesFromDir(const char *const path, bool removeSubDirs) {
|
||||
retval = removeAllFilesFromDir(dir_name, removeSubDirs);
|
||||
}
|
||||
}
|
||||
closedir(dir);
|
||||
UA_closedir(dir);
|
||||
|
||||
return retval;
|
||||
}
|
||||
@ -122,7 +115,7 @@ getCertFileName(const char *path, const UA_ByteString *certificate,
|
||||
|
||||
UA_String thumbprint = UA_STRING_NULL;
|
||||
thumbprint.length = 40;
|
||||
thumbprint.data = (UA_Byte*)UA_malloc(sizeof(UA_Byte)*thumbprint.length);
|
||||
thumbprint.data = (UA_Byte*)UA_calloc(thumbprint.length, sizeof(UA_Byte));
|
||||
|
||||
UA_String subjectName = UA_STRING_NULL;
|
||||
|
||||
@ -153,7 +146,8 @@ getCertFileName(const char *path, const UA_ByteString *certificate,
|
||||
subName = subjectNameBuffer;
|
||||
}
|
||||
|
||||
if(snprintf(fileNameBuf, fileNameLen, "%s/%s[%s]", path, subName, thumbprintBuffer) < 0)
|
||||
if(mp_snprintf(fileNameBuf, fileNameLen, "%s/%s[%s]", path, subName,
|
||||
thumbprintBuffer) < 0)
|
||||
retval = UA_STATUSCODE_BADINTERNALERROR;
|
||||
|
||||
UA_String_clear(&thumbprint);
|
||||
@ -168,54 +162,54 @@ static UA_StatusCode
|
||||
readCertificates(UA_ByteString **list, size_t *listSize, const UA_String path) {
|
||||
UA_StatusCode retval = UA_STATUSCODE_GOOD;
|
||||
|
||||
char listPath[PATH_MAX] = {0};
|
||||
mp_snprintf(listPath, PATH_MAX, "%.*s",
|
||||
char listPath[UA_PATH_MAX] = {0};
|
||||
mp_snprintf(listPath, UA_PATH_MAX, "%.*s",
|
||||
(int)path.length, (char*)path.data);
|
||||
|
||||
/* Determine number of certificates */
|
||||
size_t numCerts = 0;
|
||||
DIR *dir = opendir(listPath);
|
||||
UA_DIR *dir = UA_opendir(listPath);
|
||||
if(!dir)
|
||||
return UA_STATUSCODE_BADINTERNALERROR;
|
||||
|
||||
struct dirent *dirent;
|
||||
while((dirent = readdir(dir)) != NULL) {
|
||||
if(dirent->d_type != DT_REG)
|
||||
struct UA_DIRENT *dirent;
|
||||
while((dirent = UA_readdir(dir)) != NULL) {
|
||||
if(dirent->d_type != UA_DT_REG)
|
||||
continue;
|
||||
numCerts++;
|
||||
}
|
||||
|
||||
retval = UA_Array_resize((void **)list, listSize, numCerts, &UA_TYPES[UA_TYPES_BYTESTRING]);
|
||||
if(retval != UA_STATUSCODE_GOOD) {
|
||||
closedir(dir);
|
||||
UA_closedir(dir);
|
||||
return retval;
|
||||
}
|
||||
|
||||
/* Read files from directory */
|
||||
size_t numActCerts = 0;
|
||||
rewinddir(dir);
|
||||
UA_rewinddir(dir);
|
||||
|
||||
while((dirent = readdir(dir)) != NULL) {
|
||||
if(dirent->d_type != DT_REG)
|
||||
while((dirent = UA_readdir(dir)) != NULL) {
|
||||
if(dirent->d_type != UA_DT_REG)
|
||||
continue;
|
||||
if(numActCerts < numCerts) {
|
||||
/* Create filename to load */
|
||||
char filename[PATH_MAX];
|
||||
if(mp_snprintf(filename, PATH_MAX, "%s/%s", listPath, dirent->d_name) < 0) {
|
||||
closedir(dir);
|
||||
char filename[UA_PATH_MAX] = {0};
|
||||
if(mp_snprintf(filename, UA_PATH_MAX, "%s/%s", listPath, dirent->d_name) < 0) {
|
||||
UA_closedir(dir);
|
||||
return UA_STATUSCODE_BADINTERNALERROR;
|
||||
}
|
||||
|
||||
/* Load data from file */
|
||||
retval = readFileToByteString(filename, &((*list)[numActCerts]));
|
||||
if(retval != UA_STATUSCODE_GOOD) {
|
||||
closedir(dir);
|
||||
UA_closedir(dir);
|
||||
return retval;
|
||||
}
|
||||
}
|
||||
numActCerts++;
|
||||
}
|
||||
closedir(dir);
|
||||
UA_closedir(dir);
|
||||
|
||||
return retval;
|
||||
}
|
||||
@ -265,12 +259,17 @@ reloadTrustStore(UA_CertificateGroup *certGroup) {
|
||||
if(certGroup == NULL)
|
||||
return UA_STATUSCODE_BADINTERNALERROR;
|
||||
|
||||
#ifdef __linux__
|
||||
FileCertStore *context = (FileCertStore *)certGroup->context;
|
||||
|
||||
char buffer[BUF_LEN];
|
||||
const int length = read(context->inotifyFd, buffer, BUF_LEN );
|
||||
if(length == -1 && errno != EAGAIN)
|
||||
return UA_STATUSCODE_BADINTERNALERROR;
|
||||
#else
|
||||
/* TODO: Implement a way to check for changes in the pki folder */
|
||||
const int length = 0;
|
||||
#endif /* __linux__ */
|
||||
|
||||
/* No events, which means no changes to the pki folder */
|
||||
/* If the nonblocking read() found no events to read, then
|
||||
@ -294,8 +293,8 @@ writeCertificates(UA_CertificateGroup *certGroup, const UA_ByteString *list,
|
||||
UA_StatusCode retval = UA_STATUSCODE_GOOD;
|
||||
for(size_t i = 0; i < listSize; i++) {
|
||||
/* Create filename to load */
|
||||
char filename[PATH_MAX];
|
||||
retval = getCertFileName(listPath, &list[i], filename, PATH_MAX);
|
||||
char filename[UA_PATH_MAX] = {0};
|
||||
retval = getCertFileName(listPath, &list[i], filename, UA_PATH_MAX);
|
||||
if(retval != UA_STATUSCODE_GOOD)
|
||||
return UA_STATUSCODE_BADINTERNALERROR;
|
||||
|
||||
@ -317,8 +316,8 @@ writeTrustList(UA_CertificateGroup *certGroup, const UA_ByteString *list,
|
||||
if(listSize > 0 && list == NULL)
|
||||
return UA_STATUSCODE_BADINTERNALERROR;
|
||||
|
||||
char listPath[PATH_MAX] = {0};
|
||||
mp_snprintf(listPath, PATH_MAX, "%.*s", (int)path.length, (char*)path.data);
|
||||
char listPath[UA_PATH_MAX] = {0};
|
||||
mp_snprintf(listPath, UA_PATH_MAX, "%.*s", (int)path.length, (char *)path.data);
|
||||
/* remove existing files in directory */
|
||||
UA_StatusCode retval = removeAllFilesFromDir(listPath, false);
|
||||
if(retval != UA_STATUSCODE_GOOD)
|
||||
@ -374,13 +373,13 @@ writeTrustStore(UA_CertificateGroup *certGroup, const UA_UInt32 trustListMask) {
|
||||
static UA_StatusCode
|
||||
FileCertStore_setupStorePath(char *directory, char *rootDirectory,
|
||||
size_t rootDirectorySize, UA_String *out) {
|
||||
char path[PATH_MAX] = {0};
|
||||
char path[UA_PATH_MAX] = {0};
|
||||
size_t pathSize = 0;
|
||||
|
||||
strncpy(path, rootDirectory, PATH_MAX);
|
||||
pathSize = strnlen(path, PATH_MAX);
|
||||
strncpy(path, rootDirectory, UA_PATH_MAX);
|
||||
pathSize = strnlen(path, UA_PATH_MAX);
|
||||
|
||||
strncpy(&path[pathSize], directory, PATH_MAX - pathSize);
|
||||
strncpy(&path[pathSize], directory, UA_PATH_MAX - pathSize);
|
||||
|
||||
*out = UA_STRING_ALLOC(path);
|
||||
|
||||
@ -397,14 +396,14 @@ FileCertStore_createPkiDirectory(UA_CertificateGroup *certGroup, const UA_String
|
||||
if(!context)
|
||||
return UA_STATUSCODE_BADINTERNALERROR;
|
||||
|
||||
char rootDirectory[PATH_MAX] = {0};
|
||||
char rootDirectory[UA_PATH_MAX] = {0};
|
||||
size_t rootDirectorySize = 0;
|
||||
|
||||
if(directory.length <= 0 || directory.length >= PATH_MAX)
|
||||
if(directory.length <= 0 || directory.length >= UA_PATH_MAX)
|
||||
return UA_STATUSCODE_BADINTERNALERROR;
|
||||
|
||||
memcpy(rootDirectory, directory.data, directory.length);
|
||||
rootDirectorySize = strnlen(rootDirectory, PATH_MAX);
|
||||
rootDirectorySize = strnlen(rootDirectory, UA_PATH_MAX);
|
||||
|
||||
/* Add Certificate Group Id */
|
||||
UA_NodeId applCertGroup =
|
||||
@ -415,19 +414,19 @@ FileCertStore_createPkiDirectory(UA_CertificateGroup *certGroup, const UA_String
|
||||
UA_NODEID_NUMERIC(0, UA_NS0ID_SERVERCONFIGURATION_CERTIFICATEGROUPS_DEFAULTUSERTOKENGROUP);
|
||||
|
||||
if(UA_NodeId_equal(&certGroup->certificateGroupId, &applCertGroup)) {
|
||||
strncpy(&rootDirectory[rootDirectorySize], "/ApplCerts", PATH_MAX - rootDirectorySize);
|
||||
strncpy(&rootDirectory[rootDirectorySize], "/ApplCerts", UA_PATH_MAX - rootDirectorySize);
|
||||
} else if(UA_NodeId_equal(&certGroup->certificateGroupId, &httpCertGroup)) {
|
||||
strncpy(&rootDirectory[rootDirectorySize], "/HttpCerts", PATH_MAX - rootDirectorySize);
|
||||
strncpy(&rootDirectory[rootDirectorySize], "/HttpCerts", UA_PATH_MAX - rootDirectorySize);
|
||||
} else if(UA_NodeId_equal(&certGroup->certificateGroupId, &userTokenCertGroup)) {
|
||||
strncpy(&rootDirectory[rootDirectorySize], "/UserTokenCerts", PATH_MAX - rootDirectorySize);
|
||||
strncpy(&rootDirectory[rootDirectorySize], "/UserTokenCerts", UA_PATH_MAX - rootDirectorySize);
|
||||
} else {
|
||||
UA_String nodeIdStr;
|
||||
UA_String_init(&nodeIdStr);
|
||||
UA_NodeId_print(&certGroup->certificateGroupId, &nodeIdStr);
|
||||
strncpy(&rootDirectory[rootDirectorySize], (char*)nodeIdStr.data, PATH_MAX - rootDirectorySize);
|
||||
strncpy(&rootDirectory[rootDirectorySize], (char *)nodeIdStr.data, UA_PATH_MAX - rootDirectorySize);
|
||||
UA_String_clear(&nodeIdStr);
|
||||
}
|
||||
rootDirectorySize = strnlen(rootDirectory, PATH_MAX);
|
||||
rootDirectorySize = strnlen(rootDirectory, UA_PATH_MAX);
|
||||
|
||||
context->rootFolder = UA_STRING_ALLOC(rootDirectory);
|
||||
|
||||
@ -450,6 +449,8 @@ FileCertStore_createPkiDirectory(UA_CertificateGroup *certGroup, const UA_String
|
||||
return retval;
|
||||
}
|
||||
|
||||
#ifdef __linux__
|
||||
|
||||
static UA_StatusCode
|
||||
FileCertStore_createInotifyEvent(UA_CertificateGroup *certGroup) {
|
||||
if(certGroup == NULL)
|
||||
@ -461,10 +462,19 @@ FileCertStore_createInotifyEvent(UA_CertificateGroup *certGroup) {
|
||||
if(context->inotifyFd == -1)
|
||||
return UA_STATUSCODE_BADINTERNALERROR;
|
||||
|
||||
char rootFolder[PATH_MAX] = {0};
|
||||
mp_snprintf(rootFolder, PATH_MAX, "%.*s",
|
||||
char folder[UA_PATH_MAX] = {0};
|
||||
mp_snprintf(folder, UA_PATH_MAX, "%.*s",
|
||||
(int)context->rootFolder.length, (char*)context->rootFolder.data);
|
||||
int wd = inotify_add_watch(context->inotifyFd, rootFolder, IN_ALL_EVENTS);
|
||||
int wd = inotify_add_watch(context->inotifyFd, folder, IN_ALL_EVENTS);
|
||||
if(wd == -1) {
|
||||
close(context->inotifyFd);
|
||||
context->inotifyFd = -1;
|
||||
return UA_STATUSCODE_BADINTERNALERROR;
|
||||
}
|
||||
|
||||
mp_snprintf(folder, UA_PATH_MAX, "%.*s",
|
||||
(int)context->trustedCertFolder.length, (char*)context->trustedCertFolder.data);
|
||||
wd = inotify_add_watch(context->inotifyFd, folder, IN_ALL_EVENTS);
|
||||
if(wd == -1) {
|
||||
close(context->inotifyFd);
|
||||
context->inotifyFd = -1;
|
||||
@ -474,6 +484,8 @@ FileCertStore_createInotifyEvent(UA_CertificateGroup *certGroup) {
|
||||
return UA_STATUSCODE_GOOD;
|
||||
}
|
||||
|
||||
#endif /* __linux__ */
|
||||
|
||||
static UA_StatusCode
|
||||
FileCertStore_getTrustList(UA_CertificateGroup *certGroup, UA_TrustListDataType *trustList) {
|
||||
/* Check parameter */
|
||||
@ -625,8 +637,10 @@ FileCertStore_clear(UA_CertificateGroup *certGroup) {
|
||||
UA_String_clear(&context->ownKeyFolder);
|
||||
UA_String_clear(&context->rootFolder);
|
||||
|
||||
#ifdef __linux__
|
||||
if(context->inotifyFd > 0)
|
||||
close(context->inotifyFd);
|
||||
#endif /* __linux__ */
|
||||
|
||||
UA_free(context);
|
||||
certGroup->context = NULL;
|
||||
@ -679,10 +693,12 @@ UA_CertificateGroup_Filestore(UA_CertificateGroup *certGroup,
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
#ifdef __linux__
|
||||
retval = FileCertStore_createInotifyEvent(certGroup);
|
||||
if(retval != UA_STATUSCODE_GOOD) {
|
||||
goto cleanup;
|
||||
}
|
||||
#endif /* __linux__ */
|
||||
|
||||
retval = reloadAndWriteTrustStore(certGroup);
|
||||
if(retval != UA_STATUSCODE_GOOD) {
|
||||
@ -696,6 +712,6 @@ UA_CertificateGroup_Filestore(UA_CertificateGroup *certGroup,
|
||||
return retval;
|
||||
}
|
||||
|
||||
#endif
|
||||
#endif /* defined(__linux__) || defined(UA_ARCHITECTURE_WIN32) */
|
||||
|
||||
#endif
|
||||
#endif /* UA_ENABLE_ENCRYPTION */
|
||||
|
@ -40,7 +40,8 @@ void UA_CertificateGroup_AcceptAll(UA_CertificateGroup *certGroup) {
|
||||
UA_StatusCode
|
||||
UA_CertificateUtils_verifyApplicationURI(UA_RuleHandling ruleHandling,
|
||||
const UA_ByteString *certificate,
|
||||
const UA_String *applicationURI){
|
||||
const UA_String *applicationURI,
|
||||
UA_Logger *logger) {
|
||||
return UA_STATUSCODE_GOOD;
|
||||
}
|
||||
|
||||
|
@ -6,11 +6,21 @@
|
||||
*/
|
||||
|
||||
#include "ua_filestore_common.h"
|
||||
#include <stdio.h>
|
||||
|
||||
#ifdef UA_ENABLE_ENCRYPTION
|
||||
|
||||
#ifdef __linux__ /* Linux only so far */
|
||||
#if defined(__linux__) || defined(UA_ARCHITECTURE_WIN32)
|
||||
|
||||
#ifdef UA_ARCHITECTURE_WIN32
|
||||
/* TODO: Replace with a proper dirname implementation. This is a just minimal
|
||||
* implementation working with correct input data. */
|
||||
char *
|
||||
_UA_dirname_minimal(char *path) {
|
||||
char *lastSlash = strrchr(path, '/');
|
||||
*lastSlash = 0;
|
||||
return path;
|
||||
}
|
||||
#endif /* UA_ARCHITECTURE_WIN32 */
|
||||
|
||||
UA_StatusCode
|
||||
readFileToByteString(const char *const path, UA_ByteString *data) {
|
||||
@ -18,23 +28,23 @@ readFileToByteString(const char *const path, UA_ByteString *data) {
|
||||
return UA_STATUSCODE_BADINTERNALERROR;
|
||||
|
||||
/* Open the file */
|
||||
FILE *fp = fopen(path, "rb");
|
||||
UA_FILE *fp = UA_fopen(path, "rb");
|
||||
if(!fp)
|
||||
return UA_STATUSCODE_BADNOTFOUND;
|
||||
|
||||
/* Get the file length, allocate the data and read */
|
||||
fseek(fp, 0, SEEK_END);
|
||||
UA_StatusCode retval = UA_ByteString_allocBuffer(data, (size_t)ftell(fp));
|
||||
UA_fseek(fp, 0, UA_SEEK_END);
|
||||
UA_StatusCode retval = UA_ByteString_allocBuffer(data, (size_t)UA_ftell(fp));
|
||||
if(retval == UA_STATUSCODE_GOOD) {
|
||||
fseek(fp, 0, SEEK_SET);
|
||||
size_t read = fread(data->data, sizeof(UA_Byte), data->length * sizeof(UA_Byte), fp);
|
||||
UA_fseek(fp, 0, UA_SEEK_SET);
|
||||
size_t read = UA_fread(data->data, sizeof(UA_Byte), data->length * sizeof(UA_Byte), fp);
|
||||
if(read != data->length) {
|
||||
UA_ByteString_clear(data);
|
||||
}
|
||||
} else {
|
||||
data->length = 0;
|
||||
}
|
||||
fclose(fp);
|
||||
UA_fclose(fp);
|
||||
|
||||
return UA_STATUSCODE_GOOD;
|
||||
}
|
||||
@ -44,21 +54,21 @@ writeByteStringToFile(const char *const path, const UA_ByteString *data) {
|
||||
UA_StatusCode retval = UA_STATUSCODE_GOOD;
|
||||
|
||||
/* Open the file */
|
||||
FILE *fp = fopen(path, "wb");
|
||||
UA_FILE *fp = UA_fopen(path, "wb");
|
||||
if(!fp)
|
||||
return UA_STATUSCODE_BADINTERNALERROR;
|
||||
|
||||
/* Write byte string to file */
|
||||
size_t len = fwrite(data->data, sizeof(UA_Byte), data->length * sizeof(UA_Byte), fp);
|
||||
size_t len = UA_fwrite(data->data, sizeof(UA_Byte), data->length * sizeof(UA_Byte), fp);
|
||||
if(len != data->length) {
|
||||
fclose(fp);
|
||||
UA_fclose(fp);
|
||||
retval = UA_STATUSCODE_BADINTERNALERROR;
|
||||
}
|
||||
|
||||
fclose(fp);
|
||||
UA_fclose(fp);
|
||||
return retval;
|
||||
}
|
||||
|
||||
#endif
|
||||
#endif /* defined(__linux__) || defined(UA_ARCHITECTURE_WIN32) */
|
||||
|
||||
#endif
|
||||
#endif /* UA_ENABLE_ENCRYPTION */
|
||||
|
@ -5,11 +5,102 @@
|
||||
* Copyright 2024 (c) Fraunhofer IOSB (Author: Noel Graf)
|
||||
*/
|
||||
|
||||
#ifndef UA_FILESTORE_COMMON_H_
|
||||
#define UA_FILESTORE_COMMON_H_
|
||||
|
||||
#include <open62541/util.h>
|
||||
|
||||
#ifdef UA_ENABLE_ENCRYPTION
|
||||
|
||||
#ifdef __linux__ /* Linux only so far */
|
||||
#if defined(__linux__) || defined(UA_ARCHITECTURE_WIN32)
|
||||
|
||||
#if defined(UA_ARCHITECTURE_WIN32)
|
||||
|
||||
#include <direct.h>
|
||||
#include <minwindef.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <sys/stat.h>
|
||||
#include "tr_dirent.h"
|
||||
|
||||
_UA_BEGIN_DECLS
|
||||
char *
|
||||
_UA_dirname_minimal(char *path);
|
||||
_UA_END_DECLS
|
||||
|
||||
#define UA_STAT stat
|
||||
#define UA_DIR DIR
|
||||
#define UA_DIRENT dirent
|
||||
#define UA_FILE FILE
|
||||
#define UA_MODE uint16_t
|
||||
|
||||
#define UA_stat stat
|
||||
#define UA_opendir opendir
|
||||
#define UA_readdir readdir
|
||||
#define UA_rewinddir rewinddir
|
||||
#define UA_closedir closedir
|
||||
#define UA_mkdir(path, mode) _mkdir(path)
|
||||
#define UA_fopen fopen
|
||||
#define UA_fread fread
|
||||
#define UA_fwrite fwrite
|
||||
#define UA_fseek fseek
|
||||
#define UA_ftell ftell
|
||||
#define UA_fclose fclose
|
||||
#define UA_remove remove
|
||||
#define UA_dirname _UA_dirname_minimal
|
||||
|
||||
#define UA_SEEK_END SEEK_END
|
||||
#define UA_SEEK_SET SEEK_SET
|
||||
#define UA_DT_REG DT_REG
|
||||
#define UA_DT_DIR DT_DIR
|
||||
#define UA_PATH_MAX MAX_PATH
|
||||
#define UA_FILENAME_MAX FILENAME_MAX
|
||||
|
||||
#elif defined(__linux__)
|
||||
|
||||
#include <dirent.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <libgen.h>
|
||||
#include <linux/limits.h>
|
||||
#include <stdio.h>
|
||||
#include <sys/inotify.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#ifndef __ANDROID__
|
||||
#include <bits/stdio_lim.h>
|
||||
#endif /* !__ANDROID__ */
|
||||
|
||||
#define UA_STAT stat
|
||||
#define UA_DIR DIR
|
||||
#define UA_DIRENT dirent
|
||||
#define UA_FILE FILE
|
||||
#define UA_MODE mode_t
|
||||
|
||||
#define UA_stat stat
|
||||
#define UA_opendir opendir
|
||||
#define UA_readdir readdir
|
||||
#define UA_rewinddir rewinddir
|
||||
#define UA_closedir closedir
|
||||
#define UA_mkdir mkdir
|
||||
#define UA_fopen fopen
|
||||
#define UA_fread fread
|
||||
#define UA_fwrite fwrite
|
||||
#define UA_fseek fseek
|
||||
#define UA_ftell ftell
|
||||
#define UA_fclose fclose
|
||||
#define UA_remove remove
|
||||
#define UA_dirname dirname
|
||||
|
||||
#define UA_SEEK_END SEEK_END
|
||||
#define UA_SEEK_SET SEEK_SET
|
||||
#define UA_DT_REG DT_REG
|
||||
#define UA_DT_DIR DT_DIR
|
||||
#define UA_PATH_MAX PATH_MAX
|
||||
#define UA_FILENAME_MAX FILENAME_MAX
|
||||
|
||||
#endif
|
||||
|
||||
UA_StatusCode
|
||||
readFileToByteString(const char *const path,
|
||||
@ -19,6 +110,8 @@ UA_StatusCode
|
||||
writeByteStringToFile(const char *const path,
|
||||
const UA_ByteString *data);
|
||||
|
||||
#endif /* __linux__ */
|
||||
#endif /* __linux__ || UA_ARCHITECTURE_WIN32 */
|
||||
|
||||
#endif /* UA_ENABLE_ENCRYPTION */
|
||||
|
||||
#endif /* UA_FILESTORE_COMMON_H_ */
|
||||
|
@ -14,14 +14,7 @@
|
||||
|
||||
#ifdef UA_ENABLE_ENCRYPTION
|
||||
|
||||
#ifdef __linux__ /* Linux only so far */
|
||||
|
||||
#include <stdio.h>
|
||||
#include <dirent.h>
|
||||
|
||||
#ifndef __ANDROID__
|
||||
#include <bits/stdio_lim.h>
|
||||
#endif // !__ANDROID__
|
||||
#if defined(__linux__) || defined(UA_ARCHITECTURE_WIN32)
|
||||
|
||||
typedef struct {
|
||||
/* In-Memory security policy as a base */
|
||||
@ -35,18 +28,18 @@ checkCertificateInFilestore(char *path, const UA_ByteString newCertificate) {
|
||||
UA_StatusCode retval = UA_STATUSCODE_GOOD;
|
||||
bool isStored = false;
|
||||
UA_ByteString fileData = UA_BYTESTRING_NULL;
|
||||
DIR *dir = opendir(path);
|
||||
UA_DIR *dir = UA_opendir(path);
|
||||
if(!dir)
|
||||
return UA_STATUSCODE_BADINTERNALERROR;
|
||||
|
||||
struct dirent *dirent;
|
||||
while((dirent = readdir(dir)) != NULL) {
|
||||
if(dirent->d_type != DT_REG)
|
||||
struct UA_DIRENT *dirent;
|
||||
while((dirent = UA_readdir(dir)) != NULL) {
|
||||
if(dirent->d_type != UA_DT_REG)
|
||||
continue;
|
||||
|
||||
/* Get filename to load */
|
||||
char filename[FILENAME_MAX];
|
||||
if(mp_snprintf(filename, FILENAME_MAX, "%s/%s", path, dirent->d_name) < 0)
|
||||
char filename[UA_FILENAME_MAX];
|
||||
if(mp_snprintf(filename, UA_FILENAME_MAX, "%s/%s", path, dirent->d_name) < 0)
|
||||
return false;
|
||||
|
||||
/* Load data from file */
|
||||
@ -66,7 +59,7 @@ checkCertificateInFilestore(char *path, const UA_ByteString newCertificate) {
|
||||
cleanup:
|
||||
if(fileData.length > 0)
|
||||
UA_ByteString_clear(&fileData);
|
||||
closedir(dir);
|
||||
UA_closedir(dir);
|
||||
|
||||
return isStored;
|
||||
}
|
||||
@ -81,7 +74,7 @@ createCertName(const UA_ByteString *certificate, char *fileNameBuf, size_t fileN
|
||||
|
||||
UA_String thumbprint = UA_STRING_NULL;
|
||||
thumbprint.length = 40;
|
||||
thumbprint.data = (UA_Byte*)UA_malloc(sizeof(UA_Byte)*thumbprint.length);
|
||||
thumbprint.data = (UA_Byte*)UA_calloc(thumbprint.length, sizeof(UA_Byte));
|
||||
|
||||
UA_String subjectName = UA_STRING_NULL;
|
||||
|
||||
@ -129,13 +122,13 @@ writeCertificateAndPrivateKeyToFilestore(const UA_String storePath,
|
||||
UA_StatusCode retval = UA_STATUSCODE_GOOD;
|
||||
|
||||
/* Create the paths to the certificates and private key folders */
|
||||
char ownCertPathDir[PATH_MAX];
|
||||
if(mp_snprintf(ownCertPathDir, PATH_MAX, "%.*s%s", (int)storePath.length,
|
||||
char ownCertPathDir[UA_PATH_MAX];
|
||||
if(mp_snprintf(ownCertPathDir, UA_PATH_MAX, "%.*s%s", (int)storePath.length,
|
||||
(char *)storePath.data, "/ApplCerts/own/certs") < 0)
|
||||
return UA_STATUSCODE_BADINTERNALERROR;
|
||||
|
||||
char ownKeyPathDir[PATH_MAX];
|
||||
if(mp_snprintf(ownKeyPathDir, PATH_MAX, "%.*s%s", (int)storePath.length,
|
||||
char ownKeyPathDir[UA_PATH_MAX];
|
||||
if(mp_snprintf(ownKeyPathDir, UA_PATH_MAX, "%.*s%s", (int)storePath.length,
|
||||
(char *)storePath.data, "/ApplCerts/own/private") < 0)
|
||||
return UA_STATUSCODE_BADINTERNALERROR;
|
||||
|
||||
@ -144,24 +137,28 @@ writeCertificateAndPrivateKeyToFilestore(const UA_String storePath,
|
||||
return UA_STATUSCODE_GOOD;
|
||||
|
||||
/* Create filename for new certificate */
|
||||
char newFilename[PATH_MAX];
|
||||
retval = createCertName(&newCertificate, newFilename, PATH_MAX);
|
||||
char newFilename[UA_PATH_MAX];
|
||||
retval = createCertName(&newCertificate, newFilename, UA_PATH_MAX);
|
||||
if(retval != UA_STATUSCODE_GOOD)
|
||||
return retval;
|
||||
|
||||
char newCertFilename[PATH_MAX];
|
||||
if(mp_snprintf(newCertFilename, PATH_MAX, "%s/%s%s", ownCertPathDir, newFilename, ".der") < 0)
|
||||
char newCertFilename[UA_PATH_MAX];
|
||||
if(mp_snprintf(newCertFilename, UA_PATH_MAX, "%s/%s%s", ownCertPathDir, newFilename, ".der") < 0)
|
||||
return UA_STATUSCODE_BADINTERNALERROR;
|
||||
|
||||
char newKeyFilename[PATH_MAX];
|
||||
if(mp_snprintf(newKeyFilename, PATH_MAX, "%s/%s%s", ownKeyPathDir, newFilename, ".key") < 0)
|
||||
char newKeyFilename[UA_PATH_MAX];
|
||||
if(mp_snprintf(newKeyFilename, UA_PATH_MAX, "%s/%s%s", ownKeyPathDir, newFilename, ".key") < 0)
|
||||
return UA_STATUSCODE_BADINTERNALERROR;
|
||||
|
||||
retval = writeByteStringToFile(newCertFilename, &newCertificate);
|
||||
if(retval != UA_STATUSCODE_GOOD)
|
||||
return retval;
|
||||
|
||||
return writeByteStringToFile(newKeyFilename, &newPrivateKey);
|
||||
/* Write new private key only if it is set */
|
||||
if(newPrivateKey.length > 0)
|
||||
return writeByteStringToFile(newKeyFilename, &newPrivateKey);
|
||||
else
|
||||
return UA_STATUSCODE_GOOD;
|
||||
}
|
||||
|
||||
/********************/
|
||||
@ -347,6 +344,6 @@ UA_SecurityPolicy_Filestore(UA_SecurityPolicy *policy,
|
||||
return retval;
|
||||
}
|
||||
|
||||
#endif
|
||||
#endif /* defined(__linux__) || defined(UA_ARCHITECTURE_WIN32) */
|
||||
|
||||
#endif
|
||||
#endif /* UA_ENABLE_ENCRYPTION */
|
||||
|
@ -39,7 +39,7 @@ UA_CertificateGroup_Memorystore(UA_CertificateGroup *certGroup,
|
||||
const UA_Logger *logger,
|
||||
const UA_KeyValueMap *params);
|
||||
|
||||
#ifdef __linux__ /* Linux only so far */
|
||||
#if defined(__linux__) || defined(UA_ARCHITECTURE_WIN32)
|
||||
/*
|
||||
* Initialises and configures a certificate group with a filestore backend.
|
||||
*
|
||||
@ -92,9 +92,9 @@ UA_CertificateGroup_Filestore(UA_CertificateGroup *certGroup,
|
||||
const UA_String storePath,
|
||||
const UA_Logger *logger,
|
||||
const UA_KeyValueMap *params);
|
||||
#endif
|
||||
#endif /* defined(__linux__) || defined(UA_ARCHITECTURE_WIN32) */
|
||||
|
||||
#endif
|
||||
#endif /* UA_ENABLE_ENCRYPTION */
|
||||
|
||||
_UA_END_DECLS
|
||||
|
||||
|
@ -58,14 +58,14 @@ UA_SecurityPolicy_EccNistP256(UA_SecurityPolicy *policy,
|
||||
const UA_ByteString localPrivateKey,
|
||||
const UA_Logger *logger);
|
||||
|
||||
#ifdef __linux__ /* Linux only so far */
|
||||
#if defined(__linux__) || defined(UA_ARCHITECTURE_WIN32)
|
||||
UA_EXPORT UA_StatusCode
|
||||
UA_SecurityPolicy_Filestore(UA_SecurityPolicy *policy,
|
||||
UA_SecurityPolicy *innerPolicy,
|
||||
const UA_String storePath);
|
||||
#endif
|
||||
#endif /* defined(__linux__) || defined(UA_ARCHITECTURE_WIN32) */
|
||||
|
||||
#endif
|
||||
#endif /* UA_ENABLE_ENCRYPTION */
|
||||
|
||||
UA_EXPORT UA_StatusCode
|
||||
UA_PubSubSecurityPolicy_Aes128Ctr(UA_PubSubSecurityPolicy *policy,
|
||||
|
@ -86,7 +86,7 @@ UA_ServerConfig_setDefaultWithSecureSecurityPolicies(UA_ServerConfig *conf,
|
||||
const UA_ByteString *revocationList,
|
||||
size_t revocationListSize);
|
||||
|
||||
#ifdef __linux__ /* Linux only so far */
|
||||
#if defined(__linux__) || defined(UA_ARCHITECTURE_WIN32)
|
||||
|
||||
UA_EXPORT UA_StatusCode
|
||||
UA_ServerConfig_setDefaultWithFilestore(UA_ServerConfig *conf,
|
||||
@ -95,9 +95,9 @@ UA_ServerConfig_setDefaultWithFilestore(UA_ServerConfig *conf,
|
||||
const UA_ByteString *privateKey,
|
||||
const UA_String storePath);
|
||||
|
||||
#endif
|
||||
#endif /* defined(__linux__) || defined(UA_ARCHITECTURE_WIN32) */
|
||||
|
||||
#endif
|
||||
#endif /* UA_ENABLE_ENCRYPTION */
|
||||
|
||||
/* Creates a server config on the default port 4840 with no server
|
||||
* certificate. */
|
||||
@ -257,7 +257,7 @@ UA_ServerConfig_addAllSecureSecurityPolicies(UA_ServerConfig *config,
|
||||
const UA_ByteString *certificate,
|
||||
const UA_ByteString *privateKey);
|
||||
|
||||
#ifdef __linux__ /* Linux only so far */
|
||||
#if defined(__linux__) || defined(UA_ARCHITECTURE_WIN32)
|
||||
|
||||
/* Adds a filestore security policy based on a given security policy to the server.
|
||||
*
|
||||
@ -289,9 +289,9 @@ UA_ServerConfig_addSecurityPolicies_Filestore(UA_ServerConfig *config,
|
||||
const UA_ByteString *certificate,
|
||||
const UA_ByteString *privateKey,
|
||||
const UA_String storePath);
|
||||
#endif
|
||||
#endif /* defined(__linux__) || defined(UA_ARCHITECTURE_WIN32) */
|
||||
|
||||
#endif
|
||||
#endif /* UA_ENABLE_ENCRYPTION */
|
||||
|
||||
/* Adds an endpoint for the given security policy and mode. The security
|
||||
* policy has to be added already. See UA_ServerConfig_addXxx functions.
|
||||
|
@ -207,8 +207,7 @@ const UA_ConnectionConfig UA_ConnectionConfig_default = {
|
||||
#define PRODUCT_NAME "open62541 OPC UA Server"
|
||||
#define PRODUCT_URI "http://open62541.org"
|
||||
#define APPLICATION_NAME "open62541-based OPC UA Application"
|
||||
#define APPLICATION_URI "urn:unconfigured:application"
|
||||
#define APPLICATION_URI_SERVER "urn:open62541.server.application"
|
||||
#define APPLICATION_URI "urn:open62541.unconfigured.application"
|
||||
|
||||
#define SECURITY_POLICY_SIZE 7
|
||||
|
||||
@ -368,7 +367,7 @@ setDefaultConfig(UA_ServerConfig *conf, UA_UInt16 portNumber) {
|
||||
conf->buildInfo.buildDate = UA_DateTime_now();
|
||||
|
||||
UA_ApplicationDescription_clear(&conf->applicationDescription);
|
||||
conf->applicationDescription.applicationUri = UA_STRING_ALLOC(APPLICATION_URI_SERVER);
|
||||
conf->applicationDescription.applicationUri = UA_STRING_ALLOC(APPLICATION_URI);
|
||||
conf->applicationDescription.productUri = UA_STRING_ALLOC(PRODUCT_URI);
|
||||
conf->applicationDescription.applicationName =
|
||||
UA_LOCALIZEDTEXT_ALLOC("en", APPLICATION_NAME);
|
||||
@ -380,10 +379,12 @@ setDefaultConfig(UA_ServerConfig *conf, UA_UInt16 portNumber) {
|
||||
|
||||
#ifdef UA_ENABLE_DISCOVERY_MULTICAST
|
||||
UA_MdnsDiscoveryConfiguration_clear(&conf->mdnsConfig);
|
||||
# ifdef UA_ENABLE_DISCOVERY_MULTICAST_MDNSD
|
||||
conf->mdnsInterfaceIP = UA_STRING_NULL;
|
||||
# if !defined(UA_HAS_GETIFADDR)
|
||||
# if !defined(UA_HAS_GETIFADDR)
|
||||
conf->mdnsIpAddressList = NULL;
|
||||
conf->mdnsIpAddressListSize = 0;
|
||||
# endif
|
||||
# endif
|
||||
#endif
|
||||
|
||||
@ -1220,7 +1221,7 @@ UA_ServerConfig_setDefaultWithSecureSecurityPolicies(UA_ServerConfig *conf,
|
||||
return UA_STATUSCODE_GOOD;
|
||||
}
|
||||
|
||||
#ifdef __linux__ /* Linux only so far */
|
||||
#if defined(__linux__) || defined(UA_ARCHITECTURE_WIN32)
|
||||
|
||||
UA_StatusCode
|
||||
UA_ServerConfig_addSecurityPolicy_Filestore(UA_ServerConfig *config,
|
||||
@ -1579,9 +1580,9 @@ UA_ServerConfig_setDefaultWithFilestore(UA_ServerConfig *conf,
|
||||
return retval;
|
||||
}
|
||||
|
||||
#endif
|
||||
#endif /* defined(__linux__) || defined(UA_ARCHITECTURE_WIN32) */
|
||||
|
||||
#endif
|
||||
#endif /* UA_ENABLE_ENCRYPTION */
|
||||
|
||||
/***************************/
|
||||
/* Default Client Settings */
|
||||
|
@ -449,6 +449,7 @@ PARSE_JSON(MdnsConfigurationField) {
|
||||
parseJsonJumpTable[UA_SERVERCONFIGFIELD_STRING](ctx, &config->mdnsConfig.mdnsServerName, NULL);
|
||||
else if(strcmp(field_str, "serverCapabilities") == 0)
|
||||
parseJsonJumpTable[UA_SERVERCONFIGFIELD_STRINGARRAY](ctx, &config->mdnsConfig.serverCapabilities, &config->mdnsConfig.serverCapabilitiesSize);
|
||||
#ifdef UA_ENABLE_DISCOVERY_MULTICAST_MDNSD
|
||||
else if(strcmp(field_str, "mdnsInterfaceIP") == 0)
|
||||
parseJsonJumpTable[UA_SERVERCONFIGFIELD_STRING](ctx, &config->mdnsInterfaceIP, NULL);
|
||||
/* mdnsIpAddressList and mdnsIpAddressListSize are only available if UA_HAS_GETIFADDR is not defined: */
|
||||
@ -456,6 +457,7 @@ PARSE_JSON(MdnsConfigurationField) {
|
||||
else if(strcmp(field_str, "mdnsIpAddressList") == 0)
|
||||
parseJsonJumpTable[UA_SERVERCONFIGFIELD_UINT32ARRAY](ctx, &config->mdnsIpAddressList, &config->mdnsIpAddressListSize);
|
||||
# endif
|
||||
#endif
|
||||
else {
|
||||
UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "Unknown field name.");
|
||||
}
|
||||
@ -771,7 +773,7 @@ PARSE_JSON(SecurityPkiField) {
|
||||
if(retval != UA_STATUSCODE_GOOD)
|
||||
return retval;
|
||||
|
||||
#ifndef __linux__
|
||||
#if defined(__linux__) || defined(UA_ARCHITECTURE_WIN32)
|
||||
/* Currently not supported! */
|
||||
(void)config;
|
||||
return UA_STATUSCODE_GOOD;
|
||||
@ -957,10 +959,6 @@ parseJSONConfig(UA_ServerConfig *config, UA_ByteString json_config) {
|
||||
retval = parseJsonJumpTable[UA_SERVERCONFIGFIELD_BOOLEAN](&ctx, &config->mdnsEnabled, NULL);
|
||||
else if(strcmp(field, "mdns") == 0)
|
||||
retval = parseJsonJumpTable[UA_SERVERCONFIGFIELD_MDNSCONFIGURATION](&ctx, config, NULL);
|
||||
#if !defined(UA_HAS_GETIFADDR)
|
||||
else if(strcmp(field, "mdnsIpAddressList") == 0)
|
||||
retval = parseJsonJumpTable[UA_SERVERCONFIGFIELD_UINT32ARRAY](&ctx, &config->mdnsIpAddressList, &config->mdnsIpAddressListSize);
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
|
||||
|
@ -42,7 +42,7 @@ const char *logLevelNames[6] = {"trace", "debug",
|
||||
static const char *
|
||||
logCategoryNames[UA_LOGCATEGORIES] =
|
||||
{"network", "channel", "session", "server", "client",
|
||||
"userland", "securitypolicy", "eventloop", "pubsub", "discovery"};
|
||||
"userland", "security", "eventloop", "pubsub", "discovery"};
|
||||
|
||||
/* Protect crosstalk during logging via global lock.
|
||||
* Use a spinlock on non-POSIX as we cannot statically initialize a global lock. */
|
||||
|
@ -17,7 +17,7 @@ const char *syslogLevelNames[6] = {"trace", "debug", "info",
|
||||
"warn", "error", "fatal"};
|
||||
const char *syslogCategoryNames[UA_LOGCATEGORIES] =
|
||||
{"network", "channel", "session", "server", "client",
|
||||
"userland", "securitypolicy", "eventloop", "pubsub", "discovery"};
|
||||
"userland", "security", "eventloop", "pubsub", "discovery"};
|
||||
|
||||
#ifdef __clang__
|
||||
__attribute__((__format__(__printf__, 4 , 0)))
|
||||
|
@ -121,7 +121,27 @@ UA_Client_newWithConfig(const UA_ClientConfig *config) {
|
||||
UA_LOCK_INIT(&client->clientMutex);
|
||||
#endif
|
||||
|
||||
/* Initialize the namespace mapping */
|
||||
size_t initialNs = 2 + config->namespacesSize;
|
||||
client->namespaces = (UA_String*)UA_calloc(initialNs, sizeof(UA_String));
|
||||
if(!client->namespaces)
|
||||
goto error;
|
||||
client->namespacesSize = initialNs;
|
||||
client->namespaces[0] = UA_STRING_ALLOC("http://opcfoundation.org/UA/");
|
||||
client->namespaces[1] = UA_STRING_NULL; /* Gets set when we connect to the server */
|
||||
UA_StatusCode res = UA_STATUSCODE_GOOD;
|
||||
for(size_t i = 0; i < config->namespacesSize; i++) {
|
||||
res |= UA_String_copy(&client->namespaces[i+2], &config->namespaces[i]);
|
||||
}
|
||||
if(res != UA_STATUSCODE_GOOD)
|
||||
goto error;
|
||||
|
||||
return client;
|
||||
|
||||
error:
|
||||
memset(&client->config, 0, sizeof(UA_ClientConfig));
|
||||
UA_Client_delete(client);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void
|
||||
@ -226,8 +246,15 @@ UA_Client_clear(UA_Client *client) {
|
||||
UA_Client_removeCallback(client, client->houseKeepingCallbackId);
|
||||
client->houseKeepingCallbackId = 0;
|
||||
|
||||
/* Clean up the SecureChannel */
|
||||
UA_SecureChannel_clear(&client->channel);
|
||||
|
||||
/* Free the namespace mapping */
|
||||
UA_Array_delete(client->namespaces, client->namespacesSize,
|
||||
&UA_TYPES[UA_TYPES_STRING]);
|
||||
client->namespaces = NULL;
|
||||
client->namespacesSize = 0;
|
||||
|
||||
#if UA_MULTITHREADING >= 100
|
||||
UA_LOCK_DESTROY(&client->clientMutex);
|
||||
#endif
|
||||
@ -1168,3 +1195,48 @@ UA_Client_getConnectionAttribute_scalar(UA_Client *client,
|
||||
UA_UNLOCK(&client->clientMutex);
|
||||
return UA_STATUSCODE_GOOD;
|
||||
}
|
||||
|
||||
/* Namespace Mapping */
|
||||
|
||||
UA_StatusCode
|
||||
UA_Client_getNamespaceUri(UA_Client *client, UA_UInt16 index,
|
||||
UA_String *nsUri) {
|
||||
UA_LOCK(&client->clientMutex);
|
||||
UA_StatusCode res = UA_STATUSCODE_GOOD;
|
||||
if(index > client->namespacesSize)
|
||||
res = UA_String_copy(&client->namespaces[index], nsUri);
|
||||
else
|
||||
res = UA_STATUSCODE_BADNOTFOUND;
|
||||
UA_UNLOCK(&client->clientMutex);
|
||||
return res;
|
||||
}
|
||||
|
||||
UA_StatusCode
|
||||
UA_Client_getNamespaceIndex(UA_Client *client, const UA_String nsUri,
|
||||
UA_UInt16 *outIndex) {
|
||||
UA_LOCK(&client->clientMutex);
|
||||
for(size_t i = 0; i < client->namespacesSize; i++) {
|
||||
if(UA_String_equal(&nsUri, &client->namespaces[i])) {
|
||||
*outIndex = (UA_UInt16)i;
|
||||
UA_UNLOCK(&client->clientMutex);
|
||||
return UA_STATUSCODE_GOOD;
|
||||
}
|
||||
}
|
||||
UA_UNLOCK(&client->clientMutex);
|
||||
return UA_STATUSCODE_BADNOTFOUND;
|
||||
}
|
||||
|
||||
UA_StatusCode
|
||||
UA_Client_addNamespace(UA_Client *client, const UA_String nsUri,
|
||||
UA_UInt16 *outIndex) {
|
||||
UA_StatusCode res = UA_Client_getNamespaceIndex(client, nsUri, outIndex);
|
||||
if(res == UA_STATUSCODE_GOOD)
|
||||
return res;
|
||||
UA_LOCK(&client->clientMutex);
|
||||
res = UA_Array_appendCopy((void**)&client->namespaces, &client->namespacesSize,
|
||||
&nsUri, &UA_TYPES[UA_TYPES_STRING]);
|
||||
if(res == UA_STATUSCODE_GOOD)
|
||||
*outIndex = (UA_UInt16)(client->namespacesSize - 1);
|
||||
UA_UNLOCK(&client->clientMutex);
|
||||
return res;
|
||||
}
|
||||
|
@ -25,6 +25,7 @@
|
||||
* UA_Client and reconnect.
|
||||
* - Call GetEndpoints and select an Endpoint
|
||||
* - Open a SecureChannel and Session for that Endpoint
|
||||
* - Read the namespaces array of the server (and create the namespaces mapping)
|
||||
*/
|
||||
|
||||
#define UA_MINMESSAGESIZE 8192
|
||||
@ -429,7 +430,7 @@ sendHELMessage(UA_Client *client) {
|
||||
const UA_Byte *bufEnd = &message.data[message.length];
|
||||
client->connectStatus =
|
||||
UA_encodeBinaryInternal(&hello, &UA_TRANSPORT[UA_TRANSPORT_TCPHELLOMESSAGE],
|
||||
&bufPos, &bufEnd, NULL, NULL);
|
||||
&bufPos, &bufEnd, NULL, NULL, NULL);
|
||||
|
||||
/* Encode the message header at offset 0 */
|
||||
UA_TcpMessageHeader messageHeader;
|
||||
@ -438,7 +439,7 @@ sendHELMessage(UA_Client *client) {
|
||||
bufPos = message.data;
|
||||
retval = UA_encodeBinaryInternal(&messageHeader,
|
||||
&UA_TRANSPORT[UA_TRANSPORT_TCPMESSAGEHEADER],
|
||||
&bufPos, &bufEnd, NULL, NULL);
|
||||
&bufPos, &bufEnd, NULL, NULL, NULL);
|
||||
if(retval != UA_STATUSCODE_GOOD) {
|
||||
cm->freeNetworkBuffer(cm, client->channel.connectionId, &message);
|
||||
return retval;
|
||||
@ -649,6 +650,121 @@ UA_Client_renewSecureChannel(UA_Client *client) {
|
||||
return res;
|
||||
}
|
||||
|
||||
static void
|
||||
responseReadNamespacesArray(UA_Client *client, void *userdata, UA_UInt32 requestId,
|
||||
void *response) {
|
||||
client->namespacesHandshake = false;
|
||||
client->haveNamespaces = true;
|
||||
|
||||
UA_ReadResponse *resp = (UA_ReadResponse *)response;
|
||||
|
||||
/* Add received namespaces to the local array. */
|
||||
if(!resp->results || !resp->results[0].value.data) {
|
||||
UA_LOG_ERROR(client->config.logging, UA_LOGCATEGORY_CLIENT,
|
||||
"No result in the read namespace array response");
|
||||
return;
|
||||
}
|
||||
UA_String *ns = (UA_String *)resp->results[0].value.data;
|
||||
size_t nsSize = resp->results[0].value.arrayLength;
|
||||
UA_String_copy(&ns[1], &client->namespaces[1]);
|
||||
for(size_t i = 2; i < nsSize; ++i) {
|
||||
UA_UInt16 nsIndex = 0;
|
||||
UA_Client_addNamespace(client, ns[i], &nsIndex);
|
||||
}
|
||||
|
||||
/* Set up the mapping. */
|
||||
UA_NamespaceMapping *nsMapping = (UA_NamespaceMapping*)UA_calloc(1, sizeof(UA_NamespaceMapping));
|
||||
if(!nsMapping) {
|
||||
UA_LOG_ERROR(client->config.logging, UA_LOGCATEGORY_CLIENT,
|
||||
"Namespace mapping creation failed. Out of Memory.");
|
||||
return;
|
||||
}
|
||||
UA_StatusCode retval = UA_Array_copy(client->namespaces, client->namespacesSize, (void**)&nsMapping->namespaceUris, &UA_TYPES[UA_TYPES_STRING]);
|
||||
if(retval != UA_STATUSCODE_GOOD) {
|
||||
UA_LOG_ERROR(client->config.logging, UA_LOGCATEGORY_CLIENT,
|
||||
"Failed to copy the namespaces with StatusCode %s.",
|
||||
UA_StatusCode_name(retval));
|
||||
return;
|
||||
}
|
||||
nsMapping->namespaceUrisSize = client->namespacesSize;
|
||||
|
||||
nsMapping->remote2local = (UA_UInt16*)UA_calloc( nsSize, sizeof(UA_UInt16));
|
||||
if(!nsMapping->remote2local) {
|
||||
UA_LOG_ERROR(client->config.logging, UA_LOGCATEGORY_CLIENT,
|
||||
"Namespace mapping creation failed. Out of Memory.");
|
||||
return;
|
||||
}
|
||||
nsMapping->remote2localSize = nsSize;
|
||||
nsMapping->remote2local[0] = 0;
|
||||
nsMapping->remote2local[1] = 1;
|
||||
|
||||
for(size_t i = 2; i < nsSize; ++i) {
|
||||
UA_UInt16 nsIndex = 0;
|
||||
UA_Client_getNamespaceIndex(client, ns[i], &nsIndex);
|
||||
nsMapping->remote2local[i] = nsIndex;
|
||||
}
|
||||
|
||||
nsMapping->local2remote = (UA_UInt16*)UA_calloc( nsSize, sizeof(UA_UInt16));
|
||||
if(!nsMapping->local2remote) {
|
||||
UA_LOG_ERROR(client->config.logging, UA_LOGCATEGORY_CLIENT,
|
||||
"Namespace mapping creation failed. Out of Memory.");
|
||||
return;
|
||||
}
|
||||
nsMapping->local2remoteSize = nsSize;
|
||||
nsMapping->local2remote[0] = 0;
|
||||
nsMapping->local2remote[1] = 1;
|
||||
|
||||
for(size_t i = 2; i < nsMapping->remote2localSize; ++i) {
|
||||
UA_UInt16 localIndex = nsMapping->remote2local[i];
|
||||
nsMapping->local2remote[localIndex] = (UA_UInt16)i;
|
||||
}
|
||||
|
||||
client->channel.namespaceMapping = nsMapping;
|
||||
}
|
||||
|
||||
/* We read the namespaces right after the session has opened. The user might
|
||||
* already requests other services in parallel. That leaves a short time where
|
||||
* requests can be made before the namespace mapping is configured. */
|
||||
static void
|
||||
readNamespacesArrayAsync(UA_Client *client) {
|
||||
UA_LOCK_ASSERT(&client->clientMutex);
|
||||
|
||||
/* Check the connection status */
|
||||
if(client->sessionState != UA_SESSIONSTATE_CREATED &&
|
||||
client->sessionState != UA_SESSIONSTATE_ACTIVATED) {
|
||||
UA_LOG_ERROR(client->config.logging, UA_LOGCATEGORY_CLIENT,
|
||||
"Cannot read the namespaces array, session neither created nor "
|
||||
"activated. Actual state: '%u'", client->sessionState);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Set up the read request */
|
||||
UA_ReadRequest rr;
|
||||
UA_ReadRequest_init(&rr);
|
||||
|
||||
UA_ReadValueId nodesToRead;
|
||||
UA_ReadValueId_init(&nodesToRead);
|
||||
nodesToRead.nodeId = UA_NS0ID(SERVER_NAMESPACEARRAY);
|
||||
nodesToRead.attributeId = UA_ATTRIBUTEID_VALUE;
|
||||
|
||||
rr.nodesToRead = &nodesToRead;
|
||||
rr.nodesToReadSize = 1;
|
||||
|
||||
/* Send the async read request */
|
||||
UA_StatusCode res =
|
||||
__Client_AsyncService(client, &rr, &UA_TYPES[UA_TYPES_READREQUEST],
|
||||
(UA_ClientAsyncServiceCallback)responseReadNamespacesArray,
|
||||
&UA_TYPES[UA_TYPES_READRESPONSE],
|
||||
NULL, NULL);
|
||||
|
||||
if(res == UA_STATUSCODE_GOOD)
|
||||
client->namespacesHandshake = true;
|
||||
else
|
||||
UA_LOG_ERROR(client->config.logging, UA_LOGCATEGORY_CLIENT,
|
||||
"Could not read the namespace array with error code %s",
|
||||
UA_StatusCode_name(res));
|
||||
}
|
||||
|
||||
static void
|
||||
responseActivateSession(UA_Client *client, void *userdata,
|
||||
UA_UInt32 requestId, void *response) {
|
||||
@ -700,6 +816,10 @@ responseActivateSession(UA_Client *client, void *userdata,
|
||||
client->sessionState = UA_SESSIONSTATE_ACTIVATED;
|
||||
notifyClientState(client);
|
||||
|
||||
/* Read the namespaces array if we don't already have it */
|
||||
if(!client->haveNamespaces)
|
||||
readNamespacesArrayAsync(client);
|
||||
|
||||
/* Immediately check if publish requests are outstanding - for example when
|
||||
* an existing Session has been reattached / activated. */
|
||||
#ifdef UA_ENABLE_SUBSCRIPTIONS
|
||||
@ -838,7 +958,8 @@ matchEndpoint(UA_Client *client, const UA_EndpointDescription *endpoint, unsigne
|
||||
!UA_String_equal(&client->config.applicationUri,
|
||||
&endpoint->server.applicationUri)) {
|
||||
UA_LOG_INFO(client->config.logging, UA_LOGCATEGORY_CLIENT,
|
||||
"Rejecting endpoint %u: application uri not match", i);
|
||||
"Rejecting Endpoint %u: The server's ApplicationUri %S does not match "
|
||||
"the client configuration", i, endpoint->server.applicationUri);
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -847,14 +968,15 @@ matchEndpoint(UA_Client *client, const UA_EndpointDescription *endpoint, unsigne
|
||||
if(endpoint->transportProfileUri.length != 0 &&
|
||||
!UA_String_equal(&endpoint->transportProfileUri, &binaryTransport)) {
|
||||
UA_LOG_INFO(client->config.logging, UA_LOGCATEGORY_CLIENT,
|
||||
"Rejecting endpoint %u: transport profile does not match", i);
|
||||
"Rejecting Endpoint %u: TransportProfileUri %S not supported",
|
||||
i, endpoint->transportProfileUri);
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Valid SecurityMode? */
|
||||
if(endpoint->securityMode < 1 || endpoint->securityMode > 3) {
|
||||
UA_LOG_INFO(client->config.logging, UA_LOGCATEGORY_CLIENT,
|
||||
"Rejecting endpoint %u: invalid security mode", i);
|
||||
"Rejecting Endpoint %u: Invalid SecurityMode", i);
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -862,7 +984,7 @@ matchEndpoint(UA_Client *client, const UA_EndpointDescription *endpoint, unsigne
|
||||
if(client->config.securityMode > 0 &&
|
||||
client->config.securityMode != endpoint->securityMode) {
|
||||
UA_LOG_INFO(client->config.logging, UA_LOGCATEGORY_CLIENT,
|
||||
"Rejecting endpoint %u: security mode does not match", i);
|
||||
"Rejecting Endpoint %u: SecurityMode does not match the configuration", i);
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -870,14 +992,16 @@ matchEndpoint(UA_Client *client, const UA_EndpointDescription *endpoint, unsigne
|
||||
if(client->config.securityPolicyUri.length > 0 &&
|
||||
!UA_String_equal(&client->config.securityPolicyUri, &endpoint->securityPolicyUri)) {
|
||||
UA_LOG_INFO(client->config.logging, UA_LOGCATEGORY_CLIENT,
|
||||
"Rejecting endpoint %u: security policy does not match the configuration", i);
|
||||
"Rejecting Endpoint %u: SecurityPolicy %S does not match the configuration",
|
||||
i, endpoint->securityPolicyUri);
|
||||
return false;
|
||||
}
|
||||
|
||||
/* SecurityPolicy available? */
|
||||
if(!getSecurityPolicy(client, endpoint->securityPolicyUri)) {
|
||||
UA_LOG_INFO(client->config.logging, UA_LOGCATEGORY_CLIENT,
|
||||
"Rejecting endpoint %u: security policy not available", i);
|
||||
"Rejecting Endpoint %u: SecurityPolicy %S not supported",
|
||||
i, endpoint->securityPolicyUri);
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -1004,7 +1128,7 @@ responseGetEndpoints(UA_Client *client, void *userdata,
|
||||
* UserTokenPolicy that matches the configuration. */
|
||||
if(!client->config.noSession && !findUserTokenPolicy(client, endpoint)) {
|
||||
UA_LOG_INFO(client->config.logging, UA_LOGCATEGORY_CLIENT,
|
||||
"Rejecting endpoint %lu: No matching UserTokenPolicy",
|
||||
"Rejecting Endpoint %lu: No matching UserTokenPolicy",
|
||||
(long unsigned)i);
|
||||
continue;
|
||||
}
|
||||
@ -1491,13 +1615,15 @@ verifyClientApplicationURI(const UA_Client *client) {
|
||||
}
|
||||
|
||||
UA_StatusCode retval =
|
||||
UA_CertificateUtils_verifyApplicationURI(client->allowAllCertificateUris, &sp->localCertificate,
|
||||
&client->config.clientDescription.applicationUri);
|
||||
UA_CertificateUtils_verifyApplicationURI(client->allowAllCertificateUris,
|
||||
&sp->localCertificate,
|
||||
&client->config.clientDescription.applicationUri,
|
||||
client->config.logging);
|
||||
if(retval != UA_STATUSCODE_GOOD) {
|
||||
UA_LOG_WARNING(client->config.logging, UA_LOGCATEGORY_CLIENT,
|
||||
"The configured ApplicationURI does not match the URI "
|
||||
"specified in the certificate for the SecurityPolicy %S",
|
||||
sp->policyUri.length);
|
||||
sp->policyUri);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
@ -125,6 +125,8 @@ struct UA_Client {
|
||||
|
||||
UA_Boolean findServersHandshake; /* Ongoing FindServers */
|
||||
UA_Boolean endpointsHandshake; /* Ongoing GetEndpoints */
|
||||
UA_Boolean namespacesHandshake; /* Ongoing Namespaces read */
|
||||
UA_Boolean haveNamespaces; /* Do we have the namespaces? */
|
||||
|
||||
/* The discoveryUrl can be different from the EndpointUrl in the client
|
||||
* configuration. The EndpointUrl is used to connect initially, then the
|
||||
@ -167,6 +169,11 @@ struct UA_Client {
|
||||
UA_UInt32 monitoredItemHandles;
|
||||
UA_UInt16 currentlyOutStandingPublishRequests;
|
||||
|
||||
/* Internal namespaces. The table maps the namespace Uri to its index. This
|
||||
* is used for the automatic namespace mapping in de/encoding. */
|
||||
UA_String *namespaces;
|
||||
size_t namespacesSize;
|
||||
|
||||
/* Internal locking for thread-safety. Methods starting with UA_Client_ that
|
||||
* are marked with UA_THREADSAFE take the lock. The lock is released before
|
||||
* dropping into the EventLoop and before calling user-defined callbacks.
|
||||
|
@ -517,33 +517,19 @@ addSubscribedDataSet(UA_PubSubManager *psm, const UA_NodeId dsReaderIdent,
|
||||
|
||||
if(subscribedDataSet->content.decoded.type ==
|
||||
&UA_TYPES[UA_TYPES_TARGETVARIABLESDATATYPE]) {
|
||||
UA_TargetVariablesDataType *tmpTargetVars = (UA_TargetVariablesDataType*)
|
||||
UA_TargetVariablesDataType *targetVars = (UA_TargetVariablesDataType*)
|
||||
subscribedDataSet->content.decoded.data;
|
||||
UA_FieldTargetVariable *targetVars = (UA_FieldTargetVariable *)
|
||||
UA_calloc(tmpTargetVars->targetVariablesSize, sizeof(UA_FieldTargetVariable));
|
||||
|
||||
for(size_t index = 0; index < tmpTargetVars->targetVariablesSize; index++) {
|
||||
UA_FieldTargetDataType_copy(&tmpTargetVars->targetVariables[index],
|
||||
&targetVars[index].targetVariable);
|
||||
}
|
||||
|
||||
UA_StatusCode res = UA_STATUSCODE_BADINTERNALERROR;
|
||||
UA_DataSetReader *dsr = UA_DataSetReader_find(psm, dsReaderIdent);
|
||||
if(dsr)
|
||||
res = DataSetReader_createTargetVariables(psm, dsr,
|
||||
tmpTargetVars->targetVariablesSize,
|
||||
targetVars);
|
||||
targetVars->targetVariablesSize,
|
||||
targetVars->targetVariables);
|
||||
if(res != UA_STATUSCODE_GOOD) {
|
||||
UA_LOG_ERROR(psm->logging, UA_LOGCATEGORY_PUBSUB,
|
||||
"[UA_PubSubManager_addSubscribedDataSet] "
|
||||
"create TargetVariables failed");
|
||||
}
|
||||
|
||||
for(size_t index = 0; index < tmpTargetVars->targetVariablesSize; index++) {
|
||||
UA_FieldTargetDataType_clear(&targetVars[index].targetVariable);
|
||||
}
|
||||
|
||||
UA_free(targetVars);
|
||||
return res;
|
||||
}
|
||||
|
||||
@ -989,22 +975,21 @@ generateDataSetReaderDataType(const UA_DataSetReader *src,
|
||||
UA_TargetVariablesDataType *tmpTarget = UA_TargetVariablesDataType_new();
|
||||
if(!tmpTarget)
|
||||
return UA_STATUSCODE_BADOUTOFMEMORY;
|
||||
UA_ExtensionObject_setValue(&dst->subscribedDataSet, tmpTarget,
|
||||
&UA_TYPES[UA_TYPES_TARGETVARIABLESDATATYPE]);
|
||||
|
||||
const UA_TargetVariables *targets =
|
||||
&src->config.subscribedDataSet.subscribedDataSetTarget;
|
||||
const UA_TargetVariablesDataType *targets = &src->config.subscribedDataSet.target;
|
||||
tmpTarget->targetVariables = (UA_FieldTargetDataType *)
|
||||
UA_calloc(targets->targetVariablesSize, sizeof(UA_FieldTargetDataType));
|
||||
if(!tmpTarget->targetVariables)
|
||||
return UA_STATUSCODE_BADOUTOFMEMORY;
|
||||
tmpTarget->targetVariablesSize = targets->targetVariablesSize;
|
||||
|
||||
for(size_t i = 0; i < tmpTarget->targetVariablesSize; i++) {
|
||||
res |= UA_FieldTargetDataType_copy(&targets->targetVariables[i].targetVariable,
|
||||
res |= UA_FieldTargetDataType_copy(&targets->targetVariables[i],
|
||||
&tmpTarget->targetVariables[i]);
|
||||
}
|
||||
|
||||
UA_ExtensionObject_setValue(&dst->subscribedDataSet, tmpTarget,
|
||||
&UA_TYPES[UA_TYPES_TARGETVARIABLESDATATYPE]);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
|
@ -23,7 +23,7 @@ UA_PubSubConnection_connect(UA_PubSubManager *psm, UA_PubSubConnection *c,
|
||||
|
||||
static void
|
||||
UA_PubSubConnection_process(UA_PubSubManager *psm, UA_PubSubConnection *c,
|
||||
UA_ByteString msg);
|
||||
const UA_ByteString msg);
|
||||
|
||||
static void
|
||||
UA_PubSubConnection_disconnect(UA_PubSubConnection *c);
|
||||
@ -250,7 +250,7 @@ UA_PubSubConnection_delete(UA_PubSubManager *psm, UA_PubSubConnection *c) {
|
||||
/* The WriterGroups / ReaderGroups are not deleted. Try again in the next
|
||||
* iteration of the event loop.*/
|
||||
if(!LIST_EMPTY(&c->writerGroups) || !LIST_EMPTY(&c->readerGroups)) {
|
||||
UA_EventLoop *el = UA_PubSubConnection_getEL(psm, c);
|
||||
UA_EventLoop *el = psm->sc.server->config.eventLoop;
|
||||
c->dc.callback = delayedPubSubConnection_delete;
|
||||
c->dc.application = psm;
|
||||
c->dc.context = c;
|
||||
@ -276,33 +276,21 @@ UA_PubSubConnection_delete(UA_PubSubManager *psm, UA_PubSubConnection *c) {
|
||||
|
||||
static void
|
||||
UA_PubSubConnection_process(UA_PubSubManager *psm, UA_PubSubConnection *c,
|
||||
UA_ByteString msg) {
|
||||
const UA_ByteString msg) {
|
||||
UA_LOG_TRACE_PUBSUB(psm->logging, c, "Processing a received buffer");
|
||||
|
||||
/* Process RT ReaderGroups */
|
||||
UA_ReaderGroup *rg;
|
||||
UA_Boolean processed = false;
|
||||
UA_ReaderGroup *nonRtRg = NULL;
|
||||
LIST_FOREACH(rg, &c->readerGroups, listEntry) {
|
||||
if(rg->head.state != UA_PUBSUBSTATE_OPERATIONAL &&
|
||||
rg->head.state != UA_PUBSUBSTATE_PREOPERATIONAL)
|
||||
continue;
|
||||
if(!(rg->config.rtLevel & UA_PUBSUB_RT_FIXED_SIZE)) {
|
||||
nonRtRg = rg;
|
||||
continue;
|
||||
}
|
||||
processed |= UA_ReaderGroup_decodeAndProcessRT(psm, rg, msg);
|
||||
}
|
||||
|
||||
/* Any non-RT ReaderGroups? */
|
||||
if(!nonRtRg)
|
||||
UA_ReaderGroup *rg = LIST_FIRST(&c->readerGroups);
|
||||
/* Any interested ReaderGroups? */
|
||||
if(!rg)
|
||||
goto finish;
|
||||
|
||||
/* Decode the received message for the non-RT ReaderGroups */
|
||||
UA_StatusCode res;
|
||||
UA_NetworkMessage nm;
|
||||
memset(&nm, 0, sizeof(UA_NetworkMessage));
|
||||
if(nonRtRg->config.encodingMimeType == UA_PUBSUB_ENCODING_UADP) {
|
||||
if(rg->config.encodingMimeType == UA_PUBSUB_ENCODING_UADP) {
|
||||
res = UA_PubSubConnection_decodeNetworkMessage(psm, c, msg, &nm);
|
||||
} else { /* if(writerGroup->config.encodingMimeType == UA_PUBSUB_ENCODING_JSON) */
|
||||
#ifdef UA_ENABLE_JSON_ENCODING
|
||||
@ -326,8 +314,6 @@ UA_PubSubConnection_process(UA_PubSubManager *psm, UA_PubSubConnection *c,
|
||||
if(rg->head.state != UA_PUBSUBSTATE_OPERATIONAL &&
|
||||
rg->head.state != UA_PUBSUBSTATE_PREOPERATIONAL)
|
||||
continue;
|
||||
if(rg->config.rtLevel & UA_PUBSUB_RT_FIXED_SIZE)
|
||||
continue;
|
||||
processed |= UA_ReaderGroup_process(psm, rg, &nm);
|
||||
}
|
||||
UA_NetworkMessage_clear(&nm);
|
||||
@ -357,9 +343,20 @@ UA_PubSubConnection_setPubSubState(UA_PubSubManager *psm, UA_PubSubConnection *c
|
||||
UA_Boolean isTransient = c->head.transientState;
|
||||
c->head.transientState = true;
|
||||
|
||||
UA_Server *server = psm->sc.server;
|
||||
UA_StatusCode ret = UA_STATUSCODE_GOOD;
|
||||
UA_PubSubState oldState = c->head.state;
|
||||
|
||||
/* Custom state machine */
|
||||
if(c->config.customStateMachine) {
|
||||
UA_UNLOCK(&server->serviceMutex);
|
||||
ret = c->config.customStateMachine(server, c->head.identifier, c->config.context,
|
||||
&c->head.state, targetState);
|
||||
UA_LOCK(&server->serviceMutex);
|
||||
goto finalize_state_machine;
|
||||
}
|
||||
|
||||
/* Internal state machine */
|
||||
switch(targetState) {
|
||||
/* Disabled or Error */
|
||||
case UA_PUBSUBSTATE_ERROR:
|
||||
@ -405,6 +402,8 @@ UA_PubSubConnection_setPubSubState(UA_PubSubManager *psm, UA_PubSubConnection *c
|
||||
UA_PubSubConnection_disconnect(c);
|
||||
}
|
||||
|
||||
finalize_state_machine:
|
||||
|
||||
/* Only the top-level state update (if recursive calls are happening)
|
||||
* notifies the application and updates Reader and WriterGroups */
|
||||
c->head.transientState = isTransient;
|
||||
@ -413,15 +412,14 @@ UA_PubSubConnection_setPubSubState(UA_PubSubManager *psm, UA_PubSubConnection *c
|
||||
|
||||
/* Inform application about state change */
|
||||
if(c->head.state != oldState) {
|
||||
UA_ServerConfig *config = &psm->sc.server->config;
|
||||
UA_LOG_INFO_PUBSUB(psm->logging, c, "%s -> %s",
|
||||
UA_PubSubState_name(oldState),
|
||||
UA_PubSubState_name(c->head.state));
|
||||
if(config->pubSubConfig.stateChangeCallback) {
|
||||
UA_UNLOCK(&psm->sc.server->serviceMutex);
|
||||
config->pubSubConfig.
|
||||
stateChangeCallback(psm->sc.server, c->head.identifier, targetState, ret);
|
||||
UA_LOCK(&psm->sc.server->serviceMutex);
|
||||
if(server->config.pubSubConfig.stateChangeCallback) {
|
||||
UA_UNLOCK(&server->serviceMutex);
|
||||
server->config.pubSubConfig.
|
||||
stateChangeCallback(server, c->head.identifier, targetState, ret);
|
||||
UA_LOCK(&server->serviceMutex);
|
||||
}
|
||||
}
|
||||
|
||||
@ -457,13 +455,6 @@ disablePubSubConnection(UA_PubSubManager *psm, const UA_NodeId connectionId) {
|
||||
: UA_STATUSCODE_BADNOTFOUND;
|
||||
}
|
||||
|
||||
UA_EventLoop *
|
||||
UA_PubSubConnection_getEL(UA_PubSubManager *psm, UA_PubSubConnection *c) {
|
||||
if(c->config.eventLoop)
|
||||
return c->config.eventLoop;
|
||||
return psm->sc.server->config.eventLoop;
|
||||
}
|
||||
|
||||
/***********************/
|
||||
/* Connection Handling */
|
||||
/***********************/
|
||||
@ -822,7 +813,7 @@ UA_PubSubConnection_connect(UA_PubSubManager *psm, UA_PubSubConnection *c,
|
||||
UA_Server *server = psm->sc.server;
|
||||
UA_LOCK_ASSERT(&server->serviceMutex);
|
||||
|
||||
UA_EventLoop *el = UA_PubSubConnection_getEL(psm, c);
|
||||
UA_EventLoop *el = psm->sc.server->config.eventLoop;
|
||||
if(!el) {
|
||||
UA_LOG_ERROR_PUBSUB(psm->logging, c, "No EventLoop configured");
|
||||
return UA_STATUSCODE_BADINTERNALERROR;;
|
||||
@ -935,4 +926,29 @@ UA_Server_disablePubSubConnection(UA_Server *server, const UA_NodeId cId) {
|
||||
return res;
|
||||
}
|
||||
|
||||
UA_StatusCode
|
||||
UA_Server_processPubSubConnectionReceive(UA_Server *server,
|
||||
const UA_NodeId connectionId,
|
||||
const UA_ByteString packet) {
|
||||
if(!server)
|
||||
return UA_STATUSCODE_BADINTERNALERROR;
|
||||
UA_LOCK(&server->serviceMutex);
|
||||
UA_StatusCode res = UA_STATUSCODE_BADINTERNALERROR;
|
||||
UA_PubSubManager *psm = getPSM(server);
|
||||
if(psm) {
|
||||
UA_PubSubConnection *c = UA_PubSubConnection_find(psm, connectionId);
|
||||
if(c) {
|
||||
res = UA_STATUSCODE_GOOD;
|
||||
UA_PubSubConnection_process(psm, c, packet);
|
||||
} else {
|
||||
res = UA_STATUSCODE_BADCONNECTIONCLOSED;
|
||||
UA_LOG_WARNING_PUBSUB(psm->logging, c,
|
||||
"Cannot process a packet if the "
|
||||
"PubSubConnection is not operational");
|
||||
}
|
||||
}
|
||||
UA_UNLOCK(&server->serviceMutex);
|
||||
return res;
|
||||
}
|
||||
|
||||
#endif /* UA_ENABLE_PUBSUB */
|
||||
|
@ -121,12 +121,9 @@ generateFieldMetaData(UA_PubSubManager *psm, UA_PublishedDataSet *pds,
|
||||
const UA_DataSetVariableConfig *var = &field->config.field.variable;
|
||||
|
||||
/* Set the field identifier */
|
||||
if(!UA_Guid_equal(&var->dataSetFieldId, &UA_GUID_NULL))
|
||||
{
|
||||
if(!UA_Guid_equal(&var->dataSetFieldId, &UA_GUID_NULL)) {
|
||||
fieldMetaData->dataSetFieldId = var->dataSetFieldId;
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
fieldMetaData->dataSetFieldId = UA_PubSubManager_generateUniqueGuid(psm);
|
||||
}
|
||||
|
||||
@ -137,32 +134,6 @@ generateFieldMetaData(UA_PubSubManager *psm, UA_PublishedDataSet *pds,
|
||||
UA_StatusCode res = UA_String_copy(&var->fieldNameAlias, &fieldMetaData->name);
|
||||
UA_CHECK_STATUS(res, return res);
|
||||
|
||||
/* Static value source. ToDo after freeze PR, the value source must be
|
||||
* checked (other behavior for static value source) */
|
||||
if(var->rtValueSource.rtFieldSourceEnabled &&
|
||||
!var->rtValueSource.rtInformationModelNode) {
|
||||
const UA_DataValue *svs = *var->rtValueSource.staticValueSource;
|
||||
if(svs->value.arrayDimensionsSize > 0) {
|
||||
fieldMetaData->arrayDimensions = (UA_UInt32 *)
|
||||
UA_calloc(svs->value.arrayDimensionsSize, sizeof(UA_UInt32));
|
||||
if(fieldMetaData->arrayDimensions == NULL)
|
||||
return UA_STATUSCODE_BADOUTOFMEMORY;
|
||||
memcpy(fieldMetaData->arrayDimensions, svs->value.arrayDimensions,
|
||||
sizeof(UA_UInt32) * svs->value.arrayDimensionsSize);
|
||||
}
|
||||
fieldMetaData->arrayDimensionsSize = svs->value.arrayDimensionsSize;
|
||||
|
||||
if(svs->value.type)
|
||||
res = UA_NodeId_copy(&svs->value.type->typeId, &fieldMetaData->dataType);
|
||||
UA_CHECK_STATUS(res, return res);
|
||||
|
||||
//TODO collect value rank for the static field source
|
||||
fieldMetaData->properties = NULL;
|
||||
fieldMetaData->propertiesSize = 0;
|
||||
fieldMetaData->fieldFlags = UA_DATASETFIELDFLAGS_NONE;
|
||||
return UA_STATUSCODE_GOOD;
|
||||
}
|
||||
|
||||
/* Set the Array Dimensions */
|
||||
const UA_PublishedVariableDataType *pp = &var->publishParameters;
|
||||
UA_Variant value;
|
||||
@ -180,8 +151,6 @@ generateFieldMetaData(UA_PubSubManager *psm, UA_PublishedDataSet *pds,
|
||||
UA_calloc(value.arrayDimensionsSize, sizeof(UA_UInt32));
|
||||
if(!fieldMetaData->arrayDimensions)
|
||||
return UA_STATUSCODE_BADOUTOFMEMORY;
|
||||
memcpy(fieldMetaData->arrayDimensions, value.arrayDimensions,
|
||||
sizeof(UA_UInt32)*value.arrayDimensionsSize);
|
||||
}
|
||||
fieldMetaData->arrayDimensionsSize = value.arrayDimensionsSize;
|
||||
|
||||
@ -496,25 +465,13 @@ UA_PubSubDataSetField_sampleValue(UA_PubSubManager *psm, UA_DataSetField *field,
|
||||
UA_DataValue *value) {
|
||||
UA_PublishedVariableDataType *params = &field->config.field.variable.publishParameters;
|
||||
|
||||
/* Read the value */
|
||||
if(field->config.field.variable.rtValueSource.rtInformationModelNode) {
|
||||
const UA_VariableNode *rtNode = (const UA_VariableNode *)
|
||||
UA_NODESTORE_GET(psm->sc.server, ¶ms->publishedVariable);
|
||||
*value = **rtNode->valueBackend.backend.external.value;
|
||||
value->value.storageType = UA_VARIANT_DATA_NODELETE;
|
||||
UA_NODESTORE_RELEASE(psm->sc.server, (const UA_Node *) rtNode);
|
||||
} else if(field->config.field.variable.rtValueSource.rtFieldSourceEnabled == false){
|
||||
UA_ReadValueId rvid;
|
||||
UA_ReadValueId_init(&rvid);
|
||||
rvid.nodeId = params->publishedVariable;
|
||||
rvid.attributeId = params->attributeId;
|
||||
rvid.indexRange = params->indexRange;
|
||||
*value = readWithSession(psm->sc.server, &psm->sc.server->adminSession,
|
||||
&rvid, UA_TIMESTAMPSTORETURN_BOTH);
|
||||
} else {
|
||||
*value = **field->config.field.variable.rtValueSource.staticValueSource;
|
||||
value->value.storageType = UA_VARIANT_DATA_NODELETE;
|
||||
}
|
||||
UA_ReadValueId rvid;
|
||||
UA_ReadValueId_init(&rvid);
|
||||
rvid.nodeId = params->publishedVariable;
|
||||
rvid.attributeId = params->attributeId;
|
||||
rvid.indexRange = params->indexRange;
|
||||
*value = readWithSession(psm->sc.server, &psm->sc.server->adminSession,
|
||||
&rvid, UA_TIMESTAMPSTORETURN_BOTH);
|
||||
}
|
||||
|
||||
UA_AddPublishedDataSetResult
|
||||
@ -731,9 +688,10 @@ UA_SubscribedDataSetConfig_copy(const UA_SubscribedDataSetConfig *src,
|
||||
memcpy(dst, src, sizeof(UA_SubscribedDataSetConfig));
|
||||
res = UA_DataSetMetaDataType_copy(&src->dataSetMetaData, &dst->dataSetMetaData);
|
||||
res |= UA_String_copy(&src->name, &dst->name);
|
||||
res |= UA_TargetVariablesDataType_copy(&src->subscribedDataSet.target,
|
||||
&dst->subscribedDataSet.target);
|
||||
|
||||
if(src->subscribedDataSetType == UA_PUBSUB_SDS_TARGET) {
|
||||
res |= UA_TargetVariablesDataType_copy(&src->subscribedDataSet.target,
|
||||
&dst->subscribedDataSet.target);
|
||||
}
|
||||
if(res != UA_STATUSCODE_GOOD)
|
||||
UA_SubscribedDataSetConfig_clear(dst);
|
||||
return res;
|
||||
|
@ -184,6 +184,9 @@ typedef struct UA_PublishedDataSet {
|
||||
UA_DataSetMetaDataType dataSetMetaData;
|
||||
UA_UInt16 fieldSize;
|
||||
UA_UInt16 promotedFieldsCount;
|
||||
|
||||
/* The counter is required because the PDS has not state.
|
||||
* Check if it is actively used when changes are introduced. */
|
||||
UA_UInt16 configurationFreezeCounter;
|
||||
} UA_PublishedDataSet;
|
||||
|
||||
@ -284,11 +287,6 @@ UA_PubSubConnectionConfig_clear(UA_PubSubConnectionConfig *connectionConfig);
|
||||
void
|
||||
UA_PubSubConnection_delete(UA_PubSubManager *psm, UA_PubSubConnection *c);
|
||||
|
||||
/* Returns either the eventloop configured in the connection or, in its absence,
|
||||
* for the server */
|
||||
UA_EventLoop *
|
||||
UA_PubSubConnection_getEL(UA_PubSubManager *psm, UA_PubSubConnection *c);
|
||||
|
||||
UA_StatusCode
|
||||
UA_PubSubConnection_setPubSubState(UA_PubSubManager *psm, UA_PubSubConnection *c,
|
||||
UA_PubSubState targetState);
|
||||
@ -341,12 +339,8 @@ UA_DataSetWriter_setPubSubState(UA_PubSubManager *psm, UA_DataSetWriter *dsw,
|
||||
|
||||
UA_StatusCode
|
||||
UA_DataSetWriter_generateDataSetMessage(UA_PubSubManager *psm,
|
||||
UA_DataSetMessage *dsm,
|
||||
UA_DataSetWriter *dsw);
|
||||
|
||||
UA_StatusCode
|
||||
UA_DataSetWriter_prepareDataSet(UA_PubSubManager *psm, UA_DataSetWriter *dsw,
|
||||
UA_DataSetMessage *dsm);
|
||||
UA_DataSetWriter *dsw,
|
||||
UA_DataSetMessage *dsm);
|
||||
|
||||
UA_StatusCode
|
||||
UA_DataSetWriter_create(UA_PubSubManager *psm,
|
||||
@ -371,9 +365,7 @@ struct UA_WriterGroup {
|
||||
UA_UInt32 writersCount;
|
||||
|
||||
UA_UInt64 publishCallbackId; /* registered if != 0 */
|
||||
UA_NetworkMessageOffsetBuffer bufferedMessage;
|
||||
UA_UInt16 sequenceNumber; /* Increased after every sent message */
|
||||
UA_Boolean configurationFrozen;
|
||||
UA_DateTime lastPublishTimeStamp;
|
||||
|
||||
/* The ConnectionManager pointer is stored in the Connection. The channels
|
||||
@ -471,8 +463,6 @@ struct UA_DataSetReader {
|
||||
UA_DataSetReaderConfig config;
|
||||
UA_ReaderGroup *linkedReaderGroup;
|
||||
|
||||
UA_NetworkMessageOffsetBuffer bufferedMessage;
|
||||
|
||||
/* MessageReceiveTimeout handling */
|
||||
UA_UInt64 msgRcvTimeoutTimerId;
|
||||
};
|
||||
@ -495,38 +485,17 @@ UA_DataSetReader_create(UA_PubSubManager *psm, UA_NodeId readerGroupIdentifier,
|
||||
const UA_DataSetReaderConfig *dataSetReaderConfig,
|
||||
UA_NodeId *readerIdentifier);
|
||||
|
||||
UA_StatusCode
|
||||
UA_DataSetReader_prepareOffsetBuffer(Ctx *ctx, UA_DataSetReader *reader,
|
||||
UA_ByteString *buf);
|
||||
|
||||
void
|
||||
UA_DataSetReader_decodeAndProcessRT(UA_PubSubManager *psm, UA_DataSetReader *dsr,
|
||||
UA_ByteString buf);
|
||||
|
||||
UA_StatusCode
|
||||
UA_DataSetReader_remove(UA_PubSubManager *psm, UA_DataSetReader *dsr);
|
||||
|
||||
/* Copy the configuration of Target Variables */
|
||||
UA_StatusCode UA_TargetVariables_copy(const UA_TargetVariables *src,
|
||||
UA_TargetVariables *dst);
|
||||
|
||||
/* Clear the Target Variables configuration */
|
||||
void UA_TargetVariables_clear(UA_TargetVariables *subscribedDataSetTarget);
|
||||
|
||||
/* Copy the configuration of Field Target Variables */
|
||||
UA_StatusCode UA_FieldTargetVariable_copy(const UA_FieldTargetVariable *src,
|
||||
UA_FieldTargetVariable *dst);
|
||||
|
||||
UA_StatusCode
|
||||
DataSetReader_createTargetVariables(UA_PubSubManager *psm, UA_DataSetReader *dsr,
|
||||
size_t targetVariablesSize,
|
||||
const UA_FieldTargetVariable *targetVariables);
|
||||
size_t targetsSize, const UA_FieldTargetDataType *targets);
|
||||
|
||||
/* Returns an error reason if the target state is `Error` */
|
||||
void
|
||||
UA_DataSetReader_setPubSubState(UA_PubSubManager *psm, UA_DataSetReader *dsr,
|
||||
UA_PubSubState targetState,
|
||||
UA_StatusCode errorReason);
|
||||
UA_PubSubState targetState, UA_StatusCode errorReason);
|
||||
|
||||
/**********************************************/
|
||||
/* ReaderGroup */
|
||||
@ -541,7 +510,6 @@ struct UA_ReaderGroup {
|
||||
LIST_HEAD(, UA_DataSetReader) readers;
|
||||
UA_UInt32 readersCount;
|
||||
|
||||
UA_Boolean configurationFrozen;
|
||||
UA_Boolean hasReceived; /* Received a message since the last _connect */
|
||||
|
||||
/* The ConnectionManager pointer is stored in the Connection. The channels
|
||||
@ -595,10 +563,6 @@ UA_StatusCode
|
||||
UA_ReaderGroup_setPubSubState(UA_PubSubManager *psm, UA_ReaderGroup *rg,
|
||||
UA_PubSubState targetState);
|
||||
|
||||
UA_Boolean
|
||||
UA_ReaderGroup_decodeAndProcessRT(UA_PubSubManager *psm, UA_ReaderGroup *rg,
|
||||
UA_ByteString buf);
|
||||
|
||||
UA_Boolean
|
||||
UA_ReaderGroup_process(UA_PubSubManager *psm, UA_ReaderGroup *rg,
|
||||
UA_NetworkMessage *nm);
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user