diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index be281b82..091e902e 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -36,7 +36,8 @@ jobs: - name: cppcheck run: | sudo apt install cppcheck - cppcheck --enable=all --error-exitcode=2 --inline-suppr --suppress=missingInclude --suppress=unusedFunction ./plugins ./simulator ./src + 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 ut: runs-on: ubuntu-20.04 @@ -148,4 +149,4 @@ jobs: with: gcov: true gcov_include: include plugins src - gcov_ignore: tests simulator + gcov_ignore: tests simulator \ No newline at end of file diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e22f4890..5e36493e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -115,4 +115,4 @@ jobs: draft: false prerelease: true files: | - neuron-sdk-* + neuron-sdk-* \ No newline at end of file diff --git a/plugins/restful/group_config_handle.c b/plugins/restful/group_config_handle.c index 052b481f..10408cd9 100644 --- a/plugins/restful/group_config_handle.c +++ b/plugins/restful/group_config_handle.c @@ -41,6 +41,11 @@ void handle_add_group_config(nng_aio *aio) neu_http_response(aio, NEU_ERR_GROUP_NAME_TOO_LONG, result_error); }); + } else if (req->interval < NEU_GROUP_INTERVAL_LIMIT) { + NEU_JSON_RESPONSE_ERROR(NEU_ERR_GROUP_PARAMETER_INVALID, { + neu_http_response(aio, NEU_ERR_GROUP_PARAMETER_INVALID, + result_error); + }); } else { int ret = 0; neu_reqresp_head_t header = { 0 }; diff --git a/simulator/modbus_s.c b/simulator/modbus_s.c index 38e1657b..69d98f13 100644 --- a/simulator/modbus_s.c +++ b/simulator/modbus_s.c @@ -77,6 +77,9 @@ ssize_t modbus_s_rtu_req(uint8_t *req, uint16_t req_len, uint8_t *res, uint8_t * res_value = (uint8_t *) &res_data[1]; uint16_t * crc = NULL; + static int start_address_8000_counter = 0; + static int start_address_8001_counter = 0; + if (req_len < sizeof(struct modbus_code)) { return 0; } @@ -123,6 +126,29 @@ ssize_t modbus_s_rtu_req(uint8_t *req, uint16_t req_len, uint8_t *res, case MODBUS_READ_INPUT: case MODBUS_READ_HOLD_REG: case MODBUS_READ_INPUT_REG: + if (ntohs(address->start_address) == 7999) { + ++start_address_8000_counter; + if (start_address_8000_counter % 2 == 1) { + return -2; + } + } + + if (ntohs(address->start_address) == 8000) { + ++start_address_8001_counter; + if (start_address_8001_counter % 3 != 0) { + return -2; + } + } + + if (ntohs(address->start_address) == 8999) { + res_code->function += 0x80; + len = modbus_read(&tcp_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, address, res_value); *res_len += sizeof(struct modbus_data); @@ -172,6 +198,9 @@ ssize_t modbus_s_tcp_req(uint8_t *req, uint16_t req_len, uint8_t *res, struct modbus_data * res_data = (struct modbus_data *) &res_code[1]; uint8_t * res_value = (uint8_t *) &res_data[1]; + static int start_address_8000_counter = 0; + static int start_address_8001_counter = 0; + if (req_len < sizeof(struct modbus_header)) { return 0; } @@ -201,6 +230,29 @@ ssize_t modbus_s_tcp_req(uint8_t *req, uint16_t req_len, uint8_t *res, "register: %d\n", code->slave_id, code->function, ntohs(address->start_address), ntohs(address->n_reg)); + + if (ntohs(address->start_address) == 7999) { + ++start_address_8000_counter; + if (start_address_8000_counter % 2 == 1) { + return -2; + } + } + + if (ntohs(address->start_address) == 8000) { + ++start_address_8001_counter; + if (start_address_8001_counter % 3 != 0) { + return -2; + } + } + + if (ntohs(address->start_address) == 8999) { + res_code->function += 0x80; + len = modbus_read(&tcp_registers[code->slave_id - 1], res_code->function, + address, res_value); + res_data->n_byte = len; + break; + } + len = modbus_read(&tcp_registers[code->slave_id - 1], code->function, address, res_value); *res_len += sizeof(struct modbus_data); @@ -293,6 +345,15 @@ static int modbus_read(struct modbus_register *reg, uint8_t function, break; } + case MODBUS_READ_COIL_ERR: + case MODBUS_READ_INPUT_ERR: + case MODBUS_READ_HOLD_REG_ERR: + case MODBUS_READ_INPUT_REG_ERR: { + len = 0; + + nlog_info("modbus device read err"); + break; + } default: assert(1 != 0); @@ -358,4 +419,4 @@ static void modbus_write(struct modbus_register *reg, uint8_t function, assert(1 != 0); } pthread_mutex_unlock(®->mutex); -} +} \ No newline at end of file diff --git a/simulator/modbus_simulator.c b/simulator/modbus_simulator.c index 9101e3b1..99fed699 100644 --- a/simulator/modbus_simulator.c +++ b/simulator/modbus_simulator.c @@ -259,6 +259,9 @@ static int recv_msg(enum neu_event_io_type type, int fd, void *usr_data) free(usr_data); neu_conn_tcp_server_close_client(conn, fd); printf("recv msg parse fail, close client: %d\n", fd); + } else if (len == -2) { + neu_conn_tcp_server_send(conn, fd, NULL, 0); + printf("recv modbus adrress for retry_test, continue wait msg\n"); } else if (len > 0) { memmove(buf->buf, buf->buf + len, buf->len - len); buf->len -= len; diff --git a/src/connection/connection.c b/src/connection/connection.c index 368e794c..a34bd1e2 100644 --- a/src/connection/connection.c +++ b/src/connection/connection.c @@ -294,8 +294,10 @@ ssize_t neu_conn_tcp_server_recv(neu_conn_t *conn, int fd, uint8_t *buf, } if (ret <= 0) { - conn->disconnected(conn->data, fd); - conn_tcp_server_del_client(conn, fd); + zlog_error(conn->param.log, + "conn fd: %d, recv error, " + "errno: %s(%d)", + conn->fd, strerror(errno), errno); } pthread_mutex_unlock(&conn->mtx); diff --git a/tests/ft/driver/test_modbus.py b/tests/ft/driver/test_modbus.py index 5e778e99..3e2742ea 100644 --- a/tests/ft/driver/test_modbus.py +++ b/tests/ft/driver/test_modbus.py @@ -126,6 +126,113 @@ hold_float_precision = [{"name": "hold_float_precision", "address": "1!4000235", hold_double_precision = [{"name": "hold_double_precision", "address": "1!4000240", "attribute": config.NEU_TAG_ATTRIBUTE_RW, "type": config.NEU_TYPE_DOUBLE, "precision": 3}] +hold_int16_m1 = [{"name": "hold_int16_m1", "address": "1!400301", + "attribute": config.NEU_TAG_ATTRIBUTE_RW, "type": config.NEU_TYPE_INT16}] +hold_int16_m2 = [{"name": "hold_int16_m2", "address": "1!400302", + "attribute": config.NEU_TAG_ATTRIBUTE_RW, "type": config.NEU_TYPE_INT16}] +hold_int16_m3 = [{"name": "hold_int16_m3", "address": "1!400303", + "attribute": config.NEU_TAG_ATTRIBUTE_RW, "type": config.NEU_TYPE_INT16}] +hold_int16_m4 = [{"name": "hold_int16_m4", "address": "1!400304", + "attribute": config.NEU_TAG_ATTRIBUTE_RW, "type": config.NEU_TYPE_INT16}] +hold_int16_m5 = [{"name": "hold_int16_m5", "address": "1!400305", + "attribute": config.NEU_TAG_ATTRIBUTE_RW, "type": config.NEU_TYPE_INT16}] +hold_int16_m6 = [{"name": "hold_int16_m6", "address": "1!400307", + "attribute": config.NEU_TAG_ATTRIBUTE_RW, "type": config.NEU_TYPE_INT16}] +hold_int16_m7 = [{"name": "hold_int16_m7", "address": "1!400309", + "attribute": config.NEU_TAG_ATTRIBUTE_RW, "type": config.NEU_TYPE_INT16}] +hold_int16_m8 = [{"name": "hold_int16_m8", "address": "1!400311", + "attribute": config.NEU_TAG_ATTRIBUTE_RW, "type": config.NEU_TYPE_INT16}] +hold_int16_m9 = [{"name": "hold_int16_m9", "address": "1!400313", + "attribute": config.NEU_TAG_ATTRIBUTE_RW, "type": config.NEU_TYPE_INT16}] +hold_int16_m10 = [{"name": "hold_int16_m10", "address": "1!400315", + "attribute": config.NEU_TAG_ATTRIBUTE_RW, "type": config.NEU_TYPE_INT16}] +hold_int16_m11 = [{"name": "hold_int16_m11", "address": "1!400317", + "attribute": config.NEU_TAG_ATTRIBUTE_RW, "type": config.NEU_TYPE_INT16}] +hold_int16_m12 = [{"name": "hold_int16_m12", "address": "1!400319", + "attribute": config.NEU_TAG_ATTRIBUTE_RW, "type": config.NEU_TYPE_INT16}] +hold_int16_m13 = [{"name": "hold_int16_m13", "address": "1!400321", + "attribute": config.NEU_TAG_ATTRIBUTE_RW, "type": config.NEU_TYPE_INT16}] +hold_int16_m14 = [{"name": "hold_int16_m14", "address": "1!400323", + "attribute": config.NEU_TAG_ATTRIBUTE_RW, "type": config.NEU_TYPE_INT16}] +hold_int16_m15 = [{"name": "hold_int16_m15", "address": "1!400325", + "attribute": config.NEU_TAG_ATTRIBUTE_RW, "type": config.NEU_TYPE_INT16}] + +coil_bit_m1 = [{"name": "coil_bit_m1", "address": "1!000101", + "attribute": config.NEU_TAG_ATTRIBUTE_RW, "type": config.NEU_TYPE_BIT}] +coil_bit_m2 = [{"name": "coil_bit_m2", "address": "1!000102", + "attribute": config.NEU_TAG_ATTRIBUTE_RW, "type": config.NEU_TYPE_BIT}] +coil_bit_m3 = [{"name": "coil_bit_m3", "address": "1!000103", + "attribute": config.NEU_TAG_ATTRIBUTE_RW, "type": config.NEU_TYPE_BIT}] +coil_bit_m4 = [{"name": "coil_bit_m4", "address": "1!000104", + "attribute": config.NEU_TAG_ATTRIBUTE_RW, "type": config.NEU_TYPE_BIT}] +coil_bit_m5 = [{"name": "coil_bit_m5", "address": "1!000105", + "attribute": config.NEU_TAG_ATTRIBUTE_RW, "type": config.NEU_TYPE_BIT}] +coil_bit_m6 = [{"name": "coil_bit_m6", "address": "1!000107", + "attribute": config.NEU_TAG_ATTRIBUTE_RW, "type": config.NEU_TYPE_BIT}] +coil_bit_m7 = [{"name": "coil_bit_m7", "address": "1!000109", + "attribute": config.NEU_TAG_ATTRIBUTE_RW, "type": config.NEU_TYPE_BIT}] +coil_bit_m8 = [{"name": "coil_bit_m8", "address": "1!000111", + "attribute": config.NEU_TAG_ATTRIBUTE_RW, "type": config.NEU_TYPE_BIT}] +coil_bit_m9 = [{"name": "coil_bit_m9", "address": "1!000113", + "attribute": config.NEU_TAG_ATTRIBUTE_RW, "type": config.NEU_TYPE_BIT}] +coil_bit_m10 = [{"name": "coil_bit_m10", "address": "1!000115", + "attribute": config.NEU_TAG_ATTRIBUTE_RW, "type": config.NEU_TYPE_BIT}] +coil_bit_m11 = [{"name": "coil_bit_m11", "address": "1!000117", + "attribute": config.NEU_TAG_ATTRIBUTE_RW, "type": config.NEU_TYPE_BIT}] +coil_bit_m12 = [{"name": "coil_bit_m12", "address": "1!000119", + "attribute": config.NEU_TAG_ATTRIBUTE_RW, "type": config.NEU_TYPE_BIT}] +coil_bit_m13 = [{"name": "coil_bit_m13", "address": "1!000121", + "attribute": config.NEU_TAG_ATTRIBUTE_RW, "type": config.NEU_TYPE_BIT}] +coil_bit_m14 = [{"name": "coil_bit_m14", "address": "1!000123", + "attribute": config.NEU_TAG_ATTRIBUTE_RW, "type": config.NEU_TYPE_BIT}] +coil_bit_m15 = [{"name": "coil_bit_m15", "address": "1!000125", + "attribute": config.NEU_TAG_ATTRIBUTE_RW, "type": config.NEU_TYPE_BIT}] + +hold_int16_retry_1 = [{"name": "hold_int16_retry_1", "address": "1!408000", + "attribute": config.NEU_TAG_ATTRIBUTE_RW, "type": config.NEU_TYPE_INT16}] +hold_int16_retry_2 = [{"name": "hold_int16_retry_2", "address": "1!408001", + "attribute": config.NEU_TAG_ATTRIBUTE_RW, "type": config.NEU_TYPE_INT16}] + +hold_int16_B = [{"name": "hold_int16_B", "address": "1!400401#B", + "attribute": config.NEU_TAG_ATTRIBUTE_RW, "type": config.NEU_TYPE_INT16}] +hold_int16_L = [{"name": "hold_int16_L", "address": "1!400402#L", + "attribute": config.NEU_TAG_ATTRIBUTE_RW, "type": config.NEU_TYPE_INT16}] +hold_int32_LL = [{"name": "hold_int32_LL", "address": "1!400403#LL", + "attribute": config.NEU_TAG_ATTRIBUTE_RW, "type": config.NEU_TYPE_INT32}] +hold_int32_LB = [{"name": "hold_int32_LB", "address": "1!400405#LB", + "attribute": config.NEU_TAG_ATTRIBUTE_RW, "type": config.NEU_TYPE_INT32}] +hold_int32_BL = [{"name": "hold_int32_BL", "address": "1!400407#BL", + "attribute": config.NEU_TAG_ATTRIBUTE_RW, "type": config.NEU_TYPE_INT32}] +hold_int32_BB = [{"name": "hold_int32_BB", "address": "1!400409#BB", + "attribute": config.NEU_TAG_ATTRIBUTE_RW, "type": config.NEU_TYPE_INT32}] +hold_uint16_B = [{"name": "hold_uint16_B", "address": "1!400411#B", + "attribute": config.NEU_TAG_ATTRIBUTE_RW, "type": config.NEU_TYPE_UINT16}] +hold_uint16_L = [{"name": "hold_uint16_L", "address": "1!400412#L", + "attribute": config.NEU_TAG_ATTRIBUTE_RW, "type": config.NEU_TYPE_UINT16}] +hold_uint32_LL = [{"name": "hold_uint32_LL", "address": "1!400413#LL", + "attribute": config.NEU_TAG_ATTRIBUTE_RW, "type": config.NEU_TYPE_UINT32}] +hold_uint32_LB = [{"name": "hold_uint32_LB", "address": "1!400415#LB", + "attribute": config.NEU_TAG_ATTRIBUTE_RW, "type": config.NEU_TYPE_UINT32}] +hold_uint32_BL = [{"name": "hold_uint32_BL", "address": "1!400417#BL", + "attribute": config.NEU_TAG_ATTRIBUTE_RW, "type": config.NEU_TYPE_UINT32}] +hold_uint32_BB = [{"name": "hold_uint32_BB", "address": "1!400419#BB", + "attribute": config.NEU_TAG_ATTRIBUTE_RW, "type": config.NEU_TYPE_UINT32}] +hold_float_LL = [{"name": "hold_float_LL", "address": "1!400421#LL", + "attribute": config.NEU_TAG_ATTRIBUTE_RW, "type": config.NEU_TYPE_FLOAT}] +hold_float_LB = [{"name": "hold_float_LB", "address": "1!400423#LB", + "attribute": config.NEU_TAG_ATTRIBUTE_RW, "type": config.NEU_TYPE_FLOAT}] +hold_float_BL = [{"name": "hold_float_BL", "address": "1!400425#BL", + "attribute": config.NEU_TAG_ATTRIBUTE_RW, "type": config.NEU_TYPE_FLOAT}] +hold_float_BB = [{"name": "hold_float_BB", "address": "1!400427#BB", + "attribute": config.NEU_TAG_ATTRIBUTE_RW, "type": config.NEU_TYPE_FLOAT}] + +hold_string_l1 = [{"name": "hold_string_l1", "address": "1!400501.127", + "attribute": config.NEU_TAG_ATTRIBUTE_RW, "type": config.NEU_TYPE_STRING}] +hold_string_l2 = [{"name": "hold_string_l2", "address": "1!400628.127", + "attribute": config.NEU_TAG_ATTRIBUTE_RW, "type": config.NEU_TYPE_STRING}] + +hold_int16_device_err = [{"name": "hold_int16_device_err", "address": "1!409000", + "attribute": config.NEU_TAG_ATTRIBUTE_RW, "type": config.NEU_TYPE_INT16}] class TestModbus: @@ -169,6 +276,57 @@ class TestModbus: api.add_tags_check(node=param[0], group='group', tags=input_bit_4) api.add_tags_check(node=param[0], group='group', tags=input_bit_5) + api.add_tags_check(node=param[0], group='group', tags=hold_int16_m1) + api.add_tags_check(node=param[0], group='group', tags=hold_int16_m2) + api.add_tags_check(node=param[0], group='group', tags=hold_int16_m3) + api.add_tags_check(node=param[0], group='group', tags=hold_int16_m4) + api.add_tags_check(node=param[0], group='group', tags=hold_int16_m5) + api.add_tags_check(node=param[0], group='group', tags=hold_int16_m6) + api.add_tags_check(node=param[0], group='group', tags=hold_int16_m7) + api.add_tags_check(node=param[0], group='group', tags=hold_int16_m8) + api.add_tags_check(node=param[0], group='group', tags=hold_int16_m9) + api.add_tags_check(node=param[0], group='group', tags=hold_int16_m10) + api.add_tags_check(node=param[0], group='group', tags=hold_int16_m11) + api.add_tags_check(node=param[0], group='group', tags=hold_int16_m12) + api.add_tags_check(node=param[0], group='group', tags=hold_int16_m13) + api.add_tags_check(node=param[0], group='group', tags=hold_int16_m14) + api.add_tags_check(node=param[0], group='group', tags=hold_int16_m15) + + api.add_tags_check(node=param[0], group='group', tags=coil_bit_m1) + api.add_tags_check(node=param[0], group='group', tags=coil_bit_m2) + api.add_tags_check(node=param[0], group='group', tags=coil_bit_m3) + api.add_tags_check(node=param[0], group='group', tags=coil_bit_m4) + api.add_tags_check(node=param[0], group='group', tags=coil_bit_m5) + api.add_tags_check(node=param[0], group='group', tags=coil_bit_m6) + api.add_tags_check(node=param[0], group='group', tags=coil_bit_m7) + api.add_tags_check(node=param[0], group='group', tags=coil_bit_m8) + api.add_tags_check(node=param[0], group='group', tags=coil_bit_m9) + api.add_tags_check(node=param[0], group='group', tags=coil_bit_m10) + api.add_tags_check(node=param[0], group='group', tags=coil_bit_m11) + api.add_tags_check(node=param[0], group='group', tags=coil_bit_m12) + api.add_tags_check(node=param[0], group='group', tags=coil_bit_m13) + api.add_tags_check(node=param[0], group='group', tags=coil_bit_m14) + api.add_tags_check(node=param[0], group='group', tags=coil_bit_m15) + + api.add_tags_check(node=param[0], group='group', tags=hold_int16_B) + api.add_tags_check(node=param[0], group='group', tags=hold_int16_L) + api.add_tags_check(node=param[0], group='group', tags=hold_int32_LL) + api.add_tags_check(node=param[0], group='group', tags=hold_int32_LB) + api.add_tags_check(node=param[0], group='group', tags=hold_int32_BL) + api.add_tags_check(node=param[0], group='group', tags=hold_int32_BB) + + api.add_tags_check(node=param[0], group='group', tags=hold_uint16_B) + api.add_tags_check(node=param[0], group='group', tags=hold_uint16_L) + api.add_tags_check(node=param[0], group='group', tags=hold_uint32_LL) + api.add_tags_check(node=param[0], group='group', tags=hold_uint32_LB) + api.add_tags_check(node=param[0], group='group', tags=hold_uint32_BL) + api.add_tags_check(node=param[0], group='group', tags=hold_uint32_BB) + + api.add_tags_check(node=param[0], group='group', tags=hold_float_LL) + api.add_tags_check(node=param[0], group='group', tags=hold_float_LB) + api.add_tags_check(node=param[0], group='group', tags=hold_float_BL) + api.add_tags_check(node=param[0], group='group', tags=hold_float_BB) + @description(given="created modbus node", when="add static tags", then="add success") def test_add_static_tags(self, param): api.add_tags_check(node=param[0], group='group', tags=hold_int16_s) @@ -243,7 +401,7 @@ class TestModbus: api.write_tag_check( node=param[0], group='group', tag=coil_bit_5[0]['name'], value=0) - time.sleep(0.30) + time.sleep(0.3) assert -123 == api.read_tag( node=param[0], group='group', tag=hold_int16[0]['name']) assert 123 == api.read_tag( @@ -338,7 +496,7 @@ class TestModbus: api.add_tags_check(node=param[0], group='group1', tags=hold_int16) api.write_tag_check( node=param[0], group='group', tag=hold_int16[0]['name'], value=222) - time.sleep(0.30) + time.sleep(0.5) assert 222 == api.read_tag( node=param[0], group='group', tag=hold_int16[0]['name']) assert 222 == api.read_tag( @@ -354,7 +512,7 @@ class TestModbus: node=param[0], group='group', tag=hold_int16[0]['name'], value=333) api.write_tag_check( node=param[0], group='group1', tag=t_tag[0]['name'], value=444) - time.sleep(0.30) + time.sleep(0.5) assert 333 == api.read_tag( node=param[0], group='group', tag=hold_int16[0]['name']) assert 444 == api.read_tag( @@ -431,6 +589,18 @@ class TestModbus: response = api.node_ctl(node=param[0], ctl=config.NEU_CTL_START) assert 200 == response.status_code + @description(given="created modbus node", when="create group with invalid configuration", then="create failed") + def test_config_group_with_invalid_configuration(self, param): + response = api.add_group(node=param[0], group='group_config_test', interval = 0) + assert 400 == response.status_code + assert error.NEU_ERR_GROUP_PARAMETER_INVALID == response.json()[ + 'error'] + + response = api.add_group(node=param[0], group='group_config_test', interval = -1) + assert 400 == response.status_code + assert error.NEU_ERR_GROUP_PARAMETER_INVALID == response.json()[ + 'error'] + @description(given="created modbus node", when="add decimal", then="add success") def test_add_decimal_tag(self, param): api.add_tags_check( @@ -461,7 +631,7 @@ class TestModbus: api.write_tag_check( node=param[0], group='group', tag=hold_double_decimal[0]['name'], value=513.11) - time.sleep(0.3) + time.sleep(0.5) assert 11 == api.read_tag( node=param[0], group='group', tag=hold_int16_decimal[0]['name']) assert 23 == api.read_tag( @@ -481,6 +651,7 @@ class TestModbus: node=param[0], group='group', tags=hold_float_precision) api.add_tags_check( node=param[0], group='group', tags=hold_double_precision) + time.sleep(0.3) @description(given="created modbus node/tag", when="read/write precision tag", then="read/write success") def test_read_write_precision_tag(self, param): @@ -493,3 +664,275 @@ class TestModbus: node=param[0], group='group', tag=hold_float_precision[0]['name'])) compare_float(513.127, api.read_tag( node=param[0], group='group', tag=hold_double_precision[0]['name'])) + + @description(given="created partially continuous modbus node/tag", when="write multiple hold tags in one request", then="read/write success") + def test_write_multiple_hold_tags_partially_continuous(self, param): + api.write_tags_check( + node=param[0], group='group', tag_values=[ + {"tag": hold_int16_m1[0]['name'], "value": 1}, + {"tag": hold_int16_m2[0]['name'], "value": 2}, + {"tag": hold_int16_m3[0]['name'], "value": 3}, + {"tag": hold_int16_m4[0]['name'], "value": 4}, + {"tag": hold_int16_m5[0]['name'], "value": 5}, + {"tag": hold_int16_m6[0]['name'], "value": 6}, + {"tag": hold_int16_m7[0]['name'], "value": 7}, + {"tag": hold_int16_m8[0]['name'], "value": 8}, + {"tag": hold_int16_m9[0]['name'], "value": 9}, + {"tag": hold_int16_m10[0]['name'], "value": 10}]) + time.sleep(0.5) + assert 1 == api.read_tag( + node=param[0], group='group', tag=hold_int16_m1[0]['name']) + assert 2 == api.read_tag( + node=param[0], group='group', tag=hold_int16_m2[0]['name']) + assert 3 == api.read_tag( + node=param[0], group='group', tag=hold_int16_m3[0]['name']) + assert 4 == api.read_tag( + node=param[0], group='group', tag=hold_int16_m4[0]['name']) + assert 5 == api.read_tag( + node=param[0], group='group', tag=hold_int16_m5[0]['name']) + assert 6 == api.read_tag( + node=param[0], group='group', tag=hold_int16_m6[0]['name']) + assert 7 == api.read_tag( + node=param[0], group='group', tag=hold_int16_m7[0]['name']) + assert 8 == api.read_tag( + node=param[0], group='group', tag=hold_int16_m8[0]['name']) + assert 9 == api.read_tag( + node=param[0], group='group', tag=hold_int16_m9[0]['name']) + assert 10 == api.read_tag( + node=param[0], group='group', tag=hold_int16_m10[0]['name']) + + @description(given="created discontinuous modbus node/tag", when="write multiple hold tags in one request", then="read/write success") + def test_write_multiple_hold_tags_discontinuous(self, param): + api.write_tags_check( + node=param[0], group='group', tag_values=[ + {"tag": hold_int16_m6[0]['name'], "value": 11}, + {"tag": hold_int16_m7[0]['name'], "value": 12}, + {"tag": hold_int16_m8[0]['name'], "value": 13}, + {"tag": hold_int16_m9[0]['name'], "value": 14}, + {"tag": hold_int16_m10[0]['name'], "value": 15}, + {"tag": hold_int16_m11[0]['name'], "value": 16}, + {"tag": hold_int16_m12[0]['name'], "value": 17}, + {"tag": hold_int16_m13[0]['name'], "value": 18}, + {"tag": hold_int16_m14[0]['name'], "value": 19}, + {"tag": hold_int16_m15[0]['name'], "value": 20}]) + time.sleep(0.3) + assert 11 == api.read_tag( + node=param[0], group='group', tag=hold_int16_m6[0]['name']) + assert 12 == api.read_tag( + node=param[0], group='group', tag=hold_int16_m7[0]['name']) + assert 13 == api.read_tag( + node=param[0], group='group', tag=hold_int16_m8[0]['name']) + assert 14 == api.read_tag( + node=param[0], group='group', tag=hold_int16_m9[0]['name']) + assert 15 == api.read_tag( + node=param[0], group='group', tag=hold_int16_m10[0]['name']) + assert 16 == api.read_tag( + node=param[0], group='group', tag=hold_int16_m11[0]['name']) + assert 17 == api.read_tag( + node=param[0], group='group', tag=hold_int16_m12[0]['name']) + assert 18 == api.read_tag( + node=param[0], group='group', tag=hold_int16_m13[0]['name']) + assert 19 == api.read_tag( + node=param[0], group='group', tag=hold_int16_m14[0]['name']) + assert 20 == api.read_tag( + node=param[0], group='group', tag=hold_int16_m15[0]['name']) + + @description(given="created partially continuous modbus node/tag", when="write multiple coil tags in one request", then="read/write success") + def test_write_multiple_coil_tags_partially_continuous(self, param): + api.write_tags_check( + node=param[0], group='group', tag_values=[ + {"tag": coil_bit_m1[0]['name'], "value": 0}, + {"tag": coil_bit_m2[0]['name'], "value": 1}, + {"tag": coil_bit_m3[0]['name'], "value": 0}, + {"tag": coil_bit_m4[0]['name'], "value": 1}, + {"tag": coil_bit_m5[0]['name'], "value": 0}, + {"tag": coil_bit_m6[0]['name'], "value": 1}, + {"tag": coil_bit_m7[0]['name'], "value": 0}, + {"tag": coil_bit_m8[0]['name'], "value": 1}, + {"tag": coil_bit_m9[0]['name'], "value": 0}, + {"tag": coil_bit_m10[0]['name'], "value": 1}]) + time.sleep(0.3) + assert 0 == api.read_tag( + node=param[0], group='group', tag=coil_bit_m1[0]['name']) + assert 1 == api.read_tag( + node=param[0], group='group', tag=coil_bit_m2[0]['name']) + assert 0 == api.read_tag( + node=param[0], group='group', tag=coil_bit_m3[0]['name']) + assert 1 == api.read_tag( + node=param[0], group='group', tag=coil_bit_m4[0]['name']) + assert 0 == api.read_tag( + node=param[0], group='group', tag=coil_bit_m5[0]['name']) + assert 1 == api.read_tag( + node=param[0], group='group', tag=coil_bit_m6[0]['name']) + assert 0 == api.read_tag( + node=param[0], group='group', tag=coil_bit_m7[0]['name']) + assert 1 == api.read_tag( + node=param[0], group='group', tag=coil_bit_m8[0]['name']) + assert 0 == api.read_tag( + node=param[0], group='group', tag=coil_bit_m9[0]['name']) + assert 1 == api.read_tag( + 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): + api.write_tags_check( + node=param[0], group='group', tag_values=[ + {"tag": coil_bit_m6[0]['name'], "value": 0}, + {"tag": coil_bit_m7[0]['name'], "value": 1}, + {"tag": coil_bit_m8[0]['name'], "value": 0}, + {"tag": coil_bit_m9[0]['name'], "value": 1}, + {"tag": coil_bit_m10[0]['name'], "value": 0}, + {"tag": coil_bit_m11[0]['name'], "value": 1}, + {"tag": coil_bit_m12[0]['name'], "value": 0}, + {"tag": coil_bit_m13[0]['name'], "value": 1}, + {"tag": coil_bit_m14[0]['name'], "value": 0}, + {"tag": coil_bit_m15[0]['name'], "value": 1}]) + time.sleep(0.3) + assert 0 == api.read_tag( + node=param[0], group='group', tag=coil_bit_m6[0]['name']) + assert 1 == api.read_tag( + node=param[0], group='group', tag=coil_bit_m7[0]['name']) + assert 0 == api.read_tag( + node=param[0], group='group', tag=coil_bit_m8[0]['name']) + assert 1 == api.read_tag( + node=param[0], group='group', tag=coil_bit_m9[0]['name']) + assert 0 == api.read_tag( + node=param[0], group='group', tag=coil_bit_m10[0]['name']) + assert 1 == api.read_tag( + node=param[0], group='group', tag=coil_bit_m11[0]['name']) + assert 0 == api.read_tag( + node=param[0], group='group', tag=coil_bit_m12[0]['name']) + assert 1 == api.read_tag( + node=param[0], group='group', tag=coil_bit_m13[0]['name']) + assert 0 == api.read_tag( + node=param[0], group='group', tag=coil_bit_m14[0]['name']) + assert 1 == api.read_tag( + node=param[0], group='group', tag=coil_bit_m15[0]['name']) + + @description(given="created modbus node/tag with different byte orders", when="write and read tag", then="read/write success") + def test_read_write_tag_order(self, param): + api.write_tag_check( + node=param[0], group='group', tag=hold_int16_B[0]['name'], value=1234) + api.write_tag_check( + node=param[0], group='group', tag=hold_int16_L[0]['name'], value=1234) + api.write_tag_check( + node=param[0], group='group', tag=hold_int32_LL[0]['name'], value=12345678) + api.write_tag_check( + node=param[0], group='group', tag=hold_int32_LB[0]['name'], value=12345678) + api.write_tag_check( + node=param[0], group='group', tag=hold_int32_BL[0]['name'], value=12345678) + api.write_tag_check( + node=param[0], group='group', tag=hold_int32_BB[0]['name'], value=12345678) + + api.write_tag_check( + node=param[0], group='group', tag=hold_uint16_B[0]['name'], value=1234) + api.write_tag_check( + node=param[0], group='group', tag=hold_uint16_L[0]['name'], value=1234) + api.write_tag_check( + node=param[0], group='group', tag=hold_uint32_LL[0]['name'], value=12345678) + api.write_tag_check( + node=param[0], group='group', tag=hold_uint32_LB[0]['name'], value=12345678) + api.write_tag_check( + node=param[0], group='group', tag=hold_uint32_BL[0]['name'], value=12345678) + api.write_tag_check( + node=param[0], group='group', tag=hold_uint32_BB[0]['name'], value=12345678) + + api.write_tag_check( + node=param[0], group='group', tag=hold_float_LL[0]['name'], value=123.4) + api.write_tag_check( + node=param[0], group='group', tag=hold_float_LB[0]['name'], value=123.4) + api.write_tag_check( + node=param[0], group='group', tag=hold_float_BL[0]['name'], value=123.4) + api.write_tag_check( + node=param[0], group='group', tag=hold_float_BB[0]['name'], value=123.4) + + time.sleep(1.0) + assert 1234== api.read_tag( + node=param[0], group='group', tag=hold_int16_B[0]['name']) + assert 1234 == api.read_tag( + node=param[0], group='group', tag=hold_int16_L[0]['name']) + assert 12345678 == api.read_tag( + node=param[0], group='group', tag=hold_int32_LL[0]['name']) + assert 12345678 == api.read_tag( + node=param[0], group='group', tag=hold_int32_LB[0]['name']) + assert 12345678 == api.read_tag( + node=param[0], group='group', tag=hold_int32_BL[0]['name']) + assert 12345678 == api.read_tag( + node=param[0], group='group', tag=hold_int32_BB[0]['name']) + + assert 1234== api.read_tag( + node=param[0], group='group', tag=hold_uint16_B[0]['name']) + assert 1234 == api.read_tag( + node=param[0], group='group', tag=hold_uint16_L[0]['name']) + assert 12345678 == api.read_tag( + node=param[0], group='group', tag=hold_uint32_LL[0]['name']) + assert 12345678 == api.read_tag( + node=param[0], group='group', tag=hold_uint32_LB[0]['name']) + assert 12345678 == api.read_tag( + node=param[0], group='group', tag=hold_uint32_BL[0]['name']) + assert 12345678 == api.read_tag( + node=param[0], group='group', tag=hold_uint32_BB[0]['name']) + + assert compare_float(123.4, api.read_tag( + node=param[0], group='group', tag=hold_float_LL[0]['name'])) + assert compare_float(123.4, api.read_tag( + node=param[0], group='group', tag=hold_float_LB[0]['name'])) + assert compare_float(123.4, api.read_tag( + node=param[0], group='group', tag=hold_float_BL[0]['name'])) + assert compare_float(123.4, api.read_tag( + node=param[0], group='group', tag=hold_float_BB[0]['name'])) + + @description(given="created modbus node/tag", when="read consecutive tags with a length exceeding the protocol limit", then="read success") + def test_read_tag_length_exceeds_250(self, param): + api.add_tags_check(node=param[0], group='group', tags=hold_string_l1) + api.add_tags_check(node=param[0], group='group', tags=hold_string_l2) + + api.write_tag_check( + node=param[0], group='group', tag=hold_string_l1[0]['name'], value='a') + api.write_tag_check( + node=param[0], group='group', tag=hold_string_l2[0]['name'], value='b') + time.sleep(1.0) + assert 'a' == api.read_tag( + node=param[0], group='group', tag=hold_string_l1[0]['name']) + assert 'b' == api.read_tag( + node=param[0], group='group', tag=hold_string_l2[0]['name']) + + @description(given="created modbus device_error_test node/tag", when="read tag", then="read failed") + def test_read_modbus_device_err(self, param): + 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( + node=param[0], group='group', tag=hold_int16_device_err[0]['name']) + + @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): + 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) + + api.write_tag_check( + node=param[0], group='group', tag=hold_int16_retry_1[0]['name'], value=111) + api.write_tag_check( + node=param[0], group='group', tag=hold_int16_retry_2[0]['name'], value=222) + time.sleep(1.0) + assert 111 == api.read_tag( + node=param[0], group='group', tag=hold_int16_retry_1[0]['name']) + assert 222 == api.read_tag( + node=param[0], group='group', tag=hold_int16_retry_2[0]['name']) + + @description(given="close modbus simulator", when="create modbus node/tag, write and read tag", then="write/read failed") + 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: + api.modbus_tcp_node_setting( + node=param[0]+"_3002", port=29998) + else: + api.modbus_rtu_node_setting( + node=param[0]+"_3002", port=29999) + response = api.add_group(node=param[0]+"_3002", group='group') + assert 200 == response.status_code + + api.add_tags_check(node=param[0]+"_3002", group='group', tags=hold_int16) + time.sleep(0.3) + assert error.NEU_ERR_PLUGIN_DISCONNECTED == api.read_tag_err( + node=param[0]+"_3002", group='group', tag=hold_int16[0]['name']) \ No newline at end of file diff --git a/tests/ft/neuron/api.py b/tests/ft/neuron/api.py index 5882d319..bf35f5da 100644 --- a/tests/ft/neuron/api.py +++ b/tests/ft/neuron/api.py @@ -168,6 +168,19 @@ def read_tag_error(node, group, tag, sync=False): finally: return x[0]['error'] +def read_tag_err(node, group, tag, sync=False): + response = read_tags(node, group, sync) + assert 200 == response.status_code + x = list(filter(lambda x: x['name'] == tag, + response.json()['tags'])) + assert len(x) == 1 + try: + x[0]['error'] + except KeyError: + print(x[0]) + finally: + return x[0]['error'] + @gen_check def write_tag(node, group, tag, value): @@ -269,13 +282,13 @@ def get_template_tags(tmpl, group, name): # plugin setting -def modbus_tcp_node_setting(node, port, connection_mode=0, transport_mode=0, interval=0, host='127.0.0.1', timeout=3000): +def modbus_tcp_node_setting(node, port, connection_mode=0, transport_mode=0, interval=0, host='127.0.0.1', timeout=3000, max_retries=2): return node_setting(node, json={"connection_mode": connection_mode, "transport_mode": transport_mode, "interval": interval, - "host": host, "port": port, "timeout": timeout}) + "host": host, "port": port, "timeout": timeout, "max_retries": max_retries}) -def modbus_rtu_node_setting(node, port, connection_mode=0, transport_mode=0, interval=10, host='127.0.0.1', timeout=3000, link=1, device="", stop=0, parity=0, baud=4, data=3): +def modbus_rtu_node_setting(node, port, connection_mode=0, transport_mode=0, interval=10, 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, - "link": link, "device": device, "stop": stop, "parity": parity, - "baud": baud, "data": data}) + "max_retries": max_retries, "link": link, "device": device, "stop": stop, + "parity": parity, "baud": baud, "data": data})