Skip to content

Commit a0781eb

Browse files
committed
feat: New plugins
- Add `ioports` plugin for I/O port status control - Add `simple_event` plugin for AXIS event integration - Add `thermal` plugin for thermal camera data - Add `vinput` plugin for virtual inputs status control - Add READMEs for each plugin - Update main README to list all plugins - Refactor common VAPIX functions - Introduce rollback API for robust OPC-UA Node creation Change-Id: I7715c3f59c3758f28032ae869859a095031466d5
1 parent bba4c4e commit a0781eb

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+8645
-299
lines changed

Dockerfile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
# The ARCH can be overridden by providing it a value in the command line
44
# with the "--build-arg=XXX" argument fed to 'docker build'.
55
ARG ARCH=aarch64
6-
ARG VERSION=1.14
7-
ARG UBUNTU_VERSION=22.04
6+
ARG VERSION=12.7.0
7+
ARG UBUNTU_VERSION=24.04
88
ARG REPO=axisecp
99
ARG SDK=acap-native-sdk
1010
ARG BUILD_DIR=/opt/app

README.md

Lines changed: 70 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,10 @@
44
# OPC-UA Plugin Server ACAP application
55

66
This repository contains the source code for building an OPC-UA Server ACAP application,
7-
along with a guide on developing and building custom modules/plugins. You can
8-
create your own plugins to address specific use cases. Two example plugins
9-
are provided and described below. Additional plugins will be published in
10-
the future designed to enhance production value.
7+
along with a guide on developing and building custom modules/plugins.
8+
You can create your own plugins to address specific use cases. Example
9+
plugins are provided and briefly described below, for more information about a
10+
plugin see its README.
1111

