Skip to content

Commit

Permalink
feat(modbus-tcp):support backup ip
Browse files Browse the repository at this point in the history
  • Loading branch information
hxy7yx committed Nov 13, 2024
1 parent 141b3f5 commit 6d2dc38
Show file tree
Hide file tree
Showing 9 changed files with 145 additions and 7 deletions.
2 changes: 2 additions & 0 deletions include/neuron/connection/neu_connection.h
Original file line number Diff line number Diff line change
Expand Up @@ -301,4 +301,6 @@ int neu_conn_tcp_server_wait_msg(neu_conn_t *conn, int fd, void *context,
int is_ipv4(const char *ip);
int is_ipv6(const char *ip);

bool neu_conn_is_connected(neu_conn_t *conn);

#endif
44 changes: 44 additions & 0 deletions plugins/modbus/modbus-tcp.json
Original file line number Diff line number Diff line change
Expand Up @@ -257,5 +257,49 @@
"min": 1000,
"max": 65535
}
},
"backup": {
"name": "Enable Backup",
"name_zh": "启用主备切换",
"attribute": "required",
"type": "map",
"default": 0,
"valid": {
"map": [
{
"key": "False",
"value": 0
},
{
"key": "True",
"value": 1
}
]
}
},
"backup_host": {
"name": "Backup IP Address",
"name_zh": "备用 IP 地址",
"attribute": "required",
"type": "string",
"condition": {
"field": "backup",
"value": 1
}
},
"backup_port": {
"name": "Backup Port",
"name_zh": "备用端口号",
"attribute": "required",
"type": "int",
"default": 502,
"valid": {
"min": 1,
"max": 65535
},
"condition": {
"field": "backup",
"value": 1
}
}
}
17 changes: 17 additions & 0 deletions plugins/modbus/modbus_req.c
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,23 @@ int modbus_send_msg(void *ctx, uint16_t n_byte, uint8_t *bytes)
ret = neu_conn_tcp_server_send(plugin->conn, plugin->client_fd, bytes,
n_byte);
} else {
if (plugin->backup && neu_conn_is_connected(plugin->conn) == false) {
if (plugin->current_backup == false && plugin->first_attempt_done) {
plog_notice(plugin, "switch to backup ip:port %s:%hu",
plugin->param_backup.params.tcp_client.ip,
plugin->param_backup.params.tcp_client.port);
plugin->current_backup = true;
plugin->conn =
neu_conn_reconfig(plugin->conn, &plugin->param_backup);
} else {
plog_notice(plugin, "switch to original ip:port %s:%hu",
plugin->param.params.tcp_client.ip,
plugin->param.params.tcp_client.port);
plugin->current_backup = false;
plugin->conn = neu_conn_reconfig(plugin->conn, &plugin->param);
plugin->first_attempt_done = true;
}
}
ret = neu_conn_send(plugin->conn, bytes, n_byte);
}

Expand Down
6 changes: 6 additions & 0 deletions plugins/modbus/modbus_req.h
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,12 @@ struct neu_plugin {
bool degradation;
uint16_t degrade_cycle;
uint16_t degrade_time;

bool backup;
bool current_backup;
bool first_attempt_done;
neu_conn_param_t param;
neu_conn_param_t param_backup;
};

void modbus_conn_connected(void *data, int fd);
Expand Down
40 changes: 38 additions & 2 deletions plugins/modbus/modbus_tcp.c
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,13 @@ static int driver_uninit(neu_plugin_t *plugin)
modbus_stack_destroy(plugin->stack);
}

if (plugin->param.params.tcp_client.ip != NULL) {
free(plugin->param.params.tcp_client.ip);
}
if (plugin->param_backup.params.tcp_client.ip != NULL) {
free(plugin->param_backup.params.tcp_client.ip);
}

neu_event_close(plugin->events);

plog_notice(plugin, "%s uninit success", plugin->common.name);
Expand Down Expand Up @@ -175,6 +182,13 @@ static int driver_config(neu_plugin_t *plugin, const char *config)
neu_json_elem_t address_base = { .name = "address_base",
.t = NEU_JSON_INT };

neu_json_elem_t backup = { .name = "backup", .t = NEU_JSON_INT };
neu_json_elem_t backup_ip = { .name = "backup_host",
.t = NEU_JSON_STR,
.v.val_str = NULL };
neu_json_elem_t backup_port = { .name = "backup_port", .t = NEU_JSON_INT };
neu_conn_param_t param_backup = { 0 };

ret = neu_parse_param((char *) config, &err_param, 5, &port, &host, &mode,
&timeout, &interval);

Expand Down Expand Up @@ -229,7 +243,18 @@ static int driver_config(neu_plugin_t *plugin, const char *config)
address_base.v.val_int = base_1;
}

ret = neu_parse_param((char *) config, &err_param, 3, &backup, &backup_ip,
&backup_port);
if (ret != 0) {
free(err_param);
if (backup_ip.v.val_str != NULL) {
free(backup_ip.v.val_str);
}
backup.v.val_int = 0;
}

