From ae4623ed7e73f151ae27d134d5d7447947ba2913 Mon Sep 17 00:00:00 2001 From: fengzero Date: Thu, 26 Mar 2026 08:42:24 +0000 Subject: [PATCH] tag: option unit --- include/neuron/define.h | 1 + include/neuron/msg.h | 5 ++++ include/neuron/tag.h | 1 + persistence/0120_2.15.0_tag.sql | 5 ++++ plugins/restful/datatag_handle.c | 17 ++++++++++++ plugins/restful/rw_handle.c | 8 ++++++ src/base/tag.c | 8 ++++++ src/parser/neu_json_rw.c | 37 ++++++++++++++----------- src/parser/neu_json_tag.c | 12 +++++++++ src/parser/neu_json_tag.h | 1 + src/persist/sqlite.c | 36 +++++++++++++++---------- tests/ft/http_api/test_http_api.py | 12 ++++++--- tests/ft/tag/test_tag.py | 43 ++++++++++++++++++++++++++++++ 13 files changed, 152 insertions(+), 34 deletions(-) create mode 100644 persistence/0120_2.15.0_tag.sql diff --git a/include/neuron/define.h b/include/neuron/define.h index 7a027bce..fca9a891 100644 --- a/include/neuron/define.h +++ b/include/neuron/define.h @@ -59,6 +59,7 @@ #define NEU_TAG_META_LENGTH 20 #define NEU_TAG_META_SIZE 32 #define NEU_TAG_FORMAT_LENGTH 16 +#define NEU_TAG_UNIT_LENGTH 16 #define NEU_LOG_LEVEL_DEBUG "debug" #define NEU_LOG_LEVEL_INFO "info" diff --git a/include/neuron/msg.h b/include/neuron/msg.h index 6d68d930..1034ea2d 100644 --- a/include/neuron/msg.h +++ b/include/neuron/msg.h @@ -1470,6 +1470,11 @@ neu_tag_value_to_json_paginate(neu_resp_tag_value_meta_paginate_t *tag_value, tag_json->datatag.bias = tag_value->datatag.bias; tag_json->datatag.description = strdup(tag_value->datatag.description); tag_json->datatag.option = tag_value->datatag.option; + if (tag_value->datatag.unit) { + tag_json->datatag.unit = strdup(tag_value->datatag.unit); + } else { + tag_json->datatag.unit = strdup(""); + } memcpy(tag_json->datatag.meta, tag_value->datatag.meta, NEU_TAG_META_LENGTH); diff --git a/include/neuron/tag.h b/include/neuron/tag.h index 93206f40..f3b4f7dc 100644 --- a/include/neuron/tag.h +++ b/include/neuron/tag.h @@ -93,6 +93,7 @@ typedef struct { double decimal; double bias; char * description; + char * unit; neu_datatag_addr_option_u option; uint8_t meta[NEU_TAG_META_LENGTH]; uint8_t format[NEU_TAG_FORMAT_LENGTH]; diff --git a/persistence/0120_2.15.0_tag.sql b/persistence/0120_2.15.0_tag.sql new file mode 100644 index 00000000..d14d2d55 --- /dev/null +++ b/persistence/0120_2.15.0_tag.sql @@ -0,0 +1,5 @@ +BEGIN TRANSACTION; + +alter TABLE tags add column unit TEXT NULL check(length(unit) <= 128); + +COMMIT; \ No newline at end of file diff --git a/plugins/restful/datatag_handle.c b/plugins/restful/datatag_handle.c index a9810a39..73ff255b 100644 --- a/plugins/restful/datatag_handle.c +++ b/plugins/restful/datatag_handle.c @@ -95,6 +95,11 @@ void handle_add_tags(nng_aio *aio) } else { cmd.tags[i].description = strdup(""); } + if (req->tags[i].unit == NULL) { + cmd.tags[i].unit = strdup(""); + } else { + cmd.tags[i].unit = strdup(req->tags[i].unit); + } } int ret = neu_plugin_op(plugin, header, &cmd); @@ -216,6 +221,12 @@ void handle_add_gtags(nng_aio *aio) } else { cmd.groups[i].tags[j].description = strdup(""); } + if (req->groups[i].tags[j].unit == NULL) { + cmd.groups[i].tags[j].unit = strdup(""); + } else { + cmd.groups[i].tags[j].unit = + strdup(req->groups[i].tags[j].unit); + } } } @@ -382,6 +393,11 @@ void handle_update_tags(nng_aio *aio) } else { cmd.tags[i].description = strdup(""); } + if (req->tags[i].unit == NULL) { + cmd.tags[i].unit = strdup(""); + } else { + cmd.tags[i].unit = strdup(req->tags[i].unit); + } } ret = neu_plugin_op(plugin, header, &cmd); @@ -529,6 +545,7 @@ void handle_get_tags_resp(nng_aio *aio, neu_resp_get_tag_t *tags) tags_res.tags[index].decimal = tag->decimal; tags_res.tags[index].bias = tag->bias; tags_res.tags[index].t = NEU_JSON_UNDEFINE; + tags_res.tags[index].unit = tag->unit; } neu_json_encode_by_fn(&tags_res, neu_json_encode_get_tags_resp, &result); diff --git a/plugins/restful/rw_handle.c b/plugins/restful/rw_handle.c index 8f313de4..34b854a1 100644 --- a/plugins/restful/rw_handle.c +++ b/plugins/restful/rw_handle.c @@ -894,6 +894,12 @@ void handle_read_paginate_resp(nng_aio * aio, strdup(tag_value->datatag.address); new_tag_value.datatag.description = strdup(tag_value->datatag.description); + if (tag_value->datatag.unit) { + new_tag_value.datatag.unit = + strdup(tag_value->datatag.unit); + } else { + new_tag_value.datatag.unit = strdup(""); + } utarray_push_back(filtered_tags, &new_tag_value); } @@ -908,6 +914,7 @@ void handle_read_paginate_resp(nng_aio * aio, free(orig_tag_value->datatag.name); free(orig_tag_value->datatag.address); free(orig_tag_value->datatag.description); + free(orig_tag_value->datatag.unit); } } @@ -997,6 +1004,7 @@ void handle_read_paginate_resp(nng_aio * aio, free(final_tag_value->datatag.name); free(final_tag_value->datatag.address); free(final_tag_value->datatag.description); + free(final_tag_value->datatag.unit); } } diff --git a/src/base/tag.c b/src/base/tag.c index 202c532e..d73f84a0 100644 --- a/src/base/tag.c +++ b/src/base/tag.c @@ -40,6 +40,11 @@ static void tag_array_copy(void *_dst, const void *_src) dst->address = strdup(src->address); dst->name = strdup(src->name); dst->description = strdup(src->description); + if (src->unit == NULL) { + dst->unit = strdup(""); + } else { + dst->unit = strdup(src->unit); + } memcpy(dst->format, src->format, sizeof(src->format)); dst->n_format = src->n_format; @@ -53,6 +58,9 @@ static void tag_array_free(void *_elt) free(elt->name); free(elt->address); free(elt->description); + if (elt->unit) { + free(elt->unit); + } } void neu_tag_format_str(const neu_datatag_t *tag, char *buf, int len) diff --git a/src/parser/neu_json_rw.c b/src/parser/neu_json_rw.c index e6d02d53..6d7a87ae 100644 --- a/src/parser/neu_json_rw.c +++ b/src/parser/neu_json_rw.c @@ -137,22 +137,26 @@ int neu_json_encode_read_paginate_resp(void *json_object, void *param) tag_elems[7].t = NEU_JSON_DOUBLE; tag_elems[7].v.val_double = p_tag->datatag.bias; + tag_elems[8].name = "unit"; + tag_elems[8].t = NEU_JSON_STR; + tag_elems[8].v.val_str = p_tag->datatag.unit; + if (p_tag->error != 0) { - tag_elems[8].name = "error"; - tag_elems[8].t = NEU_JSON_INT; - tag_elems[8].v.val_int = p_tag->error; + tag_elems[9].name = "error"; + tag_elems[9].t = NEU_JSON_INT; + tag_elems[9].v.val_int = p_tag->error; } else { - tag_elems[8].name = "value"; - tag_elems[8].t = p_tag->t; - tag_elems[8].v = p_tag->value; - tag_elems[8].precision = p_tag->precision; - tag_elems[8].bias = p_tag->datatag.bias; + tag_elems[9].name = "value"; + tag_elems[9].t = p_tag->t; + tag_elems[9].v = p_tag->value; + tag_elems[9].precision = p_tag->precision; + tag_elems[9].bias = p_tag->datatag.bias; if (p_tag->t == NEU_JSON_FLOAT || p_tag->t == NEU_JSON_DOUBLE) { - if_precision = 1; - tag_elems[9].name = "transferPrecision"; - tag_elems[9].t = NEU_JSON_INT; - tag_elems[9].v.val_int = + if_precision = 1; + tag_elems[10].name = "transferPrecision"; + tag_elems[10].t = NEU_JSON_INT; + tag_elems[10].v.val_int = p_tag->precision > 0 ? p_tag->precision : 1; } } @@ -166,16 +170,17 @@ int neu_json_encode_read_paginate_resp(void *json_object, void *param) neu_json_encode_field(attributes_object, &meta_elem, 1); } - tag_elems[if_precision + 9].name = "attributes"; - tag_elems[if_precision + 9].t = NEU_JSON_OBJECT; - tag_elems[if_precision + 9].v.val_object = attributes_object; + tag_elems[if_precision + 10].name = "attributes"; + tag_elems[if_precision + 10].t = NEU_JSON_OBJECT; + tag_elems[if_precision + 10].v.val_object = attributes_object; tag_array = - neu_json_encode_array(tag_array, tag_elems, 10 + if_precision); + neu_json_encode_array(tag_array, tag_elems, 11 + if_precision); free(p_tag->datatag.name); free(p_tag->datatag.address); free(p_tag->datatag.description); + free(p_tag->datatag.unit); p_tag++; } diff --git a/src/parser/neu_json_tag.c b/src/parser/neu_json_tag.c index 05280bbe..059d07fb 100644 --- a/src/parser/neu_json_tag.c +++ b/src/parser/neu_json_tag.c @@ -84,6 +84,11 @@ int neu_json_encode_tag(void *json_obj, void *param) .t = tag->t, .v = tag->value, }, + { + .name = "unit", + .t = NEU_JSON_STR, + .v.val_str = tag->unit, + }, }; ret = neu_json_encode_field(json_obj, tag_elems, @@ -140,6 +145,11 @@ int neu_json_decode_tag_json(void *json_obj, neu_json_tag_t *tag_p) .t = NEU_JSON_DOUBLE, .attribute = NEU_JSON_ATTRIBUTE_OPTIONAL, }, + { + .name = "unit", + .t = NEU_JSON_STR, + .attribute = NEU_JSON_ATTRIBUTE_OPTIONAL, + }, }; int ret = neu_json_decode_by_json(json_obj, NEU_JSON_ELEM_SIZE(tag_elems), @@ -157,6 +167,7 @@ int neu_json_decode_tag_json(void *json_obj, neu_json_tag_t *tag_p) .t = tag_elems[7].t, .value = tag_elems[7].v, .bias = tag_elems[8].v.val_double, + .unit = tag_elems[9].v.val_str, }; if (0 != ret) { @@ -180,6 +191,7 @@ void neu_json_decode_tag_fini(neu_json_tag_t *tag) free(tag->name); free(tag->address); free(tag->description); + free(tag->unit); if (NEU_JSON_STR == tag->t) { free(tag->value.val_str); } diff --git a/src/parser/neu_json_tag.h b/src/parser/neu_json_tag.h index fd4762df..d8ee854a 100644 --- a/src/parser/neu_json_tag.h +++ b/src/parser/neu_json_tag.h @@ -42,6 +42,7 @@ typedef struct { double bias; neu_json_type_e t; neu_json_value_u value; + char * unit; } neu_json_tag_t; int neu_json_encode_tag(void *json_obj, void *param); diff --git a/src/persist/sqlite.c b/src/persist/sqlite.c index 5c7202af..ded4771e 100644 --- a/src/persist/sqlite.c +++ b/src/persist/sqlite.c @@ -623,11 +623,11 @@ int neu_sqlite_persister_store_tag(neu_persister_t * self, ((neu_sqlite_persister_t *) self)->db, "INSERT INTO tags (" " driver_name, group_name, name, address, attribute," - " precision, type, decimal, bias, description, value, format" - ") VALUES (%Q, %Q, %Q, %Q, %i, %i, %i, %lf, %lf, %Q, %Q, %Q)", + " precision, type, decimal, bias, description, value, format, unit" + ") VALUES (%Q, %Q, %Q, %Q, %i, %i, %i, %lf, %lf, %Q, %Q, %Q, %Q)", driver_name, group_name, tag->name, tag->address, tag->attribute, tag->precision, tag->type, tag->decimal, tag->bias, tag->description, - "", format_buf); + "", format_buf, tag->unit); return rv; } @@ -706,6 +706,12 @@ static int put_tags(sqlite3 *db, const char *query, sqlite3_stmt *stmt, return -1; } + if (SQLITE_OK != sqlite3_bind_text(stmt, 13, tag->unit, -1, NULL)) { + nlog_error("bind `%s` with unit=`%s` fail: %s", query, tag->unit, + sqlite3_errmsg(db)); + return -1; + } + if (SQLITE_DONE != sqlite3_step(stmt)) { nlog_error("sqlite3_step fail: %s", sqlite3_errmsg(db)); return -1; @@ -726,8 +732,8 @@ int neu_sqlite_persister_store_tags(neu_persister_t * self, const char * query = "INSERT INTO tags (" " driver_name, group_name, name, address, attribute," - " precision, type, decimal, bias, description, value, format" - ") VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12)"; + " precision, type, decimal, bias, description, value, format, unit" + ") VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13)"; if (SQLITE_OK != sqlite3_exec(persister->db, "BEGIN", NULL, NULL, NULL)) { nlog_error("begin transaction fail: %s", sqlite3_errmsg(persister->db)); @@ -788,6 +794,7 @@ static int collect_tag_info(sqlite3_stmt *stmt, UT_array **tags) .decimal = sqlite3_column_double(stmt, 5), .bias = sqlite3_column_double(stmt, 6), .description = (char *) sqlite3_column_text(stmt, 7), + .unit = (char *) sqlite3_column_text(stmt, 10), }; tag.n_format = neu_format_from_str(format, tag.format); @@ -812,7 +819,7 @@ int neu_sqlite_persister_load_tags(neu_persister_t *self, sqlite3_stmt *stmt = NULL; const char * query = "SELECT name, address, attribute, precision, type, " - "decimal, bias, description, value, format " + "decimal, bias, description, value, format, unit " "FROM tags WHERE driver_name=? AND group_name=? " "ORDER BY rowid ASC"; @@ -856,14 +863,15 @@ int neu_sqlite_persister_update_tag(neu_persister_t * self, const char * group_name, const neu_datatag_t *tag) { - int rv = execute_sql(((neu_sqlite_persister_t *) self)->db, - "UPDATE tags SET" - " address=%Q, attribute=%i, precision=%i, type=%i," - " decimal=%lf, bias=%lf, description=%Q, value=%Q " - "WHERE driver_name=%Q AND group_name=%Q AND name=%Q", - tag->address, tag->attribute, tag->precision, - tag->type, tag->decimal, tag->bias, tag->description, - "", driver_name, group_name, tag->name); + int rv = + execute_sql(((neu_sqlite_persister_t *) self)->db, + "UPDATE tags SET" + " address=%Q, attribute=%i, precision=%i, type=%i," + " decimal=%lf, bias=%lf, description=%Q, value=%Q, unit=%Q " + "WHERE driver_name=%Q AND group_name=%Q AND name=%Q", + tag->address, tag->attribute, tag->precision, tag->type, + tag->decimal, tag->bias, tag->description, "", tag->unit, + driver_name, group_name, tag->name); return rv; } diff --git a/tests/ft/http_api/test_http_api.py b/tests/ft/http_api/test_http_api.py index f20131ac..1ca4ba41 100644 --- a/tests/ft/http_api/test_http_api.py +++ b/tests/ft/http_api/test_http_api.py @@ -156,7 +156,8 @@ driver_1 = { "decimal": 0.0, "bias": 1.0, "address": "1!400001", - "description": "" + "description": "", + "unit": "" }, { "type": 3, @@ -166,7 +167,8 @@ driver_1 = { "decimal": 0.0, "bias": 1.0, "address": "1!400002", - "description": "" + "description": "", + "unit": "" } ], }, @@ -204,7 +206,8 @@ driver_2 = { "decimal": 0.0, "bias": 1.0, "address": "1!400001", - "description": "" + "description": "", + "unit": "" }, { "type": 3, @@ -214,7 +217,8 @@ driver_2 = { "decimal": 0.0, "bias": 1.0, "address": "1!400002", - "description": "" + "description": "", + "unit": "" } ], } diff --git a/tests/ft/tag/test_tag.py b/tests/ft/tag/test_tag.py index 32b0acce..9c64979c 100644 --- a/tests/ft/tag/test_tag.py +++ b/tests/ft/tag/test_tag.py @@ -14,6 +14,8 @@ hold_int16_bit = [{"name": "hold_int16_type", "address": "1!400001", "attribute": NEU_TAG_ATTRIBUTE_RW, "type": NEU_TYPE_BIT}] hold_int16_description = [{"name": "hold_int16_description", "address": "1!400001", "attribute": NEU_TAG_ATTRIBUTE_RW, "type": NEU_TYPE_INT16, "description": "description info"}] +hold_int16_unit = [{"name": "hold_int16_unit", "address": "1!400001", + "attribute": NEU_TAG_ATTRIBUTE_RW, "type": NEU_TYPE_INT16, "unit": "unit_info"}] hold_int16_decimal = [{"name": "hold_int16_decimal", "address": "1!400001", "attribute": NEU_TAG_ATTRIBUTE_RW, "type": NEU_TYPE_INT16, "decimal": 0.1}] hold_int16_bias = [{"name": "hold_int16_bias", "address": "1!400001", @@ -1013,3 +1015,44 @@ class TestTag: old_name="some_tag", new_name="new_tag") assert 404 == response.status_code assert NEU_ERR_NODE_NOT_EXIST == response.json()['error'] + + @description(given="a node and group", when="add a tag with unit", then="add success") + def test_add_tag_with_unit(self): + response = api.add_tags(node="modbus-tcp", group="group1", tags=hold_int16_unit) + assert 200 == response.status_code + assert NEU_ERR_SUCCESS == response.json()['error'] + + response = api.get_tags(node="modbus-tcp", group="group1") + assert 200 == response.status_code + assert "hold_int16_unit" == response.json()["tags"][0]["name"] + assert "1!400001" == response.json()["tags"][0]["address"] + assert NEU_TAG_ATTRIBUTE_RW == response.json()["tags"][0]["attribute"] + assert NEU_TYPE_INT16 == response.json()["tags"][0]["type"] + assert "unit_info" == response.json()["tags"][0]["unit"] + + response = api.del_tags(node="modbus-tcp", group="group1", tags=["hold_int16_unit"]) + assert 200 == response.status_code + assert NEU_ERR_SUCCESS == response.json()['error'] + + @description(given="a node and group", when="add a tag and update with unit", then="update success") + def test_update_tag_with_unit(self): + api.add_tags_check(node="modbus-tcp", group="group1", tags=hold_int16) + + tag = { + **hold_int16[0], + "unit": "unit_info", + } + try: + response = api.update_tags(node="modbus-tcp", group="group1", tags=[tag]) + assert 200 == response.status_code + assert NEU_ERR_SUCCESS == response.json()['error'] + + response = api.get_tags(node="modbus-tcp", group="group1") + assert 200 == response.status_code + assert tag["name"] == response.json()["tags"][0]["name"] + assert tag["address"] == response.json()["tags"][0]["address"] + assert tag["attribute"] == response.json()["tags"][0]["attribute"] + assert tag["type"] == response.json()["tags"][0]["type"] + assert tag["unit"] == response.json()["tags"][0]["unit"] + finally: + api.del_tags(node="modbus-tcp", group="group1", tags=[tag["name"]])