ft(modbus): add serial port testing

This commit is contained in:
hxy7yx
2024-02-20 17:04:00 +08:00
parent 91b847dc3e
commit 31112b1a2a
7 changed files with 352 additions and 20 deletions
+2 -1
View File
@@ -37,7 +37,7 @@ jobs:
run: |
sudo apt install cppcheck
cppcheck --enable=all --error-exitcode=2 --inline-suppr --suppress=missingInclude --suppress=unusedFunction ./plugins ./src
cppcheck --enable=all --error-exitcode=2 --inline-suppr --suppress=missingInclude --suppress=unusedFunction --suppress=variableScope ./simulator
cppcheck --enable=all --error-exitcode=2 --inline-suppr --suppress=missingInclude --suppress=variableScope ./simulator
ut:
runs-on: ubuntu-20.04
@@ -138,6 +138,7 @@ jobs:
sudo apt-get install -y mosquitto
fi
pip install -U pytest requests prometheus-client gmqtt pynng sniffio
sudo apt-get install -y socat
- name: compile
run: |
+4
View File
@@ -20,3 +20,7 @@ find_package(Threads)
add_executable(modbus_simulator modbus_simulator.c modbus_s.c)
target_include_directories(modbus_simulator PRIVATE ${CMAKE_SOURCE_DIR}/include/neuron ${CMAKE_SOURCE_DIR})
target_link_libraries(modbus_simulator neuron-base ${CMAKE_THREAD_LIBS_INIT} dl)
add_executable(modbus_tty_simulator modbus_tty_simulator.c modbus_s.c)
target_include_directories(modbus_tty_simulator PRIVATE ${CMAKE_SOURCE_DIR}/include/neuron ${CMAKE_SOURCE_DIR})
target_link_libraries(modbus_tty_simulator neuron-base ${CMAKE_THREAD_LIBS_INIT} dl)
+8 -7
View File
@@ -48,12 +48,12 @@ void modbus_s_init()
{
for (int i = 0; i < 3; i++) {
for (int k = 0; k < 65536; k += 2) {
rtu_registers[i].input[k].value = 1;
tcp_registers[i].input[k].value = 1;
rtu_registers[i].input[k].value = 0;
tcp_registers[i].input[k].value = 0;
}
for (int k = 0; k < 65536; k++) {
rtu_registers[i].input_register[k].value = k;
tcp_registers[i].input_register[k].value = k;
rtu_registers[i].input_register[k].value = 0;
tcp_registers[i].input_register[k].value = 0;
}
pthread_mutex_init(&rtu_registers[i].mutex, NULL);
@@ -142,14 +142,14 @@ ssize_t modbus_s_rtu_req(uint8_t *req, uint16_t req_len, uint8_t *res,
if (ntohs(address->start_address) == 8999) {
res_code->function += 0x80;
len = modbus_read(&tcp_registers[code->slave_id - 1], res_code->function,
len = modbus_read(&rtu_registers[code->slave_id - 1], res_code->function,
address, res_value);
res_data->n_byte = len;
crc = (uint16_t *) (res_value + len);
break;
}
len = modbus_read(&tcp_registers[code->slave_id - 1], code->function,
len = modbus_read(&rtu_registers[code->slave_id - 1], code->function,
address, res_value);
*res_len += sizeof(struct modbus_data);
*res_len += len;
@@ -162,7 +162,7 @@ ssize_t modbus_s_rtu_req(uint8_t *req, uint16_t req_len, uint8_t *res,
case MODBUS_WRITE_M_COIL:
case MODBUS_WRITE_S_HOLD_REG:
case MODBUS_WRITE_M_HOLD_REG:
modbus_write(&tcp_registers[code->slave_id - 1], code->function,
modbus_write(&rtu_registers[code->slave_id - 1], code->function,
address, data, value);
*res_address = *address;
@@ -308,6 +308,7 @@ static int modbus_read(struct modbus_register *reg, uint8_t function,
len += 1;
}
memset(value, 0, ntohs(address->n_reg) / 8 + 1);
for (int i = 0; i < ntohs(address->n_reg); i++) {
if (function == MODBUS_READ_COIL) {
value[i / 8] |=
+1 -1
View File
@@ -134,7 +134,7 @@ int main(int argc, char *argv[])
zlog_init("./config/dev.conf");
neuron = zlog_get_category("neuron");
modbus_s_init();
neu_conn_param_t param = {
.log = neuron,
.type = NEU_CONN_TCP_SERVER,
+271
View File
@@ -0,0 +1,271 @@
/**
* 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 <memory.h>
#include <signal.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <termios.h>
#include <neuron.h>
#include "modbus_s.h"
#define UNUSED(X) ((void) (X))
#define BUFFER_SIZE (4096)
zlog_category_t *neuron = NULL;
neu_events_t * events = NULL;
neu_event_io_t * event = NULL;
neu_conn_t * conn = NULL;
struct cycle_buf {
int len;
uint8_t buf[BUFFER_SIZE];
};
static struct cycle_buf recv_buf, res_buf;
static void connected(void *data, int fd);
static void disconnected(void *data, int fd);
static void sig_handler(int sig)
{
(void) sig;
nlog_warn("recv sig: %d\n", sig);
neu_conn_destory(conn);
neu_event_close(events);
exit(0);
}
static int slave_io_cb(enum neu_event_io_type type, int fd, void *usr_data)
{
UNUSED(usr_data);
if (type != NEU_EVENT_IO_READ) {
nlog_warn("read close!");
return -1;
}
int ret = read(fd, recv_buf.buf + recv_buf.len, BUFFER_SIZE - recv_buf.len);
if (ret <= 0) {
nlog_warn("recv failed!");
return -1;
}
recv_buf.len += ret;
ret = modbus_s_rtu_req(recv_buf.buf, recv_buf.len, res_buf.buf, BUFFER_SIZE, &res_buf.len);
if (ret == -2) {
printf("recv modbus adrress for retry_test, continue wait msg\n");
return 0;
}
neu_msleep(2);
ret = write(fd, res_buf.buf, res_buf.len);
if (ret <= 0) {
nlog_warn("send failed!");
return -1;
}
recv_buf.len = 0;
return 0;
}
int main(int argc, char *argv[])
{
if (argc != 2 && argc != 7) {
printf("./modbus_simulator device or device baud data "
"parity stop flow\n");
return -1;
}
char *device = argv[1];
neu_conn_tty_baud_e baud = NEU_CONN_TTY_BAUD_9600;
neu_conn_tty_parity_e parity = NEU_CONN_TTY_PARITY_NONE;
neu_conn_tty_stop_e stop = NEU_CONN_TTY_STOP_1;
neu_conn_tty_data_e data = NEU_CONN_TTY_DATA_8;
neu_conn_tty_flow_e flow = NEU_CONN_TTYP_FLOW_DISABLE;
char *s_baud = argv[2];
char *s_data = argv[3];
char *s_parity = argv[4];
char *s_stop = argv[5];
char *s_flow = argv[6];
if (argc == 7) {
int t_baud = atoi(s_baud);
switch (t_baud) {
case 2400:
baud = NEU_CONN_TTY_BAUD_2400;
break;
case 4800:
baud = NEU_CONN_TTY_BAUD_4800;
break;
case 9600:
baud = NEU_CONN_TTY_BAUD_9600;
break;
case 19200:
baud = NEU_CONN_TTY_BAUD_19200;
break;
case 38400:
baud = NEU_CONN_TTY_BAUD_38400;
break;
case 57600:
baud = NEU_CONN_TTY_BAUD_57600;
break;
case 115200:
baud = NEU_CONN_TTY_BAUD_115200;
break;
default:
printf("baud no match!input:2400 4800 9600 19200 38400 57600 "
"115200\n");
return -1;
}
int t_data = atoi(s_data);
switch (t_data) {
case 5:
data = NEU_CONN_TTY_DATA_5;
break;
case 6:
data = NEU_CONN_TTY_DATA_6;
break;
case 7:
data = NEU_CONN_TTY_DATA_7;
break;
case 8:
data = NEU_CONN_TTY_DATA_8;
break;
default:
printf("data no match!input:5 6 7 8");
return -1;
}
if (strcmp(s_parity, "none") == 0) {
parity = NEU_CONN_TTY_PARITY_NONE;
} else if (strcmp(s_parity, "odd") == 0) {
parity = NEU_CONN_TTY_PARITY_ODD;
} else if (strcmp(s_parity, "even") == 0) {
parity = NEU_CONN_TTY_PARITY_EVEN;
} else if (strcmp(s_parity, "mark") == 0) {
parity = NEU_CONN_TTY_PARITY_MARK;
} else if (strcmp(s_parity, "space") == 0) {
parity = NEU_CONN_TTY_PARITY_SPACE;
} else {
printf("parity no match!input:none odd even mark space\n");
return -1;
}
int t_stop = atoi(s_stop);
switch (t_stop) {
case 1:
stop = NEU_CONN_TTY_STOP_1;
break;
case 2:
stop = NEU_CONN_TTY_STOP_2;
break;
default:
printf("stop no match!input:1 2\n");
return -1;
}
int t_flow = atoi(s_flow);
switch (t_flow) {
case 0:
flow = NEU_CONN_TTYP_FLOW_DISABLE;
break;
case 1:
flow = NEU_CONN_TTYP_FLOW_ENABLE;
break;
default:
printf("flow no match!input:0 1\n");
return -1;
}
}
zlog_init("./config/dev.conf");
neuron = zlog_get_category("neuron");
modbus_s_init();
neu_conn_param_t param = {
.log = neuron,
.type = NEU_CONN_TTY_CLIENT,
.params.tty_client.device = device,
.params.tty_client.baud = baud,
.params.tty_client.data = data,
.params.tty_client.parity = parity,
.params.tty_client.stop = stop,
.params.tty_client.flow = flow,
.params.tty_client.timeout = 3000,
};
events = neu_event_new();
conn = neu_conn_new(&param, NULL, connected, disconnected);
neu_conn_start(conn);
neu_conn_connect(conn);
int fd = neu_conn_fd(conn);
if (fd <= 0) {
return -1;
}
neu_event_io_param_t ioparam = { 0 };
ioparam.fd = fd;
ioparam.cb = slave_io_cb;
ioparam.usr_data = conn;
event = neu_event_add_io(events, ioparam);
signal(SIGINT, sig_handler);
signal(SIGTERM, sig_handler);
signal(SIGABRT, sig_handler);
while (1) {
sleep(10);
}
return 0;
}
static void connected(void *data, int fd)
{
(void) data;
nlog_debug("connected %d", fd);
return;
}
static void disconnected(void *data, int fd)
{
(void) data;
nlog_debug("disconnected %d", fd);
return;
}
+65 -10
View File
@@ -1,3 +1,8 @@
import subprocess
import select
import fcntl
import re
import os
import neuron.api as api
import neuron.error as error
import neuron.config as config
@@ -7,20 +12,53 @@ tcp_port = random_port()
rtu_port = random_port()
def start_socat():
socat_cmd = ["socat", "-d", "-d", "pty,raw,echo=0", "pty,raw,echo=0"]
socat_proc = subprocess.Popen(
socat_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE
)
fl = fcntl.fcntl(socat_proc.stderr.fileno(), fcntl.F_GETFL)
fcntl.fcntl(socat_proc.stderr.fileno(), fcntl.F_SETFL, fl | os.O_NONBLOCK)
pty_regex = re.compile(r'N PTY is (/dev/pts/\d+)')
dev1 = dev2 = None
while not dev1 or not dev2:
ready = select.select([socat_proc.stderr], [], [], 5)
if ready[0]:
line = socat_proc.stderr.readline()
match = pty_regex.search(line.decode('utf-8'))
if match:
if not dev1:
dev1 = match.group(1)
else:
dev2 = match.group(1)
return socat_proc, dev1, dev2
@pytest.fixture(params=[('modbus-tcp', config.PLUGIN_MODBUS_TCP),
('modbus-rtu', config.PLUGIN_MODBUS_RTU)], scope='class')
('modbus-rtu', config.PLUGIN_MODBUS_RTU),
('modbus-rtu-tty', config.PLUGIN_MODBUS_RTU)], scope='class')
def param(request):
return request.param
@pytest.fixture(autouse=True, scope='class')
def simulator_setup_teardown(param):
if param[1] == config.PLUGIN_MODBUS_TCP:
if param[0] == 'modbus-tcp':
p = process.start_simulator(
['./modbus_simulator', 'tcp', f'{tcp_port}', 'ip_v4'])
else:
elif param[0] == 'modbus-rtu':
p = process.start_simulator(
['./modbus_simulator', 'rtu', f'{rtu_port}', 'ip_v4'])
elif param[0] == 'modbus-rtu-tty':
socat_proc, modbus_neuron_dev, modbus_simulator_dev = start_socat()
print(modbus_neuron_dev)
print(modbus_simulator_dev)
p = process.start_simulator(
['./modbus_tty_simulator', f'{modbus_simulator_dev}'])
response = api.add_node(node=param[0], plugin=param[1])
assert 200 == response.status_code
@@ -28,15 +66,23 @@ def simulator_setup_teardown(param):
response = api.add_group(node=param[0], group='group')
assert 200 == response.status_code
if param[1] == config.PLUGIN_MODBUS_TCP:
if param[0] == 'modbus-tcp':
response = api.modbus_tcp_node_setting(
node=param[0], interval=1, port=tcp_port)
else:
elif param[0] == 'modbus-rtu':
response = api.modbus_rtu_node_setting(
node=param[0], interval=1, port=rtu_port)
elif param[0] == 'modbus-rtu-tty':
api.update_group(param[0], group='group', interval=100)
response = api.modbus_rtu_node_setting(
node=param[0], interval=1, device=modbus_neuron_dev, link=0)
yield
process.stop_simulator(p)
if param[0] == 'modbus-rtu-tty':
socat_proc.terminate()
time.sleep(2)
api.node_ctl_check(node=param[0], ctl=1)
hold_bit = [{"name": "hold_bit", "address": "1!400001.15",
@@ -926,7 +972,7 @@ class TestModbus:
node=param[0], group='group', tag=coil_bit_m10[0]['name'])
@description(given="created discontinuous modbus node/tag", when="write multiple coil tags in one request", then="read/write success")
def test_write_multiple_hold_tags_discontinuous(self, param):
def test_write_multiple_coil_tags_discontinuous(self, param):
api.write_tags_check(
node=param[0], group='group', tag_values=[
{"tag": coil_bit_m6[0]['name'], "value": 0},
@@ -1075,6 +1121,8 @@ class TestModbus:
@description(given="created modbus device_error_test node/tag", when="read tag", then="read failed")
def test_read_modbus_device_err(self, param):
if param[0] == 'modbus-rtu-tty':
pytest.skip("modbus rtu tty pass")
api.add_tags_check(node=param[0], group='group', tags=hold_int16_device_err)
time.sleep(4.0)
assert error.NEU_ERR_PLUGIN_READ_FAILURE == api.read_tag_err(
@@ -1082,6 +1130,8 @@ class TestModbus:
@description(given="created modbus node", when="create modbus retry_test tag, write and read tag", then="read/write success after retrying")
def test_read_tag_retry(self, param):
if param[0] == 'modbus-rtu-tty':
pytest.skip("modbus rtu tty pass")
api.add_tags_check(node=param[0], group='group', tags=hold_int16_retry_1)
api.add_tags_check(node=param[0], group='group', tags=hold_int16_retry_2)
@@ -1099,12 +1149,15 @@ class TestModbus:
def test_write_read_modbus_disconnected(self, param):
response = api.add_node(node=param[0]+"_3002", plugin=param[1])
assert 200 == response.status_code
if param[1] == config.PLUGIN_MODBUS_TCP:
if param[0] == 'modbus-tcp':
api.modbus_tcp_node_setting(
node=param[0]+"_3002", port=29998)
else:
elif param[0] == 'modbus-rtu':
api.modbus_rtu_node_setting(
node=param[0]+"_3002", port=29999)
elif param[0] == 'modbus-rtu-tty':
response = api.modbus_rtu_node_setting(
node=param[0]+"_3002", device='tty', link=0)
response = api.add_group(node=param[0]+"_3002", group='group')
assert 200 == response.status_code
@@ -1115,12 +1168,14 @@ class TestModbus:
@description(given="created modbus node", when="set modbus tcp_server mode", then="set success")
def test_set_modbus_tcp_server_mode(self, param):
if param[1] == config.PLUGIN_MODBUS_TCP:
if param[0] == 'modbus-tcp':
response = api.modbus_tcp_node_setting(
node=param[0]+"_3002", port=29997, connection_mode=1)
else:
elif param[0] == 'modbus-rtu':
response = api.modbus_rtu_node_setting(
node=param[0]+"_3002", port=29999, connection_mode=1)
else:
pytest.skip("modbus rtu tty pass")
assert 200 == response.status_code
assert error.NEU_ERR_SUCCESS == response.json()['error']
+1 -1
View File
@@ -311,7 +311,7 @@ def modbus_tcp_node_setting(node, port, connection_mode=0, transport_mode=0, int
"host": host, "port": port, "timeout": timeout, "max_retries": max_retries})
def modbus_rtu_node_setting(node, port, connection_mode=0, transport_mode=0, interval=0, host='127.0.0.1', timeout=3000, max_retries=2, link=1, device="", stop=0, parity=0, baud=4, data=3):
def modbus_rtu_node_setting(node, port=502, connection_mode=0, transport_mode=0, interval=0, host='127.0.0.1', timeout=3000, max_retries=2, link=1, device="", stop=0, parity=0, baud=4, data=3):
return node_setting(node, json={"connection_mode": connection_mode, "transport_mode": transport_mode,
"interval": interval, "host": host, "port": port, "timeout": timeout,
"max_retries": max_retries, "link": link, "device": device, "stop": stop,