diff --git a/include/neuron/tag.h b/include/neuron/tag.h index f3b4f7dc3..9fb6a0b2e 100644 --- a/include/neuron/tag.h +++ b/include/neuron/tag.h @@ -47,6 +47,8 @@ typedef enum { NEU_DATATAG_ENDIAN_LB64 = 9, NEU_DATATAG_ENDIAN_BB64 = 10, NEU_DATATAG_ENDIAN_BL64 = 11, + NEU_DATATAG_ENDIAN_WS64 = 12, // #WS 3,4,1,2,7,8,5,6 + NEU_DATATAG_ENDIAN_WR64 = 13, // #WR 5,6,7,8,1,2,3,4 } neu_datatag_endian_e; typedef enum { diff --git a/include/neuron/type.h b/include/neuron/type.h index c021b16f1..f2706747d 100644 --- a/include/neuron/type.h +++ b/include/neuron/type.h @@ -1187,6 +1187,42 @@ static inline uint64_t neu_htonbl(uint64_t u) return out; } +static inline uint64_t neu_htonws(uint64_t u) +{ + uint8_t *in = (uint8_t *) &u; + uint64_t out = 0; + uint8_t *out_bytes = (uint8_t *) &out; + + out_bytes[0] = in[2]; + out_bytes[1] = in[3]; + out_bytes[2] = in[0]; + out_bytes[3] = in[1]; + out_bytes[4] = in[6]; + out_bytes[5] = in[7]; + out_bytes[6] = in[4]; + out_bytes[7] = in[5]; + + return out; +} + +static inline uint64_t neu_htonwr(uint64_t u) +{ + uint8_t *in = (uint8_t *) &u; + uint64_t out = 0; + uint8_t *out_bytes = (uint8_t *) &out; + + out_bytes[0] = in[4]; + out_bytes[1] = in[5]; + out_bytes[2] = in[6]; + out_bytes[3] = in[7]; + out_bytes[4] = in[0]; + out_bytes[5] = in[1]; + out_bytes[6] = in[2]; + out_bytes[7] = in[3]; + + return out; +} + static inline uint64_t neu_ntohll(uint64_t u) { return neu_htonll(u); diff --git a/plugins/modbus/modbus-rtu.json b/plugins/modbus/modbus-rtu.json index f7f78e1e6..3daddc38a 100644 --- a/plugins/modbus/modbus-rtu.json +++ b/plugins/modbus/modbus-rtu.json @@ -18,11 +18,11 @@ }, { "type": 7, - "regex": "^[0-9]+![3-4][0-9]+(#B|#L|#BB|#BL|#LL|#LB|)$" + "regex": "^[0-9]+![3-4][0-9]+(#B|#L|#BB|#BL|#LL|#LB|#WS|#WR|)$" }, { "type": 8, - "regex": "^[0-9]+![3-4][0-9]+(#B|#L|#BB|#BL|#LL|#LB|)$" + "regex": "^[0-9]+![3-4][0-9]+(#B|#L|#BB|#BL|#LL|#LB|#WS|#WR|)$" }, { "type": 9, @@ -30,7 +30,7 @@ }, { "type": 10, - "regex": "^[0-9]+![3-4][0-9]+(#B|#L|#BB|#BL|#LL|#LB|)$" + "regex": "^[0-9]+![3-4][0-9]+(#B|#L|#BB|#BL|#LL|#LB|#WS|#WR|)$" }, { "type": 11, @@ -211,6 +211,14 @@ { "key": "78 56 34 12", "value": 4 + }, + { + "key": "34 12 78 56", + "value": 5 + }, + { + "key": "56 78 12 34", + "value": 6 } ] } diff --git a/plugins/modbus/modbus-tcp.json b/plugins/modbus/modbus-tcp.json index 7c80e3b97..2b65f8f67 100644 --- a/plugins/modbus/modbus-tcp.json +++ b/plugins/modbus/modbus-tcp.json @@ -18,11 +18,11 @@ }, { "type": 7, - "regex": "^[0-9]+![3-4][0-9]+(#B|#L|#BB|#BL|#LL|#LB|)$" + "regex": "^[0-9]+![3-4][0-9]+(#B|#L|#BB|#BL|#LL|#LB|#WS|#WR|)$" }, { "type": 8, - "regex": "^[0-9]+![3-4][0-9]+(#B|#L|#BB|#BL|#LL|#LB|)$" + "regex": "^[0-9]+![3-4][0-9]+(#B|#L|#BB|#BL|#LL|#LB|#WS|#WR|)$" }, { "type": 9, @@ -30,7 +30,7 @@ }, { "type": 10, - "regex": "^[0-9]+![3-4][0-9]+(#B|#L|#BB|#BL|#LL|#LB|)$" + "regex": "^[0-9]+![3-4][0-9]+(#B|#L|#BB|#BL|#LL|#LB|#WS|#WR|)$" }, { "type": 11, @@ -219,6 +219,14 @@ { "key": "78 56 34 12", "value": 4 + }, + { + "key": "34 12 78 56", + "value": 5 + }, + { + "key": "56 78 12 34", + "value": 6 } ] } diff --git a/plugins/modbus/modbus.h b/plugins/modbus/modbus.h index 933f701f6..9cefa1e10 100644 --- a/plugins/modbus/modbus.h +++ b/plugins/modbus/modbus.h @@ -78,6 +78,8 @@ typedef enum modbus_endianess_64 { MODBUS_LB = 2, // 21 43 65 87 MODBUS_BB = 3, // 87 65 43 21 MODBUS_BL = 4, // 78 56 34 12 + MODBUS_WS = 5, // 34 12 78 56 + MODBUS_WR = 6, // 56 78 12 34 } modbus_endianess_64; typedef enum modbus_address_base { diff --git a/plugins/modbus/modbus_point.c b/plugins/modbus/modbus_point.c index e80edb4a9..3f55ab07d 100644 --- a/plugins/modbus/modbus_point.c +++ b/plugins/modbus/modbus_point.c @@ -585,6 +585,12 @@ void modbus_convert_endianess_64(neu_value_u * value, case MODBUS_BL: value->u64 = neu_htonbl(value->u64); break; + case MODBUS_WS: + value->u64 = neu_htonws(value->u64); + break; + case MODBUS_WR: + value->u64 = neu_htonwr(value->u64); + break; default: break; } diff --git a/plugins/modbus/modbus_req.c b/plugins/modbus/modbus_req.c index 23e4bcb67..1bdf12a03 100644 --- a/plugins/modbus/modbus_req.c +++ b/plugins/modbus/modbus_req.c @@ -47,6 +47,41 @@ static int process_protocol_buf_test(neu_plugin_t *plugin, void *req, modbus_point_t *point, uint16_t response_size); +static void +convert_point_endianess_64_for_test(neu_value_u *value, modbus_point_t *point, + modbus_endianess_64 default_endianess) +{ + modbus_endianess_64 endianess = default_endianess; + + if (!point->option.value64.is_default) { + switch (point->option.value64.endian) { + case NEU_DATATAG_ENDIAN_B64: + case NEU_DATATAG_ENDIAN_BB64: + endianess = MODBUS_BB; + break; + case NEU_DATATAG_ENDIAN_LB64: + endianess = MODBUS_LB; + break; + case NEU_DATATAG_ENDIAN_BL64: + endianess = MODBUS_BL; + break; + case NEU_DATATAG_ENDIAN_WS64: + endianess = MODBUS_WS; + break; + case NEU_DATATAG_ENDIAN_WR64: + endianess = MODBUS_WR; + break; + case NEU_DATATAG_ENDIAN_L64: + case NEU_DATATAG_ENDIAN_LL64: + default: + endianess = MODBUS_LL; + break; + } + } + + modbus_convert_endianess_64(value, endianess); +} + void modbus_conn_connected(void *data, int fd) { struct neu_plugin *plugin = (struct neu_plugin *) data; @@ -595,6 +630,11 @@ int modbus_value_handle_test(neu_plugin_t *plugin, void *req, case NEU_TYPE_DOUBLE: { uint64_t tmp_vald; memcpy(&tmp_vald, recv_bytes, sizeof(uint64_t)); + neu_value_u value = { 0 }; + value.u64 = tmp_vald; + convert_point_endianess_64_for_test(&value, point, + plugin->endianess_64); + tmp_vald = value.u64; jtype = NEU_JSON_DOUBLE; jvalue.val_int = neu_ntohll(tmp_vald); break; @@ -603,6 +643,11 @@ int modbus_value_handle_test(neu_plugin_t *plugin, void *req, jtype = NEU_JSON_INT; int64_t tmp_val64; memcpy(&tmp_val64, recv_bytes, sizeof(int64_t)); + neu_value_u value = { 0 }; + value.u64 = (uint64_t) tmp_val64; + convert_point_endianess_64_for_test(&value, point, + plugin->endianess_64); + tmp_val64 = (int64_t) value.u64; jvalue.val_int = (int64_t) neu_ntohll(tmp_val64); break; } @@ -610,6 +655,11 @@ int modbus_value_handle_test(neu_plugin_t *plugin, void *req, jtype = NEU_JSON_INT; uint64_t tmp_val64; memcpy(&tmp_val64, recv_bytes, sizeof(uint64_t)); + neu_value_u value = { 0 }; + value.u64 = tmp_val64; + convert_point_endianess_64_for_test(&value, point, + plugin->endianess_64); + tmp_val64 = value.u64; jvalue.val_int = neu_ntohll(tmp_val64); break; } @@ -1025,4 +1075,4 @@ static int process_protocol_buf_test(neu_plugin_t *plugin, void *req, free(recv_buf); return ret; -} \ No newline at end of file +} diff --git a/src/adapter/driver/driver.c b/src/adapter/driver/driver.c index d5848e413..07016a113 100644 --- a/src/adapter/driver/driver.c +++ b/src/adapter/driver/driver.c @@ -1121,6 +1121,12 @@ static void fix_value(neu_datatag_t *tag, neu_type_e value_type, case NEU_DATATAG_ENDIAN_BL64: value->value.u64 = neu_htonbl(value->value.u64); break; + case NEU_DATATAG_ENDIAN_WS64: + value->value.u64 = neu_htonws(value->value.u64); + break; + case NEU_DATATAG_ENDIAN_WR64: + value->value.u64 = neu_htonwr(value->value.u64); + break; case NEU_DATATAG_ENDIAN_L64: case NEU_DATATAG_ENDIAN_LL64: default: @@ -2896,6 +2902,12 @@ static void read_report_group(int64_t timestamp, int64_t timeout, case NEU_DATATAG_ENDIAN_BL64: value.value.value.u64 = neu_htonbl(value.value.value.u64); break; + case NEU_DATATAG_ENDIAN_WS64: + value.value.value.u64 = neu_htonws(value.value.value.u64); + break; + case NEU_DATATAG_ENDIAN_WR64: + value.value.value.u64 = neu_htonwr(value.value.value.u64); + break; case NEU_DATATAG_ENDIAN_L64: case NEU_DATATAG_ENDIAN_LL64: default: @@ -3071,6 +3083,12 @@ static void read_group(int64_t timestamp, int64_t timeout, case NEU_DATATAG_ENDIAN_BL64: value.value.value.u64 = neu_htonbl(value.value.value.u64); break; + case NEU_DATATAG_ENDIAN_WS64: + value.value.value.u64 = neu_htonws(value.value.value.u64); + break; + case NEU_DATATAG_ENDIAN_WR64: + value.value.value.u64 = neu_htonwr(value.value.value.u64); + break; case NEU_DATATAG_ENDIAN_L64: case NEU_DATATAG_ENDIAN_LL64: default: @@ -3260,6 +3278,12 @@ static void read_group_paginate(int64_t timestamp, int64_t timeout, case NEU_DATATAG_ENDIAN_BL64: value.value.value.u64 = neu_htonbl(value.value.value.u64); break; + case NEU_DATATAG_ENDIAN_WS64: + value.value.value.u64 = neu_htonws(value.value.value.u64); + break; + case NEU_DATATAG_ENDIAN_WR64: + value.value.value.u64 = neu_htonwr(value.value.value.u64); + break; case NEU_DATATAG_ENDIAN_L64: case NEU_DATATAG_ENDIAN_LL64: default: diff --git a/src/base/tag.c b/src/base/tag.c index d73f84a0a..c49c3d561 100644 --- a/src/base/tag.c +++ b/src/base/tag.c @@ -290,6 +290,12 @@ int neu_datatag_parse_addr_option(const neu_datatag_t * datatag, } else if (e1 == 'L' && e2 == 'B') { option->value64.endian = NEU_DATATAG_ENDIAN_LB64; option->value64.is_default = false; + } else if (e1 == 'W' && e2 == 'S') { + option->value64.endian = NEU_DATATAG_ENDIAN_WS64; + option->value64.is_default = false; + } else if (e1 == 'W' && e2 == 'R') { + option->value64.endian = NEU_DATATAG_ENDIAN_WR64; + option->value64.is_default = false; } } else if (n == 1) { if (e1 == 'B') { diff --git a/tests/ft/driver/test_modbus.py b/tests/ft/driver/test_modbus.py index e5c865f94..952cd8bd9 100644 --- a/tests/ft/driver/test_modbus.py +++ b/tests/ft/driver/test_modbus.py @@ -355,6 +355,14 @@ def simulator_setup_teardown(param): "attribute": config.NEU_TAG_ATTRIBUTE_RW, "type": config.NEU_TYPE_UINT64}] hold_uint64_L = [{"name": "hold_uint64_L", "address": "1!400441#L", "attribute": config.NEU_TAG_ATTRIBUTE_RW, "type": config.NEU_TYPE_UINT64}] +hold_uint64_WS_default = [{"name": "hold_uint64_WS_default", "address": "1!400453", + "attribute": config.NEU_TAG_ATTRIBUTE_RW, "type": config.NEU_TYPE_UINT64}] +hold_uint64_WS_suffix = [{"name": "hold_uint64_WS_suffix", "address": "1!400453#WS", + "attribute": config.NEU_TAG_ATTRIBUTE_RW, "type": config.NEU_TYPE_UINT64}] +hold_uint64_WR_default = [{"name": "hold_uint64_WR_default", "address": "1!400457", + "attribute": config.NEU_TAG_ATTRIBUTE_RW, "type": config.NEU_TYPE_UINT64}] +hold_uint64_WR_suffix = [{"name": "hold_uint64_WR_suffix", "address": "1!400457#WR", + "attribute": config.NEU_TAG_ATTRIBUTE_RW, "type": config.NEU_TYPE_UINT64}] hold_double_B = [{"name": "hold_double_B", "address": "1!400445#B", "attribute": config.NEU_TAG_ATTRIBUTE_RW, "type": config.NEU_TYPE_DOUBLE}] hold_double_L = [{"name": "hold_double_L", "address": "1!400449#L", @@ -1704,6 +1712,115 @@ def test_write_read_endianness_tag(self, param): assert 12345678 == api.read_tag( node=param[0], group='group', tag=hold_uint32_BB[0]['name']) + @description(given="default 64-bit endianness in node configuration and WS/WR suffix in tag address", when="read and test-read tag", then="tag suffix should override node default") + def test_write_read_endianness_tag_64_override(self, param): + if param[0] != 'modbus-tcp': + pytest.skip("test modbus tcp only") + + writer_node = param[0] + "_64_writer" + reader_node = param[0] + "_64_reader" + created_nodes = [] + group = 'group' + + writer_params = { + "connection_mode": 0, + "transport_mode": 0, + "interval": 1, + "host": "127.0.0.1", + "port": tcp_port, + "timeout": 3000, + "max_retries": 2, + "retry_interval": 1, + "endianess_64": 5, + } + reader_params = { + "connection_mode": 0, + "transport_mode": 0, + "interval": 1, + "host": "127.0.0.1", + "port": tcp_port, + "timeout": 3000, + "max_retries": 2, + "retry_interval": 1, + "endianess_64": 1, + } + + ws_value = 0x0102030405060708 + wr_value = 0x1112131415161718 + + try: + api.add_node_check(node=writer_node, plugin=param[1]) + created_nodes.append(writer_node) + api.add_group_check(node=writer_node, group=group, interval=100) + api.node_setting_check(node=writer_node, json=writer_params) + + api.add_node_check(node=reader_node, plugin=param[1]) + created_nodes.append(reader_node) + api.add_group_check(node=reader_node, group=group, interval=100) + api.node_setting_check(node=reader_node, json=reader_params) + + api.add_tags_check(node=writer_node, group=group, + tags=hold_uint64_WS_default) + api.add_tags_check(node=writer_node, group=group, + tags=hold_uint64_WR_default) + api.add_tags_check(node=reader_node, group=group, + tags=hold_uint64_WS_suffix) + api.add_tags_check(node=reader_node, group=group, + tags=hold_uint64_WR_suffix) + + api.write_tag_check(node=writer_node, group=group, + tag=hold_uint64_WS_default[0]['name'], + value=ws_value) + + writer_params["endianess_64"] = 6 + api.node_setting_check(node=writer_node, json=writer_params) + time.sleep(0.3) + + api.write_tag_check(node=writer_node, group=group, + tag=hold_uint64_WR_default[0]['name'], + value=wr_value) + + time.sleep(0.3) + + assert ws_value == api.read_tag( + node=reader_node, group=group, tag=hold_uint64_WS_suffix[0]['name']) + assert wr_value == api.read_tag( + node=reader_node, group=group, tag=hold_uint64_WR_suffix[0]['name']) + + test_hold_uint64_ws = { + "driver": reader_node, + "group": group, + "tag": "hold_uint64_ws_test", + "address": hold_uint64_WS_suffix[0]["address"], + "attribute": hold_uint64_WS_suffix[0]["attribute"], + "type": hold_uint64_WS_suffix[0]["type"], + "precision": 0, + "decimal": 0, + "bias": 0.0, + } + test_hold_uint64_wr = { + "driver": reader_node, + "group": group, + "tag": "hold_uint64_wr_test", + "address": hold_uint64_WR_suffix[0]["address"], + "attribute": hold_uint64_WR_suffix[0]["attribute"], + "type": hold_uint64_WR_suffix[0]["type"], + "precision": 0, + "decimal": 0, + "bias": 0.0, + } + + response = api.test_read_tag(json=test_hold_uint64_ws) + assert ws_value == response.json()["value"] + response = api.test_read_tag(json=test_hold_uint64_wr) + assert wr_value == response.json()["value"] + finally: + for node in reversed(created_nodes): + try: + api.del_node_check(node=node) + except Exception: + pass + @description(given="created modbus node", when="set modbus start base to 0", then="set success and read/write success") def test_set_modbus_start_base(self, param): assert 1 == api.read_tag(