aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/nodes_config.c467
-rw-r--r--src/nodes_config.h18
-rw-r--r--src/server_register.c25
3 files changed, 207 insertions, 303 deletions
diff --git a/src/nodes_config.c b/src/nodes_config.c
index 79d5919..3468436 100644
--- a/src/nodes_config.c
+++ b/src/nodes_config.c
@@ -11,8 +11,6 @@
#include <open62541/types.h>
#include <open62541/types_generated.h>
-#include <errno.h>
-#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@@ -48,24 +46,6 @@ static const type_map_entry _s_type_map[] = {
* ======================================================================== */
/**
- * @brief Trims leading and trailing whitespace from a string in place.
- *
- * Returns a pointer past any leading whitespace and writes a NUL
- * terminator after the last non-whitespace character.
- */
-static char *
-_s_trim (char *s)
-{
- while (*s == ' ' || *s == '\t')
- s++;
- size_t len = strlen (s);
- while (len > 0 && (s[len - 1] == ' ' || s[len - 1] == '\t'))
- len--;
- s[len] = '\0';
- return s;
-}
-
-/**
* @brief Resolves a type string to a node_type and array flag.
*
* Strips a trailing "[]" suffix if present (setting is_array to true),
@@ -159,220 +139,6 @@ _s_find_max_node_index (const config *cfg)
return max_idx;
}
-/**
- * @brief Parses a single scalar value string into a typed buffer.
- *
- * For numeric types uses strtol/strtod family with range checks.
- * For bool accepts "true"/"false". For string creates a UA_String
- * via UA_String_fromChars (written to out_str).
- *
- * @param str The value string to parse.
- * @param type The target data type.
- * @param out Buffer to write the parsed value into (must be large
- * enough for the target type; unused for string).
- * @param out_str Output for UA_String when type == NODE_TYPE_STRING.
- * @return 0 on success, -1 on parse error.
- */
-static int
-_s_parse_scalar_value (const char *str, node_type type, void *out,
- UA_String *out_str)
-{
- char *endptr;
- errno = 0;
-
- switch (type)
- {
- case NODE_TYPE_BOOL:
- if (strcmp (str, "true") == 0)
- *(UA_Boolean *)out = true;
- else if (strcmp (str, "false") == 0)
- *(UA_Boolean *)out = false;
- else
- return -1;
- return 0;
-
- case NODE_TYPE_INT16:
- {
- long v = strtol (str, &endptr, 10);
- if (*endptr != '\0' || errno == ERANGE || v < INT16_MIN
- || v > INT16_MAX)
- return -1;
- *(UA_Int16 *)out = (UA_Int16)v;
- return 0;
- }
-
- case NODE_TYPE_UINT16:
- {
- unsigned long v = strtoul (str, &endptr, 10);
- if (*endptr != '\0' || errno == ERANGE || v > UINT16_MAX)
- return -1;
- *(UA_UInt16 *)out = (UA_UInt16)v;
- return 0;
- }
-
- case NODE_TYPE_INT32:
- {
- long v = strtol (str, &endptr, 10);
- if (*endptr != '\0' || errno == ERANGE || v < INT32_MIN
- || v > INT32_MAX)
- return -1;
- *(UA_Int32 *)out = (UA_Int32)v;
- return 0;
- }
-
- case NODE_TYPE_UINT32:
- {
- unsigned long v = strtoul (str, &endptr, 10);
- if (*endptr != '\0' || errno == ERANGE || v > UINT32_MAX)
- return -1;
- *(UA_UInt32 *)out = (UA_UInt32)v;
- return 0;
- }
-
- case NODE_TYPE_INT64:
- {
- long long v = strtoll (str, &endptr, 10);
- if (*endptr != '\0' || errno == ERANGE)
- return -1;
- *(UA_Int64 *)out = (UA_Int64)v;
- return 0;
- }
-
- case NODE_TYPE_UINT64:
- {
- unsigned long long v = strtoull (str, &endptr, 10);
- if (*endptr != '\0' || errno == ERANGE)
- return -1;
- *(UA_UInt64 *)out = (UA_UInt64)v;
- return 0;
- }
-
- case NODE_TYPE_FLOAT:
- {
- float v = strtof (str, &endptr);
- if (*endptr != '\0' || errno == ERANGE)
- return -1;
- *(UA_Float *)out = (UA_Float)v;
- return 0;
- }
-
- case NODE_TYPE_DOUBLE:
- {
- double v = strtod (str, &endptr);
- if (*endptr != '\0' || errno == ERANGE)
- return -1;
- *(UA_Double *)out = (UA_Double)v;
- return 0;
- }
-
- case NODE_TYPE_STRING:
- *out_str = UA_String_fromChars (str);
- return 0;
- }
-
- return -1;
-}
-
-/**
- * @brief Counts the number of comma-separated elements in a string.
- */
-static size_t
-_s_count_elements (const char *str)
-{
- if (*str == '\0')
- return 0;
- size_t count = 1;
- for (const char *p = str; *p; p++)
- {
- if (*p == ',')
- count++;
- }
- return count;
-}
-
-/**
- * @brief Parses a comma-separated value string into a UA array.
- *
- * Tokenizes by comma, trims whitespace, parses each element with
- * _s_parse_scalar_value, and writes into a heap-allocated array.
- *
- * @param str The comma-separated value string.
- * @param type The node data type.
- * @param ua_type_index The UA_TYPES index for the data type.
- * @param out_array Output: heap-allocated array (free with
- * UA_Array_delete).
- * @param out_size Output: number of elements.
- * @return 0 on success, -1 on error.
- */
-static int
-_s_parse_array_value (const char *str, node_type type, int ua_type_index,
- void **out_array, size_t *out_size)
-{
- size_t count = _s_count_elements (str);
- if (count == 0)
- {
- *out_array = NULL;
- *out_size = 0;
- return 0;
- }
-
- const UA_DataType *dt = &UA_TYPES[ua_type_index];
- void *array = UA_Array_new (count, dt);
- if (!array)
- return -1;
-
- char *tmp = strdup (str);
- if (!tmp)
- {
- UA_Array_delete (array, count, dt);
- return -1;
- }
-
- size_t i = 0;
- char *saveptr;
- char *token = strtok_r (tmp, ",", &saveptr);
-
- while (token && i < count)
- {
- char *trimmed = _s_trim (token);
- if (*trimmed == '\0')
- {
- UA_LOG_ERROR (UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
- "NodesConfig: empty element at position %zu", i);
- goto fail;
- }
-
- /* Parse into the array element at offset i. */
- void *elem = (char *)array + i * dt->memSize;
- UA_String str_val = UA_STRING_NULL;
-
- if (_s_parse_scalar_value (trimmed, type, elem, &str_val) != 0)
- {
- UA_LOG_ERROR (UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
- "NodesConfig: failed to parse array element '%s'",
- trimmed);
- goto fail;
- }
-
- /* For strings, copy into the array slot. */
- if (type == NODE_TYPE_STRING)
- *(UA_String *)elem = str_val;
-
- i++;
- token = strtok_r (NULL, ",", &saveptr);
- }
-
- free (tmp);
- *out_array = array;
- *out_size = count;
- return 0;
-
-fail:
- free (tmp);
- UA_Array_delete (array, count, dt);
- return -1;
-}
-
/* ========================================================================
* Parsing: nodes_config_load
* ======================================================================== */
@@ -387,7 +153,6 @@ _s_free_raw_nodes (node_config *raw, size_t count)
{
free (raw[i].name);
free (raw[i].description);
- free (raw[i].value_str);
}
free (raw);
}
@@ -467,11 +232,6 @@ nodes_config_load (const char *path, nodes_config *out)
}
type_set[idx] = true;
}
- else if (strcmp (field, "value") == 0)
- {
- free (raw[idx].value_str);
- raw[idx].value_str = strdup (value);
- }
else if (strcmp (field, "accessLevel") == 0)
{
if (_s_resolve_access (value, &raw[idx].access) != 0)
@@ -524,14 +284,6 @@ nodes_config_load (const char *path, nodes_config *out)
rc = -1;
goto done;
}
- if (!raw[i].value_str)
- {
- UA_LOG_ERROR (UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
- "NodesConfig: node.%zu missing required key 'value'",
- i);
- rc = -1;
- goto done;
- }
if (!access_set[i])
{
UA_LOG_ERROR (
@@ -579,6 +331,9 @@ done:
* Node Creation: nodes_config_add
* ======================================================================== */
+/** Default number of elements for array-typed nodes. */
+#define NODES_DEFAULT_ARRAY_SIZE 5
+
int
nodes_config_add (UA_Server *server, const nodes_config *nc)
{
@@ -610,77 +365,55 @@ nodes_config_add (UA_Server *server, const nodes_config *nc)
else
attr.accessLevel = UA_ACCESSLEVELMASK_READ | UA_ACCESSLEVELMASK_WRITE;
- /* Parse value and set the variant. */
- UA_Boolean value_ok = false;
-
- /* Union large enough for any scalar type. */
- union
- {
- UA_Boolean b;
- UA_Int16 i16;
- UA_UInt16 u16;
- UA_Int32 i32;
- UA_UInt32 u32;
- UA_Int64 i64;
- UA_UInt64 u64;
- UA_Float f;
- UA_Double d;
- } scalar_buf;
- UA_String string_buf = UA_STRING_NULL;
+ /* Initialize with zero/default values. The caller should call
+ nodes_config_randomize() afterwards to assign random values. */
void *array_buf = NULL;
- size_t array_size = 0;
UA_UInt32 array_dim = 0;
if (!n->is_array)
{
- /* Scalar */
- if (_s_parse_scalar_value (n->value_str, n->type, &scalar_buf,
- &string_buf)
- != 0)
+ union
+ {
+ UA_Boolean b;
+ UA_Int16 i16;
+ UA_UInt16 u16;
+ UA_Int32 i32;
+ UA_UInt32 u32;
+ UA_Int64 i64;
+ UA_UInt64 u64;
+ UA_Float f;
+ UA_Double d;
+ } zero = { 0 };
+ if (n->type == NODE_TYPE_STRING)
{
- UA_LOG_ERROR (UA_Log_Stdout, UA_LOGCATEGORY_SERVER,
- "NodesConfig: failed to parse value '%s' "
- "for node '%s'",
- n->value_str, n->name);
- failures++;
- continue;
+ UA_String empty_str = UA_STRING ("");
+ UA_Variant_setScalarCopy (&attr.value, &empty_str, dt);
}
-
- if (n->type == NODE_TYPE_STRING)
- UA_Variant_setScalar (&attr.value, &string_buf, dt);
else
- UA_Variant_setScalar (&attr.value, &scalar_buf, dt);
+ {
+ UA_Variant_setScalar (&attr.value, &zero, dt);
+ }
attr.valueRank = UA_VALUERANK_SCALAR;
- value_ok = true;
}
else
{
- /* Array */
- if (_s_parse_array_value (n->value_str, n->type, ua_idx, &array_buf,
- &array_size)
- != 0)
+ array_buf = UA_Array_new (NODES_DEFAULT_ARRAY_SIZE, dt);
+ if (!array_buf)
{
UA_LOG_ERROR (UA_Log_Stdout, UA_LOGCATEGORY_SERVER,
- "NodesConfig: failed to parse array value "
- "for node '%s'",
+ "NodesConfig: out of memory for node '%s'",
n->name);
failures++;
continue;
}
- UA_Variant_setArray (&attr.value, array_buf, array_size, dt);
+ UA_Variant_setArray (&attr.value, array_buf,
+ NODES_DEFAULT_ARRAY_SIZE, dt);
attr.valueRank = UA_VALUERANK_ONE_DIMENSION;
- array_dim = (UA_UInt32)array_size;
+ array_dim = (UA_UInt32)NODES_DEFAULT_ARRAY_SIZE;
attr.arrayDimensions = &array_dim;
attr.arrayDimensionsSize = 1;
- value_ok = true;
- }
-
- if (!value_ok)
- {
- failures++;
- continue;
}
UA_NodeId node_id = UA_NODEID_STRING (1, n->name);
@@ -709,12 +442,145 @@ nodes_config_add (UA_Server *server, const nodes_config *nc)
n->access == NODE_ACCESS_READ ? "read" : "readwrite");
}
- /* Clean up parsed values. UA_Server_addVariableNode deep-copies
- everything, so our local buffers can be freed now. */
+ /* UA_Server_addVariableNode deep-copies everything. */
if (n->type == NODE_TYPE_STRING && !n->is_array)
- UA_String_clear (&string_buf);
+ UA_Variant_clear (&attr.value);
if (array_buf)
- UA_Array_delete (array_buf, array_size, dt);
+ UA_Array_delete (array_buf, NODES_DEFAULT_ARRAY_SIZE, dt);
+ }
+
+ return failures > 0 ? -1 : 0;
+}
+
+/* ========================================================================
+ * Random Value Generation: nodes_config_randomize
+ * ======================================================================== */
+
+/**
+ * @brief Fills a buffer with a random value for the given node type.
+ *
+ * For non-string types the value is written into @p out.
+ * For NODE_TYPE_STRING a heap-allocated UA_String is written into
+ * @p out_str (the caller must UA_String_clear it).
+ */
+static void
+_s_random_scalar (node_type type, void *out, UA_String *out_str)
+{
+ switch (type)
+ {
+ case NODE_TYPE_BOOL:
+ *(UA_Boolean *)out = (UA_Boolean)(rand () % 2);
+ break;
+ case NODE_TYPE_INT16:
+ *(UA_Int16 *)out = (UA_Int16)(rand () % 201 - 100);
+ break;
+ case NODE_TYPE_UINT16:
+ *(UA_UInt16 *)out = (UA_UInt16)(rand () % 1000);
+ break;
+ case NODE_TYPE_INT32:
+ *(UA_Int32 *)out = (UA_Int32)(rand () % 2001 - 1000);
+ break;
+ case NODE_TYPE_UINT32:
+ *(UA_UInt32 *)out = (UA_UInt32)(rand () % 10000);
+ break;
+ case NODE_TYPE_INT64:
+ *(UA_Int64 *)out = (UA_Int64)(rand () % 2001 - 1000);
+ break;
+ case NODE_TYPE_UINT64:
+ *(UA_UInt64 *)out = (UA_UInt64)(rand () % 10000);
+ break;
+ case NODE_TYPE_FLOAT:
+ *(UA_Float *)out = (UA_Float)rand () / (UA_Float)RAND_MAX * 100.0f;
+ break;
+ case NODE_TYPE_DOUBLE:
+ *(UA_Double *)out = (UA_Double)rand () / (UA_Double)RAND_MAX * 100.0;
+ break;
+ case NODE_TYPE_STRING:
+ {
+ char buf[32];
+ snprintf (buf, sizeof (buf), "str_%d", rand () % 10000);
+ *out_str = UA_String_fromChars (buf);
+ break;
+ }
+ }
+}
+
+int
+nodes_config_randomize (UA_Server *server, const nodes_config *nc)
+{
+ int failures = 0;
+
+ for (size_t i = 0; i < nc->count; i++)
+ {
+ const node_config *n = &nc->nodes[i];
+ int ua_idx = _s_ua_type_index (n->type);
+ if (ua_idx < 0)
+ {
+ failures++;
+ continue;
+ }
+
+ const UA_DataType *dt = &UA_TYPES[ua_idx];
+ UA_NodeId node_id = UA_NODEID_STRING (1, n->name);
+ UA_Variant value;
+ UA_Variant_init (&value);
+
+ if (!n->is_array)
+ {
+ union
+ {
+ UA_Boolean b;
+ UA_Int16 i16;
+ UA_UInt16 u16;
+ UA_Int32 i32;
+ UA_UInt32 u32;
+ UA_Int64 i64;
+ UA_UInt64 u64;
+ UA_Float f;
+ UA_Double d;
+ } buf;
+ UA_String str_buf = UA_STRING_NULL;
+
+ _s_random_scalar (n->type, &buf, &str_buf);
+
+ if (n->type == NODE_TYPE_STRING)
+ {
+ UA_Variant_setScalarCopy (&value, &str_buf, dt);
+ UA_String_clear (&str_buf);
+ }
+ else
+ {
+ UA_Variant_setScalarCopy (&value, &buf, dt);
+ }
+ }
+ else
+ {
+ void *array = UA_Array_new (NODES_DEFAULT_ARRAY_SIZE, dt);
+ if (!array)
+ {
+ failures++;
+ continue;
+ }
+
+ for (size_t j = 0; j < NODES_DEFAULT_ARRAY_SIZE; j++)
+ {
+ void *elem = (char *)array + j * dt->memSize;
+ UA_String str_buf = UA_STRING_NULL;
+ _s_random_scalar (n->type, elem, &str_buf);
+ if (n->type == NODE_TYPE_STRING)
+ *(UA_String *)elem = str_buf;
+ }
+
+ UA_Variant_setArrayCopy (&value, array, NODES_DEFAULT_ARRAY_SIZE,
+ dt);
+ UA_Array_delete (array, NODES_DEFAULT_ARRAY_SIZE, dt);
+ }
+
+ UA_StatusCode rv = UA_Server_writeValue (server, node_id, value);
+ UA_Variant_clear (&value);
+
+ if (rv != UA_STATUSCODE_GOOD)
+ failures++;
}
return failures > 0 ? -1 : 0;
@@ -731,7 +597,6 @@ nodes_config_free (nodes_config *nc)
{
free (nc->nodes[i].name);
free (nc->nodes[i].description);
- free (nc->nodes[i].value_str);
}
free (nc->nodes);
memset (nc, 0, sizeof (*nc));
diff --git a/src/nodes_config.h b/src/nodes_config.h
index 0207a55..cb42942 100644
--- a/src/nodes_config.h
+++ b/src/nodes_config.h
@@ -8,7 +8,8 @@
* Reads a config file containing node.N.field entries, parses them into
* an array of node descriptors, and creates corresponding OPC UA variable
* nodes in the server address space. Each node gets a string NodeId in
- * namespace 1 (ns=1;s=<name>).
+ * namespace 1 (ns=1;s=<name>). Nodes are created with zero-initialized
+ * values; call nodes_config_randomize() to assign random values.
*
* Supported types (scalar and 1D array via "type[]" suffix):
* bool, int16, uint16, int32, uint32, int64, uint64, float, double, string
@@ -62,7 +63,6 @@ typedef struct
char *description; /**< Optional description text (NULL when absent). */
node_type type; /**< OPC UA data type. */
UA_Boolean is_array; /**< true when the type name ended with "[]". */
- char *value_str; /**< Raw value string (comma-separated for arrays). */
node_access_level access; /**< Read or read-write. */
} node_config;
@@ -114,6 +114,20 @@ int nodes_config_load (const char *path, nodes_config *out);
int nodes_config_add (UA_Server *server, const nodes_config *nc);
/**
+ * @brief Writes random values to all configured variable nodes.
+ *
+ * Generates a random value appropriate for each node's type and writes
+ * it to the server using UA_Server_writeValue. For array nodes, each
+ * element is randomized independently. Call srand() before the first
+ * invocation to seed the random number generator.
+ *
+ * @param server The UA_Server containing the nodes.
+ * @param nc The parsed nodes configuration.
+ * @return 0 when all nodes were updated, -1 if any write failed.
+ */
+int nodes_config_randomize (UA_Server *server, const nodes_config *nc);
+
+/**
* @brief Frees all memory owned by a nodes_config structure.
*
* After this call the structure is zeroed and must not be used
diff --git a/src/server_register.c b/src/server_register.c
index b938ca4..bbd1c31 100644
--- a/src/server_register.c
+++ b/src/server_register.c
@@ -102,6 +102,21 @@ _s_deregister_from_lds (UA_Server *server, const lds_client_params *p,
}
/* ========================================================================
+ * Node-Update Callback
+ * ======================================================================== */
+
+/**
+ * Repeated callback that randomizes all configured variable node values.
+ * Registered via UA_Server_addRepeatedCallback with a nodes_config pointer.
+ */
+static void
+_s_update_nodes_cb (UA_Server *server, void *data)
+{
+ nodes_config *nc = (nodes_config *)data;
+ nodes_config_randomize (server, nc);
+}
+
+/* ========================================================================
* Main
* ======================================================================== */
@@ -110,6 +125,7 @@ main (int argc, char **argv)
{
signal (SIGINT, _s_stop_handler);
signal (SIGTERM, _s_stop_handler);
+ srand ((unsigned)time (NULL));
if (argc < 4 || argc > 6)
{
@@ -241,6 +257,15 @@ main (int argc, char **argv)
can periodically re-register with the LDS between iterations. */
UA_Server_run_startup (server);
+ /* Assign initial random values and schedule periodic updates. */
+ UA_UInt64 update_cb_id = 0;
+ if (nodes_cfg.count > 0)
+ {
+ nodes_config_randomize (server, &nodes_cfg);
+ UA_Server_addRepeatedCallback (server, _s_update_nodes_cb, &nodes_cfg,
+ 1000, &update_cb_id);
+ }
+
retval = _s_register_with_lds (server, &lds_params, discovery_endpoint);
if (retval != UA_STATUSCODE_GOOD)
UA_LOG_WARNING (UA_Log_Stdout, UA_LOGCATEGORY_SERVER,