From e1ca981860f8e7d5bba768cb00f156e4998fa36b Mon Sep 17 00:00:00 2001 From: hxy7yx <1595670487@qq.com> Date: Mon, 10 Apr 2023 09:24:20 +0800 Subject: [PATCH] Open source Modbus TCP/RTU, delete existing Modbus TCP community --- .clang-format | 1 - default_plugins.json | 4 +- ft/modbus_rtu_test.robot | 184 ++++++++++++++ ft/modbus_tcp_test.robot | 167 ++++++------- ft/persist_test.robot | 6 +- ft/resource/api.resource | 2 +- ft/resource/common.resource | 4 +- ft/resource/driver.resource | 14 +- plugins/modbus/CMakeLists.txt | 36 ++- plugins/modbus/modbus-qh-tcp.json | 104 ++++++++ plugins/modbus/modbus-rtu.json | 305 +++++++++++++++++++++++ plugins/modbus/modbus-tcp-community.json | 67 ----- plugins/modbus/modbus-tcp.json | 117 +++++++++ plugins/modbus/modbus.c | 53 ++++ plugins/modbus/modbus.h | 9 + plugins/modbus/modbus_point.c | 10 + plugins/modbus/modbus_qh_tcp.c | 224 +++++++++++++++++ plugins/modbus/modbus_req.c | 137 +++++++--- plugins/modbus/modbus_req.h | 12 +- plugins/modbus/modbus_rtu.c | 276 ++++++++++++++++++++ plugins/modbus/modbus_stack.c | 103 ++++++-- plugins/modbus/modbus_stack.h | 23 +- plugins/modbus/modbus_tcp.c | 79 +++--- plugins/restful/normal_handle.c | 8 - simulator/modbus_s.c | 6 +- simulator/modbus_simulator.c | 5 +- tests/CMakeLists.txt | 2 +- 27 files changed, 1676 insertions(+), 282 deletions(-) create mode 100644 ft/modbus_rtu_test.robot create mode 100644 plugins/modbus/modbus-qh-tcp.json create mode 100644 plugins/modbus/modbus-rtu.json delete mode 100644 plugins/modbus/modbus-tcp-community.json create mode 100644 plugins/modbus/modbus-tcp.json create mode 100644 plugins/modbus/modbus_qh_tcp.c create mode 100644 plugins/modbus/modbus_rtu.c diff --git a/.clang-format b/.clang-format index 823c4437..02d9b926 100644 --- a/.clang-format +++ b/.clang-format @@ -12,4 +12,3 @@ PointerAlignment: Right SpaceAfterCStyleCast: true AllowShortFunctionsOnASingleLine: Inline BreakBeforeBinaryOperators: None - diff --git a/default_plugins.json b/default_plugins.json index ce8981b9..5223a3d3 100644 --- a/default_plugins.json +++ b/default_plugins.json @@ -3,7 +3,9 @@ "libplugin-mqtt.so", "libplugin-ekuiper.so", "libplugin-monitor.so", - "libplugin-modbus-tcp-comm.so", + "libplugin-modbus-tcp.so", + "libplugin-modbus-rtu.so", + "libplugin-modbus-qh-tcp.so", "libplugin-file.so" ] } \ No newline at end of file diff --git a/ft/modbus_rtu_test.robot b/ft/modbus_rtu_test.robot new file mode 100644 index 00000000..d92ab73e --- /dev/null +++ b/ft/modbus_rtu_test.robot @@ -0,0 +1,184 @@ +*** Settings *** +Resource resource/api.resource +Resource resource/common.resource +Resource resource/error.resource +Resource resource/driver.resource +Suite Setup Modbus Test Setup +Suite Teardown Modbus Test Teardown + +*** Variables *** +${modbus_rtu_node} modbus_rtu_node +${PLUGIN-MODBUS-RTU} Modbus RTU + +${MODBUS_RTU_CONFIG} {"link": 1, "timeout": 3000, "interval": 10, "device": "", "stop": 0, "parity": 0, "baud": 4,"data": 3, "host": "127.0.0.1", "port": 60503, "connection_mode": 0} +${MODBUS_RTU_CONFIG_WRANING} {"link": 1, "host": "127.0.0.1","port": 60503,"connection_mode": 0} + +${hold_bit} {"name": "hold_bit", "address": "1!400001.15", "attribute": ${TAG_ATTRIBUTE_READ}, "type": ${TAG_TYPE_BIT}} +${hold_int16} {"name": "hold_int16", "address": "1!400001", "attribute": ${TAG_ATTRIBUTE_RW}, "type": ${TAG_TYPE_INT16}} +${hold_uint16} {"name": "hold_uint16", "address": "1!400002", "attribute": ${TAG_ATTRIBUTE_RW}, "type": ${TAG_TYPE_UINT16}} +${hold_int32} {"name": "hold_int32", "address": "1!400003", "attribute": ${TAG_ATTRIBUTE_RW}, "type": ${TAG_TYPE_INT32}} +${hold_uint32} {"name": "hold_uint32", "address": "1!400015", "attribute": ${TAG_ATTRIBUTE_RW}, "type": ${TAG_TYPE_UINT32}} +${hold_float} {"name": "hold_float", "address": "1!400017", "attribute": ${TAG_ATTRIBUTE_RW}, "type": ${TAG_TYPE_FLOAT}} +${hold_string} {"name": "hold_string", "address": "1!400020.10", "attribute": ${TAG_ATTRIBUTE_RW}, "type": ${TAG_TYPE_STRING}} + +${coil_bit_1} {"name": "coil_bit_1", "address": "1!000001", "attribute": ${TAG_ATTRIBUTE_RW}, "type": ${TAG_TYPE_BIT}} +${coil_bit_2} {"name": "coil_bit_2", "address": "1!000002", "attribute": ${TAG_ATTRIBUTE_RW}, "type": ${TAG_TYPE_BIT}} +${coil_bit_3} {"name": "coil_bit_3", "address": "1!000003", "attribute": ${TAG_ATTRIBUTE_RW}, "type": ${TAG_TYPE_BIT}} +${coil_bit_4} {"name": "coil_bit_4", "address": "1!000005", "attribute": ${TAG_ATTRIBUTE_RW}, "type": ${TAG_TYPE_BIT}} +${coil_bit_5} {"name": "coil_bit_5", "address": "1!000010", "attribute": ${TAG_ATTRIBUTE_RW}, "type": ${TAG_TYPE_BIT}} + +${input_bit_1} {"name": "input_bit_1", "address": "1!100001", "attribute": ${TAG_ATTRIBUTE_READ}, "type": ${TAG_TYPE_BIT}} +${input_bit_2} {"name": "input_bit_2", "address": "1!100002", "attribute": ${TAG_ATTRIBUTE_READ}, "type": ${TAG_TYPE_BIT}} +${input_bit_3} {"name": "input_bit_3", "address": "1!100003", "attribute": ${TAG_ATTRIBUTE_READ}, "type": ${TAG_TYPE_BIT}} +${input_bit_4} {"name": "input_bit_4", "address": "1!100005", "attribute": ${TAG_ATTRIBUTE_READ}, "type": ${TAG_TYPE_BIT}} +${input_bit_5} {"name": "input_bit_5", "address": "1!100010", "attribute": ${TAG_ATTRIBUTE_READ}, "type": ${TAG_TYPE_BIT}} + +${input_register_bit} {"name": "input_register_bit", "address": "1!30101.15", "attribute": ${TAG_ATTRIBUTE_READ}, "type": ${TAG_TYPE_BIT}} +${input_register_int16} {"name": "input_register_int16", "address": "1!30101", "attribute": ${TAG_ATTRIBUTE_READ}, "type": ${TAG_TYPE_INT16}} +${input_register_uint16} {"name": "input_register_uint16", "address": "1!30102", "attribute": ${TAG_ATTRIBUTE_READ}, "type": ${TAG_TYPE_UINT16}} +${input_register_int32} {"name": "input_register_int32", "address": "1!30103", "attribute": ${TAG_ATTRIBUTE_READ}, "type": ${TAG_TYPE_INT32}} +${input_register_uint32} {"name": "input_register_uint32", "address": "1!30115", "attribute": ${TAG_ATTRIBUTE_READ}, "type": ${TAG_TYPE_UINT32}} +${input_register_float} {"name": "input_register_float", "address": "1!30117", "attribute": ${TAG_ATTRIBUTE_READ}, "type": ${TAG_TYPE_FLOAT}} +${input_register_string} {"name": "input_register_string", "address": "1!30120.10", "attribute": ${TAG_ATTRIBUTE_READ}, "type": ${TAG_TYPE_STRING}} + +${hold_int16_address_update} {"name": "hold_int16", "address": "1!400100", "attribute": ${TAG_ATTRIBUTE_RW}, "type": ${TAG_TYPE_INT16}} +${hold_int16_type_update} {"name": "hold_int16", "address": "1!400001", "attribute": ${TAG_ATTRIBUTE_RW}, "type": ${TAG_TYPE_INT32}} + +${input_bit_1_name_wrong} {"name": "input_bit", "address": "1!100001", "attribute": ${TAG_ATTRIBUTE_READ}, "type": ${TAG_TYPE_BIT}} +${input_bit_1_attribute_unmatch} {"name": "input_bit_1", "address": "1!100001", "attribute": ${TAG_ATTRIBUTE_RW}, "type": ${TAG_TYPE_BIT}} +${input_bit_1_type_unmatch} {"name": "input_bit_1", "address": "1!100001", "attribute": ${TAG_ATTRIBUTE_READ}, "type": ${TAG_TYPE_INT16}} + +*** Test Cases *** +Set a node with right settings, it should be success. + [Template] Set a ${node} with right ${node_settings}, it will be success. + ${modbus_rtu_node} ${MODBUS_RTU_CONFIG} + +Set a node with wrong settings, it should be failure. + [Template] Set a ${node} with wrong ${node_settings}, it will be failure. + ${modbus_rtu_node} ${MODBUS_RTU_CONFIG_WRANING} + +Set tags with multiple address and types and attribute, it should be success. + [Template] Add a ${tag} with right address and type and attribute from ${node}, it will be success. + ${hold_int16} ${modbus_rtu_node} + ${hold_uint16} ${modbus_rtu_node} + ${hold_int32} ${modbus_rtu_node} + ${hold_uint32} ${modbus_rtu_node} + ${hold_float} ${modbus_rtu_node} + ${hold_string} ${modbus_rtu_node} + + ${coil_bit_1} ${modbus_rtu_node} + ${coil_bit_2} ${modbus_rtu_node} + ${coil_bit_3} ${modbus_rtu_node} + ${coil_bit_4} ${modbus_rtu_node} + ${coil_bit_5} ${modbus_rtu_node} + + ${input_register_int16} ${modbus_rtu_node} + ${input_register_uint16} ${modbus_rtu_node} + ${input_register_int32} ${modbus_rtu_node} + ${input_register_uint32} ${modbus_rtu_node} + ${input_register_float} ${modbus_rtu_node} + ${input_register_string} ${modbus_rtu_node} + + ${input_bit_1} ${modbus_rtu_node} + ${input_bit_2} ${modbus_rtu_node} + ${input_bit_3} ${modbus_rtu_node} + ${input_bit_4} ${modbus_rtu_node} + ${input_bit_5} ${modbus_rtu_node} + +Set tags with unmatch address, type or attribute, it should be failure. + [Template] Set a ${tag} with unmatch address, type or attribute from ${node}, it will be failure and return ${resp_status} and ${error_code}. + ${input_bit_1_type_unmatch} ${modbus_rtu_node} 200 ${NEU_ERR_TAG_TYPE_NOT_SUPPORT} + ${input_bit_1_attribute_unmatch} ${modbus_rtu_node} 200 ${NEU_ERR_TAG_ATTRIBUTE_NOT_SUPPORT} + +Write and Read all type tag with same slave id in hold register and coil, it should be success. + [Template] Write and Read a ${tag} named ${tag_name} from ${node}, using ${check} to check the ${value}, it will be success. + ${hold_int16} hold_int16 ${modbus_rtu_node} Compare Tag Value As Int 120 + ${hold_uint16} hold_uint16 ${modbus_rtu_node} Compare Tag Value As Int 65535 + ${hold_int32} hold_int32 ${modbus_rtu_node} Compare Tag Value As Int 77889912 + ${hold_uint32} hold_uint32 ${modbus_rtu_node} Compare Tag Value As Int 88991232 + ${hold_float} hold_float ${modbus_rtu_node} Compare Tag Value As Float 12.34 + ${hold_string} hold_string ${modbus_rtu_node} Compare Tag Value As Strings "abc123" + + ${coil_bit_1} coil_bit_1 ${modbus_rtu_node} Compare Tag Value As Int 1 + ${coil_bit_2} coil_bit_2 ${modbus_rtu_node} Compare Tag Value As Int 0 + ${coil_bit_3} coil_bit_3 ${modbus_rtu_node} Compare Tag Value As Int 0 + ${coil_bit_4} coil_bit_4 ${modbus_rtu_node} Compare Tag Value As Int 1 + ${coil_bit_5} coil_bit_5 ${modbus_rtu_node} Compare Tag Value As Int 1 + +Read all type tag with same slave id in input register and input, it should be success. + [Template] Read a ${tag} named ${tag_name} from ${node}, using ${check} to check the ${value}, it will be success. + ${input_register_int16} input_register_int16 ${modbus_rtu_node} Compare Tag Value As Int 0 + ${input_register_uint16} input_register_uint16 ${modbus_rtu_node} Compare Tag Value As Int 0 + ${input_register_int32} input_register_int32 ${modbus_rtu_node} Compare Tag Value As Int 0 + ${input_register_uint32} input_register_uint32 ${modbus_rtu_node} Compare Tag Value As Int 0 + ${input_register_float} input_register_float ${modbus_rtu_node} Compare Tag Value As Float 0 + ${input_register_string} input_register_string ${modbus_rtu_node} Compare Tag Value As Strings "" + + ${input_bit_1} input_bit_1 ${modbus_rtu_node} Compare Tag Value As Int 0 + ${input_bit_2} input_bit_2 ${modbus_rtu_node} Compare Tag Value As Int 0 + ${input_bit_3} input_bit_3 ${modbus_rtu_node} Compare Tag Value As Int 0 + ${input_bit_4} input_bit_4 ${modbus_rtu_node} Compare Tag Value As Int 0 + ${input_bit_5} input_bit_5 ${modbus_rtu_node} Compare Tag Value As Int 0 + +Read some tags at time, it will be success. + [Template] Read some ${tags} from ${node}, it will be success. + ${input_register_int16},${input_register_uint16},${input_register_int32},${input_register_uint32},${input_register_float},${input_register_string},${input_register_bit} ${modbus_rtu_node} + ${hold_int16},${hold_uint16},${hold_int32},${hold_uint32},${hold_float},${hold_string},${hold_bit} ${modbus_rtu_node} + ${coil_bit_1},${coil_bit_2},${coil_bit_3},${coil_bit_4},${coil_bit_5} ${modbus_rtu_node} + ${input_bit_1},${input_bit_2},${input_bit_3},${input_bit_4},${input_bit_5} ${modbus_rtu_node} + +Write and read a tag with same name and same address that in different groups, it should be success. + ${groups}= Create List group-1 group-2 group-3 + Write and read a ${hold_int16} with same hold_int16 and same address in different ${groups} that from ${modbus_rtu_node}, using Compare Tag Value As Int to check the 2, it will be success. + +Write and read a tag with same name but different address that in different groups, it should be success. + ${groups}= Create List group-1 group-2 group-3 + Write and read ${hold_int16} ${hold_int16_address_update} with same hold_int16 but different address in different ${groups} that from ${modbus_rtu_node}, using Compare Tag Value As Int to check the 2, it will be success. + +Update a tag with right address and types and attribute, it will be success + [Template] Update a ${tag} by a ${tag_update} named ${tag_name} from ${node} , it can read and write, using ${check} to check the ${value}, it will be success. + ${hold_int16} ${hold_int16_address_update} hold_int16 ${modbus_rtu_node} Compare Tag Value As Int 120 + ${hold_int16} ${hold_int16_type_update} hold_int16 ${modbus_rtu_node} Compare Tag Value As Int 120 + +Update a tag with unmatch address and types and attribute, it should be failure. + [Template] Update a ${tag} by a ${tag_update} with the unmatch settings from ${node} , it will be failure and return ${resp_status} and ${error_code}. + ${input_bit_1} ${input_bit_1_attribute_unmatch} ${modbus_rtu_node} 200 ${NEU_ERR_TAG_ATTRIBUTE_NOT_SUPPORT} + ${input_bit_1} ${input_bit_1_type_unmatch} ${modbus_rtu_node} 200 ${NEU_ERR_TAG_TYPE_NOT_SUPPORT} + ${input_bit_1} ${input_bit_1_name_wrong} ${modbus_rtu_node} 200 ${NEU_ERR_TAG_NOT_EXIST} + +Write a value to a tag that don't exit in plc or neuron, it will be failure. + [Template] Write a ${value} to a ${tag} with named ${tag_name} that don't exist in neuron or plc from ${node}, it will be failure. + 12 ${input_bit_1} input ${modbus_rtu_node} + +Write value to a tag without write attribute, it will be failure. + [Template] Write a ${value} to a ${tag} named ${tag_name} that without write attribute from ${node}, it will be failure. + 1 ${input_bit_1} input_bit_1 ${modbus_rtu_node} + +Write and read a value to a tag when the node is stoped, it will be failure. + [Template] Write and read a ${value} to a ${tag} named ${tag_name} when the ${node} is stoped, it will be failure. + 1 ${hold_int16} hold_int16 ${modbus_rtu_node} + +*** Keywords *** +Modbus Test Setup + Start Modbus-RTU Simulator + + Start Neuronx + + ${res}= Add Node ${modbus_rtu_node} ${PLUGIN-MODBUS-RTU} + Check Response Status ${res} 200 + + ${res}= Node Setting ${modbus_rtu_node} ${MODBUS_RTU_CONFIG} + Check Response Status ${res} 200 + + ${state}= Get Node State ${modbus_rtu_node} + Should Be Equal As Integers ${state}[running] ${NODE_STATE_RUNNING} + Should Be Equal As Integers ${state}[link] ${NODE_LINK_STATE_DISCONNECTED} + +Modbus Test Teardown + Del Node ${modbus_rtu_node} + Stop Neuronx + Terminate All Processes kill=false + +Start Modbus-RTU Simulator + ${handle} = Start Process ${MODBUS_TCP_SERVER_SIMULATOR} rtu 60503 cwd=build/simulator + [Return] ${handle} diff --git a/ft/modbus_tcp_test.robot b/ft/modbus_tcp_test.robot index 1cd3505f..e9bf5822 100644 --- a/ft/modbus_tcp_test.robot +++ b/ft/modbus_tcp_test.robot @@ -1,4 +1,7 @@ *** Settings *** +Library Process +Library OperatingSystem + Resource resource/api.resource Resource resource/common.resource Resource resource/error.resource @@ -7,9 +10,13 @@ Suite Setup Modbus Test Setup Suite Teardown Modbus Test Teardown *** Variables *** -${modbus_node} modbus_node +${PLUGIN-MODBUS-TCP} Modbus TCP -${MODBUS_TCP_CONFIG_WRONG} {"host": "127.0.0.1", "port": 60502} +${modbus_tcp_node} modbus_tcp_node + +${MODBUS_TCP_CONFIG} {"connection_mode": 0,"interval": 0, "host": "127.0.0.1","port": 60502,"timeout": 3000} + +${MODBUS_TCP_CONFIG_WRONG} {"connection_mode": 0,"host": "127.0.0.1", "port": 60502} ${hold_bit} {"name": "hold_bit", "address": "1!400001.15", "attribute": ${TAG_ATTRIBUTE_READ}, "type": ${TAG_TYPE_BIT}} ${hold_int16} {"name": "hold_int16", "address": "1!400001", "attribute": ${TAG_ATTRIBUTE_RW}, "type": ${TAG_TYPE_INT16}} @@ -46,135 +53,115 @@ ${input_bit_1_name_wrong} {"name": "input_bit", "address": "1!100001", "att ${input_bit_1_attribute_unmatch} {"name": "input_bit_1", "address": "1!100001", "attribute": ${TAG_ATTRIBUTE_RW}, "type": ${TAG_TYPE_BIT}} ${input_bit_1_type_unmatch} {"name": "input_bit_1", "address": "1!100001", "attribute": ${TAG_ATTRIBUTE_READ}, "type": ${TAG_TYPE_INT16}} -${hold_int16_static} {"name": "hold_int16", "address": "1!400001", "attribute": ${TAG_ATTRIBUTE_STATIC_RW}, "type": ${TAG_TYPE_INT16}, "value": 1} - *** Test Cases *** Set a node with right settings, it should be success. [Template] Set a ${node} with right ${node_settings}, it will be success. - ${modbus_node} ${MODBUS_TCP_CONFIG} + ${modbus_tcp_node} ${MODBUS_TCP_CONFIG} Set a node with wrong settings, it should be failure. [Template] Set a ${node} with wrong ${node_settings}, it will be failure. - ${modbus_node} ${MODBUS_TCP_CONFIG_WRONG} + ${modbus_tcp_node} ${MODBUS_TCP_CONFIG_WRONG} Set tags with multiple address and types and attribute, it should be success. [Template] Add a ${tag} with right address and type and attribute from ${node}, it will be success. - ${hold_int16} ${modbus_node} - ${hold_uint16} ${modbus_node} - ${hold_int32} ${modbus_node} - ${hold_uint32} ${modbus_node} - ${hold_float} ${modbus_node} - ${hold_string} ${modbus_node} + ${hold_int16} ${modbus_tcp_node} + ${hold_uint16} ${modbus_tcp_node} + ${hold_int32} ${modbus_tcp_node} + ${hold_uint32} ${modbus_tcp_node} + ${hold_float} ${modbus_tcp_node} + ${hold_string} ${modbus_tcp_node} - ${coil_bit_1} ${modbus_node} - ${coil_bit_2} ${modbus_node} - ${coil_bit_3} ${modbus_node} - ${coil_bit_4} ${modbus_node} - ${coil_bit_5} ${modbus_node} + ${coil_bit_1} ${modbus_tcp_node} + ${coil_bit_2} ${modbus_tcp_node} + ${coil_bit_3} ${modbus_tcp_node} + ${coil_bit_4} ${modbus_tcp_node} + ${coil_bit_5} ${modbus_tcp_node} - ${input_register_int16} ${modbus_node} - ${input_register_uint16} ${modbus_node} - ${input_register_int32} ${modbus_node} - ${input_register_uint32} ${modbus_node} - ${input_register_float} ${modbus_node} - ${input_register_string} ${modbus_node} + ${input_register_int16} ${modbus_tcp_node} + ${input_register_uint16} ${modbus_tcp_node} + ${input_register_int32} ${modbus_tcp_node} + ${input_register_uint32} ${modbus_tcp_node} + ${input_register_float} ${modbus_tcp_node} + ${input_register_string} ${modbus_tcp_node} - ${input_bit_1} ${modbus_node} - ${input_bit_2} ${modbus_node} - ${input_bit_3} ${modbus_node} - ${input_bit_4} ${modbus_node} - ${input_bit_5} ${modbus_node} + ${input_bit_1} ${modbus_tcp_node} + ${input_bit_2} ${modbus_tcp_node} + ${input_bit_3} ${modbus_tcp_node} + ${input_bit_4} ${modbus_tcp_node} + ${input_bit_5} ${modbus_tcp_node} Set tags with unmatch address, type or attribute, it should be failure. [Template] Set a ${tag} with unmatch address, type or attribute from ${node}, it will be failure and return ${resp_status} and ${error_code}. - ${input_bit_1_type_unmatch} ${modbus_node} 200 ${NEU_ERR_TAG_TYPE_NOT_SUPPORT} - ${input_bit_1_attribute_unmatch} ${modbus_node} 200 ${NEU_ERR_TAG_ATTRIBUTE_NOT_SUPPORT} + ${input_bit_1_type_unmatch} ${modbus_tcp_node} 200 ${NEU_ERR_TAG_TYPE_NOT_SUPPORT} + ${input_bit_1_attribute_unmatch} ${modbus_tcp_node} 200 ${NEU_ERR_TAG_ATTRIBUTE_NOT_SUPPORT} Write and Read all type tag with same slave id in hold register and coil, it should be success. [Template] Write and Read a ${tag} named ${tag_name} from ${node}, using ${check} to check the ${value}, it will be success. - ${hold_int16} hold_int16 ${modbus_node} Compare Tag Value As Int 120 - ${hold_uint16} hold_uint16 ${modbus_node} Compare Tag Value As Int 65535 - ${hold_int32} hold_int32 ${modbus_node} Compare Tag Value As Int 77889912 - ${hold_uint32} hold_uint32 ${modbus_node} Compare Tag Value As Int 88991232 - ${hold_float} hold_float ${modbus_node} Compare Tag Value As Float 12.34 - ${hold_string} hold_string ${modbus_node} Compare Tag Value As Strings "abc123" + ${hold_int16} hold_int16 ${modbus_tcp_node} Compare Tag Value As Int 120 + ${hold_uint16} hold_uint16 ${modbus_tcp_node} Compare Tag Value As Int 65535 + ${hold_int32} hold_int32 ${modbus_tcp_node} Compare Tag Value As Int 77889912 + ${hold_uint32} hold_uint32 ${modbus_tcp_node} Compare Tag Value As Int 88991232 + ${hold_float} hold_float ${modbus_tcp_node} Compare Tag Value As Float 12.34 + ${hold_string} hold_string ${modbus_tcp_node} Compare Tag Value As Strings "abc123" - ${coil_bit_1} coil_bit_1 ${modbus_node} Compare Tag Value As Int 1 - ${coil_bit_2} coil_bit_2 ${modbus_node} Compare Tag Value As Int 0 - ${coil_bit_3} coil_bit_3 ${modbus_node} Compare Tag Value As Int 0 - ${coil_bit_4} coil_bit_4 ${modbus_node} Compare Tag Value As Int 1 - ${coil_bit_5} coil_bit_5 ${modbus_node} Compare Tag Value As Int 1 + ${coil_bit_1} coil_bit_1 ${modbus_tcp_node} Compare Tag Value As Int 1 + ${coil_bit_2} coil_bit_2 ${modbus_tcp_node} Compare Tag Value As Int 0 + ${coil_bit_3} coil_bit_3 ${modbus_tcp_node} Compare Tag Value As Int 0 + ${coil_bit_4} coil_bit_4 ${modbus_tcp_node} Compare Tag Value As Int 1 + ${coil_bit_5} coil_bit_5 ${modbus_tcp_node} Compare Tag Value As Int 1 Read all type tag with same slave id in input register and input, it should be success. [Template] Read a ${tag} named ${tag_name} from ${node}, using ${check} to check the ${value}, it will be success. - ${input_register_int16} input_register_int16 ${modbus_node} Compare Tag Value As Int 0 - ${input_register_uint16} input_register_uint16 ${modbus_node} Compare Tag Value As Int 0 - ${input_register_int32} input_register_int32 ${modbus_node} Compare Tag Value As Int 0 - ${input_register_uint32} input_register_uint32 ${modbus_node} Compare Tag Value As Int 0 - ${input_register_float} input_register_float ${modbus_node} Compare Tag Value As Float 0 - ${input_register_string} input_register_string ${modbus_node} Compare Tag Value As Strings "" + ${input_register_int16} input_register_int16 ${modbus_tcp_node} Compare Tag Value As Int 0 + ${input_register_uint16} input_register_uint16 ${modbus_tcp_node} Compare Tag Value As Int 0 + ${input_register_int32} input_register_int32 ${modbus_tcp_node} Compare Tag Value As Int 0 + ${input_register_uint32} input_register_uint32 ${modbus_tcp_node} Compare Tag Value As Int 0 + ${input_register_float} input_register_float ${modbus_tcp_node} Compare Tag Value As Float 0 + ${input_register_string} input_register_string ${modbus_tcp_node} Compare Tag Value As Strings "" - ${input_bit_1} input_bit_1 ${modbus_node} Compare Tag Value As Int 0 - ${input_bit_2} input_bit_2 ${modbus_node} Compare Tag Value As Int 0 - ${input_bit_3} input_bit_3 ${modbus_node} Compare Tag Value As Int 0 - ${input_bit_4} input_bit_4 ${modbus_node} Compare Tag Value As Int 0 - ${input_bit_5} input_bit_5 ${modbus_node} Compare Tag Value As Int 0 + ${input_bit_1} input_bit_1 ${modbus_tcp_node} Compare Tag Value As Int 0 + ${input_bit_2} input_bit_2 ${modbus_tcp_node} Compare Tag Value As Int 0 + ${input_bit_3} input_bit_3 ${modbus_tcp_node} Compare Tag Value As Int 0 + ${input_bit_4} input_bit_4 ${modbus_tcp_node} Compare Tag Value As Int 0 + ${input_bit_5} input_bit_5 ${modbus_tcp_node} Compare Tag Value As Int 0 Read some tags at time, it will be success. - ${input_register_tags}= Create List ${input_register_int16} ${input_register_uint16} ${input_register_int32} ${input_register_uint32} ${input_register_float} ${input_register_string} ${input_register_bit} - Read some ${input_register_tags} from ${modbus_node}, it will be success. - - ${hold_tags}= Create List ${hold_int16} ${hold_uint16} ${hold_int32} ${hold_uint32} ${hold_float} ${hold_string} ${hold_bit} - Read some ${hold_tags} from ${modbus_node}, it will be success. - - ${coil_tags}= Create List ${coil_bit_1} ${coil_bit_2} ${coil_bit_3} ${coil_bit_4} ${coil_bit_5} - Read some ${coil_tags} from ${modbus_node}, it will be success. - - ${input_tags}= Create List ${input_bit_1} ${input_bit_2} ${input_bit_3} ${input_bit_4} ${input_bit_5} - Read some ${input_tags} from ${modbus_node}, it will be success. - -Read static tag, it should be success. - [Template] Read a ${tag} named ${tag_name} from ${node}, using ${check} to check the ${value}, it will be success. - ${hold_int16_static} hold_int16 ${modbus_node} Compare Tag Value As Int 1 - -Write a static tag, it should be success. - [Template] Write and Read a ${tag} named ${tag_name} from ${node}, using ${check} to check the ${value}, it will be success. - ${hold_int16_static} hold_int16 ${modbus_node} Compare Tag Value As Int 2 + [Template] Read some ${tags} from ${node}, it will be success. + ${input_register_int16},${input_register_uint16},${input_register_int32},${input_register_uint32},${input_register_float},${input_register_string},${input_register_bit} ${modbus_tcp_node} + ${hold_int16},${hold_uint16},${hold_int32},${hold_uint32},${hold_float},${hold_string},${hold_bit} ${modbus_tcp_node} + ${coil_bit_1},${coil_bit_2},${coil_bit_3},${coil_bit_4},${coil_bit_5} ${modbus_tcp_node} + ${input_bit_1},${input_bit_2},${input_bit_3},${input_bit_4},${input_bit_5} ${modbus_tcp_node} Write and read a tag with same name and same address that in different groups, it should be success. ${groups}= Create List group-1 group-2 group-3 - Write and read a ${hold_int16} with same hold_int16 and same address in different ${groups} that from ${modbus_node}, using Compare Tag Value As Int to check the 2, it will be success. + Write and read a ${hold_int16} with same hold_int16 and same address in different ${groups} that from ${modbus_tcp_node}, using Compare Tag Value As Int to check the 2, it will be success. Write and read a tag with same name but different address that in different groups, it should be success. ${groups}= Create List group-1 group-2 group-3 - Write and read ${hold_int16} ${hold_int16_address_update} with same hold_int16 but different address in different ${groups} that from ${modbus_node}, using Compare Tag Value As Int to check the 2, it will be success. + Write and read ${hold_int16} ${hold_int16_address_update} with same hold_int16 but different address in different ${groups} that from ${modbus_tcp_node}, using Compare Tag Value As Int to check the 2, it will be success. Update a tag with right address and types and attribute, it will be success [Template] Update a ${tag} by a ${tag_update} named ${tag_name} from ${node} , it can read and write, using ${check} to check the ${value}, it will be success. - ${hold_int16} ${hold_int16_address_update} hold_int16 ${modbus_node} Compare Tag Value As Int 120 - ${hold_int16} ${hold_int16_type_update} hold_int16 ${modbus_node} Compare Tag Value As Int 120 + ${hold_int16} ${hold_int16_address_update} hold_int16 ${modbus_tcp_node} Compare Tag Value As Int 120 + ${hold_int16} ${hold_int16_type_update} hold_int16 ${modbus_tcp_node} Compare Tag Value As Int 120 Update a tag with unmatch address and types and attribute, it should be failure. [Template] Update a ${tag} by a ${tag_update} with the unmatch settings from ${node} , it will be failure and return ${resp_status} and ${error_code}. - ${input_bit_1} ${input_bit_1_attribute_unmatch} ${modbus_node} 200 ${NEU_ERR_TAG_ATTRIBUTE_NOT_SUPPORT} - ${input_bit_1} ${input_bit_1_type_unmatch} ${modbus_node} 200 ${NEU_ERR_TAG_TYPE_NOT_SUPPORT} - ${input_bit_1} ${input_bit_1_name_wrong} ${modbus_node} 200 ${NEU_ERR_TAG_NOT_EXIST} - -Update to be a static, it will be success - [Template] Update a ${tag} by a ${tag_update} named ${tag_name} from ${node} , it can read and write, using ${check} to check the ${value}, it will be success. - ${hold_int16} ${hold_int16_static} hold_int16 ${modbus_node} Compare Tag Value As Int 42 + ${input_bit_1} ${input_bit_1_attribute_unmatch} ${modbus_tcp_node} 200 ${NEU_ERR_TAG_ATTRIBUTE_NOT_SUPPORT} + ${input_bit_1} ${input_bit_1_type_unmatch} ${modbus_tcp_node} 200 ${NEU_ERR_TAG_TYPE_NOT_SUPPORT} + ${input_bit_1} ${input_bit_1_name_wrong} ${modbus_tcp_node} 200 ${NEU_ERR_TAG_NOT_EXIST} Write a value to a tag that don't exit in plc or neuron, it will be failure. [Template] Write a ${value} to a ${tag} with named ${tag_name} that don't exist in neuron or plc from ${node}, it will be failure. - 12 ${input_bit_1} input ${modbus_node} + 12 ${input_bit_1} input ${modbus_tcp_node} Write value to a tag without write attribute, it will be failure. [Template] Write a ${value} to a ${tag} named ${tag_name} that without write attribute from ${node}, it will be failure. - 1 ${input_bit_1} input_bit_1 ${modbus_node} + 1 ${input_bit_1} input_bit_1 ${modbus_tcp_node} Write and read a value to a tag when the node is stoped, it will be failure. [Template] Write and read a ${value} to a ${tag} named ${tag_name} when the ${node} is stoped, it will be failure. - 1 ${hold_int16} hold_int16 ${modbus_node} + 1 ${hold_int16} hold_int16 ${modbus_tcp_node} *** Keywords *** Modbus Test Setup @@ -182,17 +169,17 @@ Modbus Test Setup Start Neuronx - ${res}= Add Node ${modbus_node} ${PLUGIN-MODBUS-TCP} + ${res}= Add Node ${modbus_tcp_node} ${PLUGIN-MODBUS-TCP} Check Response Status ${res} 200 - ${res}= Node Setting ${modbus_node} ${MODBUS_TCP_CONFIG} + ${res}= Node Setting ${modbus_tcp_node} ${MODBUS_TCP_CONFIG} Check Response Status ${res} 200 - ${state}= Get Node State ${modbus_node} + ${state}= Get Node State ${modbus_tcp_node} Should Be Equal As Integers ${state}[running] ${NODE_STATE_RUNNING} - Should Be Equal As Integers ${state}[link] ${NODE_LINK_STATE_DISCONNECTED} + # Should Be Equal As Integers ${state}[link] ${NODE_LINK_STATE_CONNECTED} Modbus Test Teardown - Del Node ${modbus_node} + Del Node ${modbus_tcp_node} Stop Neuronx Terminate All Processes kill=false diff --git a/ft/persist_test.robot b/ft/persist_test.robot index b959ef03..c69588be 100644 --- a/ft/persist_test.robot +++ b/ft/persist_test.robot @@ -72,8 +72,8 @@ Get the deleted adapter after stopping and restarting Neuron, it should return f Get the setted adapters information after stopping and restarting Neuron, it should return the correct information ${process} = Neuron Start Running - Node Setting ${modbus-tcp-1} {"host": "127.0.0.1", "port": 502, "timeout": 3000} - Node Setting ${modbus-tcp-2} {"host": "127.0.0.1", "port": 502, "timeout": 3000} + Node Setting ${modbus-tcp-1} {"host": "127.0.0.1", "port": 502, "timeout": 3000, "connection_mode": 0, "interval": 20} + Node Setting ${modbus-tcp-2} {"host": "127.0.0.1", "port": 502, "timeout": 3000, "connection_mode": 0, "interval": 20} Node Setting ${mqtt-1} {"client-id":"123456", "upload-topic":"/neuron/mqtt-1/upload", "heartbeat-topic": "/neuron/mqtt-1/heartbeat","format": 0,"cache-mem-size": 10,"cache-disk-size": 10,"ssl":false,"host":"127.0.0.1","port":1883,"username":"admin","password":"0000","ca":"", "cert":"", "key":"", "keypass":""} Neuron Stop Running ${process} @@ -117,7 +117,7 @@ Get the setted adapters information after stopping and restarting Neuron, it sho Get the updated adapters settings information after stopping and restarting Neuron, it should return the correct information ${process} = Neuron Start Running - Node Setting ${modbus-tcp-2} {"host": "127.0.0.1", "port": 502, "timeout": 3000} + Node Setting ${modbus-tcp-2} {"host": "127.0.0.1", "port": 502, "timeout": 3000, "connection_mode": 0, "interval": 20} Neuron Stop Running ${process} ${process} = Neuron Start Running diff --git a/ft/resource/api.resource b/ft/resource/api.resource index 9025c94b..f00cacb6 100644 --- a/ft/resource/api.resource +++ b/ft/resource/api.resource @@ -3,7 +3,7 @@ Library REST http://127.0.0.1:7000 Resource common.resource *** Variables *** -${PLUGIN-MODBUS-TCP} Modbus TCP community +${PLUGIN-MODBUS-TCP} Modbus TCP ${PLUGIN-MQTT} MQTT ${PLUGIN-EKUIPER} eKuiper diff --git a/ft/resource/common.resource b/ft/resource/common.resource index 396b7e50..daebfd1b 100644 --- a/ft/resource/common.resource +++ b/ft/resource/common.resource @@ -17,7 +17,7 @@ Resource simulator.resource ${test_data_dir} ft/data -${PLUGIN_MODBUS_TCP} Modbus TCP community +${PLUGIN_MODBUS_TCP} Modbus TCP ${PLUGIN_MQTT} MQTT ${MQTT_CMD_NONE} @@ -39,7 +39,7 @@ ${MQTT_CONFIG} {"client-id": "${MQTT_CLIENTID}", "u ... "ssl": false, "host": "${MQTT_CONFIG_HOST}", "port": ${MQTT_CONFIG_PORT}, ... "username": "", "password": "", "ca":"", "cert":"", "key":"", "keypass":""} -${MODBUS_TCP_CONFIG} {"host": "127.0.0.1", "port": 60502, "timeout": 3000} +${MODBUS_TCP_CONFIG} {"host": "127.0.0.1", "port": 60502, "connection_mode": 0, "timeout": 3000, "interval": 20} *** Variables *** ${neuron_process} diff --git a/ft/resource/driver.resource b/ft/resource/driver.resource index c5b4d660..dc30de5a 100644 --- a/ft/resource/driver.resource +++ b/ft/resource/driver.resource @@ -70,13 +70,9 @@ Read some ${tags} from ${node}, it will be success. Check Response Status ${res} 200 Check Error Code ${res} ${NEU_ERR_SUCCESS} - Log ${tags} - - FOR ${tag} IN @{tags} - ${res}= Add Tags ${node} group ${tag} - Check Response Status ${res} 200 - Check Error Code ${res} ${NEU_ERR_SUCCESS} - END + ${res}= Add Tags ${node} group ${tags} + Check Response Status ${res} 200 + Check Error Code ${res} ${NEU_ERR_SUCCESS} Sleep 290ms ${res}= Read Tags ${node} group @@ -238,7 +234,7 @@ Write a ${value} to a ${tag} with named ${tag_name} that don't exist in neuron o [Teardown] Del Group ${node} group -Read a ${tag} with named ${tag_name} that don't exist in plc form ${node}, it will be failure. +Read a ${tag} with named ${tag_name} that don't exist in plc form ${node}, it will be failure ${error}. [Tags] Driver 15 ${res}= Add Group ${node} group 100 @@ -252,7 +248,7 @@ Read a ${tag} with named ${tag_name} that don't exist in plc form ${node}, it wi Sleep 290ms ${res}= Read Tags ${node} group Check Response Status ${res} 200 - Check Tag Error As Int ${res} ${tag_name} ${NEU_ERR_TAG_NOT_EXIST} + Check Tag Error As Int ${res}[tags] ${tag_name} ${error} [Teardown] Del Group ${node} group diff --git a/plugins/modbus/CMakeLists.txt b/plugins/modbus/CMakeLists.txt index 9d16c6d5..f419d5b3 100644 --- a/plugins/modbus/CMakeLists.txt +++ b/plugins/modbus/CMakeLists.txt @@ -3,13 +3,33 @@ set(LIBRARY_OUTPUT_PATH "${CMAKE_BINARY_DIR}/plugins") set(MODBUS_SRC modbus.c modbus_point.c modbus_req.c modbus_stack.c) set(CMAKE_BUILD_RPATH ./) -file(COPY ${CMAKE_SOURCE_DIR}/plugins/modbus/modbus-tcp-community.json DESTINATION ${CMAKE_BINARY_DIR}/plugins/schema/) - +file(COPY ${CMAKE_SOURCE_DIR}/plugins/modbus/modbus-tcp.json DESTINATION ${CMAKE_BINARY_DIR}/plugins/schema/) +file(COPY ${CMAKE_SOURCE_DIR}/plugins/modbus/modbus-rtu.json DESTINATION ${CMAKE_BINARY_DIR}/plugins/schema/) +file(COPY ${CMAKE_SOURCE_DIR}/plugins/modbus/modbus-qh-tcp.json DESTINATION ${CMAKE_BINARY_DIR}/plugins/schema/) # modbus rtu plugin -set(MODBUS_TCP_PLUGIN plugin-modbus-tcp-comm) -set(MODBUS_TCP_PLUGIN_SOURCES modbus_tcp.c +set(MODBUS_RTU_PLUGIN plugin-modbus-rtu) +set(MODBUS_RTU_PLUGIN_SOURCES modbus_rtu.c + ${MODBUS_SRC}) +add_library(${MODBUS_RTU_PLUGIN} SHARED) +target_include_directories(${MODBUS_RTU_PLUGIN} PRIVATE ${CMAKE_SOURCE_DIR}/include/neuron) +target_sources(${MODBUS_RTU_PLUGIN} PRIVATE ${MODBUS_RTU_PLUGIN_SOURCES}) +target_link_libraries(${MODBUS_RTU_PLUGIN} neuron-base) + +# modbus tcp plugin +set(MODBUS_TCP_PLUS_PLUGIN plugin-modbus-tcp) +set(MODBUS_TCP_PLUS_PLUGIN_SOURCES modbus_tcp.c ${MODBUS_SRC}) -add_library(${MODBUS_TCP_PLUGIN} SHARED) -target_include_directories(${MODBUS_TCP_PLUGIN} PRIVATE ${CMAKE_SOURCE_DIR}/include/neuron) -target_sources(${MODBUS_TCP_PLUGIN} PRIVATE ${MODBUS_TCP_PLUGIN_SOURCES}) -target_link_libraries(${MODBUS_TCP_PLUGIN} neuron-base) \ No newline at end of file +add_library(${MODBUS_TCP_PLUS_PLUGIN} SHARED) +target_include_directories(${MODBUS_TCP_PLUS_PLUGIN} PRIVATE ${CMAKE_SOURCE_DIR}/include/neuron) +target_sources(${MODBUS_TCP_PLUS_PLUGIN} PRIVATE ${MODBUS_TCP_PLUS_PLUGIN_SOURCES}) +target_link_libraries(${MODBUS_TCP_PLUS_PLUGIN} neuron-base) + +# modbus qh tcp plugin +set(MODBUS_TCP_QH_PLUGIN plugin-modbus-qh-tcp) +set(MODBUS_TCP_QH_PLUGIN_SOURCES modbus_qh_tcp.c + ${MODBUS_SRC}) +add_library(${MODBUS_TCP_QH_PLUGIN} SHARED) +target_include_directories(${MODBUS_TCP_QH_PLUGIN} PRIVATE ${CMAKE_SOURCE_DIR}/include/neuron) +target_sources(${MODBUS_TCP_QH_PLUGIN} PRIVATE ${MODBUS_TCP_QH_PLUGIN_SOURCES}) +target_link_libraries(${MODBUS_TCP_QH_PLUGIN} neuron-base) + diff --git a/plugins/modbus/modbus-qh-tcp.json b/plugins/modbus/modbus-qh-tcp.json new file mode 100644 index 00000000..167a9a94 --- /dev/null +++ b/plugins/modbus/modbus-qh-tcp.json @@ -0,0 +1,104 @@ +{ + "tag_regex": [ + { + "type": 3, + "regex": "^[0-9]+![3-4][0-9]+(#B|#L|)$" + }, + { + "type": 4, + "regex": "^[0-9]+![3-4][0-9]+(#B|#L|)$" + }, + { + "type": 5, + "regex": "^[0-9]+![3-4][0-9]+(#BB|#BL|#LL|#LB|)$" + }, + { + "type": 6, + "regex": "^[0-9]+![3-4][0-9]+(#BB|#BL|#LL|#LB|)$" + }, + { + "type": 7, + "regex": "^[0-9]+![3-4][0-9]+(#B|#L|)$" + }, + { + "type": 8, + "regex": "^[0-9]+![3-4][0-9]+(#B|#L|)$" + }, + { + "type": 9, + "regex": "^[0-9]+![3-4][0-9]+(#BB|#BL|#LL|#LB|)$" + }, + { + "type": 10, + "regex": "^[0-9]+![3-4][0-9]+(#B|#L|)$" + }, + { + "type": 11, + "regex": "^[0-9]+!([0-1][0-9]+|[3-4][0-9]+\\.([0-9]|[0-1][0-5]))$" + }, + { + "type": 13, + "regex": "^[0-9]+![3-4][0-9]+\\.[0-9]+(H|L|)$" + } + ], + "group_interval": 100, + "host": { + "name": "IP Address", + "name_zh": "IP地址", + "description": "Local IP in server mode, remote device IP in client mode", + "description_zh": "服务端模式中填写本地 IP,客户端模式中填写目标设备 IP", + "attribute": "required", + "type": "string", + "valid": { + "regex": "/^((2[0-4]\\d|25[0-5]|[01]?\\d\\d?)\\.){3}(2[0-4]\\d|25[0-5]|[01]?\\d\\d?)$/", + "length": 30 + } + }, + "port": { + "name": "Port", + "name_zh": "端口号", + "description": "Local port in server mode, remote device port in client mode", + "description_zh": "服务端模式中填写本地端口号,客户端模式中填写远程设备端口号", + "attribute": "required", + "type": "int", + "default": 502, + "valid": { + "min": 1, + "max": 65535 + } + }, + "timeout": { + "name": "Connection Timeout", + "name_zh": "连接超时时间", + "description": "Connection timeout(ms)", + "description_zh": "连接超时时间,单位为毫秒", + "attribute": "required", + "type": "int", + "default": 3000, + "valid": { + "min": 1000, + "max": 65535 + } + }, + "connection_mode": { + "name": "Connection Mode", + "name_zh": "连接模式", + "description": "Neuron as the client, or as the server", + "description_zh": "Neuron 作为客户端或服务端", + "attribute": "required", + "type": "map", + "default": 0, + "valid": { + "map": [ + { + "key": "Client", + "value": 0 + }, + { + "key": "Server", + "value": 1 + } + ] + } + } +} \ No newline at end of file diff --git a/plugins/modbus/modbus-rtu.json b/plugins/modbus/modbus-rtu.json new file mode 100644 index 00000000..e084b638 --- /dev/null +++ b/plugins/modbus/modbus-rtu.json @@ -0,0 +1,305 @@ +{ + "tag_regex": [ + { + "type": 3, + "regex": "^[0-9]+![3-4][0-9]+(#B|#L|)$" + }, + { + "type": 4, + "regex": "^[0-9]+![3-4][0-9]+(#B|#L|)$" + }, + { + "type": 5, + "regex": "^[0-9]+![3-4][0-9]+(#BB|#BL|#LL|#LB|)$" + }, + { + "type": 6, + "regex": "^[0-9]+![3-4][0-9]+(#BB|#BL|#LL|#LB|)$" + }, + { + "type": 7, + "regex": "^[0-9]+![3-4][0-9]+(#B|#L|)$" + }, + { + "type": 8, + "regex": "^[0-9]+![3-4][0-9]+(#B|#L|)$" + }, + { + "type": 9, + "regex": "^[0-9]+![3-4][0-9]+(#BB|#BL|#LL|#LB|)$" + }, + { + "type": 10, + "regex": "^[0-9]+![3-4][0-9]+(#B|#L|)$" + }, + { + "type": 11, + "regex": "^[0-9]+!([0-1][0-9]+|[3-4][0-9]+\\.([0-9]|[0-1][0-5]))$" + }, + { + "type": 13, + "regex": "^[0-9]+![3-4][0-9]+\\.[0-9]+(H|L|)$" + } + ], + "group_interval": 1000, + "link": { + "name": "Physical Link", + "name_zh": "物理链路", + "description": "Serial or Ethernet", + "description_zh": "串口连接或以太网连接", + "attribute": "required", + "type": "map", + "default": 0, + "valid": { + "map": [ + { + "key": "Serial", + "value": 0 + }, + { + "key": "TCP", + "value": 1 + } + ] + } + }, + "timeout": { + "name": "Connection Timeout", + "name_zh": "连接超时时间", + "description": "Connection timeout(ms)", + "description_zh": "连接超时时间,单位为毫秒", + "attribute": "required", + "type": "int", + "default": 3000, + "valid": { + "min": 1000, + "max": 30000 + } + }, + "interval": { + "name": "Send Interval", + "name_zh": "指令发送间隔", + "description": "Send reading instruction interval(ms)", + "description_zh": "发送读指令时间间隔,单位为毫秒", + "attribute": "required", + "type": "int", + "default": 20, + "valid": { + "min": 0, + "max": 3000 + } + }, + "device": { + "name": "Serial Device", + "name_zh": "串口设备", + "description": "Serial device path", + "description_zh": "串口设备路径", + "attribute": "required", + "type": "string", + "condition": { + "field": "link", + "value": 0 + }, + "valid": { + "length": 30 + } + }, + "stop": { + "name": "Stop Bits", + "name_zh": "停止位", + "description": "Stop bits", + "description_zh": "停止位", + "attribute": "required", + "type": "map", + "default": 0, + "valid": { + "map": [ + { + "key": "1", + "value": 0 + }, + { + "key": "2", + "value": 1 + } + ] + }, + "condition": { + "field": "link", + "value": 0 + } + }, + "parity": { + "name": "Parity", + "name_zh": "校验位", + "description": "Parity", + "description_zh": "校验位", + "attribute": "required", + "type": "map", + "default": 0, + "valid": { + "map": [ + { + "key": "none", + "value": 0 + }, + { + "key": "odd", + "value": 1 + }, + { + "key": "even", + "value": 2 + }, + { + "key": "mark", + "value": 3 + }, + { + "key": "space", + "value": 4 + } + ] + }, + "condition": { + "field": "link", + "value": 0 + } + }, + "baud": { + "name": "Baud Rate", + "name_zh": "波特率", + "description": "Baud rate", + "description_zh": "波特率", + "attribute": "required", + "type": "map", + "default": 4, + "valid": { + "map": [ + { + "key": "2400", + "value": 6 + }, + { + "key": "4800", + "value": 5 + }, + { + "key": "9600", + "value": 4 + }, + { + "key": "19200", + "value": 3 + }, + { + "key": "38400", + "value": 2 + }, + { + "key": "57600", + "value": 1 + }, + { + "key": "115200", + "value": 0 + } + ] + }, + "condition": { + "field": "link", + "value": 0 + } + }, + "data": { + "name": "Data Bits", + "name_zh": "数据位", + "description": "Data bits", + "description_zh": "数据位", + "attribute": "required", + "type": "map", + "default": 3, + "valid": { + "map": [ + { + "key": "5", + "value": 0 + }, + { + "key": "6", + "value": 1 + }, + { + "key": "7", + "value": 2 + }, + { + "key": "8", + "value": 3 + } + ] + }, + "condition": { + "field": "link", + "value": 0 + } + }, + "host": { + "name": "IP Address", + "name_zh": "IP地址", + "description": "Local IP in server mode, remote device IP in client mode", + "description_zh": "服务端模式中填写本地 IP,客户端模式中填写目标设备 IP", + "attribute": "required", + "type": "string", + "valid": { + "regex": "/^((2[0-4]\\d|25[0-5]|[01]?\\d\\d?)\\.){3}(2[0-4]\\d|25[0-5]|[01]?\\d\\d?)$/", + "length": 30 + }, + "condition": { + "field": "link", + "value": 1 + } + }, + "port": { + "name": "Port", + "name_zh": "端口号", + "description": "Local port in server mode, remote device port in client mode", + "description_zh": "服务端模式中填写本地端口号,客户端模式中填写远程设备端口号", + "attribute": "required", + "type": "int", + "default": 502, + "valid": { + "min": 1, + "max": 65535 + }, + "condition": { + "field": "link", + "value": 1 + } + }, + "connection_mode": { + "name": "Connection Mode", + "name_zh": "连接模式", + "description": "Neuron as the client, or as the server", + "description_zh": "Neuron 作为客户端或服务端", + "attribute": "required", + "type": "map", + "default": 0, + "valid": { + "map": [ + { + "key": "Client", + "value": 0 + }, + { + "key": "Server", + "value": 1 + } + ] + }, + "condition": { + "field": "link", + "value": 1 + } + } +} \ No newline at end of file diff --git a/plugins/modbus/modbus-tcp-community.json b/plugins/modbus/modbus-tcp-community.json deleted file mode 100644 index 60ac8834..00000000 --- a/plugins/modbus/modbus-tcp-community.json +++ /dev/null @@ -1,67 +0,0 @@ -{ - "tag_regex": [ - { - "type": 3, - "regex": "^[0-9]+![3-4][0-9]+(#B|#L|)$" - }, - { - "type": 4, - "regex": "^[0-9]+![3-4][0-9]+(#B|#L|)$" - }, - { - "type": 5, - "regex": "^[0-9]+![3-4][0-9]+(#BB|#BL|#LL|#LB|)$" - }, - { - "type": 6, - "regex": "^[0-9]+![3-4][0-9]+(#BB|#BL|#LL|#LB|)$" - }, - { - "type": 9, - "regex": "^[0-9]+![3-4][0-9]+(#BB|#BL|#LL|#LB|)$" - }, - { - "type": 11, - "regex": "^[0-9]+!([0-1][0-9]+|[3-4][0-9]+\\.([0-9]|[0-1][0-5]))$" - } - ], - "group_interval": 1000, - "host": { - "name": "Device IP Address", - "name_zh": "设备IP地址", - "description": "Target device IPv4 address", - "description_zh": "目标设备IPv4地址", - "attribute": "required", - "type": "string", - "valid": { - "regex": "/^((2[0-4]\\d|25[0-5]|[01]?\\d\\d?)\\.){3}(2[0-4]\\d|25[0-5]|[01]?\\d\\d?)$/", - "length": 30 - } - }, - "port": { - "name": "Device Port", - "name_zh": "设备端口", - "description": "Target device port number", - "description_zh": "目标设备端口号", - "attribute": "required", - "type": "int", - "default": 502, - "valid": { - "min": 1, - "max": 65535 - } - }, - "timeout": { - "name": "Recv Timeout", - "name_zh": "接收超时时间", - "description": "Recv msg timeout(ms)", - "description_zh": "接收消息超时时间(毫秒)", - "attribute": "required", - "type": "int", - "default": 3000, - "valid": { - "min": 1000, - "max": 65535 - } - } -} \ No newline at end of file diff --git a/plugins/modbus/modbus-tcp.json b/plugins/modbus/modbus-tcp.json new file mode 100644 index 00000000..1ea5e5b3 --- /dev/null +++ b/plugins/modbus/modbus-tcp.json @@ -0,0 +1,117 @@ +{ + "tag_regex": [ + { + "type": 3, + "regex": "^[0-9]+![3-4][0-9]+(#B|#L|)$" + }, + { + "type": 4, + "regex": "^[0-9]+![3-4][0-9]+(#B|#L|)$" + }, + { + "type": 5, + "regex": "^[0-9]+![3-4][0-9]+(#BB|#BL|#LL|#LB|)$" + }, + { + "type": 6, + "regex": "^[0-9]+![3-4][0-9]+(#BB|#BL|#LL|#LB|)$" + }, + { + "type": 7, + "regex": "^[0-9]+![3-4][0-9]+(#B|#L|)$" + }, + { + "type": 8, + "regex": "^[0-9]+![3-4][0-9]+(#B|#L|)$" + }, + { + "type": 9, + "regex": "^[0-9]+![3-4][0-9]+(#BB|#BL|#LL|#LB|)$" + }, + { + "type": 10, + "regex": "^[0-9]+![3-4][0-9]+(#B|#L|)$" + }, + { + "type": 11, + "regex": "^[0-9]+!([0-1][0-9]+|[3-4][0-9]+\\.([0-9]|[0-1][0-5]))$" + }, + { + "type": 13, + "regex": "^[0-9]+![3-4][0-9]+\\.[0-9]+(H|L|)$" + } + ], + "group_interval": 1000, + "connection_mode": { + "name": "Connection Mode", + "name_zh": "连接模式", + "description": "Neuron as the client, or as the server", + "description_zh": "Neuron 作为客户端或服务端", + "attribute": "required", + "type": "map", + "default": 0, + "valid": { + "map": [ + { + "key": "Client", + "value": 0 + }, + { + "key": "Server", + "value": 1 + } + ] + } + }, + "interval": { + "name": "Send Interval", + "name_zh": "指令发送间隔", + "description": "Send reading instruction interval(ms)", + "description_zh": "发送读指令时间间隔,单位为毫秒", + "attribute": "required", + "type": "int", + "default": 20, + "valid": { + "min": 0, + "max": 3000 + } + }, + "host": { + "name": "IP Address", + "name_zh": "IP地址", + "description": "Local IP in server mode, remote device IP in client mode", + "description_zh": "服务端模式中填写本地 IP,客户端模式中填写目标设备 IP", + "attribute": "required", + "type": "string", + "valid": { + "regex": "/^((2[0-4]\\d|25[0-5]|[01]?\\d\\d?)\\.){3}(2[0-4]\\d|25[0-5]|[01]?\\d\\d?)$/", + "length": 30 + } + }, + "port": { + "name": "Port", + "name_zh": "端口号", + "description": "Local port in server mode, remote device port in client mode", + "description_zh": "服务端模式中填写本地端口号,客户端模式中填写远程设备端口号", + "attribute": "required", + "type": "int", + "default": 502, + "valid": { + "min": 1, + "max": 65535 + } + }, + "timeout": { + "name": "Connection Timeout", + "name_zh": "连接超时时间", + "description": "Connection timeout(ms)", + "description_zh": "连接超时时间,单位为毫秒", + "attribute": "required", + "type": "int", + "default": 3000, + "valid": { + "min": 1000, + "max": 65535 + } + } +} \ No newline at end of file diff --git a/plugins/modbus/modbus.c b/plugins/modbus/modbus.c index 270c3c07..3aceb8eb 100644 --- a/plugins/modbus/modbus.c +++ b/plugins/modbus/modbus.c @@ -23,6 +23,25 @@ #include "modbus.h" +static uint16_t calcrc(uint8_t *buf, int len) +{ + uint16_t crc = 0xffff; + uint16_t crcpoly = 0xa001; + + for (int i = 0; i < len; i++) { + uint8_t x = buf[i]; + crc ^= x; + for (int k = 0; k < 8; k++) { + uint16_t usepoly = crc & 0x1; + crc >>= 1; + if (usepoly) + crc ^= crcpoly; + } + } + + return crc; +} + void modbus_header_wrap(neu_protocol_pack_buf_t *buf, uint16_t seq) { assert(neu_protocol_pack_buf_unused_size(buf) >= @@ -149,6 +168,40 @@ int modbus_data_unwrap(neu_protocol_unpack_buf_t *buf, return sizeof(struct modbus_data); } +void modbus_crc_set(neu_protocol_pack_buf_t *buf) +{ + uint16_t crc = calcrc(neu_protocol_pack_buf_get(buf), + neu_protocol_pack_buf_used_size(buf) - 2); + uint16_t *p_crc = (uint16_t *) neu_protocol_pack_buf_set( + buf, neu_protocol_pack_buf_used_size(buf) - 2, 2); + + *p_crc = crc; +} + +void modbus_crc_wrap(neu_protocol_pack_buf_t *buf) +{ + assert(neu_protocol_pack_buf_unused_size(buf) >= sizeof(struct modbus_crc)); + struct modbus_crc *crc = (struct modbus_crc *) neu_protocol_pack_buf( + buf, sizeof(struct modbus_crc)); + + crc->crc = 0; +} + +int modbus_crc_unwrap(neu_protocol_unpack_buf_t *buf, + struct modbus_crc * out_crc) +{ + struct modbus_crc *crc = (struct modbus_crc *) neu_protocol_unpack_buf( + buf, sizeof(struct modbus_crc)); + + if (crc == NULL) { + return 0; + } + + *out_crc = *crc; + + return sizeof(struct modbus_crc); +} + const char *modbus_area_to_str(modbus_area_e area) { switch (area) { diff --git a/plugins/modbus/modbus.h b/plugins/modbus/modbus.h index 1427804f..ee1e871c 100644 --- a/plugins/modbus/modbus.h +++ b/plugins/modbus/modbus.h @@ -92,6 +92,15 @@ void modbus_data_wrap(neu_protocol_pack_buf_t *buf, uint8_t n_byte, int modbus_data_unwrap(neu_protocol_unpack_buf_t *buf, struct modbus_data * out_data); +struct modbus_crc { + uint16_t crc; +} __attribute__((packed)); + +void modbus_crc_set(neu_protocol_pack_buf_t *buf); +void modbus_crc_wrap(neu_protocol_pack_buf_t *buf); +int modbus_crc_unwrap(neu_protocol_unpack_buf_t *buf, + struct modbus_crc * out_crc); + const char *modbus_area_to_str(modbus_area_e area); #endif \ No newline at end of file diff --git a/plugins/modbus/modbus_point.c b/plugins/modbus/modbus_point.c index 0330074c..30c19c69 100644 --- a/plugins/modbus/modbus_point.c +++ b/plugins/modbus/modbus_point.c @@ -123,6 +123,16 @@ int modbus_tag_to_point(neu_datatag_t *tag, modbus_point_t *point) point->n_register = 2; } break; + case NEU_TYPE_UINT64: + case NEU_TYPE_INT64: + case NEU_TYPE_DOUBLE: + if (point->area == MODBUS_AREA_COIL || + point->area == MODBUS_AREA_INPUT) { + ret = NEU_ERR_TAG_TYPE_NOT_SUPPORT; + } else { + point->n_register = 4; + } + break; case NEU_TYPE_STRING: if (point->area == MODBUS_AREA_COIL || point->area == MODBUS_AREA_INPUT) { diff --git a/plugins/modbus/modbus_qh_tcp.c b/plugins/modbus/modbus_qh_tcp.c new file mode 100644 index 00000000..61904972 --- /dev/null +++ b/plugins/modbus/modbus_qh_tcp.c @@ -0,0 +1,224 @@ +/** + * NEURON IIoT System for Industry 4.0 + * Copyright (C) 2020-2022 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 "errcodes.h" + +#include "modbus_point.h" +#include "modbus_req.h" +#include "modbus_stack.h" + +static neu_plugin_t *driver_open(void); + +static int driver_close(neu_plugin_t *plugin); +static int driver_init(neu_plugin_t *plugin); +static int driver_uninit(neu_plugin_t *plugin); +static int driver_start(neu_plugin_t *plugin); +static int driver_stop(neu_plugin_t *plugin); +static int driver_config(neu_plugin_t *plugin, const char *config); +static int driver_request(neu_plugin_t *plugin, neu_reqresp_head_t *head, + void *data); + +static int driver_validate_tag(neu_plugin_t *plugin, neu_datatag_t *tag); +static int driver_group_timer(neu_plugin_t *plugin, neu_plugin_group_t *group); +static int driver_write(neu_plugin_t *plugin, void *req, neu_datatag_t *tag, + neu_value_u value); + +static const neu_plugin_intf_funs_t plugin_intf_funs = { + .open = driver_open, + .close = driver_close, + .init = driver_init, + .uninit = driver_uninit, + .start = driver_start, + .stop = driver_stop, + .setting = driver_config, + .request = driver_request, + + .driver.validate_tag = driver_validate_tag, + .driver.group_timer = driver_group_timer, + .driver.write_tag = driver_write, +}; + +const neu_plugin_module_t neu_plugin_module = { + .version = NEURON_PLUGIN_VER_1_0, + .schema = "modbus-qh-tcp", + .module_name = "Modbus TCP QH", + .module_descr = + "The modbus-qh-tcp plugin is an optimized version of modbus tcp, which " + "expands the packet description field to two bytes, limiting the " + "maximum length of Modbus tcp packets to 64KB", + .module_descr_zh = + "该插件是 modbus-tcp 插件的优化版,数据包描述字段扩展为连个字节,限制 " + "Modbus TCP 数据包的最大长度为 64KB。", + .intf_funs = &plugin_intf_funs, + .kind = NEU_PLUGIN_KIND_SYSTEM, + .type = NEU_NA_TYPE_DRIVER, + .display = true, + .single = false, + .timer_type = NEU_EVENT_TIMER_NOBLOCK, +}; + +static neu_plugin_t *driver_open(void) +{ + neu_plugin_t *plugin = calloc(1, sizeof(neu_plugin_t)); + + neu_plugin_common_init(&plugin->common); + + return plugin; +} + +static int driver_close(neu_plugin_t *plugin) +{ + free(plugin); + + return 0; +} + +static int driver_init(neu_plugin_t *plugin) +{ + plugin->protocol = MODBUS_PROTOCOL_TCP; + plugin->events = neu_event_new(); + plugin->stack = modbus_stack_create((void *) plugin, MODBUS_PROTOCOL_TCP, + modbus_send_msg, modbus_value_handle, + modbus_write_resp); + + return 0; +} + +static int driver_uninit(neu_plugin_t *plugin) +{ + if (plugin->conn != NULL) { + neu_conn_destory(plugin->conn); + } + + if (plugin->stack) { + modbus_stack_destroy(plugin->stack); + } + + neu_event_close(plugin->events); + + plog_info(plugin, "node: modbus uninit ok"); + + return 0; +} + +static int driver_start(neu_plugin_t *plugin) +{ + neu_conn_start(plugin->conn); + return 0; +} + +static int driver_stop(neu_plugin_t *plugin) +{ + neu_conn_stop(plugin->conn); + return 0; +} + +static int driver_config(neu_plugin_t *plugin, const char *config) +{ + int ret = 0; + char * err_param = NULL; + neu_json_elem_t port = { .name = "port", .t = NEU_JSON_INT }; + neu_json_elem_t timeout = { .name = "timeout", .t = NEU_JSON_INT }; + neu_json_elem_t host = { .name = "host", .t = NEU_JSON_STR }; + neu_json_elem_t mode = { .name = "connection_mode", .t = NEU_JSON_INT }; + neu_conn_param_t param = { 0 }; + + ret = neu_parse_param((char *) config, &err_param, 4, &port, &host, &mode, + &timeout); + + if (ret != 0) { + plog_info(plugin, "config: %s, decode error: %s", config, err_param); + free(err_param); + return -1; + } + + param.log = plugin->common.log; + if (mode.v.val_int == 1) { + param.type = NEU_CONN_TCP_SERVER; + param.params.tcp_server.ip = host.v.val_str; + param.params.tcp_server.port = port.v.val_int; + param.params.tcp_server.start_listen = modbus_tcp_server_listen; + param.params.tcp_server.stop_listen = modbus_tcp_server_stop; + param.params.tcp_server.timeout = timeout.v.val_int; + param.params.tcp_server.max_link = 1; + plugin->is_server = true; + } else { + param.type = NEU_CONN_TCP_CLIENT; + param.params.tcp_client.ip = host.v.val_str; + param.params.tcp_client.port = port.v.val_int; + param.params.tcp_client.timeout = timeout.v.val_int; + plugin->is_server = false; + } + + plog_info(plugin, "config: host: %s, port: %" PRId64 ", mode: %" PRId64 "", + host.v.val_str, port.v.val_int, mode.v.val_int); + + if (plugin->conn != NULL) { + plugin->conn = neu_conn_reconfig(plugin->conn, ¶m); + } else { + plugin->common.link_state = NEU_NODE_LINK_STATE_DISCONNECTED; + plugin->conn = + neu_conn_new(¶m, (void *) plugin, modbus_conn_connected, + modbus_conn_disconnected); + } + + free(host.v.val_str); + return 0; +} + +static int driver_request(neu_plugin_t *plugin, neu_reqresp_head_t *head, + void *data) +{ + (void) plugin; + (void) head; + (void) data; + return 0; +} + +static int driver_validate_tag(neu_plugin_t *plugin, neu_datatag_t *tag) +{ + modbus_point_t point = { 0 }; + + int ret = modbus_tag_to_point(tag, &point); + if (ret == 0) { + plog_info( + plugin, + "validate tag success, name: %s, address: %s, type: %d, slave id: " + "%d, start address: %d, n register: %d, area: %s", + tag->name, tag->address, tag->type, point.slave_id, + point.start_address, point.n_register, + modbus_area_to_str(point.area)); + } + + return ret; +} + +static int driver_group_timer(neu_plugin_t *plugin, neu_plugin_group_t *group) +{ + return modbus_group_timer(plugin, group, 0xffff); +} + +static int driver_write(neu_plugin_t *plugin, void *req, neu_datatag_t *tag, + neu_value_u value) +{ + return modbus_write(plugin, req, tag, value); +} \ No newline at end of file diff --git a/plugins/modbus/modbus_req.c b/plugins/modbus/modbus_req.c index 79eecab2..7b6d7fd1 100644 --- a/plugins/modbus/modbus_req.c +++ b/plugins/modbus/modbus_req.c @@ -16,14 +16,16 @@ * 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 "modbus_point.h" #include "modbus_stack.h" #include "modbus_req.h" struct modbus_group_data { - char * group; UT_array * tags; + char * group; modbus_read_cmd_sort_t *cmd_sort; }; @@ -46,22 +48,76 @@ void modbus_conn_disconnected(void *data, int fd) plugin->common.link_state = NEU_NODE_LINK_STATE_DISCONNECTED; } +void modbus_tcp_server_listen(void *data, int fd) +{ + struct neu_plugin * plugin = (struct neu_plugin *) data; + neu_event_io_param_t param = { + .cb = modbus_tcp_server_io_callback, + .fd = fd, + .usr_data = (void *) plugin, + }; + + plugin->tcp_server_io = neu_event_add_io(plugin->events, param); +} + +void modbus_tcp_server_stop(void *data, int fd) +{ + struct neu_plugin *plugin = (struct neu_plugin *) data; + (void) fd; + + neu_event_del_io(plugin->events, plugin->tcp_server_io); +} + +int modbus_tcp_server_io_callback(enum neu_event_io_type type, int fd, + void *usr_data) +{ + neu_plugin_t *plugin = (neu_plugin_t *) usr_data; + + switch (type) { + case NEU_EVENT_IO_READ: { + int client_fd = neu_conn_tcp_server_accept(plugin->conn); + if (client_fd > 0) { + plugin->client_fd = client_fd; + } + + break; + } + case NEU_EVENT_IO_CLOSED: + case NEU_EVENT_IO_HUP: + plog_warn(plugin, "tcp server recv: %d, conn closed, fd: %d", type, fd); + neu_event_del_io(plugin->events, plugin->tcp_server_io); + neu_conn_disconnect(plugin->conn); + break; + } + + return 0; +} + int modbus_send_msg(void *ctx, uint16_t n_byte, uint8_t *bytes) { neu_plugin_t *plugin = (neu_plugin_t *) ctx; + int ret = 0; plog_send_protocol(plugin, bytes, n_byte); - return neu_conn_send(plugin->conn, bytes, n_byte); + + if (plugin->is_server) { + ret = neu_conn_tcp_server_send(plugin->conn, plugin->client_fd, bytes, + n_byte); + } else { + ret = neu_conn_send(plugin->conn, bytes, n_byte); + } + + return ret; } int modbus_group_timer(neu_plugin_t *plugin, neu_plugin_group_t *group, uint16_t max_byte) { + neu_conn_state_t state = { 0 }; neu_adapter_update_metric_cb_t update_metric = plugin->common.adapter_callbacks->update_metric; - struct modbus_group_data *gd = NULL; - neu_conn_state_t state = { 0 }; - int64_t rtt = NEU_METRIC_LAST_RTT_MS_MAX; + struct modbus_group_data *gd = NULL; + int64_t rtt = NEU_METRIC_LAST_RTT_MS_MAX; if (group->user_data == NULL) { gd = calloc(1, sizeof(struct modbus_group_data)); @@ -81,32 +137,39 @@ int modbus_group_timer(neu_plugin_t *plugin, neu_plugin_group_t *group, gd->group = strdup(group->group_name); gd->cmd_sort = modbus_tag_sort(gd->tags, max_byte); - } else { - gd = (struct modbus_group_data *) group->user_data; } + + gd = (struct modbus_group_data *) group->user_data; plugin->plugin_group_data = gd; for (uint16_t i = 0; i < gd->cmd_sort->n_cmd; i++) { - uint16_t response_size = 0; - int64_t start = neu_time_ms(); plugin->cmd_idx = i; + uint16_t response_size = 0; + uint64_t read_tms = neu_time_ms(); int ret = modbus_stack_read( plugin->stack, gd->cmd_sort->cmd[i].slave_id, gd->cmd_sort->cmd[i].area, gd->cmd_sort->cmd[i].start_address, gd->cmd_sort->cmd[i].n_register, &response_size); if (ret > 0) { - process_protocol_buf(plugin, response_size); - - int64_t end = neu_time_ms(); - if (end - start < rtt) { - rtt = end - start; - } + ret = process_protocol_buf(plugin, response_size); + rtt = neu_time_ms() - read_tms; + } + if (ret <= 0) { + modbus_value_handle(plugin, gd->cmd_sort->cmd[i].slave_id, 0, NULL, + NEU_ERR_PLUGIN_DISCONNECTED); + rtt = NEU_METRIC_LAST_RTT_MS_MAX; + } + if (plugin->interval > 0) { + struct timespec t1 = { .tv_sec = plugin->interval / 1000, + .tv_nsec = 1000 * 1000 * + (plugin->interval % 1000) }; + struct timespec t2 = { 0 }; + nanosleep(&t1, &t2); } } state = neu_conn_state(plugin->conn); - update_metric(plugin->common.adapter, NEU_METRIC_SEND_BYTES, state.send_bytes, NULL); update_metric(plugin->common.adapter, NEU_METRIC_RECV_BYTES, @@ -114,12 +177,11 @@ int modbus_group_timer(neu_plugin_t *plugin, neu_plugin_group_t *group, update_metric(plugin->common.adapter, NEU_METRIC_LAST_RTT_MS, rtt, NULL); update_metric(plugin->common.adapter, NEU_METRIC_GROUP_LAST_SEND_MSGS, gd->cmd_sort->n_cmd, group->group_name); - return 0; } int modbus_value_handle(void *ctx, uint8_t slave_id, uint16_t n_byte, - uint8_t *bytes) + uint8_t *bytes, int error) { neu_plugin_t * plugin = (neu_plugin_t *) ctx; struct modbus_group_data *gd = @@ -127,11 +189,11 @@ int modbus_value_handle(void *ctx, uint8_t slave_id, uint16_t n_byte, uint16_t start_address = gd->cmd_sort->cmd[plugin->cmd_idx].start_address; uint16_t n_register = gd->cmd_sort->cmd[plugin->cmd_idx].n_register; - if (bytes == NULL) { + if (error != NEU_ERR_SUCCESS) { neu_dvalue_t dvalue = { 0 }; dvalue.type = NEU_TYPE_ERROR; - dvalue.value.i32 = NEU_ERR_PLUGIN_DISCONNECTED; + dvalue.value.i32 = error; plugin->common.adapter_callbacks->driver.update( plugin->common.adapter, gd->group, NULL, dvalue); return 0; @@ -182,6 +244,11 @@ int modbus_value_handle(void *ctx, uint8_t slave_id, uint16_t n_byte, case NEU_TYPE_UINT32: dvalue.value.u32 = ntohl(dvalue.value.u32); break; + case NEU_TYPE_DOUBLE: + case NEU_TYPE_INT64: + case NEU_TYPE_UINT64: + dvalue.value.u64 = neu_ntohll(dvalue.value.u64); + break; case NEU_TYPE_BIT: { switch ((*p_tag)->area) { case MODBUS_AREA_HOLD_REGISTER: @@ -196,7 +263,6 @@ int modbus_value_handle(void *ctx, uint8_t slave_id, uint16_t n_byte, case MODBUS_AREA_INPUT: break; } - break; } case NEU_TYPE_STRING: { @@ -218,7 +284,6 @@ int modbus_value_handle(void *ctx, uint8_t slave_id, uint16_t n_byte, dvalue.value.str[0] = '?'; dvalue.value.str[1] = 0; } - break; } default: @@ -242,18 +307,23 @@ int modbus_write(neu_plugin_t *plugin, void *req, neu_datatag_t *tag, switch (tag->type) { case NEU_TYPE_UINT16: - case NEU_TYPE_INT16: { + case NEU_TYPE_INT16: value.u16 = htons(value.u16); n_byte = sizeof(uint16_t); break; - } + case NEU_TYPE_FLOAT: case NEU_TYPE_UINT32: case NEU_TYPE_INT32: - case NEU_TYPE_FLOAT: { value.u32 = htonl(value.u32); n_byte = sizeof(uint32_t); break; - } + + case NEU_TYPE_DOUBLE: + case NEU_TYPE_INT64: + case NEU_TYPE_UINT64: + value.u64 = neu_htonll(value.u64); + n_byte = sizeof(uint64_t); + break; case NEU_TYPE_BIT: { n_byte = sizeof(uint8_t); break; @@ -318,12 +388,21 @@ static int process_protocol_buf(neu_plugin_t *plugin, uint16_t response_size) neu_protocol_unpack_buf_t pbuf = { 0 }; ssize_t ret = 0; - ret = neu_conn_recv(plugin->conn, recv_buf, response_size); + if (plugin->is_server) { + ret = neu_conn_tcp_server_recv(plugin->conn, plugin->client_fd, + recv_buf, response_size); + } else { + ret = neu_conn_recv(plugin->conn, recv_buf, response_size); + } - if (ret > 0) { + if (ret == response_size) { + if (response_size < 512) { + plog_recv_protocol(plugin, recv_buf, ret); + } neu_protocol_unpack_buf_init(&pbuf, recv_buf, ret); - plog_recv_protocol(plugin, recv_buf, ret); ret = modbus_stack_recv(plugin->stack, &pbuf); + } else { + ret = -1; } free(recv_buf); diff --git a/plugins/modbus/modbus_req.h b/plugins/modbus/modbus_req.h index d195c812..81f42f5b 100644 --- a/plugins/modbus/modbus_req.h +++ b/plugins/modbus/modbus_req.h @@ -33,18 +33,28 @@ struct neu_plugin { uint16_t cmd_idx; neu_event_io_t *tcp_server_io; + bool is_server; + bool is_serial; int client_fd; neu_events_t * events; + + modbus_protocol_e protocol; + + uint16_t interval; }; void modbus_conn_connected(void *data, int fd); void modbus_conn_disconnected(void *data, int fd); +void modbus_tcp_server_listen(void *data, int fd); +void modbus_tcp_server_stop(void *data, int fd); +int modbus_tcp_server_io_callback(enum neu_event_io_type type, int fd, + void *usr_data); int modbus_group_timer(neu_plugin_t *plugin, neu_plugin_group_t *group, uint16_t max_byte); int modbus_send_msg(void *ctx, uint16_t n_byte, uint8_t *bytes); int modbus_value_handle(void *ctx, uint8_t slave_id, uint16_t n_byte, - uint8_t *bytes); + uint8_t *bytes, int error); int modbus_write(neu_plugin_t *plugin, void *req, neu_datatag_t *tag, neu_value_u value); int modbus_write_resp(void *ctx, void *req, int error); diff --git a/plugins/modbus/modbus_rtu.c b/plugins/modbus/modbus_rtu.c new file mode 100644 index 00000000..9d64319b --- /dev/null +++ b/plugins/modbus/modbus_rtu.c @@ -0,0 +1,276 @@ +/** + * NEURON IIoT System for Industry 4.0 + * Copyright (C) 2020-2022 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 "errcodes.h" + +#include "modbus_point.h" +#include "modbus_req.h" +#include "modbus_stack.h" + +static neu_plugin_t *driver_open(void); + +static int driver_close(neu_plugin_t *plugin); +static int driver_init(neu_plugin_t *plugin); +static int driver_uninit(neu_plugin_t *plugin); +static int driver_start(neu_plugin_t *plugin); +static int driver_stop(neu_plugin_t *plugin); +static int driver_config(neu_plugin_t *plugin, const char *config); +static int driver_request(neu_plugin_t *plugin, neu_reqresp_head_t *head, + void *data); + +static int driver_validate_tag(neu_plugin_t *plugin, neu_datatag_t *tag); +static int driver_group_timer(neu_plugin_t *plugin, neu_plugin_group_t *group); +static int driver_write(neu_plugin_t *plugin, void *req, neu_datatag_t *tag, + neu_value_u value); + +static const neu_plugin_intf_funs_t plugin_intf_funs = { + .open = driver_open, + .close = driver_close, + .init = driver_init, + .uninit = driver_uninit, + .start = driver_start, + .stop = driver_stop, + .setting = driver_config, + .request = driver_request, + + .driver.validate_tag = driver_validate_tag, + .driver.group_timer = driver_group_timer, + .driver.write_tag = driver_write, +}; + +const neu_plugin_module_t neu_plugin_module = { + .version = NEURON_PLUGIN_VER_1_0, + .schema = "modbus-rtu", + .module_name = "Modbus RTU", + .module_descr = + "This plugin is used to connect devices using modbus rtu protocol. " + "Support serial and TCP communication methods. When using TCP " + "communication, it is necessary to use the pass-through function of " + "DTU to convert serial data into IP data and transmit it through " + "the network. Selecting client/server is also supported.", + .module_descr_zh = + "该插件用于连接使用 modbus rtu 协议的设备。支持串口和 TCP " + "两种通信方式。使用 TCP 通信时,需要使用 DTU " + "的透传功能,将串口数据转为 IP " + "数据并通过网络传输。也支持选择客户端/服务端。", + .intf_funs = &plugin_intf_funs, + .kind = NEU_PLUGIN_KIND_SYSTEM, + .type = NEU_NA_TYPE_DRIVER, + .display = true, + .single = false, +}; + +static neu_plugin_t *driver_open(void) +{ + neu_plugin_t *plugin = calloc(1, sizeof(neu_plugin_t)); + + neu_plugin_common_init(&plugin->common); + + return plugin; +} + +static int driver_close(neu_plugin_t *plugin) +{ + free(plugin); + + return 0; +} + +static int driver_init(neu_plugin_t *plugin) +{ + plugin->protocol = MODBUS_PROTOCOL_RTU; + plugin->events = neu_event_new(); + plugin->stack = modbus_stack_create((void *) plugin, MODBUS_PROTOCOL_RTU, + modbus_send_msg, modbus_value_handle, + modbus_write_resp); + + return 0; +} + +static int driver_uninit(neu_plugin_t *plugin) +{ + if (plugin->conn != NULL) { + neu_conn_destory(plugin->conn); + } + + if (plugin->stack) { + modbus_stack_destroy(plugin->stack); + } + + neu_event_close(plugin->events); + + plog_info(plugin, "node: modbus uninit ok"); + + return 0; +} + +static int driver_start(neu_plugin_t *plugin) +{ + neu_conn_start(plugin->conn); + return 0; +} + +static int driver_stop(neu_plugin_t *plugin) +{ + neu_conn_stop(plugin->conn); + return 0; +} + +static int driver_config(neu_plugin_t *plugin, const char *config) +{ + int ret = 0; + char * err_param = NULL; + neu_conn_param_t param = { 0 }; + + neu_json_elem_t link = { .name = "link", .t = NEU_JSON_INT }; + neu_json_elem_t timeout = { .name = "timeout", .t = NEU_JSON_INT }; + neu_json_elem_t interval = { .name = "interval", .t = NEU_JSON_INT }; + + neu_json_elem_t mode = { .name = "connection_mode", .t = NEU_JSON_INT }; + neu_json_elem_t host = { .name = "host", .t = NEU_JSON_STR }; + neu_json_elem_t port = { .name = "port", .t = NEU_JSON_INT }; + + neu_json_elem_t device = { .name = "device", .t = NEU_JSON_STR }; + neu_json_elem_t stop = { .name = "stop", .t = NEU_JSON_INT }; + neu_json_elem_t parity = { .name = "parity", .t = NEU_JSON_INT }; + neu_json_elem_t baud = { .name = "baud", .t = NEU_JSON_INT }; + neu_json_elem_t data = { .name = "data", .t = NEU_JSON_INT }; + + ret = neu_parse_param((char *) config, &err_param, 3, &link, &timeout, + &interval); + if (ret != 0) { + plog_warn(plugin, "config: %s, decode link & timeout error: %s", config, + err_param); + free(err_param); + return -1; + } + + plugin->interval = interval.v.val_int; + if (link.v.val_int == 0) { + ret = neu_parse_param((char *) config, &err_param, 5, &device, &stop, + &parity, &baud, &data); + } else { + ret = neu_parse_param((char *) config, &err_param, 3, &mode, &host, + &port); + } + + if (ret != 0) { + plog_warn(plugin, "config: %s, decode error: %s", config, err_param); + free(err_param); + return -1; + } + + param.log = plugin->common.log; + if (link.v.val_int == 0) { + param.type = NEU_CONN_TTY_CLIENT; + + param.params.tty_client.device = device.v.val_str; + param.params.tty_client.baud = baud.v.val_int; + param.params.tty_client.data = data.v.val_int; + param.params.tty_client.parity = parity.v.val_int; + param.params.tty_client.stop = stop.v.val_int; + param.params.tty_client.timeout = timeout.v.val_int; + + plugin->is_serial = true; + plog_info(plugin, + "config: device: %s, baud: %" PRId64 ", data: %" PRId64 + ", parity: %" PRId64 ", stop: %" PRId64 "", + device.v.val_str, baud.v.val_int, data.v.val_int, + parity.v.val_int, stop.v.val_int); + } else { + if (mode.v.val_int == 1) { + param.type = NEU_CONN_TCP_SERVER; + param.params.tcp_server.ip = host.v.val_str; + param.params.tcp_server.port = port.v.val_int; + param.params.tcp_server.start_listen = modbus_tcp_server_listen; + param.params.tcp_server.stop_listen = modbus_tcp_server_stop; + param.params.tcp_server.timeout = timeout.v.val_int; + param.params.tcp_server.max_link = 1; + plugin->is_server = true; + } else { + param.type = NEU_CONN_TCP_CLIENT; + param.params.tcp_client.ip = host.v.val_str; + param.params.tcp_client.port = port.v.val_int; + param.params.tcp_client.timeout = timeout.v.val_int; + plugin->is_server = false; + } + + plog_info(plugin, + "config: host: %s, port: %" PRId64 ", mode: %" PRId64 "", + host.v.val_str, port.v.val_int, mode.v.val_int); + } + + if (plugin->conn != NULL) { + plugin->conn = neu_conn_reconfig(plugin->conn, ¶m); + } else { + plugin->common.link_state = NEU_NODE_LINK_STATE_DISCONNECTED; + plugin->conn = + neu_conn_new(¶m, (void *) plugin, modbus_conn_connected, + modbus_conn_disconnected); + } + + if (link.v.val_int == 0) { + free(device.v.val_str); + } else { + free(host.v.val_str); + } + + return 0; +} + +static int driver_request(neu_plugin_t *plugin, neu_reqresp_head_t *head, + void *data) +{ + (void) plugin; + (void) head; + (void) data; + return 0; +} + +static int driver_validate_tag(neu_plugin_t *plugin, neu_datatag_t *tag) +{ + modbus_point_t point = { 0 }; + + int ret = modbus_tag_to_point(tag, &point); + if (ret == 0) { + plog_debug( + plugin, + "validate tag success, name: %s, address: %s, type: %d, slave id: " + "%d, start address: %d, n register: %d, area: %s", + tag->name, tag->address, tag->type, point.slave_id, + point.start_address, point.n_register, + modbus_area_to_str(point.area)); + } + + return ret; +} + +static int driver_group_timer(neu_plugin_t *plugin, neu_plugin_group_t *group) +{ + return modbus_group_timer(plugin, group, 0xfa); +} + +static int driver_write(neu_plugin_t *plugin, void *req, neu_datatag_t *tag, + neu_value_u value) +{ + return modbus_write(plugin, req, tag, value); +} \ No newline at end of file diff --git a/plugins/modbus/modbus_stack.c b/plugins/modbus/modbus_stack.c index ede09346..c796d45a 100644 --- a/plugins/modbus/modbus_stack.c +++ b/plugins/modbus/modbus_stack.c @@ -20,6 +20,7 @@ #include +#include "modbus_req.h" #include "modbus_stack.h" struct modbus_stack { @@ -28,10 +29,16 @@ struct modbus_stack { modbus_stack_value value_fn; modbus_stack_write_resp write_resp; - uint16_t seq; + modbus_protocol_e protocol; + uint16_t read_seq; + uint16_t write_seq; + + uint8_t *buf; + uint16_t buf_size; }; -modbus_stack_t *modbus_stack_create(void *ctx, modbus_stack_send send_fn, +modbus_stack_t *modbus_stack_create(void *ctx, modbus_protocol_e protocol, + modbus_stack_send send_fn, modbus_stack_value value_fn, modbus_stack_write_resp write_resp) { @@ -41,12 +48,17 @@ modbus_stack_t *modbus_stack_create(void *ctx, modbus_stack_send send_fn, stack->send_fn = send_fn; stack->value_fn = value_fn; stack->write_resp = write_resp; + stack->protocol = protocol; + + stack->buf_size = 256; + stack->buf = calloc(stack->buf_size, 1); return stack; } void modbus_stack_destroy(modbus_stack_t *stack) { + free(stack->buf); free(stack); } @@ -56,9 +68,14 @@ int modbus_stack_recv(modbus_stack_t *stack, neu_protocol_unpack_buf_t *buf) struct modbus_code code = { 0 }; int ret = 0; - ret = modbus_header_unwrap(buf, &header); - if (ret <= 0) { - return ret; + if (stack->protocol == MODBUS_PROTOCOL_TCP) { + ret = modbus_header_unwrap(buf, &header); + if (ret < 0) { + plog_warn((neu_plugin_t *) stack->ctx, "try modbus rtu driver"); + } + if (ret <= 0) { + return ret; + } } ret = modbus_code_unwrap(buf, &code); @@ -78,8 +95,19 @@ int modbus_stack_recv(modbus_stack_t *stack, neu_protocol_unpack_buf_t *buf) return ret; } - bytes = neu_protocol_unpack_buf(buf, data.n_byte); - stack->value_fn(stack->ctx, code.slave_id, data.n_byte, bytes); + if (stack->protocol == MODBUS_PROTOCOL_TCP && data.n_byte == 0xff) { + bytes = neu_protocol_unpack_buf(buf, + header.len - + sizeof(struct modbus_code) - + sizeof(struct modbus_data)); + stack->value_fn(stack->ctx, code.slave_id, + header.len - sizeof(struct modbus_code) - + sizeof(struct modbus_data), + bytes, 0); + } else { + bytes = neu_protocol_unpack_buf(buf, data.n_byte); + stack->value_fn(stack->ctx, code.slave_id, data.n_byte, bytes, 0); + } break; } @@ -95,6 +123,14 @@ int modbus_stack_recv(modbus_stack_t *stack, neu_protocol_unpack_buf_t *buf) break; } + if (stack->protocol == MODBUS_PROTOCOL_RTU) { + struct modbus_crc crc = { 0 }; + ret = modbus_crc_unwrap(buf, &crc); + if (ret <= 0) { + return ret; + } + } + return neu_protocol_unpack_buf_used_size(buf); } @@ -108,6 +144,9 @@ int modbus_stack_read(modbus_stack_t *stack, uint8_t slave_id, neu_protocol_pack_buf_init(&pbuf, buf, sizeof(buf)); + if (stack->protocol == MODBUS_PROTOCOL_RTU) { + modbus_crc_wrap(&pbuf); + } modbus_address_wrap(&pbuf, start_address, n_reg); switch (area) { @@ -132,14 +171,23 @@ int modbus_stack_read(modbus_stack_t *stack, uint8_t slave_id, *response_size += sizeof(struct modbus_code); *response_size += sizeof(struct modbus_data); - modbus_header_wrap(&pbuf, stack->seq++); - *response_size += sizeof(struct modbus_header); + switch (stack->protocol) { + case MODBUS_PROTOCOL_TCP: + modbus_header_wrap(&pbuf, stack->read_seq++); + *response_size += sizeof(struct modbus_header); + break; + case MODBUS_PROTOCOL_RTU: + modbus_crc_set(&pbuf); + *response_size += 2; + break; + } ret = stack->send_fn(stack->ctx, neu_protocol_pack_buf_used_size(&pbuf), neu_protocol_pack_buf_get(&pbuf)); if (ret <= 0) { - stack->value_fn(stack->ctx, 0, 0, NULL); + stack->value_fn(stack->ctx, 0, 0, NULL, NEU_ERR_PLUGIN_DISCONNECTED); } + return ret; } @@ -148,11 +196,14 @@ int modbus_stack_write(modbus_stack_t *stack, void *req, uint8_t slave_id, uint16_t n_reg, uint8_t *bytes, uint8_t n_byte, uint16_t *response_size) { - uint8_t * buf = calloc(256, 1); - neu_protocol_pack_buf_t pbuf = { 0 }; - int ret = 0; + static __thread neu_protocol_pack_buf_t pbuf = { 0 }; - neu_protocol_pack_buf_init(&pbuf, buf, 256); + memset(stack->buf, 0, stack->buf_size); + neu_protocol_pack_buf_init(&pbuf, stack->buf, stack->buf_size); + + if (stack->protocol == MODBUS_PROTOCOL_RTU) { + modbus_crc_wrap(&pbuf); + } switch (area) { case MODBUS_AREA_COIL: @@ -169,22 +220,34 @@ int modbus_stack_write(modbus_stack_t *stack, void *req, uint8_t slave_id, modbus_code_wrap(&pbuf, slave_id, MODBUS_WRITE_M_HOLD_REG); break; default: - assert(false); + stack->write_resp(stack->ctx, req, NEU_ERR_PLUGIN_TAG_NOT_ALLOW_WRITE); break; } *response_size += sizeof(struct modbus_code); *response_size += sizeof(struct modbus_address); - modbus_header_wrap(&pbuf, stack->seq++); - *response_size += sizeof(struct modbus_header); + switch (stack->protocol) { + case MODBUS_PROTOCOL_TCP: + modbus_header_wrap(&pbuf, stack->write_seq++); + *response_size += sizeof(struct modbus_header); + break; + case MODBUS_PROTOCOL_RTU: + modbus_crc_set(&pbuf); + *response_size += 2; + break; + } - ret = stack->send_fn(stack->ctx, neu_protocol_pack_buf_used_size(&pbuf), - neu_protocol_pack_buf_get(&pbuf)); + int ret = stack->send_fn(stack->ctx, neu_protocol_pack_buf_used_size(&pbuf), + neu_protocol_pack_buf_get(&pbuf)); if (ret > 0) { stack->write_resp(stack->ctx, req, NEU_ERR_SUCCESS); } else { stack->write_resp(stack->ctx, req, NEU_ERR_PLUGIN_DISCONNECTED); } - free(buf); return ret; +} + +bool modbus_stack_is_rtu(modbus_stack_t *stack) +{ + return stack->protocol == MODBUS_PROTOCOL_RTU; } \ No newline at end of file diff --git a/plugins/modbus/modbus_stack.h b/plugins/modbus/modbus_stack.h index 9056e154..04682167 100644 --- a/plugins/modbus/modbus_stack.h +++ b/plugins/modbus/modbus_stack.h @@ -29,22 +29,29 @@ typedef struct modbus_stack modbus_stack_t; typedef int (*modbus_stack_send)(void *ctx, uint16_t n_byte, uint8_t *bytes); typedef int (*modbus_stack_value)(void *ctx, uint8_t slave_id, uint16_t n_byte, - uint8_t *bytes); + uint8_t *bytes, int error); typedef int (*modbus_stack_write_resp)(void *ctx, void *req, int error); -modbus_stack_t *modbus_stack_create(void *ctx, modbus_stack_send send_fn, +typedef enum modbus_protocol { + MODBUS_PROTOCOL_TCP = 1, + MODBUS_PROTOCOL_RTU = 2, +} modbus_protocol_e; + +modbus_stack_t *modbus_stack_create(void *ctx, modbus_protocol_e protocol, + modbus_stack_send send_fn, modbus_stack_value value_fn, modbus_stack_write_resp write_resp); void modbus_stack_destroy(modbus_stack_t *stack); int modbus_stack_recv(modbus_stack_t *stack, neu_protocol_unpack_buf_t *buf); -int modbus_stack_read(modbus_stack_t *stack, uint8_t slave_id, - enum modbus_area area, uint16_t start_address, - uint16_t n_reg, uint16_t *response_size); -int modbus_stack_write(modbus_stack_t *stack, void *req, uint8_t slave_id, +int modbus_stack_read(modbus_stack_t *stack, uint8_t slave_id, enum modbus_area area, uint16_t start_address, - uint16_t n_reg, uint8_t *bytes, uint8_t n_byte, - uint16_t *response_size); + uint16_t n_reg, uint16_t *response_size); +int modbus_stack_write(modbus_stack_t *stack, void *req, uint8_t slave_id, + enum modbus_area area, uint16_t start_address, + uint16_t n_reg, uint8_t *bytes, uint8_t n_byte, + uint16_t *response_size); +bool modbus_stack_is_rtu(modbus_stack_t *stack); #endif \ No newline at end of file diff --git a/plugins/modbus/modbus_tcp.c b/plugins/modbus/modbus_tcp.c index 496f6b06..a99881dd 100644 --- a/plugins/modbus/modbus_tcp.c +++ b/plugins/modbus/modbus_tcp.c @@ -18,19 +18,22 @@ **/ #include -#include "neuron.h" +#include + +#include "errcodes.h" #include "modbus_point.h" #include "modbus_req.h" #include "modbus_stack.h" static neu_plugin_t *driver_open(void); -static int driver_close(neu_plugin_t *plugin); -static int driver_init(neu_plugin_t *plugin); -static int driver_uninit(neu_plugin_t *plugin); -static int driver_start(neu_plugin_t *plugin); -static int driver_stop(neu_plugin_t *plugin); -static int driver_config(neu_plugin_t *plugin, const char *config); + +static int driver_close(neu_plugin_t *plugin); +static int driver_init(neu_plugin_t *plugin); +static int driver_uninit(neu_plugin_t *plugin); +static int driver_start(neu_plugin_t *plugin); +static int driver_stop(neu_plugin_t *plugin); +static int driver_config(neu_plugin_t *plugin, const char *config); static int driver_request(neu_plugin_t *plugin, neu_reqresp_head_t *head, void *data); @@ -56,18 +59,18 @@ static const neu_plugin_intf_funs_t plugin_intf_funs = { const neu_plugin_module_t neu_plugin_module = { .version = NEURON_PLUGIN_VER_1_0, - .schema = "modbus-tcp-community", - .module_name = "Modbus TCP community", + .schema = "modbus-tcp", + .module_name = "Modbus TCP", .module_descr = - "The modbus-tcp community plugin is used for devices connected using " - "the modbus protocol tcp mode.", - .module_descr_zh = - "modbus-tcp community 插件用于连接使用 modbus tcp 协议的设备", + "The modbus tcp plugin is an enhanced version of the modbus tcp " + "plugin, users can choose to connect as a client or a server.", + .module_descr_zh = "该插件是 modbus tcp " + "插件的加强版,用户可选择作为客户端连接,还是服务端连接", .intf_funs = &plugin_intf_funs, .kind = NEU_PLUGIN_KIND_SYSTEM, .type = NEU_NA_TYPE_DRIVER, - .single = false, .display = true, + .single = false, }; static neu_plugin_t *driver_open(void) @@ -88,9 +91,11 @@ static int driver_close(neu_plugin_t *plugin) static int driver_init(neu_plugin_t *plugin) { - plugin->events = neu_event_new(); - plugin->stack = modbus_stack_create((void *) plugin, modbus_send_msg, - modbus_value_handle, modbus_write_resp); + plugin->protocol = MODBUS_PROTOCOL_TCP; + plugin->events = neu_event_new(); + plugin->stack = modbus_stack_create((void *) plugin, MODBUS_PROTOCOL_TCP, + modbus_send_msg, modbus_value_handle, + modbus_write_resp); return 0; } @@ -131,32 +136,46 @@ static int driver_config(neu_plugin_t *plugin, const char *config) neu_json_elem_t port = { .name = "port", .t = NEU_JSON_INT }; neu_json_elem_t timeout = { .name = "timeout", .t = NEU_JSON_INT }; neu_json_elem_t host = { .name = "host", .t = NEU_JSON_STR }; - neu_conn_param_t param = { 0 }; + neu_json_elem_t interval = { .name = "interval", .t = NEU_JSON_INT }; + neu_json_elem_t mode = { .name = "connection_mode", .t = NEU_JSON_INT }; + neu_conn_param_t param = { 0 }; - ret = - neu_parse_param((char *) config, &err_param, 3, &port, &host, &timeout); + ret = neu_parse_param((char *) config, &err_param, 5, &port, &host, &mode, + &timeout, &interval); if (ret != 0) { - plog_warn(plugin, "config: %s, decode error: %s", (char *) config, - err_param); + plog_warn(plugin, "config: %s, decode error: %s", config, err_param); free(err_param); free(host.v.val_str); return -1; } - param.log = plugin->common.log; - param.type = NEU_CONN_TCP_CLIENT; - param.params.tcp_client.ip = host.v.val_str; - param.params.tcp_client.port = port.v.val_int; - param.params.tcp_client.timeout = timeout.v.val_int; + param.log = plugin->common.log; + plugin->interval = interval.v.val_int; + if (mode.v.val_int == 1) { + param.type = NEU_CONN_TCP_SERVER; + param.params.tcp_server.ip = host.v.val_str; + param.params.tcp_server.port = port.v.val_int; + param.params.tcp_server.start_listen = modbus_tcp_server_listen; + param.params.tcp_server.stop_listen = modbus_tcp_server_stop; + param.params.tcp_server.timeout = timeout.v.val_int; + param.params.tcp_server.max_link = 1; + plugin->is_server = true; + } else { + param.type = NEU_CONN_TCP_CLIENT; + param.params.tcp_client.ip = host.v.val_str; + param.params.tcp_client.port = port.v.val_int; + param.params.tcp_client.timeout = timeout.v.val_int; + plugin->is_server = false; + } - plog_info(plugin, "config: host: %s, port: %" PRId64 ", timeout: %" PRId64, - host.v.val_str, port.v.val_int, timeout.v.val_int); + plog_info(plugin, "config: host: %s, port: %" PRId64 ", mode: %" PRId64 "", + host.v.val_str, port.v.val_int, mode.v.val_int); - plugin->common.link_state = NEU_NODE_LINK_STATE_DISCONNECTED; if (plugin->conn != NULL) { plugin->conn = neu_conn_reconfig(plugin->conn, ¶m); } else { + plugin->common.link_state = NEU_NODE_LINK_STATE_DISCONNECTED; plugin->conn = neu_conn_new(¶m, (void *) plugin, modbus_conn_connected, modbus_conn_disconnected); diff --git a/plugins/restful/normal_handle.c b/plugins/restful/normal_handle.c index 86e2008b..5e329eaa 100644 --- a/plugins/restful/normal_handle.c +++ b/plugins/restful/normal_handle.c @@ -224,14 +224,6 @@ static struct { .plugin = "Modbus TCP", .schema = "modbus-tcp", }, - { - .plugin = "Modbus TCP community", - .schema = "modbus-tcp-community", - }, - { - .plugin = "Modbus TCP QH", - .schema = "modbus-qh-tcp", - }, { .plugin = "Monitor", .schema = "monitor", diff --git a/simulator/modbus_s.c b/simulator/modbus_s.c index bcc46a41..8d54c051 100644 --- a/simulator/modbus_s.c +++ b/simulator/modbus_s.c @@ -205,7 +205,11 @@ ssize_t modbus_s_tcp_req(uint8_t *req, uint16_t req_len, uint8_t *res, *res_len += sizeof(struct modbus_data); *res_len += len; - res_data->n_byte = len; + if (len > 0xff) { + res_data->n_byte = 0xff; + } else { + res_data->n_byte = len; + } res_header->len += sizeof(struct modbus_data) + len; break; case MODBUS_WRITE_S_COIL: diff --git a/simulator/modbus_simulator.c b/simulator/modbus_simulator.c index 6ce6d20d..ba9afc20 100644 --- a/simulator/modbus_simulator.c +++ b/simulator/modbus_simulator.c @@ -23,7 +23,7 @@ #include #include -#include +#include "neuron.h" #include "modbus_s.h" @@ -31,6 +31,7 @@ zlog_category_t *neuron = NULL; neu_events_t * events = NULL; neu_event_io_t * tcp_server_event = NULL; neu_conn_t * conn = NULL; +int64_t global_timestamp = 0; static void start_listen(void *data, int fd); static void stop_listen(void *data, int fd); @@ -222,7 +223,7 @@ static int recv_msg(enum neu_event_io_type type, int fd, void *usr_data) switch (type) { case NEU_EVENT_IO_READ: { ssize_t len = 0; - uint8_t res[60000] = { 0 }; + uint8_t res[20000] = { 0 }; int res_len = 0; len = neu_conn_tcp_server_recv(conn, fd, buf->buf + buf->len, diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index d834b568..12fd65ac 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -47,4 +47,4 @@ gtest_discover_tests(json_test) gtest_discover_tests(http_test) gtest_discover_tests(jwt_test) gtest_discover_tests(base64_test) -gtest_discover_tests(tag_sort_test) +gtest_discover_tests(tag_sort_test) \ No newline at end of file