From 07d29a0b6c1d4f550fa8c69f6bbe20443f0bdec3 Mon Sep 17 00:00:00 2001 From: Thomas Vanbesien Date: Fri, 20 Feb 2026 13:28:27 +0100 Subject: Replace static node values with random updates every second MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove the value field from nodes config — nodes are now created with zero-initialized values, then randomized at startup and every 1000ms via UA_Server_addRepeatedCallback. This is intended for testing OPC UA monitoring and subscriptions. --- src/nodes_config.c | 467 +++++++++++++++++++---------------------------------- 1 file changed, 166 insertions(+), 301 deletions(-) (limited to 'src/nodes_config.c') 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 #include -#include -#include #include #include #include @@ -47,24 +45,6 @@ static const type_map_entry _s_type_map[] = { * Static Helpers * ======================================================================== */ -/** - * @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. * @@ -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)); -- cgit v1.2.3