From 02eeb40728eec991a30e11019627c8b1bdc3e16a Mon Sep 17 00:00:00 2001 From: alde103 Date: Wed, 17 Jun 2020 20:45:06 -0500 Subject: [PATCH] UA_Variant refactor for read funtions --- lib/opc_ua/common.ex | 33 ++ src/common.c | 360 ++++++++++++--------- src/common.h | 5 +- src/opc_ua_client.c | 124 +------ src/opc_ua_server.c | 1 + test/client_tests/arrays_test.exs | 99 ++++-- test/client_tests/write_read_attr_test.exs | 3 + 7 files changed, 337 insertions(+), 288 deletions(-) diff --git a/lib/opc_ua/common.ex b/lib/opc_ua/common.ex index 48c70e7..8e69e33 100644 --- a/lib/opc_ua/common.ex +++ b/lib/opc_ua/common.ex @@ -374,6 +374,21 @@ defmodule OpcUA.Common do end end + @doc """ + Reads 'value' attribute of a node in the server. + Note: If the value is an array you can search a scalar using `index` parameter. + """ + @spec read_node_value_by_index(GenServer.server(), %NodeId{}, integer()) :: + {:ok, term()} | {:error, binary()} | {:error, :einval} + def read_node_value_by_index(pid, node_id, index \\ 0) do + if(@mix_env != :test) do + GenServer.call(pid, {:read, {:value_by_index, {node_id, index}}}) + else + # Valgrind + GenServer.call(pid, {:read, {:value_by_index, {node_id, index}}}, :infinity) + end + end + @doc """ Reads 'Value' attribute (matching data type) of a node in the server. """ @@ -639,6 +654,12 @@ defmodule OpcUA.Common do {:noreply, state} end + def handle_call({:read, {:value_by_index, {node_id, index}}}, caller_info, state) do + c_args = {to_c(node_id), index} + call_port(state, :read_node_value_by_index, caller_info, c_args) + {:noreply, state} + end + def handle_call({:read, {:value_by_data_type, {node_id, data_type}}}, caller_info, state) do c_args = {to_c(node_id), data_type} call_port(state, :read_node_value_by_data_type, caller_info, c_args) @@ -846,6 +867,12 @@ defmodule OpcUA.Common do state end + defp handle_c_response({:read_node_value_by_index, caller_metadata, value_response}, state) do + response = parse_value(value_response) + GenServer.reply(caller_metadata, response) + state + end + defp handle_c_response( {:read_node_value_by_data_type, caller_metadata, value_response}, state @@ -948,6 +975,9 @@ defmodule OpcUA.Common do defp parse_value({:ok, {ns_index, name}}) when is_integer(ns_index), do: {:ok, QualifiedName.new(ns_index: ns_index, name: name)} + defp parse_value({:ok, array}) when is_list(array), + do: {:ok, Enum.map(array, fn(data) -> parse_c_value(data) end)} + defp parse_value(response), do: response defp parse_c_value({ns_index, type, name, name_space_uri, server_index}), @@ -970,6 +1000,9 @@ defmodule OpcUA.Common do defp parse_c_value({ns_index, name}) when is_integer(ns_index), do: QualifiedName.new(ns_index: ns_index, name: name) + defp parse_c_value(array) when is_list(array), + do: Enum.map(array, fn(data) -> parse_c_value(data) end) + defp parse_c_value(response), do: response @doc false diff --git a/src/common.c b/src/common.c index 962e5ac..756e4ee 100644 --- a/src/common.c +++ b/src/common.c @@ -645,9 +645,162 @@ void encode_array_dimensions_struct(char *resp, int *resp_index, void *data, int ei_encode_empty_list(resp, resp_index); } -void encode_variant_struct(char *resp, int *resp_index, void *data, int data_len) +void encode_variant_scalar_struct(char *resp, int *resp_index, void *data, size_t index) { - return; + UA_Variant value = *(UA_Variant *) data; + switch (value.type->typeIndex) + { + case UA_TYPES_BOOLEAN: + ei_encode_boolean(resp, resp_index, *((UA_Boolean *)value.data + index)); + break; + + case UA_TYPES_SBYTE: + ei_encode_long(resp, resp_index, *((UA_SByte *)value.data + index)); + break; + + case UA_TYPES_BYTE: + ei_encode_ulong(resp, resp_index, *((UA_Byte *)value.data + index)); + break; + + case UA_TYPES_INT16: + ei_encode_long(resp, resp_index, *((UA_Int16 *)value.data + index)); + break; + + case UA_TYPES_UINT16: + ei_encode_ulong(resp, resp_index, *((UA_UInt16 *)value.data + index)); + break; + + case UA_TYPES_INT32: + ei_encode_long(resp, resp_index, *((UA_Int32 *)value.data + index)); + break; + + case UA_TYPES_UINT32: + ei_encode_ulong(resp, resp_index, *((UA_UInt32 *)value.data + index)); + break; + + case UA_TYPES_INT64: + ei_encode_longlong(resp, resp_index, *((UA_Int64 *)value.data + index)); + break; + + case UA_TYPES_UINT64: + ei_encode_ulonglong(resp, resp_index, *((UA_UInt64 *)value.data + index)); + break; + + case UA_TYPES_FLOAT: + encode_ua_float(resp, resp_index, ((UA_Float *)value.data + index)); + break; + + case UA_TYPES_DOUBLE: + ei_encode_double(resp, resp_index, *((UA_Double *)value.data + index)); + break; + + case UA_TYPES_STRING: + ei_encode_binary(resp, resp_index, (*((UA_String *)value.data + index)).data, (*((UA_String *)value.data + index)).length); + break; + + case UA_TYPES_DATETIME: + ei_encode_ulonglong(resp, resp_index, *((UA_DateTime *)value.data + index)); + break; + + case UA_TYPES_GUID: + encode_ua_guid(resp, resp_index, ((UA_Guid *)value.data + index)); + break; + + case UA_TYPES_BYTESTRING: + ei_encode_binary(resp, resp_index, (*((UA_ByteString *)value.data + index)).data, (*((UA_ByteString *)value.data + index)).length); + break; + + case UA_TYPES_XMLELEMENT: + ei_encode_binary(resp, resp_index, (*((UA_XmlElement *)value.data + index)).data, (*((UA_XmlElement *)value.data + index)).length); + break; + + case UA_TYPES_NODEID: + encode_node_id(resp, resp_index, ((UA_NodeId *)value.data + index)); + break; + + case UA_TYPES_EXPANDEDNODEID: + encode_expanded_node_id(resp, resp_index, ((UA_ExpandedNodeId *)value.data + index)); + break; + + case UA_TYPES_STATUSCODE: + encode_status_code(resp, resp_index, ((UA_StatusCode *)value.data + index)); + break; + + case UA_TYPES_QUALIFIEDNAME: + encode_qualified_name(resp, resp_index, ((UA_QualifiedName *)value.data + index)); + break; + + case UA_TYPES_LOCALIZEDTEXT: + encode_localized_text(resp, resp_index, ((UA_LocalizedText *)value.data + index)); + break; + + // // TODO: UA_TYPES_EXTENSIONOBJECT + + // // TODO: UA_TYPES_DATAVALUE + + // // TODO: UA_TYPES_VARIANT + + // // TODO: UA_TYPES_DIAGNOSTICINFO + + case UA_TYPES_SEMANTICCHANGESTRUCTUREDATATYPE: + encode_semantic_change_structure_data_type(resp, resp_index, ((UA_SemanticChangeStructureDataType *)value.data + index)); + break; + + case UA_TYPES_TIMESTRING: + ei_encode_binary(resp, resp_index, (*((UA_TimeString *)value.data + index)).data, (*((UA_TimeString *)value.data + index)).length); + break; + + // // TODO: UA_TYPES_VIEWATTRIBUTES + + case UA_TYPES_UADPNETWORKMESSAGECONTENTMASK: + ei_encode_ulong(resp, resp_index, *((UA_UadpDataSetMessageContentMask *)value.data + index)); + break; + + case UA_TYPES_XVTYPE: + encode_xv_type(resp, resp_index, ((UA_XVType *)value.data + index)); + break; + + case UA_TYPES_ELEMENTOPERAND: + ei_encode_long(resp, resp_index, (*((UA_ElementOperand *)value.data + index)).index); + break; + + default: + ei_encode_atom(resp, resp_index, "error"); + break; + } +} + +void encode_variant_array_struct(char *resp, int *resp_index, void *data) +{ + UA_Variant value = *(UA_Variant *) data; + + ei_encode_list_header(resp, resp_index, value.arrayLength); + + for(size_t i = 0; i < value.arrayLength; i++) + { + encode_variant_scalar_struct(resp, resp_index, data, i); + } + + if(value.arrayLength) + ei_encode_empty_list(resp, resp_index); +} + + +void encode_variant_struct(char *resp, int *resp_index, void *data) +{ + if(UA_Variant_isEmpty((UA_Variant *)data)) + { + ei_encode_atom(resp, resp_index, "nil"); + return; + } + + if(UA_Variant_isScalar((UA_Variant *)data)) + { + encode_variant_scalar_struct(resp, resp_index, data, 0); + return; + } + + encode_variant_array_struct(resp, resp_index, data); } void encode_data_response(char *resp, int *resp_index, void *data, int data_type, int data_len) @@ -771,7 +924,7 @@ void encode_data_response(char *resp, int *resp_index, void *data, int data_type break; case 29: //UA_Variant - encode_variant_struct(resp, resp_index, data, data_len); + encode_variant_struct(resp, resp_index, data); break; default: @@ -803,7 +956,6 @@ void decode_caller_metadata(const char *req, int *req_index, const char* cmd) caller_ref = malloc(sizeof(erlang_ref)); if (ei_decode_ref(req, req_index, caller_ref) < 0) errx(EXIT_FAILURE, "Expecting ref"); - } void free_caller_metadata() @@ -856,9 +1008,9 @@ void send_subscription_deleted_response(void *data, int data_type, int data_len) /** * @brief Send changed data back to Elixir in form of {:subscription, {:data, subId, monId, data}} */ -void send_monitored_item_response(void *subscription_id, void *monitored_id, void *data, int data_type, int data_len) +void send_monitored_item_response(void *subscription_id, void *monitored_id, void *data, int data_type) { - char resp[1024]; + char resp[ERLCMD_BUF_SIZE]; long i_struct; int resp_index = sizeof(uint16_t); // Space for payload size resp[resp_index++] = response_id; @@ -871,10 +1023,7 @@ void send_monitored_item_response(void *subscription_id, void *monitored_id, voi encode_data_response(resp, &resp_index, subscription_id, 27, 0); encode_data_response(resp, &resp_index, monitored_id, 27, 0); - if(data_len != -1) - encode_data_response(resp, &resp_index, data, data_type, data_len); - else - ei_encode_atom(resp, &resp_index, "error"); + encode_data_response(resp, &resp_index, data, data_type, 0); erlcmd_send(resp, resp_index); } @@ -903,9 +1052,9 @@ void send_monitored_item_delete_response(void *subscription_id, void *monitored_ /** * @brief Send write data back to Elixir in form of {:write, node_id, value} */ -void send_write_data_response(const UA_NodeId *nodeId, void *data, int data_type, int data_len) +void send_write_data_response(const UA_NodeId *nodeId, void *data, int data_type) { - char resp[1024]; + char resp[ERLCMD_BUF_SIZE]; long i_struct; int resp_index = sizeof(uint16_t); // Space for payload size resp[resp_index++] = response_id; @@ -914,11 +1063,7 @@ void send_write_data_response(const UA_NodeId *nodeId, void *data, int data_type ei_encode_atom(resp, &resp_index, "write"); encode_node_id(resp, &resp_index, (UA_NodeId *) nodeId); - - if(data_len != -1) - encode_data_response(resp, &resp_index, data, data_type, data_len); - else - ei_encode_atom(resp, &resp_index, "error"); + encode_data_response(resp, &resp_index, data, data_type, 0); erlcmd_send(resp, resp_index); } @@ -928,7 +1073,7 @@ void send_write_data_response(const UA_NodeId *nodeId, void *data, int data_type */ void send_data_response(void *data, int data_type, int data_len) { - char resp[1024]; + char resp[ERLCMD_BUF_SIZE]; long i_struct; int resp_index = sizeof(uint16_t); // Space for payload size resp[resp_index++] = response_id; @@ -1015,126 +1160,8 @@ void send_write_response(UA_Server *server, return; } - switch(data->value.type->typeIndex) - { - case UA_TYPES_BOOLEAN: - send_write_data_response(nodeId, data->value.data, 0, 0); - break; - - case UA_TYPES_SBYTE: - send_write_data_response(nodeId, data->value.data, 23, 0); - break; - - case UA_TYPES_BYTE: - send_write_data_response(nodeId, data->value.data, 24, 0); - break; - - case UA_TYPES_INT16: - send_write_data_response(nodeId, data->value.data, 25, 0); - break; - - case UA_TYPES_UINT16: - send_write_data_response(nodeId, data->value.data, 26, 0); - break; - - case UA_TYPES_INT32: - send_write_data_response(nodeId, data->value.data, 1, 0); - break; - - case UA_TYPES_UINT32: - send_write_data_response(nodeId, data->value.data, 2, 0); - break; - - case UA_TYPES_INT64: - send_write_data_response(nodeId, data->value.data, 15, 0); - break; - - case UA_TYPES_UINT64: - send_write_data_response(nodeId, data->value.data, 16, 0); - break; - - case UA_TYPES_FLOAT: - send_write_data_response(nodeId, data->value.data, 17, 0); - break; - - case UA_TYPES_DOUBLE: - send_write_data_response(nodeId, data->value.data, 4, 0); - break; - - case UA_TYPES_STRING: - send_write_data_response(nodeId, (*(UA_String *)data->value.data).data, 5, (*(UA_String *)data->value.data).length); - break; - - case UA_TYPES_DATETIME: - send_write_data_response(nodeId, data->value.data, 15, 0); - break; - - case UA_TYPES_GUID: - send_write_data_response(nodeId, data->value.data, 18, 0); - break; - - case UA_TYPES_BYTESTRING: - send_write_data_response(nodeId, (*(UA_ByteString *)data->value.data).data, 5, (*(UA_ByteString *)data->value.data).length); - break; - - case UA_TYPES_XMLELEMENT: - send_write_data_response(nodeId, (*(UA_XmlElement *)data->value.data).data, 5, (*(UA_XmlElement *)data->value.data).length); - break; - - case UA_TYPES_NODEID: - send_write_data_response(nodeId, data->value.data, 12, 0); - break; - - case UA_TYPES_EXPANDEDNODEID: - send_write_data_response(nodeId, data->value.data, 19, 0); - break; - - case UA_TYPES_STATUSCODE: - send_write_data_response(nodeId, data->value.data, 20, 0); - break; - - case UA_TYPES_QUALIFIEDNAME: - send_write_data_response(nodeId, data->value.data, 13, 0); - break; - - case UA_TYPES_LOCALIZEDTEXT: - send_write_data_response(nodeId, data->value.data, 14, 0); - break; - - // TODO: UA_TYPES_EXTENSIONOBJECT - - // TODO: UA_TYPES_DATAVALUE - - // TODO: UA_TYPES_VARIANT - - // TODO: UA_TYPES_DIAGNOSTICINFO - - case UA_TYPES_SEMANTICCHANGESTRUCTUREDATATYPE: - send_write_data_response(nodeId, data->value.data, 21, 0); - break; - - case UA_TYPES_TIMESTRING: - send_write_data_response(nodeId, (*(UA_TimeString *)data->value.data).data, 5, (*(UA_TimeString *)data->value.data).length); - break; - - // TODO: UA_TYPES_VIEWATTRIBUTES - - case UA_TYPES_UADPNETWORKMESSAGECONTENTMASK: - send_write_data_response(nodeId, data->value.data, 2, 0); - break; - - case UA_TYPES_XVTYPE: - send_write_data_response(nodeId, data->value.data, 22, 0); - break; - - case UA_TYPES_ELEMENTOPERAND: - send_write_data_response(nodeId, data->value.data, 27, 0); - break; - - default: - send_write_data_response(nodeId, data->value.data, 2, -1); - break; - } + UA_Variant variant = data->value; + send_write_data_response(nodeId, &variant, 29); } /******************************/ @@ -1150,7 +1177,6 @@ void handle_add_variable_node(void *entity, bool entity_type, const char *req, i int term_type; UA_StatusCode retval; - if(ei_decode_tuple_header(req, req_index, &term_size) < 0 || term_size != 5) errx(EXIT_FAILURE, ":handle_add_variable_node requires a 5-tuple, term_size = %d", term_size); @@ -2696,13 +2722,7 @@ void handle_write_node_value(void *entity, bool entity_type, const char *req, in UA_NodeId_clear(&node_id); - UA_NodeId_clear(&node_id_arg_1); - UA_NodeId_clear(&node_id_arg_2); - if(data_type == UA_TYPES_EXPANDEDNODEID) - UA_ExpandedNodeId_clear(&expanded_node_id_arg_1); - if(data_type == UA_TYPES_QUALIFIEDNAME) - UA_QualifiedName_clear(&qualified_name); if(retval != UA_STATUSCODE_GOOD) { send_opex_response(retval); @@ -2719,6 +2739,14 @@ void handle_write_node_value(void *entity, bool entity_type, const char *req, in free(arg1); if(arg1 != NULL) free(arg2); + + UA_NodeId_clear(&node_id_arg_1); + UA_NodeId_clear(&node_id_arg_2); + + if(data_type == UA_TYPES_EXPANDEDNODEID) + UA_ExpandedNodeId_clear(&expanded_node_id_arg_1); + if(data_type == UA_TYPES_QUALIFIEDNAME) + UA_QualifiedName_clear(&qualified_name); } send_ok_response(); @@ -3718,6 +3746,48 @@ void handle_read_node_value(void *entity, bool entity_type, const char *req, int return; } + send_data_response(value, 29, 0); + + UA_Variant_clear(value); + UA_Variant_delete(value); +} + +/* + * Read 'value' of a node in the server. + */ +void handle_read_node_value_by_index(void *entity, bool entity_type, const char *req, int *req_index) +{ + int term_size; + UA_Variant *value = UA_Variant_new(); + UA_Variant_init(value); + UA_StatusCode retval; + + if(ei_decode_tuple_header(req, req_index, &term_size) < 0 || + term_size != 2) + errx(EXIT_FAILURE, ":handle_read_node_value requires a 2-tuple, term_size = %d", term_size); + + UA_NodeId node_id = assemble_node_id(req, req_index); + + unsigned long data_index; + if (ei_decode_ulong(req, req_index, &data_index) < 0) { + send_error_response("einval"); + return; + } + + if(entity_type) + retval = UA_Client_readValueAttribute((UA_Client *)entity, node_id, value); + else + retval = UA_Server_readValue((UA_Server *)entity, node_id, value); + + UA_NodeId_clear(&node_id); + + if(retval != UA_STATUSCODE_GOOD) { + UA_Variant_clear(value); + UA_Variant_delete(value); + send_opex_response(retval); + return; + } + if(UA_Variant_isEmpty(value)) { UA_Variant_clear(value); UA_Variant_delete(value); diff --git a/src/common.h b/src/common.h index 648338a..ead8f71 100644 --- a/src/common.h +++ b/src/common.h @@ -24,6 +24,7 @@ #include //#define DEBUG +#define ERLCMD_BUF_SIZE 32768 #ifdef DEBUG FILE *log_location; @@ -46,7 +47,6 @@ FILE *log_location; #define ONE_YEAR_MILLIS (1000ULL * 60 * 60 * 24 * 365) uint64_t current_time(); - #endif // UTIL_H static erlang_pid *caller_pid; @@ -67,7 +67,7 @@ void encode_array_dimensions_struct(char *resp, int *resp_index, void *data, int void encode_server_config(char *resp, int *resp_index, void *data); void send_subscription_timeout_response(void *data, int data_type, int data_len); void send_subscription_deleted_response(void *data, int data_type, int data_len); -void send_monitored_item_response(void *subscription_id, void *monitored_id, void *data, int data_type, int data_len); +void send_monitored_item_response(void *subscription_id, void *monitored_id, void *data, int data_type); void send_monitored_item_delete_response(void *subscription_id, void *monitored_id); void send_data_response(void *data, int data_type, int data_len); void send_error_response(const char *reason); @@ -127,4 +127,5 @@ void handle_read_node_historizing(void *entity, bool entity_type, const char *re void handle_read_node_executable(void *entity, bool entity_type, const char *req, int *req_index); void handle_read_node_event_notifier(void *entity, bool entity_type, const char *req, int *req_index); void handle_read_node_value(void *entity, bool entity_type, const char *req, int *req_index); +void handle_read_node_value_by_index(void *entity, bool entity_type, const char *req, int *req_index); void handle_read_node_value_by_data_type(void *entity, bool entity_type, const char *req, int *req_index); \ No newline at end of file diff --git a/src/opc_ua_client.c b/src/opc_ua_client.c index c4e9e58..d189ac7 100644 --- a/src/opc_ua_client.c +++ b/src/opc_ua_client.c @@ -28,127 +28,8 @@ static void deleteSubscriptionCallback(UA_Client *client, UA_UInt32 subscription static void dataChangeNotificationCallback(UA_Client *client, UA_UInt32 subscription_id, void *subContext, UA_UInt32 monitored_id, void *monContext, UA_DataValue *data) { - switch(data->value.type->typeIndex) - { - case UA_TYPES_BOOLEAN: - send_monitored_item_response(&subscription_id, &monitored_id, data->value.data, 0, 0); - break; - - case UA_TYPES_SBYTE: - send_monitored_item_response(&subscription_id, &monitored_id, data->value.data, 23, 0); - break; - - case UA_TYPES_BYTE: - send_monitored_item_response(&subscription_id, &monitored_id, data->value.data, 24, 0); - break; - - case UA_TYPES_INT16: - send_monitored_item_response(&subscription_id, &monitored_id, data->value.data, 25, 0); - break; - - case UA_TYPES_UINT16: - send_monitored_item_response(&subscription_id, &monitored_id, data->value.data, 26, 0); - break; - - case UA_TYPES_INT32: - send_monitored_item_response(&subscription_id, &monitored_id, data->value.data, 1, 0); - break; - - case UA_TYPES_UINT32: - send_monitored_item_response(&subscription_id, &monitored_id, data->value.data, 2, 0); - break; - - case UA_TYPES_INT64: - send_monitored_item_response(&subscription_id, &monitored_id, data->value.data, 15, 0); - break; - - case UA_TYPES_UINT64: - send_monitored_item_response(&subscription_id, &monitored_id, data->value.data, 16, 0); - break; - - case UA_TYPES_FLOAT: - send_monitored_item_response(&subscription_id, &monitored_id, data->value.data, 17, 0); - break; - - case UA_TYPES_DOUBLE: - send_monitored_item_response(&subscription_id, &monitored_id, data->value.data, 4, 0); - break; - - case UA_TYPES_STRING: - send_monitored_item_response(&subscription_id, &monitored_id, (*(UA_String *)data->value.data).data, 5, (*(UA_String *)data->value.data).length); - break; - - case UA_TYPES_DATETIME: - send_monitored_item_response(&subscription_id, &monitored_id, data->value.data, 15, 0); - break; - - case UA_TYPES_GUID: - send_monitored_item_response(&subscription_id, &monitored_id, data->value.data, 18, 0); - break; - - case UA_TYPES_BYTESTRING: - send_monitored_item_response(&subscription_id, &monitored_id, (*(UA_ByteString *)data->value.data).data, 5, (*(UA_ByteString *)data->value.data).length); - break; - - case UA_TYPES_XMLELEMENT: - send_monitored_item_response(&subscription_id, &monitored_id, (*(UA_XmlElement *)data->value.data).data, 5, (*(UA_XmlElement *)data->value.data).length); - break; - - case UA_TYPES_NODEID: - send_monitored_item_response(&subscription_id, &monitored_id, data->value.data, 12, 0); - break; - - case UA_TYPES_EXPANDEDNODEID: - send_monitored_item_response(&subscription_id, &monitored_id, data->value.data, 19, 0); - break; - - case UA_TYPES_STATUSCODE: - send_monitored_item_response(&subscription_id, &monitored_id, data->value.data, 20, 0); - break; - - case UA_TYPES_QUALIFIEDNAME: - send_monitored_item_response(&subscription_id, &monitored_id, data->value.data, 13, 0); - break; - - case UA_TYPES_LOCALIZEDTEXT: - send_monitored_item_response(&subscription_id, &monitored_id, data->value.data, 14, 0); - break; - - // TODO: UA_TYPES_EXTENSIONOBJECT - - // TODO: UA_TYPES_DATAVALUE - - // TODO: UA_TYPES_VARIANT - - // TODO: UA_TYPES_DIAGNOSTICINFO - - case UA_TYPES_SEMANTICCHANGESTRUCTUREDATATYPE: - send_monitored_item_response(&subscription_id, &monitored_id, data->value.data, 21, 0); - break; - - case UA_TYPES_TIMESTRING: - send_monitored_item_response(&subscription_id, &monitored_id, (*(UA_TimeString *)data->value.data).data, 5, (*(UA_TimeString *)data->value.data).length); - break; - - // TODO: UA_TYPES_VIEWATTRIBUTES - - case UA_TYPES_UADPNETWORKMESSAGECONTENTMASK: - send_monitored_item_response(&subscription_id, &monitored_id, data->value.data, 2, 0); - break; - - case UA_TYPES_XVTYPE: - - send_monitored_item_response(&subscription_id, &monitored_id, data->value.data, 22, 0); - break; - - case UA_TYPES_ELEMENTOPERAND: - send_monitored_item_response(&subscription_id, &monitored_id, data->value.data, 27, 0); - break; - - default: - send_monitored_item_response(&subscription_id, &monitored_id, data->value.data, 2, -1); - break; - } + UA_Variant variant = data->value; + send_monitored_item_response(&subscription_id, &monitored_id, &variant, 29); } static void deleteMonitoredItemCallback(UA_Client *client, UA_UInt32 subscription_id, void *subContext, UA_UInt32 monitored_id, void *monContext) @@ -1031,6 +912,7 @@ static struct request_handler request_handlers[] = { // TODO: Add UA_Server_writeArrayDimensions, inverse name (read) {"write_node_value", handle_write_node_value}, {"read_node_value", handle_read_node_value}, + {"read_node_value_by_index", handle_read_node_value_by_index}, {"read_node_value_by_data_type", handle_read_node_value_by_data_type}, {"write_node_node_id", handle_write_node_node_id}, {"write_node_node_class", handle_write_node_node_class}, diff --git a/src/opc_ua_server.c b/src/opc_ua_server.c index de0b859..4bdb926 100644 --- a/src/opc_ua_server.c +++ b/src/opc_ua_server.c @@ -568,6 +568,7 @@ static struct request_handler request_handlers[] = { // TODO: Add UA_Server_writeArrayDimensions, {"write_node_value", handle_write_node_value}, {"read_node_value", handle_read_node_value}, + {"read_node_value_by_index", handle_read_node_value_by_index}, {"write_node_browse_name", handle_write_node_browse_name}, {"write_node_display_name", handle_write_node_display_name}, {"write_node_description", handle_write_node_description}, diff --git a/test/client_tests/arrays_test.exs b/test/client_tests/arrays_test.exs index cb2bedd..9e94723 100644 --- a/test/client_tests/arrays_test.exs +++ b/test/client_tests/arrays_test.exs @@ -78,19 +78,19 @@ defmodule ClientArraysTest do test "write/read array node by index (string)", state do node_id = NodeId.new(ns_index: state.ns_index, identifier_type: "string", identifier: "R1_TS1_Temperature") - resp = Client.read_node_value(state.c_pid, node_id, 0) + resp = Client.read_node_value_by_index(state.c_pid, node_id, 0) assert resp == {:ok, ""} - resp = Client.read_node_value(state.c_pid, node_id, 1) + resp = Client.read_node_value_by_index(state.c_pid, node_id, 1) assert resp == {:ok, ""} - resp = Client.read_node_value(state.c_pid, node_id, 2) + resp = Client.read_node_value_by_index(state.c_pid, node_id, 2) assert resp == {:ok, ""} - resp = Client.read_node_value(state.c_pid, node_id, 3) + resp = Client.read_node_value_by_index(state.c_pid, node_id, 3) assert resp == {:ok, ""} - resp = Client.read_node_value(state.c_pid, node_id, 4) + resp = Client.read_node_value_by_index(state.c_pid, node_id, 4) assert resp == {:error, "BadTypeMismatch"} resp = Client.write_node_value(state.c_pid, node_id, 11, "alde103_1", 0) @@ -108,39 +108,42 @@ defmodule ClientArraysTest do resp = Client.write_node_value(state.c_pid, node_id, 11, "alde103_error", 4) assert resp == {:error, "BadTypeMismatch"} - resp = Client.read_node_value(state.c_pid, node_id, 0) + resp = Client.read_node_value_by_index(state.c_pid, node_id, 0) assert resp == {:ok, "alde103_1"} - resp = Client.read_node_value(state.c_pid, node_id, 1) + resp = Client.read_node_value_by_index(state.c_pid, node_id, 1) assert resp == {:ok, "alde103_2"} - resp = Client.read_node_value(state.c_pid, node_id, 2) + resp = Client.read_node_value_by_index(state.c_pid, node_id, 2) assert resp == {:ok, "alde103_3"} - resp = Client.read_node_value(state.c_pid, node_id, 3) + resp = Client.read_node_value_by_index(state.c_pid, node_id, 3) assert resp == {:ok, "alde103_4"} - resp = Client.read_node_value(state.c_pid, node_id, 4) + resp = Client.read_node_value_by_index(state.c_pid, node_id, 4) assert resp == {:error, "BadTypeMismatch"} + + resp = Client.read_node_value(state.c_pid, node_id) + assert resp == {:ok, ["alde103_1", "alde103_2", "alde103_3", "alde103_4"]} end test "write/read array node by index (double)", state do node_id = NodeId.new(ns_index: state.ns_index, identifier_type: "string", identifier: "R1_TS1_Temperature") :ok = Server.write_node_blank_array(state.s_pid, node_id, 10, [4]) - resp = Client.read_node_value(state.c_pid, node_id, 0) + resp = Client.read_node_value_by_index(state.c_pid, node_id, 0) assert resp == {:ok, 0.0} - resp = Client.read_node_value(state.c_pid, node_id, 1) + resp = Client.read_node_value_by_index(state.c_pid, node_id, 1) assert resp == {:ok, 0.0} - resp = Client.read_node_value(state.c_pid, node_id, 2) + resp = Client.read_node_value_by_index(state.c_pid, node_id, 2) assert resp == {:ok, 0.0} - resp = Client.read_node_value(state.c_pid, node_id, 3) + resp = Client.read_node_value_by_index(state.c_pid, node_id, 3) assert resp == {:ok, 0.0} - resp = Client.read_node_value(state.c_pid, node_id, 4) + resp = Client.read_node_value_by_index(state.c_pid, node_id, 4) assert resp == {:error, "BadTypeMismatch"} resp = Client.write_node_value(state.c_pid, node_id, 10, 103.0, 0) @@ -158,19 +161,75 @@ defmodule ClientArraysTest do resp = Client.write_node_value(state.c_pid, node_id, 10, 103103.0, 4) assert resp == {:error, "BadTypeMismatch"} - resp = Client.read_node_value(state.c_pid, node_id, 0) + resp = Client.read_node_value_by_index(state.c_pid, node_id, 0) assert resp == {:ok, 103.0} - resp = Client.read_node_value(state.c_pid, node_id, 1) + resp = Client.read_node_value_by_index(state.c_pid, node_id, 1) assert resp == {:ok, 103103.0} - resp = Client.read_node_value(state.c_pid, node_id, 2) + resp = Client.read_node_value_by_index(state.c_pid, node_id, 2) assert resp == {:ok, 103103103.0} - resp = Client.read_node_value(state.c_pid, node_id, 3) + resp = Client.read_node_value_by_index(state.c_pid, node_id, 3) assert resp == {:ok, 103103.103} - resp = Client.read_node_value(state.c_pid, node_id, 4) + resp = Client.read_node_value_by_index(state.c_pid, node_id, 4) + assert resp == {:error, "BadTypeMismatch"} + + resp = Client.read_node_value(state.c_pid, node_id) + assert resp == {:ok, [103.0, 103103.0, 103103103.0, 103103.103]} + end + + test "write/read array node by index (node_id)", state do + node_id = NodeId.new(ns_index: state.ns_index, identifier_type: "string", identifier: "R1_TS1_Temperature") + :ok = Server.write_node_blank_array(state.s_pid, node_id, 16, [4]) + + resp = Client.read_node_value_by_index(state.c_pid, node_id, 0) + assert resp == {:ok, %OpcUA.NodeId{identifier: 0, identifier_type: 0, ns_index: 0}} + + resp = Client.read_node_value_by_index(state.c_pid, node_id, 1) + assert resp == {:ok, %OpcUA.NodeId{identifier: 0, identifier_type: 0, ns_index: 0}} + + resp = Client.read_node_value_by_index(state.c_pid, node_id, 2) + assert resp == {:ok, %OpcUA.NodeId{identifier: 0, identifier_type: 0, ns_index: 0}} + + resp = Client.read_node_value_by_index(state.c_pid, node_id, 3) + assert resp == {:ok, %OpcUA.NodeId{identifier: 0, identifier_type: 0, ns_index: 0}} + + resp = Client.read_node_value_by_index(state.c_pid, node_id, 4) assert resp == {:error, "BadTypeMismatch"} + + resp = Client.write_node_value(state.c_pid, node_id, 16, node_id, 0) + assert resp == :ok + + resp = Client.write_node_value(state.c_pid, node_id, 16, node_id, 1) + assert resp == :ok + + resp = Client.write_node_value(state.c_pid, node_id, 16, node_id, 2) + assert resp == :ok + + resp = Client.write_node_value(state.c_pid, node_id, 16, node_id, 3) + assert resp == :ok + + # resp = Client.write_node_value(state.c_pid, node_id, 16, node_id, 4) + # assert resp == {:error, "BadTypeMismatch"} + + resp = Client.read_node_value_by_index(state.c_pid, node_id, 0) + assert resp == {:ok, node_id} + + resp = Client.read_node_value_by_index(state.c_pid, node_id, 1) + assert resp == {:ok, node_id} + + resp = Client.read_node_value_by_index(state.c_pid, node_id, 2) + assert resp == {:ok, node_id} + + resp = Client.read_node_value_by_index(state.c_pid, node_id, 3) + assert resp == {:ok, node_id} + + resp = Client.read_node_value_by_index(state.c_pid, node_id, 4) + assert resp == {:error, "BadTypeMismatch"} + + resp = Client.read_node_value(state.c_pid, node_id) + assert resp == {:ok, [node_id, node_id, node_id, node_id]} end end diff --git a/test/client_tests/write_read_attr_test.exs b/test/client_tests/write_read_attr_test.exs index fcef690..28eec3b 100644 --- a/test/client_tests/write_read_attr_test.exs +++ b/test/client_tests/write_read_attr_test.exs @@ -226,6 +226,9 @@ defmodule CClientWriteAttrTest do test "Write and Read Value Attributes", %{c_pid: c_pid, ns_index: ns_index} do node_id = NodeId.new(ns_index: ns_index, identifier_type: "string", identifier: "R1_TS1_Temperature") + c_response = Client.read_node_value(c_pid, node_id) + assert c_response == {:ok, nil} + assert :ok == Client.write_node_value(c_pid, node_id, 0, true) c_response = Client.read_node_value(c_pid, node_id) assert c_response == {:ok, true}