param.log = plugin->common.log;
param_backup.log = plugin->common.log;
plugin->interval = interval.v.val_int;
plugin->max_retries = max_retries.v.val_int;
plugin->retry_interval = retry_interval.v.val_int;
Expand All @@ -249,15 +274,27 @@ static int driver_config(neu_plugin_t *plugin, const char *config)
param.params.tcp_server.timeout = timeout.v.val_int;
param.params.tcp_server.max_link = 1;
plugin->is_server = true;
backup.v.val_int = 0;
}
if (mode.v.val_int == 0) {
plugin->is_server = false;
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;

param_backup.type = NEU_CONN_TCP_CLIENT;
if (backup_ip.v.val_str != NULL) {
param_backup.params.tcp_client.ip = backup_ip.v.val_str;
}
param_backup.params.tcp_client.port = backup_port.v.val_int;
param_backup.params.tcp_client.timeout = timeout.v.val_int;
}

plugin->backup = backup.v.val_int;
plugin->param = param;
plugin->param_backup = param_backup;

plog_notice(plugin,
"config: host: %s, port: %" PRId64 ", mode: %" PRId64 "",
host.v.val_str, port.v.val_int, mode.v.val_int);
Expand All @@ -271,7 +308,6 @@ static int driver_config(neu_plugin_t *plugin, const char *config)
modbus_conn_disconnected);
}

free(host.v.val_str);
return 0;
}

Expand Down
5 changes: 5 additions & 0 deletions src/connection/connection.c
Original file line number Diff line number Diff line change
Expand Up @@ -1425,4 +1425,9 @@ int is_ipv6(const char *ip)
{
struct sockaddr_in6 sa;
return inet_pton(AF_INET6, ip, &(sa.sin6_addr)) != 0;
}

bool neu_conn_is_connected(neu_conn_t *conn)
{
return conn->is_connected;
}
32 changes: 28 additions & 4 deletions tests/ft/driver/test_modbus.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,11 @@
import neuron.config as config
from neuron.common import *

tcp_port = random_port()
rtu_port = random_port()
otel_port = random_port()
tcp_port = random_port()
rtu_port = tcp_port + 1
otel_port = tcp_port + 2
tcp_port_2 = tcp_port + 3
backup_tcp_port = tcp_port + 4


def start_socat():
Expand Down Expand Up @@ -1703,4 +1705,26 @@ def test_set_modbus_start_base(self, param):
assert 1 == api.read_tag(
node=param[0], group='group', tag=coil_bit_2[0]['name'])
assert 0 == api.read_tag(
node=param[0], group='group', tag=coil_bit_3[0]['name'])
node=param[0], group='group', tag=coil_bit_3[0]['name'])

@description(given="configed modbus node with backup ip&port and start corresponding sinulators",
when="stop the original simulator",
then="success to read tags from backup simulator")
def test_modbus_backup(self, param):
if param[0] == 'modbus-tcp':
p_1 = process.start_simulator(['./modbus_simulator', 'tcp', f'{tcp_port_2}', 'ip_v4'])
p_2 = process.start_simulator(['./modbus_simulator', 'tcp', f'{backup_tcp_port}', 'ip_v4'])

api.modbus_tcp_node_setting(node=param[0], port=tcp_port_2)
time.sleep(0.3)
api.write_tag(node=param[0], group='group', tag=hold_int16[0]['name'], value=17)
time.sleep(0.3)
assert 17 == api.read_tag(node=param[0], group='group', tag=hold_int16[0]['name'])

api.modbus_tcp_node_setting_backup(node=param[0], port=tcp_port, backup_port=backup_tcp_port)
process.stop_simulator(p_1)
time.sleep(3)
assert 0 == api.read_tag(node=param[0], group='group', tag=hold_int16[0]['name'])
process.stop_simulator(p_2)
else:
pytest.skip()
4 changes: 4 additions & 0 deletions tests/ft/neuron/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -378,6 +378,10 @@ def modbus_rtu_node_setting_base(node, port=502, connection_mode=0, transport_mo
"max_retries": max_retries, "retry_interval": retry_interval, "link": link, "device": device, "stop": stop,
"parity": parity, "baud": baud, "data": data, "address_base": base})

def modbus_tcp_node_setting_backup(node, port, backup_port, connection_mode=0, transport_mode=0, interval=0, host='127.0.0.1', timeout=3000, max_retries=2, retry_interval=1):
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, "retry_interval": retry_interval, "backup": 1, "backup_host": "127.0.0.1", "backup_port": backup_port})


def mqtt_node_setting(node, client_id="neuron_aBcDeF", host="broker.emqx.io", port=1883):
return node_setting(node, json={"client-id": client_id, "qos": 0, "format": 0,
Expand Down
2 changes: 1 addition & 1 deletion tests/ft/neuron/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ def wrapper(*args, **kwargs):


def random_port():
return random.randint(30000, 65535)
return random.randint(30000, 65530)


def compare_float(v1, v2, delta=0.001):
Expand Down

0 comments on commit 6d2dc38

Please sign in to comment.