diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/nodes_config.c | 467 | ||||
| -rw-r--r-- | src/nodes_config.h | 18 | ||||
| -rw-r--r-- | src/server_register.c | 25 |
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, |