1212
The OPC-UA Server is based on the [**open62541**](https://www.open62541.org/)
1313
library. For an introduction to OPC-UA, see
@@ -26,6 +26,7 @@ library. For an introduction to OPC-UA, see
2626
- [Install](#install)
2727
- [Setup](#setup)
2828
- [Usage](#usage)
29+
- [Available plugins](#available-plugins)
2930
- [Create your own plugin](#create-your-own-plugin)
3031
- [Limitations](#limitations)
3132
- [Security](#security)
@@ -256,16 +257,21 @@ options are not available.
256257

257258
To connect to the server right click on the name and click **Connect** or click
258259
on the **Connect Server** icon in the tool bar. If the connection is
259-
established, the server will present its OPC-UA information as shown in the
260+
established, the server will present its OPC-UA information similar to the
260261
picture below.
261262

262263
![UAExpert](assets/UAExpert_InfoModel.png)
263264

264-
There is one *object node* called **BasicDeviceInfo** with *property nodes*
265-
presenting different information about the active device. This is implemented in
266-
the **plugins/bdi** module. The **plugins/hello_world** module is responsible
267-
for creating a *variable node*, called **HelloWorldNode** with the string value
268-
"Hello World!".
265+
#### Available Plugins
266+
267+
This application includes the source code for several plugins, which serve both as functional components and as examples for custom development. The currently available plugins are:
268+
269+
- The `bdi` plugin (`plugins/bdi`) provides a **BasicDeviceInfo** object node with device-specific properties (See [README](app/plugins/bdi/README.md) for details).
270+
- The `hello_world` plugin (`plugins/hello_world`) creates a **HelloWorldNode** variable (See [README](app/plugins/hello_world/README.md) for details).
271+
- The `ioports` plugin (`plugins/ioports`) exposes I/O port status and control (See [README](app/plugins/ioports/README.md) for details).
272+
- The `simple_event` plugin (`plugins/simple_event`) demonstrates Axis event integration via OPC-UA events (See [README](app/plugins/simple_event/README.md) for details).
273+
- The `thermal` plugin (`plugins/thermal`) exposes thermal camera data and controls (See [README](app/plugins/thermal/README.md) for details).
274+
- The `vinput` plugin (`plugins/vinput`) provides control over virtual inputs (See [README](app/plugins/vinput/README.md) for details).
269275

270276
## Create your own plugin
271277

@@ -276,13 +282,18 @@ GModule APIs.
276282
Create a new directory under *app/plugins/* and copy a Makefile from the example
277283
plugin. Each plugin will need its own Makefile.
278284

285+
The directory structure looks like this,
286+
where **`your_plugin`** represents the new plugin you would create:
287+
279288
```text
280289
opc-ua-plugin-server
281290
├── app
282291
│   ├── include
283292
│   │   ├── error.h
284293
│   │   ├── log.h
285-
│   │   └── plugin.h
294+
│   │   ├── plugin.h
295+
│   │   ├── ua_utils.h
296+
│   │   └── vapix_utils.h
286297
│   ├── LICENSE
287298
│   ├── Makefile
288299
│   ├── manifest.json
@@ -293,19 +304,54 @@ opc-ua-plugin-server
293304
│   ├── opcua_server.c
294305
│   ├── opcua_server.h
295306
│   ├── plugin.c
296-
│   └── plugins
297-
│   ├── bdi
298-
│   │   ├── bdi_plugin.c
299-
│   │   ├── bdi_plugin.h
300-
│   │   └── Makefile
301-
│   ├── hello_world
302-
│   │ ├── hello_world_plugin.c
303-
│   │ ├── hello_world_plugin.h
304-
│   │ └── Makefile
305-
│   └── your_plugin
306-
│   ├── your_plugin.c
307-
│   ├── your_plugin.h
308-
│   └── Makefile
307+
│   ├── plugins
308+
│   │   ├── bdi
309+
│   │   │   ├── bdi_plugin.c
310+
│   │   │   ├── bdi_plugin.h
311+
│   │   │   ├── Makefile
312+
│   │   │   └── README.md
313+
│   │   ├── hello_world
314+
│   │   │   ├── hello_world_plugin.c
315+
│   │   │   ├── hello_world_plugin.h
316+
│   │   │   ├── Makefile
317+
│   │   │   └── README.md
318+
│   │   ├── ioports
319+
│   │   │   ├── ioports_nodeids.h
320+
│   │   │   ├── ioports_ns.c
321+
│   │   │   ├── ioports_ns.h
322+
│   │   │   ├── ioports_plugin.c
323+
│   │   │   ├── ioports_plugin.h
324+
│   │   │   ├── ioports_types.c
325+
│   │   │   ├── ioports_types.h
326+
│   │   │   ├── ioports_vapix.c
327+
│   │   │   ├── ioports_vapix.h
328+
│   │   │   ├── Makefile
329+
│   │   │   └── README.md
330+
│   │   ├── simple_event
331+
│   │   │   ├── Makefile
332+
│   │   │   ├── README.md
333+
│   │   │   ├── simple_event_plugin.c
334+
│   │   │   └── simple_event_plugin.h
335+
│   │   ├── thermal
336+
│   │   │   ├── Makefile
337+
│   │   │   ├── README.md
338+
│   │   │   ├── thermal_plugin.c
339+
│   │   │   ├── thermal_plugin.h
340+
│   │   │   ├── thermal_vapix.c
341+
│   │   │   └── thermal_vapix.h
342+
│   │   ├── vinput
343+
│   │   │   ├── Makefile
344+
│   │   │   ├── README.md
345+
│   │   │   ├── vinput_plugin.c
346+
│   │   │   ├── vinput_plugin.h
347+
│   │   │   ├── vinput_vapix.c
348+
│   │   │   └── vinput_vapix.h
349+
│   │   └── your_plugin
350+
│   │   ├── Makefile
351+
│   │   ├── your_plugin.c
352+
│   │   └── your_plugin.h
353+
│   ├── ua_utils.c
354+
│   └── vapix_utils.c
309355
├── assets
310356
├── CODEOWNERS
311357
├── CONTRIBUTING.md

app/Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ SRCS = $(wildcard *.c)
33
OBJS = $(SRCS:.c=.o)
44
DEPS = $(patsubst %.c,%.d,$(SRCS))
55

6-
PKGS = gio-2.0 glib-2.0 gio-unix-2.0 gmodule-2.0 axparameter
6+
PKGS = gio-2.0 glib-2.0 gio-unix-2.0 gmodule-2.0 libcurl axparameter
77
CFLAGS += $(shell PKG_CONFIG_PATH=$(PKG_CONFIG_PATH) pkg-config --cflags $(PKGS))
88
LDLIBS += $(shell PKG_CONFIG_PATH=$(PKG_CONFIG_PATH) pkg-config --libs $(PKGS))
99

app/include/ua_utils.h

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
/**
2+
* MIT License
3+
*
4+
* Copyright (c) 2025 Axis Communications AB
5+
*
6+
* Permission is hereby granted, free of charge, to any person obtaining a copy
7+
* of this software and associated documentation files (the "Software"), to deal
8+
* in the Software without restriction, including without limitation the rights
9+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10+
* copies of the Software, and to permit persons to whom the Software is
11+
* furnished to do so, subject to the following conditions:
12+
*
13+
* The above copyright notice and this permission notice (including the next
14+
* paragraph) shall be included in all copies or substantial portions of the
15+
* Software.
16+
*
17+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23+
* SOFTWARE.
24+
*/
25+
26+
#ifndef __UA_UTILS_H__
27+
#define __UA_UTILS_H__
28+
29+
#include <glib.h>
30+
#include <open62541/types.h>
31+
32+
typedef struct rollback_data {
33+
/* used to save the existing 'customDataTypes' of the server configuration
34+
* (struct UA_ServerConfig) */
35+
const UA_DataTypeArray *saved_cdt;
36+
37+
/* a list of 'struct UA_NodeId' that have been added to the server */
38+
GList *node_ids;
39+
} rollback_data_t;
40+
41+
/* Performs a 'deep' free() to deallocate the 'rollback_data_t' structure with
42+
* its associated 'node_ids' list */
43+
void
44+
ua_utils_clear_rbd(rollback_data_t **rbd);
45+
46+
/* Loops over the rbd->node_ids list in reverse order and deletes the nodes
47+
* from the information model.
48+
* NOTE: the list is populated by pre-pending, traversing it in forward
49+
* direction will visit the nodes in the reverse order of their addition.
50+
* IMPORTANT: This can only be called before the server thread gets started
51+
* as it can change the server configuration. */
52+
gboolean
53+
ua_utils_do_rollback(UA_Server *server, rollback_data_t *rbd, GError **err);
54+
55+
/* wrapper around the open62541 UA_Server_addObjectNode()
56+
* If underlying UA_Server_addObjectNode() succeeds it also adds the nodeId to
57+
* the rbd (rollback data) */
58+
UA_StatusCode
59+
UA_Server_addObjectNode_rb(UA_Server *server,
60+
const UA_NodeId requestedNewNodeId,
61+
const UA_NodeId parentNodeId,
62+
const UA_NodeId referenceTypeId,
63+
const UA_QualifiedName browseName,
64+
const UA_NodeId typeDefinition,
65+
const UA_ObjectAttributes attr,
66+
void *nodeContext,
67+
rollback_data_t *rbd,
68+
UA_NodeId *outNewNodeId);
69+
70+
/* wrapper around the open62541 UA_Server_addDataTypeNode()
71+
* If underlying UA_Server_addDataTypeNode() succeeds it also adds the nodeId to
72+
* the rbd (rollback data) */
73+
UA_StatusCode
74+
UA_Server_addDataTypeNode_rb(UA_Server *server,
75+
const UA_NodeId requestedNewNodeId,
76+
const UA_NodeId parentNodeId,
77+
const UA_NodeId referenceTypeId,
78+
const UA_QualifiedName browseName,
79+
const UA_DataTypeAttributes attr,
80+
void *nodeContext,
81+
rollback_data_t *rbd,
82+
UA_NodeId *outNewNodeId);
83+
84+
/* wrapper around the open62541 UA_Server_addVariableNode()
85+
* If underlying UA_Server_addVariableNode() succeeds it also adds the nodeId to
86+
* the rbd (rollback data) */
87+
UA_StatusCode
88+
UA_Server_addVariableNode_rb(UA_Server *server,
89+
const UA_NodeId requestedNewNodeId,
90+
const UA_NodeId parentNodeId,
91+
const UA_NodeId referenceTypeId,
92+
const UA_QualifiedName browseName,
93+
const UA_NodeId typeDefinition,
94+
const UA_VariableAttributes attr,
95+
void *nodeContext,
96+
rollback_data_t *rbd,
97+
UA_NodeId *outNewNodeId);
98+
99+
/* wrapper around the open62541 UA_Server_addObjectTypeNode()
100+
* If underlying UA_Server_addObjectTypeNode() succeeds it also adds the nodeId
101+
* to the rbd (rollback data) */
102+
UA_StatusCode
103+
UA_Server_addObjectTypeNode_rb(UA_Server *server,
104+
const UA_NodeId requestedNewNodeId,
105+
const UA_NodeId parentNodeId,
106+
const UA_NodeId referenceTypeId,
107+
const UA_QualifiedName browseName,
108+
const UA_ObjectTypeAttributes attr,
109+
void *nodeContext,
110+
rollback_data_t *rbd,
111+
UA_NodeId *outNewNodeId);
112+
113+
/* wrapper around the open62541 UA_Server_addMethodNode()
114+
* If underlying UA_Server_addMethodNode() succeeds it also adds the nodeId
115+
* to the rbd (rollback data) */
116+
UA_StatusCode
117+
UA_Server_addMethodNode_rb(UA_Server *server,
118+
const UA_NodeId requestedNewNodeId,
119+
const UA_NodeId parentNodeId,
120+
const UA_NodeId referenceTypeId,
121+
const UA_QualifiedName browseName,
122+
const UA_MethodAttributes attr,
123+
UA_MethodCallback method,
124+
size_t inputArgumentsSize,
125+
const UA_Argument *inputArguments,
126+
size_t outputArgumentsSize,
127+
const UA_Argument *outputArguments,
128+
void *nodeContext,
129+
rollback_data_t *rbd,
130+
UA_NodeId *outNewNodeId);
131+
132+
#endif /* __UA_UTILS_H__ */

app/include/vapix_utils.h

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
/**
2+
* MIT License
3+
*
4+
* Copyright (c) 2025 Axis Communications AB
5+
*
6+
* Permission is hereby granted, free of charge, to any person obtaining a copy
7+
* of this software and associated documentation files (the "Software"), to deal
8+
* in the Software without restriction, including without limitation the rights
9+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10+
* copies of the Software, and to permit persons to whom the Software is
11+
* furnished to do so, subject to the following conditions:
12+
*
13+
* The above copyright notice and this permission notice (including the next
14+
* paragraph) shall be included in all copies or substantial portions of the
15+
* Software.
16+
*
17+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23+
* SOFTWARE.
24+
*/
25+
26+
#ifndef __VAPIX_UTILS_H__
27+
#define __VAPIX_UTILS_H__
28+
29+
#include <curl/curl.h>
30+
#include <glib.h>
31+
32+
/* HTTP request methods we use for VAPIX APIs */
33+
typedef enum {
34+
HTTP_GET,
35+
HTTP_POST,
36+
} HTTP_req_method_t;
37+
38+
typedef enum { NONE_data, XML_data, JSON_data } HTTP_media_t;
39+
40+
/**
41+
* vapix_get_credentials:
42+
* @username: a user name which will be used to get credentials for to perform
43+
* VAPIX calls
44+
* @err: return location for a #GError
45+
*
46+
* Returns the required credentials for @username to perform VAPIX calls.
47+
*
48+
* Returns: a newly-allocated string holding the credentials on success, NULL
49+
* if @err is set.
50+
*/
51+
gchar *
52+
vapix_get_credentials(const gchar *username, GError **err);
53+
54+
/**
55+
* vapix_request:
56+
* @handle: a cURL handle obtained with curl_easy_init()
57+
* @credentials: credentials string obtained via vapix_get_credentials()
58+
* @endpoint: the endpoint part of the VAPIX API
59+
* @req_type: HTTP_GET or HTTP_POST
60+
* @post_req: NULL if @req_type is HTTP_GET or a string holding the POST data
61+
* if @req_type is HTTP_POST
62+
* @err: return location for a #GError
63+
*
64+
* Performs a VAPIX API request.
65+
*
66+
* Returns: a newly-allocated string holding the VAPIX response on success, NULL
67+
* if @err is set.
68+
*/
69+
gchar *
70+
vapix_request(CURL *handle,
71+
const gchar *credentials,
72+
const gchar *endpoint,
73+
HTTP_req_method_t req_type,
74+
HTTP_media_t media_type,
75+
const gchar *post_req,
76+
GError **err);
77+
78+
#endif /* __VAPIX_UTILS_H__ */

app/manifest.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,11 @@
2828
"name": "Port",
2929
"type": "int:min=1024,max=65535",
3030
"default": "4840"
31+
},
32+
{
33+
"name": "ExtendLogs",
34+
"type": "bool:no,yes",
35+
"default": "no"
3136
}
3237
]
3338
}

app/opcua_open62541.c

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,9 @@ ua_server_init(app_context_t *ctx,
9595
/* Adjust the logging level for the server thread to be in sync with the
9696
* logging level set for the ACAP via the 'LogLevel' configuration parameter.
9797
*/
98-
config->logging->context = (void *) log_level;
98+
if (ctx->extend_logs) {
99+
config->logging->context = (void *) log_level;
100+
}
99101

100102
/* Name of the server */
101103
UA_String_clear(&config->applicationDescription.applicationName.text);

0 commit comments

Comments
 (0)