diff --git a/simulator/CMakeLists.txt b/simulator/CMakeLists.txt index df64a82e..608dbe82 100644 --- a/simulator/CMakeLists.txt +++ b/simulator/CMakeLists.txt @@ -1,3 +1,6 @@ add_executable(modbus_tcp_server_simulator modbus_tcp_server_sim.c) target_include_directories(modbus_tcp_server_simulator PRIVATE ${CMAKE_SOURCE_DIR}) -target_link_libraries(modbus_tcp_server_simulator neuron-base) \ No newline at end of file +target_link_libraries(modbus_tcp_server_simulator neuron-base) + +add_executable(opcua_simulator opcua_simulator.c) +target_link_libraries(opcua_simulator rt open62541 ssl crypto) diff --git a/simulator/opcua_simulator.c b/simulator/opcua_simulator.c new file mode 100644 index 00000000..df83e883 --- /dev/null +++ b/simulator/opcua_simulator.c @@ -0,0 +1,248 @@ +/** + * NEURON IIoT System for Industry 4.0 + * Copyright (C) 2020-2021 EMQ Technologies Co., Ltd All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + **/ + +#include +#include +#include +#include +#include + +#define UNUSED(x) (void) (x) + +static volatile UA_Boolean running = true; + +static void node_attributes_set(UA_Server *server, char *name, char *node_id, + int type, void *data, const int readonly) +{ + UA_VariableAttributes attr = UA_VariableAttributes_default; + UA_Variant_setScalar(&attr.value, data, &UA_TYPES[type]); + attr.description = UA_LOCALIZEDTEXT("en-US", name); + attr.displayName = UA_LOCALIZEDTEXT("en-US", name); + attr.dataType = UA_TYPES[type].typeId; + + if (readonly) { + attr.accessLevel = UA_ACCESSLEVELMASK_READ; + } else { + attr.accessLevel = UA_ACCESSLEVELMASK_READ | UA_ACCESSLEVELMASK_WRITE; + } + + UA_NodeId integer_node_id = UA_NODEID_STRING(1, node_id); + UA_QualifiedName integer_name = UA_QUALIFIEDNAME(1, name); + UA_NodeId parent_node_id = UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER); + UA_NodeId parent_reference_node_id = + UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES); + UA_Server_addVariableNode( + server, integer_node_id, parent_node_id, parent_reference_node_id, + integer_name, UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE), attr, + NULL, NULL); +} + +// UA_SByte <-> int8_t +static void data_int8_add(UA_Server *server, char *name, char *node_id, + int8_t data, const int readonly) +{ + UA_SByte sbyte = data; + node_attributes_set(server, name, node_id, UA_TYPES_SBYTE, &sbyte, + readonly); +} + +// UA_Byte <-> uint8_t +static void data_uint8_add(UA_Server *server, char *name, char *node_id, + uint8_t data, const int readonly) +{ + UA_Byte byte = data; + node_attributes_set(server, name, node_id, UA_TYPES_SBYTE, &byte, readonly); +} + +// UA_Boolean +static void data_bool_add(UA_Server *server, char *name, char *node_id, + bool data, const int readonly) +{ + UA_Boolean boolean = data; + node_attributes_set(server, name, node_id, UA_TYPES_BOOLEAN, &boolean, + readonly); +} + +// UA_Int16 +static void data_int16_add(UA_Server *server, char *name, char *node_id, + int16_t data, const int readonly) +{ + UA_Int16 i16 = data; + node_attributes_set(server, name, node_id, UA_TYPES_INT16, &i16, readonly); +} + +// UA_UInt16 +static void data_uint16_add(UA_Server *server, char *name, char *node_id, + int16_t data, const int readonly) +{ + UA_UInt16 u16 = data; + node_attributes_set(server, name, node_id, UA_TYPES_UINT16, &u16, readonly); +} + +// UA_Int32 +static void data_int32_add(UA_Server *server, char *name, char *node_id, + int32_t data, const int readonly) +{ + UA_Int32 i32 = data; + node_attributes_set(server, name, node_id, UA_TYPES_INT32, &i32, readonly); +} + +// UA_UInt32 +static void data_uint32_add(UA_Server *server, char *name, char *node_id, + uint32_t data, const int readonly) +{ + UA_UInt32 u32 = data; + node_attributes_set(server, name, node_id, UA_TYPES_UINT32, &u32, readonly); +} + +// UA_DateTime +// static void data_datetime_add(UA_Server *server, char *name, char *node_id, +// uint32_t data, const int readonly) +// { +// UNUSED(server); +// UNUSED(name); +// UNUSED(node_id); +// UNUSED(data); +// UNUSED(readonly); +// } + +// UA_Float +static void data_float_add(UA_Server *server, char *name, char *node_id, + float data, const int readonly) +{ + UA_Float f32 = data; + node_attributes_set(server, name, node_id, UA_TYPES_FLOAT, &f32, readonly); +} + +// UA_Double +static void data_double_add(UA_Server *server, char *name, char *node_id, + double data, const int readonly) +{ + UA_Double f64 = data; + node_attributes_set(server, name, node_id, UA_TYPES_DOUBLE, &f64, readonly); +} + +static void data_string_add(UA_Server *server, char *name, char *node_id, + char *data, const int readonly) +{ + UA_String str = UA_STRING(data); + node_attributes_set(server, name, node_id, UA_TYPES_STRING, &str, readonly); +} + +static UA_StatusCode +current_time_read(UA_Server *server, const UA_NodeId *sessionId, + void *sessionContext, const UA_NodeId *nodeId, + void *nodeContext, UA_Boolean sourceTimeStamp, + const UA_NumericRange *range, UA_DataValue *dataValue) +{ + UNUSED(server); + UNUSED(sessionId); + UNUSED(sessionContext); + UNUSED(nodeId); + UNUSED(nodeContext); + UNUSED(sourceTimeStamp); + UNUSED(range); + + UA_DateTime now = UA_DateTime_now(); + UA_Variant_setScalarCopy(&dataValue->value, &now, + &UA_TYPES[UA_TYPES_DATETIME]); + dataValue->hasValue = true; + return UA_STATUSCODE_GOOD; +} + +static UA_StatusCode +current_time_write(UA_Server *server, const UA_NodeId *sessionId, + void *sessionContext, const UA_NodeId *nodeId, + void *nodeContext, const UA_NumericRange *range, + const UA_DataValue *data) +{ + UNUSED(server); + UNUSED(sessionId); + UNUSED(sessionContext); + UNUSED(nodeId); + UNUSED(nodeContext); + UNUSED(range); + UNUSED(data); + + UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, + "Changing the system time is not implemented"); + return UA_STATUSCODE_BADINTERNALERROR; +} + +static void current_time_datasource_variable_add(UA_Server *server) +{ + UA_VariableAttributes attr = UA_VariableAttributes_default; + attr.displayName = UA_LOCALIZEDTEXT("en-US", "type_datetime"); + attr.accessLevel = UA_ACCESSLEVELMASK_READ | UA_ACCESSLEVELMASK_WRITE; + + UA_NodeId currentNodeId = UA_NODEID_STRING(1, "neu.type_datetime"); + UA_QualifiedName currentName = UA_QUALIFIEDNAME(1, "type_datetime"); + UA_NodeId parentNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER); + UA_NodeId parentReferenceNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES); + UA_NodeId variableTypeNodeId = + UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE); + + UA_DataSource timeDataSource; + timeDataSource.read = current_time_read; + timeDataSource.write = current_time_write; + UA_Server_addDataSourceVariableNode( + server, currentNodeId, parentNodeId, parentReferenceNodeId, currentName, + variableTypeNodeId, attr, timeDataSource, NULL, NULL); +} + +int main(int argc, char **argv) +{ + UNUSED(argc); + UNUSED(argv); + + UA_ServerNetworkLayer nl = + UA_ServerNetworkLayerTCP(UA_ConnectionConfig_default, 4840, 12); + UA_Server *server = UA_Server_new(); + UA_ServerConfig_setDefault(UA_Server_getConfig(server)); + UA_ServerConfig *config = UA_Server_getConfig(server); + config->networkLayers = &nl; + config->networkLayersSize = 1; + config->verifyRequestTimestamp = UA_RULEHANDLING_ACCEPT; + + static UA_UsernamePasswordLogin logins[2] = { + { UA_STRING_STATIC("user"), UA_STRING_STATIC("123456") }, + }; + + UA_AccessControl_default( + config, false, + &config->securityPolicies[config->securityPoliciesSize - 1].policyUri, + 2, logins); + + data_int8_add(server, "type_int8", "neu.type.int8", -10, 0); + data_uint8_add(server, "type_uint8", "neu.type.uint8", 10, 0); + data_bool_add(server, "type_bool", "neu.type_bool", true, 0); + data_int16_add(server, "type_int16", "neu.type_int16", -1001, 0); + data_uint16_add(server, "type_uint16", "neu.type_uint16", 1001, 0); + data_int32_add(server, "type_int32", "neu.type_int32", -100, 0); + data_uint32_add(server, "type_uint32", "neu.type_uint32", 111111, 0); + data_float_add(server, "type_float", "neu.type_float", 38.9, 0); + data_double_add(server, "type_double", "neu.type_double", 100038.9999, 0); + data_string_add(server, "type_cstr", "nue.type_cstr", "hello world!", 0); + current_time_datasource_variable_add(server); + + UA_StatusCode retval = UA_Server_run(server, &running); + UNUSED(retval); + UA_Server_delete(server); + return 0; +}