From 411cdc4f02c0c54facf23d88aea83aeabb00f202 Mon Sep 17 00:00:00 2001 From: hxy7yx <1595670487@qq.com> Date: Mon, 3 Jun 2024 16:03:26 +0800 Subject: [PATCH] feat(read):support pagination and filter(error) when reading tags --- include/neuron/json/neu_json_rw.h | 42 ++++ include/neuron/msg.h | 196 +++++++++++++++- plugins/restful/handle.c | 9 + plugins/restful/rest.c | 4 + plugins/restful/rw_handle.c | 166 +++++++++++++ plugins/restful/rw_handle.h | 3 + src/adapter/adapter.c | 30 +++ src/adapter/driver/driver.c | 298 +++++++++++++++++++++++- src/adapter/driver/driver_internal.h | 2 + src/base/msg_internal.h | 2 + src/core/manager.c | 5 + src/parser/neu_json_rw.c | 293 +++++++++++++++++++++++ tests/ft/http_api/test_read_paginate.py | 293 +++++++++++++++++++++++ tests/ft/neuron/api.py | 5 + 14 files changed, 1331 insertions(+), 17 deletions(-) create mode 100644 tests/ft/http_api/test_read_paginate.py diff --git a/include/neuron/json/neu_json_rw.h b/include/neuron/json/neu_json_rw.h index e45747fa..9c0490fd 100644 --- a/include/neuron/json/neu_json_rw.h +++ b/include/neuron/json/neu_json_rw.h @@ -62,6 +62,30 @@ typedef struct { int neu_json_encode_read_resp(void *json_object, void *param); int neu_json_encode_read_resp1(void *json_object, void *param); +int neu_json_encode_read_paginate_resp(void *json_object, void *param); + +typedef struct { + int64_t error; + char * name; + enum neu_json_type t; + union neu_json_value value; + uint8_t precision; + int n_meta; + neu_json_tag_meta_t *metas; + neu_datatag_t datatag; +} neu_json_read_paginate_resp_tag_t; + +typedef struct { + unsigned int current_page; + unsigned int page_size; + unsigned int total; +} neu_json_read_meta_resp_t; + +typedef struct { + neu_json_read_meta_resp_t meta; + int n_tag; + neu_json_read_paginate_resp_tag_t *tags; +} neu_json_read_paginate_resp_t; typedef struct { char * group; @@ -134,10 +158,28 @@ typedef struct { int neu_json_decode_read_req(char *buf, neu_json_read_req_t **result); void neu_json_decode_read_req_free(neu_json_read_req_t *req); +typedef struct { + char *group; + char *node; + char *name; + char *desc; + bool sync; + int current_page; + int page_size; + bool is_error; +} neu_json_read_paginate_req_t; + +int neu_json_decode_read_paginate_req(char * buf, + neu_json_read_paginate_req_t **result); +void neu_json_decode_read_paginate_req_free(neu_json_read_paginate_req_t *req); + int neu_json_encode_read_periodic_resp(void *json_object, void *param); void neu_json_metas_to_json(neu_tag_meta_t *metas, int n_meta, neu_json_read_resp_tag_t *json_tag); +void neu_json_metas_to_json_paginate( + neu_tag_meta_t *metas, int n_meta, + neu_json_read_paginate_resp_tag_t *json_tag); #ifdef __cplusplus } diff --git a/include/neuron/msg.h b/include/neuron/msg.h index 58ae0269..32102464 100644 --- a/include/neuron/msg.h +++ b/include/neuron/msg.h @@ -46,6 +46,8 @@ typedef enum neu_reqresp_type { NEU_REQ_READ_GROUP, NEU_RESP_READ_GROUP, + NEU_REQ_READ_GROUP_PAGINATE, + NEU_RESP_READ_GROUP_PAGINATE, NEU_REQ_WRITE_TAG, NEU_REQ_WRITE_TAGS, NEU_REQ_WRITE_GTAGS, @@ -123,11 +125,13 @@ typedef enum neu_reqresp_type { static const char *neu_reqresp_type_string_t[] = { [NEU_RESP_ERROR] = "NEU_RESP_ERROR", - [NEU_REQ_READ_GROUP] = "NEU_REQ_READ_GROUP", - [NEU_RESP_READ_GROUP] = "NEU_RESP_READ_GROUP", - [NEU_REQ_WRITE_TAG] = "NEU_REQ_WRITE_TAG", - [NEU_REQ_WRITE_TAGS] = "NEU_REQ_WRITE_TAGS", - [NEU_REQ_WRITE_GTAGS] = "NEU_REQ_WRITE_GTAGS", + [NEU_REQ_READ_GROUP] = "NEU_REQ_READ_GROUP", + [NEU_RESP_READ_GROUP] = "NEU_RESP_READ_GROUP", + [NEU_REQ_READ_GROUP_PAGINATE] = "NEU_REQ_READ_GROUP_PAGINATE", + [NEU_RESP_READ_GROUP_PAGINATE] = "NEU_RESP_READ_GROUP_PAGINATE", + [NEU_REQ_WRITE_TAG] = "NEU_REQ_WRITE_TAG", + [NEU_REQ_WRITE_TAGS] = "NEU_REQ_WRITE_TAGS", + [NEU_REQ_WRITE_GTAGS] = "NEU_REQ_WRITE_GTAGS", [NEU_REQ_SUBSCRIBE_GROUP] = "NEU_REQ_SUBSCRIBE_GROUP", [NEU_REQ_UNSUBSCRIBE_GROUP] = "NEU_REQ_UNSUBSCRIBE_GROUP", @@ -619,6 +623,26 @@ static inline void neu_req_read_group_fini(neu_req_read_group_t *req) free(req->desc); } +typedef struct neu_req_read_group_paginate { + char *driver; + char *group; + char *name; + char *desc; + bool sync; + int current_page; + int page_size; + bool is_error; +} neu_req_read_group_paginate_t; + +static inline void +neu_req_read_group_paginate_fini(neu_req_read_group_paginate_t *req) +{ + free(req->driver); + free(req->group); + free(req->name); + free(req->desc); +} + typedef struct neu_req_write_tag { char * driver; char * group; @@ -701,6 +725,30 @@ typedef struct { UT_array *tags; // neu_resp_tag_value_meta_t } neu_resp_read_group_t; +typedef struct neu_resp_tag_value_meta_paginate { + char tag[NEU_TAG_NAME_LEN]; + neu_dvalue_t value; + neu_tag_meta_t metas[NEU_TAG_META_SIZE]; + neu_datatag_t datatag; +} neu_resp_tag_value_meta_paginate_t; + +static inline UT_icd *neu_resp_tag_value_meta_paginate_icd() +{ + static UT_icd icd = { sizeof(neu_resp_tag_value_meta_paginate_t), NULL, + NULL, NULL }; + return &icd; +} + +typedef struct { + char *driver; + char *group; + int current_page; + int page_size; + bool is_error; + + UT_array *tags; // neu_resp_tag_value_meta_paginate_t +} neu_resp_read_group_paginate_t; + static inline void neu_resp_read_free(neu_resp_read_group_t *resp) { utarray_foreach(resp->tags, neu_resp_tag_value_meta_t *, tag_value) @@ -714,6 +762,20 @@ static inline void neu_resp_read_free(neu_resp_read_group_t *resp) utarray_free(resp->tags); } +static inline void +neu_resp_read_paginate_free(neu_resp_read_group_paginate_t *resp) +{ + utarray_foreach(resp->tags, neu_resp_tag_value_meta_t *, tag_value) + { + if (tag_value->value.type == NEU_TYPE_PTR) { + free(tag_value->value.value.ptr.ptr); + } + } + free(resp->driver); + free(resp->group); + utarray_free(resp->tags); +} + typedef struct { uint16_t index; pthread_mutex_t mtx; @@ -897,6 +959,130 @@ static inline void neu_tag_value_to_json(neu_resp_tag_value_meta_t *tag_value, } } +static inline void +neu_tag_value_to_json_paginate(neu_resp_tag_value_meta_paginate_t *tag_value, + neu_json_read_paginate_resp_tag_t * tag_json) +{ + tag_json->name = tag_value->tag; + tag_json->error = 0; + + tag_json->datatag.name = strdup(tag_value->datatag.name); + tag_json->datatag.address = strdup(tag_value->datatag.address); + tag_json->datatag.attribute = tag_value->datatag.attribute; + tag_json->datatag.type = tag_value->datatag.type; + tag_json->datatag.precision = tag_value->datatag.precision; + tag_json->datatag.decimal = tag_value->datatag.decimal; + 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; + memcpy(tag_json->datatag.meta, tag_value->datatag.meta, + NEU_TAG_META_LENGTH); + + for (int k = 0; k < NEU_TAG_META_SIZE; k++) { + if (strlen(tag_value->metas[k].name) > 0) { + tag_json->n_meta++; + } else { + break; + } + } + if (tag_json->n_meta > 0) { + tag_json->metas = (neu_json_tag_meta_t *) calloc( + tag_json->n_meta, sizeof(neu_json_tag_meta_t)); + } + neu_json_metas_to_json_paginate(tag_value->metas, NEU_TAG_META_SIZE, + tag_json); + + switch (tag_value->value.type) { + case NEU_TYPE_ERROR: + tag_json->t = NEU_JSON_INT; + tag_json->value.val_int = tag_value->value.value.i32; + tag_json->error = tag_value->value.value.i32; + break; + case NEU_TYPE_UINT8: + tag_json->t = NEU_JSON_INT; + tag_json->value.val_int = tag_value->value.value.u8; + break; + case NEU_TYPE_INT8: + tag_json->t = NEU_JSON_INT; + tag_json->value.val_int = tag_value->value.value.i8; + break; + case NEU_TYPE_INT16: + tag_json->t = NEU_JSON_INT; + tag_json->value.val_int = tag_value->value.value.i16; + break; + case NEU_TYPE_INT32: + tag_json->t = NEU_JSON_INT; + tag_json->value.val_int = tag_value->value.value.i32; + break; + case NEU_TYPE_INT64: + tag_json->t = NEU_JSON_INT; + tag_json->value.val_int = tag_value->value.value.i64; + break; + case NEU_TYPE_WORD: + case NEU_TYPE_UINT16: + tag_json->t = NEU_JSON_INT; + tag_json->value.val_int = tag_value->value.value.u16; + break; + case NEU_TYPE_DWORD: + case NEU_TYPE_UINT32: + tag_json->t = NEU_JSON_INT; + tag_json->value.val_int = tag_value->value.value.u32; + break; + case NEU_TYPE_LWORD: + case NEU_TYPE_UINT64: + tag_json->t = NEU_JSON_INT; + tag_json->value.val_int = tag_value->value.value.u64; + break; + case NEU_TYPE_FLOAT: + if (isnan(tag_value->value.value.f32)) { + tag_json->t = NEU_JSON_FLOAT; + tag_json->value.val_float = tag_value->value.value.f32; + tag_json->error = NEU_ERR_PLUGIN_TAG_VALUE_EXPIRED; + } else { + tag_json->t = NEU_JSON_FLOAT; + tag_json->value.val_float = tag_value->value.value.f32; + tag_json->precision = tag_value->value.precision; + } + break; + case NEU_TYPE_DOUBLE: + if (isnan(tag_value->value.value.d64)) { + tag_json->t = NEU_JSON_DOUBLE; + tag_json->value.val_double = tag_value->value.value.d64; + tag_json->error = NEU_ERR_PLUGIN_TAG_VALUE_EXPIRED; + } else { + tag_json->t = NEU_JSON_DOUBLE; + tag_json->value.val_double = tag_value->value.value.d64; + tag_json->precision = tag_value->value.precision; + } + break; + case NEU_TYPE_BOOL: + tag_json->t = NEU_JSON_BOOL; + tag_json->value.val_bool = tag_value->value.value.boolean; + break; + case NEU_TYPE_BIT: + tag_json->t = NEU_JSON_BIT; + tag_json->value.val_bit = tag_value->value.value.u8; + break; + case NEU_TYPE_STRING: + case NEU_TYPE_TIME: + case NEU_TYPE_DATA_AND_TIME: + tag_json->t = NEU_JSON_STR; + tag_json->value.val_str = tag_value->value.value.str; + break; + case NEU_TYPE_PTR: + tag_json->t = NEU_JSON_STR; + tag_json->value.val_str = (char *) tag_value->value.value.ptr.ptr; + break; + case NEU_TYPE_BYTES: + tag_json->t = NEU_JSON_BYTES; + tag_json->value.val_bytes.length = tag_value->value.value.bytes.length; + tag_json->value.val_bytes.bytes = tag_value->value.value.bytes.bytes; + break; + default: + break; + } +} + typedef struct { char node[NEU_NODE_NAME_LEN]; } neu_reqresp_node_deleted_t; diff --git a/plugins/restful/handle.c b/plugins/restful/handle.c index 090118b2..86c04a09 100644 --- a/plugins/restful/handle.c +++ b/plugins/restful/handle.c @@ -73,6 +73,9 @@ static struct neu_http_handler cors_handler[] = { { .url = "/api/v2/read", }, + { + .url = "/api/v2/read/paginate", + }, { .url = "/api/v2/write", }, @@ -263,6 +266,12 @@ static struct neu_http_handler rest_handlers[] = { .url = "/api/v2/read", .value.handler = handle_read, }, + { + .method = NEU_HTTP_METHOD_POST, + .type = NEU_HTTP_HANDLER_FUNCTION, + .url = "/api/v2/read/paginate", + .value.handler = handle_read_paginate, + }, { .method = NEU_HTTP_METHOD_POST, .type = NEU_HTTP_HANDLER_FUNCTION, diff --git a/plugins/restful/rest.c b/plugins/restful/rest.c index e4f264fe..4d62aaa0 100644 --- a/plugins/restful/rest.c +++ b/plugins/restful/rest.c @@ -224,6 +224,10 @@ static int dashb_plugin_request(neu_plugin_t * plugin, case NEU_RESP_READ_GROUP: handle_read_resp(header->ctx, (neu_resp_read_group_t *) data); break; + case NEU_RESP_READ_GROUP_PAGINATE: + handle_read_paginate_resp(header->ctx, + (neu_resp_read_group_paginate_t *) data); + break; case NEU_RESP_SCAN_TAGS: handle_scan_tags_resp(header->ctx, (neu_resp_scan_tags_t *) data); break; diff --git a/plugins/restful/rw_handle.c b/plugins/restful/rw_handle.c index 73b219da..0abe4994 100644 --- a/plugins/restful/rw_handle.c +++ b/plugins/restful/rw_handle.c @@ -80,6 +80,61 @@ void handle_read(nng_aio *aio) }) } +void handle_read_paginate(nng_aio *aio) +{ + neu_plugin_t *plugin = neu_rest_get_plugin(); + + NEU_PROCESS_HTTP_REQUEST_VALIDATE_JWT( + aio, neu_json_read_paginate_req_t, neu_json_decode_read_paginate_req, { + int ret = 0; + neu_reqresp_head_t header = { 0 }; + neu_req_read_group_paginate_t cmd = { 0 }; + int err_type; + header.ctx = aio; + header.type = NEU_REQ_READ_GROUP_PAGINATE; + + if (strlen(req->node) >= NEU_NODE_NAME_LEN) { + err_type = NEU_ERR_NODE_NAME_TOO_LONG; + goto error; + } + + if (strlen(req->node) >= NEU_GROUP_NAME_LEN) { + err_type = NEU_ERR_GROUP_NAME_TOO_LONG; + goto error; + } + + if (strlen(req->node) >= NEU_TAG_NAME_LEN) { + err_type = NEU_ERR_TAG_NAME_TOO_LONG; + goto error; + } + + cmd.driver = req->node; + cmd.group = req->group; + cmd.name = req->name; + cmd.desc = req->desc; + cmd.sync = req->sync; + cmd.current_page = req->current_page; + cmd.page_size = req->page_size; + cmd.is_error = req->is_error; + req->node = NULL; + req->group = NULL; + ret = neu_plugin_op(plugin, header, &cmd); + if (ret != 0) { + neu_req_read_group_paginate_fini(&cmd); + NEU_JSON_RESPONSE_ERROR(NEU_ERR_IS_BUSY, { + neu_http_response(aio, NEU_ERR_IS_BUSY, result_error); + }); + } + goto success; + + error: + NEU_JSON_RESPONSE_ERROR( + err_type, { neu_http_response(aio, err_type, result_error); }); + + success:; + }) +} + void handle_write(nng_aio *aio) { neu_plugin_t *plugin = neu_rest_get_plugin(); @@ -384,3 +439,114 @@ void handle_read_resp(nng_aio *aio, neu_resp_read_group_t *resp) free(api_res.tags); free(result); } + +void handle_read_paginate_resp(nng_aio * aio, + neu_resp_read_group_paginate_t *resp) +{ + neu_json_read_paginate_resp_t api_res = { 0 }; + char * result = NULL; + + if (resp->is_error) { + UT_array *filtered_tags; + utarray_new(filtered_tags, neu_resp_tag_value_meta_paginate_icd()); + + utarray_foreach(resp->tags, neu_resp_tag_value_meta_paginate_t *, + tag_value) + { + if (tag_value->value.type == NEU_TYPE_ERROR) { + neu_resp_tag_value_meta_paginate_t new_tag_value; + memcpy(&new_tag_value, tag_value, + sizeof(neu_resp_tag_value_meta_paginate_t)); + + new_tag_value.datatag.name = strdup(tag_value->datatag.name); + new_tag_value.datatag.address = + strdup(tag_value->datatag.address); + new_tag_value.datatag.description = + strdup(tag_value->datatag.description); + + utarray_push_back(filtered_tags, &new_tag_value); + } + } + + for (unsigned int i = 0; i < utarray_len(resp->tags); ++i) { + neu_resp_tag_value_meta_paginate_t *orig_tag_value; + orig_tag_value = + (neu_resp_tag_value_meta_paginate_t *) utarray_eltptr( + resp->tags, i); + if (orig_tag_value) { + free(orig_tag_value->datatag.name); + free(orig_tag_value->datatag.address); + free(orig_tag_value->datatag.description); + } + } + + utarray_free(resp->tags); + resp->tags = filtered_tags; + } + + unsigned int total_tags = utarray_len(resp->tags); + + api_res.meta.current_page = resp->current_page; + api_res.meta.page_size = resp->page_size; + api_res.meta.total = total_tags; + + if (resp->current_page > 0 && resp->page_size > 0) { + unsigned int start_index = (resp->current_page - 1) * resp->page_size; + unsigned int end_index = start_index + resp->page_size; + + if (start_index >= total_tags) { + api_res.n_tag = 0; + api_res.tags = calloc(1, sizeof(neu_json_read_paginate_resp_tag_t)); + } else { + if (end_index > total_tags) { + end_index = total_tags; + } + + api_res.n_tag = end_index - start_index; + api_res.tags = calloc(api_res.n_tag, + sizeof(neu_json_read_paginate_resp_tag_t)); + + for (unsigned int i = start_index; i < end_index; i++) { + neu_resp_tag_value_meta_paginate_t *tag_value = + (neu_resp_tag_value_meta_paginate_t *) utarray_eltptr( + resp->tags, i); + neu_tag_value_to_json_paginate(tag_value, + &api_res.tags[i - start_index]); + } + } + } else { + api_res.n_tag = total_tags; + api_res.tags = + calloc(api_res.n_tag, sizeof(neu_json_read_paginate_resp_tag_t)); + int index = 0; + + utarray_foreach(resp->tags, neu_resp_tag_value_meta_paginate_t *, + tag_value) + { + neu_tag_value_to_json_paginate(tag_value, &api_res.tags[index++]); + } + } + + neu_json_encode_by_fn(&api_res, neu_json_encode_read_paginate_resp, + &result); + + for (unsigned int i = 0; i < utarray_len(resp->tags); ++i) { + neu_resp_tag_value_meta_paginate_t *final_tag_value; + final_tag_value = (neu_resp_tag_value_meta_paginate_t *) utarray_eltptr( + resp->tags, i); + if (final_tag_value) { + free(final_tag_value->datatag.name); + free(final_tag_value->datatag.address); + free(final_tag_value->datatag.description); + } + } + + for (int i = 0; i < api_res.n_tag; i++) { + if (api_res.tags[i].n_meta > 0) { + free(api_res.tags[i].metas); + } + } + neu_http_ok(aio, result); + free(api_res.tags); + free(result); +} \ No newline at end of file diff --git a/plugins/restful/rw_handle.h b/plugins/restful/rw_handle.h index b0c3494d..d475f7e7 100644 --- a/plugins/restful/rw_handle.h +++ b/plugins/restful/rw_handle.h @@ -27,9 +27,12 @@ void handle_rw_init(); void handle_rw_uninit(); void handle_read(nng_aio *aio); +void handle_read_paginate(nng_aio *aio); void handle_write(nng_aio *aio); void handle_write_tags(nng_aio *aio); void handle_write_gtags(nng_aio *aio); void handle_read_resp(nng_aio *aio, neu_resp_read_group_t *resp); +void handle_read_paginate_resp(nng_aio * aio, + neu_resp_read_group_paginate_t *resp); #endif \ No newline at end of file diff --git a/src/adapter/adapter.c b/src/adapter/adapter.c index 2f5ffbaf..3b065880 100644 --- a/src/adapter/adapter.c +++ b/src/adapter/adapter.c @@ -444,6 +444,12 @@ static int adapter_command(neu_adapter_t *adapter, neu_reqresp_head_t header, strcpy(pheader->receiver, cmd->driver); break; } + case NEU_REQ_READ_GROUP_PAGINATE: { + neu_req_read_group_paginate_t *cmd = + (neu_req_read_group_paginate_t *) data; + strcpy(pheader->receiver, cmd->driver); + break; + } case NEU_REQ_WRITE_TAG: { neu_req_write_tag_t *cmd = (neu_req_write_tag_t *) data; strcpy(pheader->receiver, cmd->driver); @@ -732,6 +738,30 @@ static int adapter_loop(enum neu_event_io_type type, int fd, void *usr_data) break; } + case NEU_RESP_READ_GROUP_PAGINATE: + adapter->module->intf_funs->request( + adapter->plugin, (neu_reqresp_head_t *) header, &header[1]); + neu_resp_read_paginate_free( + (neu_resp_read_group_paginate_t *) &header[1]); + neu_msg_free(msg); + break; + case NEU_REQ_READ_GROUP_PAGINATE: { + neu_resp_error_t error = { 0 }; + + if (adapter->module->type == NEU_NA_TYPE_DRIVER) { + neu_adapter_driver_read_group_paginate( + (neu_adapter_driver_t *) adapter, header); + } else { + neu_req_read_group_paginate_fini( + (neu_req_read_group_paginate_t *) &header[1]); + error.error = NEU_ERR_GROUP_NOT_ALLOW; + header->type = NEU_RESP_ERROR; + neu_msg_exchange(header); + reply(adapter, header, &error); + } + + break; + } case NEU_REQ_WRITE_TAG: { neu_resp_error_t error = { 0 }; diff --git a/src/adapter/driver/driver.c b/src/adapter/driver/driver.c index 7ca36082..0c504430 100644 --- a/src/adapter/driver/driver.c +++ b/src/adapter/driver/driver.c @@ -92,6 +92,10 @@ static void read_group(int64_t timestamp, int64_t timeout, neu_tag_cache_type_e cache_type, neu_driver_cache_t *cache, const char *group, UT_array *tags, UT_array *tag_values); +static void read_group_paginate(int64_t timestamp, int64_t timeout, + neu_tag_cache_type_e cache_type, + neu_driver_cache_t *cache, const char *group, + UT_array *tags, UT_array *tag_values); static void read_report_group(int64_t timestamp, int64_t timeout, neu_tag_cache_type_e cache_type, neu_driver_cache_t *cache, const char *group, @@ -111,19 +115,19 @@ static inline void start_group_timer(neu_adapter_driver_t *driver, group_t * grp); static inline void stop_group_timer(neu_adapter_driver_t *driver, group_t *grp); -static void format_tag_value(neu_resp_tag_value_meta_t *value) +static void format_tag_value(neu_dvalue_t *value) { double scale = pow(10, 5); int negative = 1; - if (value->value.value.d64 < 0) { - value->value.value.d64 *= -1; + if (value->value.d64 < 0) { + value->value.d64 *= -1; negative = -1; } - int64_t integer_part = (int64_t) value->value.value.d64; - double decimal_part = value->value.value.d64 - integer_part; + int64_t integer_part = (int64_t) value->value.d64; + double decimal_part = value->value.d64 - integer_part; decimal_part *= scale; decimal_part = round(decimal_part); char str[6] = { 0 }; @@ -139,14 +143,13 @@ static void format_tag_value(neu_resp_tag_value_meta_t *value) } } if (flag != 0 && i != 0) { - decimal_part = round(decimal_part / pow(10, 5 - i)); - value->value.value.d64 = - (double) integer_part + decimal_part / pow(10, i); + decimal_part = round(decimal_part / pow(10, 5 - i)); + value->value.d64 = (double) integer_part + decimal_part / pow(10, i); } else { - value->value.value.d64 = (double) integer_part + decimal_part / scale; + value->value.d64 = (double) integer_part + decimal_part / scale; } - value->value.value.d64 *= negative; + value->value.d64 *= negative; } static void write_response(neu_adapter_t *adapter, void *r, neu_error error) @@ -554,6 +557,106 @@ void neu_adapter_driver_read_group(neu_adapter_driver_t *driver, driver->adapter.cb_funs.response(&driver->adapter, req, &resp); } +void neu_adapter_driver_read_group_paginate(neu_adapter_driver_t *driver, + neu_reqresp_head_t * req) +{ + neu_req_read_group_paginate_t *cmd = + (neu_req_read_group_paginate_t *) &req[1]; + group_t *g = find_group(driver, cmd->group); + if (g == NULL) { + neu_resp_error_t error = { .error = NEU_ERR_GROUP_NOT_EXIST }; + req->type = NEU_RESP_ERROR; + neu_req_read_group_paginate_fini(cmd); + driver->adapter.cb_funs.response(&driver->adapter, req, &error); + return; + } + + neu_resp_read_group_paginate_t resp = { 0 }; + neu_group_t * group = g->group; + UT_array *tags = neu_group_query_read_tag(group, cmd->name, cmd->desc); + + utarray_new(resp.tags, neu_resp_tag_value_meta_paginate_icd()); + + if (driver->adapter.state != NEU_NODE_RUNNING_STATE_RUNNING) { + utarray_foreach(tags, neu_datatag_t *, tag) + { + neu_resp_tag_value_meta_paginate_t tag_value = { 0 }; + strcpy(tag_value.tag, tag->name); + tag_value.value.type = NEU_TYPE_ERROR; + tag_value.value.value.i32 = NEU_ERR_PLUGIN_NOT_RUNNING; + + tag_value.datatag.name = strdup(tag->name); + tag_value.datatag.address = strdup(tag->address); + tag_value.datatag.attribute = tag->attribute; + tag_value.datatag.type = tag->type; + tag_value.datatag.precision = tag->precision; + tag_value.datatag.decimal = tag->decimal; + tag_value.datatag.bias = tag->bias; + tag_value.datatag.description = strdup(tag->description); + tag_value.datatag.option = tag->option; + memcpy(tag_value.datatag.meta, tag->meta, NEU_TAG_META_LENGTH); + + utarray_push_back(resp.tags, &tag_value); + } + } else if (cmd->sync) { + if (NULL == driver->adapter.module->intf_funs->driver.group_sync) { + // plugin does not support sync read + utarray_foreach(tags, neu_datatag_t *, tag) + { + neu_resp_tag_value_meta_paginate_t tag_value = { 0 }; + strcpy(tag_value.tag, tag->name); + tag_value.value.type = NEU_TYPE_ERROR; + tag_value.value.value.i32 = + NEU_ERR_PLUGIN_NOT_SUPPORT_READ_SYNC; + + tag_value.datatag.name = strdup(tag->name); + tag_value.datatag.address = strdup(tag->address); + tag_value.datatag.attribute = tag->attribute; + tag_value.datatag.type = tag->type; + tag_value.datatag.precision = tag->precision; + tag_value.datatag.decimal = tag->decimal; + tag_value.datatag.bias = tag->bias; + tag_value.datatag.description = strdup(tag->description); + tag_value.datatag.option = tag->option; + memcpy(tag_value.datatag.meta, tag->meta, NEU_TAG_META_LENGTH); + + utarray_push_back(resp.tags, &tag_value); + } + } else { + // sync read to update cache + driver->adapter.module->intf_funs->driver.group_sync( + driver->adapter.plugin, &g->grp); + // fetch updated data from cache + read_group_paginate( + global_timestamp, + neu_group_get_interval(group) * + NEU_DRIVER_TAG_CACHE_EXPIRE_TIME, + neu_adapter_get_tag_cache_type(&driver->adapter), driver->cache, + cmd->group, tags, resp.tags); + } + } else { + read_group_paginate(global_timestamp, + neu_group_get_interval(group) * + NEU_DRIVER_TAG_CACHE_EXPIRE_TIME, + neu_adapter_get_tag_cache_type(&driver->adapter), + driver->cache, cmd->group, tags, resp.tags); + } + + resp.driver = cmd->driver; + resp.group = cmd->group; + resp.current_page = cmd->current_page; + resp.page_size = cmd->page_size; + resp.is_error = cmd->is_error; + cmd->driver = NULL; // ownership moved + cmd->group = NULL; // ownership moved + + utarray_free(tags); + neu_req_read_group_paginate_fini(cmd); + + req->type = NEU_RESP_READ_GROUP_PAGINATE; + driver->adapter.cb_funs.response(&driver->adapter, req, &resp); +} + static void fix_value(neu_datatag_t *tag, neu_type_e value_type, neu_dvalue_t *value) { @@ -2129,7 +2232,7 @@ static void read_report_group(int64_t timestamp, int64_t timeout, } } if (tag->precision == 0 && tag->type == NEU_TYPE_DOUBLE) { - format_tag_value(&tag_value); + format_tag_value(&tag_value.value); } } @@ -2290,7 +2393,178 @@ static void read_group(int64_t timestamp, int64_t timeout, } if (tag->precision == 0 && tag->type == NEU_TYPE_DOUBLE) { - format_tag_value(&tag_value); + format_tag_value(&tag_value.value); + } + } + utarray_push_back(tag_values, &tag_value); + } +} + +static void read_group_paginate(int64_t timestamp, int64_t timeout, + neu_tag_cache_type_e cache_type, + neu_driver_cache_t *cache, const char *group, + UT_array *tags, UT_array *tag_values) +{ + utarray_foreach(tags, neu_datatag_t *, tag) + { + neu_resp_tag_value_meta_paginate_t tag_value = { 0 }; + neu_driver_cache_value_t value = { 0 }; + + strcpy(tag_value.tag, tag->name); + + tag_value.datatag.name = strdup(tag->name); + tag_value.datatag.address = strdup(tag->address); + tag_value.datatag.attribute = tag->attribute; + tag_value.datatag.type = tag->type; + tag_value.datatag.precision = tag->precision; + tag_value.datatag.decimal = tag->decimal; + tag_value.datatag.bias = tag->bias; + tag_value.datatag.description = strdup(tag->description); + tag_value.datatag.option = tag->option; + memcpy(tag_value.datatag.meta, tag->meta, NEU_TAG_META_LENGTH); + + if (neu_driver_cache_meta_get(cache, group, tag->name, &value, + tag_value.metas, + NEU_TAG_META_SIZE) != 0) { + tag_value.value.type = NEU_TYPE_ERROR; + tag_value.value.value.i32 = NEU_ERR_PLUGIN_TAG_NOT_READY; + + utarray_push_back(tag_values, &tag_value); + continue; + } + + if (value.value.type == NEU_TYPE_ERROR) { + tag_value.value = value.value; + utarray_push_back(tag_values, &tag_value); + continue; + } + + switch (tag->type) { + case NEU_TYPE_UINT16: + case NEU_TYPE_INT16: + switch (tag->option.value16.endian) { + case NEU_DATATAG_ENDIAN_B16: + value.value.value.u16 = htons(value.value.value.u16); + break; + case NEU_DATATAG_ENDIAN_L16: + default: + break; + } + break; + case NEU_TYPE_FLOAT: + case NEU_TYPE_UINT32: + case NEU_TYPE_INT32: + switch (tag->option.value32.endian) { + case NEU_DATATAG_ENDIAN_LB32: { + uint16_t *v1 = (uint16_t *) value.value.value.bytes.bytes; + uint16_t *v2 = (uint16_t *) (value.value.value.bytes.bytes + 2); + + neu_htons_p(v1); + neu_htons_p(v2); + break; + } + case NEU_DATATAG_ENDIAN_BB32: + value.value.value.u32 = htonl(value.value.value.u32); + break; + case NEU_DATATAG_ENDIAN_BL32: + value.value.value.u32 = htonl(value.value.value.u32); + uint16_t *v1 = (uint16_t *) value.value.value.bytes.bytes; + uint16_t *v2 = (uint16_t *) (value.value.value.bytes.bytes + 2); + + neu_htons_p(v1); + neu_htons_p(v2); + break; + case NEU_DATATAG_ENDIAN_LL32: + default: + break; + } + break; + case NEU_TYPE_DOUBLE: + case NEU_TYPE_INT64: + case NEU_TYPE_UINT64: + switch (tag->option.value64.endian) { + case NEU_DATATAG_ENDIAN_B64: + value.value.value.u64 = neu_htonll(value.value.value.u64); + break; + case NEU_DATATAG_ENDIAN_L64: + default: + break; + } + break; + default: + break; + } + + if (cache_type != NEU_TAG_CACHE_TYPE_NEVER && + !neu_tag_attribute_test(tag, NEU_ATTRIBUTE_STATIC) && + (timestamp - value.timestamp) > timeout) { + if (value.value.type == NEU_TYPE_PTR) { + free(value.value.value.ptr.ptr); + } + tag_value.value.type = NEU_TYPE_ERROR; + tag_value.value.value.i32 = NEU_ERR_PLUGIN_TAG_VALUE_EXPIRED; + } else { + if (value.value.type == NEU_TYPE_PTR) { + tag_value.value.type = NEU_TYPE_PTR; + tag_value.value.value.ptr.length = value.value.value.ptr.length; + tag_value.value.value.ptr.type = value.value.value.ptr.type; + tag_value.value.value.ptr.ptr = value.value.value.ptr.ptr; + } else { + tag_value.value = value.value; + } + if (tag->decimal != 0 || tag->bias != 0) { + tag_value.value.type = NEU_TYPE_DOUBLE; + double decimal = tag->decimal != 0 ? tag->decimal : 1; + double bias = tag->bias; + switch (tag->type) { + case NEU_TYPE_INT8: + tag_value.value.value.d64 = + (double) tag_value.value.value.i8 * decimal + bias; + break; + case NEU_TYPE_UINT8: + tag_value.value.value.d64 = + (double) tag_value.value.value.u8 * decimal + bias; + break; + case NEU_TYPE_INT16: + tag_value.value.value.d64 = + (double) tag_value.value.value.i16 * decimal + bias; + break; + case NEU_TYPE_UINT16: + tag_value.value.value.d64 = + (double) tag_value.value.value.u16 * decimal + bias; + break; + case NEU_TYPE_INT32: + tag_value.value.value.d64 = + (double) tag_value.value.value.i32 * decimal + bias; + break; + case NEU_TYPE_UINT32: + tag_value.value.value.d64 = + (double) tag_value.value.value.u32 * decimal + bias; + break; + case NEU_TYPE_INT64: + tag_value.value.value.d64 = + (double) tag_value.value.value.i64 * decimal + bias; + break; + case NEU_TYPE_UINT64: + tag_value.value.value.d64 = + (double) tag_value.value.value.u64 * decimal + bias; + break; + case NEU_TYPE_FLOAT: + tag_value.value.value.d64 = + (double) tag_value.value.value.f32 * decimal + bias; + break; + case NEU_TYPE_DOUBLE: + tag_value.value.value.d64 = + (double) tag_value.value.value.d64 * decimal + bias; + break; + default: + tag_value.value.type = tag->type; + break; + } + } + + if (tag->precision == 0 && tag->type == NEU_TYPE_DOUBLE) { + format_tag_value(&tag_value.value); } } utarray_push_back(tag_values, &tag_value); diff --git a/src/adapter/driver/driver_internal.h b/src/adapter/driver/driver_internal.h index eb566229..6e93c3c0 100644 --- a/src/adapter/driver/driver_internal.h +++ b/src/adapter/driver/driver_internal.h @@ -33,6 +33,8 @@ void neu_adapter_driver_stop_group_timer(neu_adapter_driver_t *driver); void neu_adapter_driver_read_group(neu_adapter_driver_t *driver, neu_reqresp_head_t * req); +void neu_adapter_driver_read_group_paginate(neu_adapter_driver_t *driver, + neu_reqresp_head_t * req); void neu_adapter_driver_write_tag(neu_adapter_driver_t *driver, neu_reqresp_head_t * req); diff --git a/src/base/msg_internal.h b/src/base/msg_internal.h index b6b34e5b..07137f76 100644 --- a/src/base/msg_internal.h +++ b/src/base/msg_internal.h @@ -34,6 +34,8 @@ extern "C" { XX(NEU_RESP_ERROR, neu_resp_error_t) \ XX(NEU_REQ_READ_GROUP, neu_req_read_group_t) \ XX(NEU_RESP_READ_GROUP, neu_resp_read_group_t) \ + XX(NEU_REQ_READ_GROUP_PAGINATE, neu_req_read_group_paginate_t) \ + XX(NEU_RESP_READ_GROUP_PAGINATE, neu_resp_read_group_paginate_t) \ XX(NEU_REQ_WRITE_TAG, neu_req_write_tag_t) \ XX(NEU_REQ_WRITE_TAGS, neu_req_write_tags_t) \ XX(NEU_REQ_WRITE_GTAGS, neu_req_write_gtags_t) \ diff --git a/src/core/manager.c b/src/core/manager.c index fd843520..41d9f30d 100644 --- a/src/core/manager.c +++ b/src/core/manager.c @@ -1007,6 +1007,7 @@ static int manager_loop(enum neu_event_io_type type, int fd, void *usr_data) case NEU_REQ_GET_GROUP: case NEU_REQ_GET_NODE_SETTING: case NEU_REQ_READ_GROUP: + case NEU_REQ_READ_GROUP_PAGINATE: case NEU_REQ_WRITE_TAG: case NEU_REQ_WRITE_TAGS: case NEU_REQ_WRITE_GTAGS: @@ -1017,6 +1018,9 @@ static int manager_loop(enum neu_event_io_type type, int fd, void *usr_data) NULL) { if (NEU_REQ_READ_GROUP == header->type) { neu_req_read_group_fini((neu_req_read_group_t *) &header[1]); + } else if (NEU_REQ_READ_GROUP_PAGINATE == header->type) { + neu_req_read_group_paginate_fini( + (neu_req_read_group_paginate_t *) &header[1]); } else if (NEU_REQ_WRITE_TAG == header->type) { neu_req_write_tag_fini((neu_req_write_tag_t *) &header[1]); } else if (NEU_REQ_WRITE_TAGS == header->type) { @@ -1245,6 +1249,7 @@ static int manager_loop(enum neu_event_io_type type, int fd, void *usr_data) case NEU_RESP_GET_NODE_SETTING: case NEU_RESP_ERROR: case NEU_RESP_READ_GROUP: + case NEU_RESP_READ_GROUP_PAGINATE: case NEU_RESP_PRGFILE_PROCESS: case NEU_RESP_SCAN_TAGS: forward_msg(manager, header, header->receiver); diff --git a/src/parser/neu_json_rw.c b/src/parser/neu_json_rw.c index d66da303..d7adff54 100644 --- a/src/parser/neu_json_rw.c +++ b/src/parser/neu_json_rw.c @@ -90,6 +90,107 @@ int neu_json_encode_read_resp(void *json_object, void *param) return ret; } +int neu_json_encode_read_paginate_resp(void *json_object, void *param) +{ + int ret = 0; + neu_json_read_paginate_resp_t *resp = + (neu_json_read_paginate_resp_t *) param; + + void * tag_array = neu_json_array(); + neu_json_read_paginate_resp_tag_t *p_tag = resp->tags; + + for (int i = 0; i < resp->n_tag; i++) { + neu_json_elem_t tag_elems[10 + NEU_TAG_META_SIZE] = { 0 }; + int if_precision = 0; + + tag_elems[0].name = "name"; + tag_elems[0].t = NEU_JSON_STR; + tag_elems[0].v.val_str = p_tag->name; + + tag_elems[1].name = "type"; + tag_elems[1].t = NEU_JSON_INT; + tag_elems[1].v.val_int = p_tag->datatag.type; + + tag_elems[2].name = "address"; + tag_elems[2].t = NEU_JSON_STR; + tag_elems[2].v.val_str = p_tag->datatag.address; + + tag_elems[3].name = "attribute"; + tag_elems[3].t = NEU_JSON_INT; + tag_elems[3].v.val_int = p_tag->datatag.attribute; + + tag_elems[4].name = "description"; + tag_elems[4].t = NEU_JSON_STR; + tag_elems[4].v.val_str = p_tag->datatag.description; + + tag_elems[5].name = "precision"; + tag_elems[5].t = NEU_JSON_INT; + tag_elems[5].v.val_int = p_tag->datatag.precision; + + tag_elems[6].name = "decimal"; + tag_elems[6].t = NEU_JSON_DOUBLE; + tag_elems[6].v.val_double = p_tag->datatag.decimal; + + tag_elems[7].name = "bias"; + tag_elems[7].t = NEU_JSON_DOUBLE; + tag_elems[7].v.val_double = p_tag->datatag.bias; + + 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; + } 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; + + if (p_tag->t == NEU_JSON_FLOAT || p_tag->t == NEU_JSON_DOUBLE) { + if_precision = 1; + tag_elems[9].name = "precision"; + tag_elems[9].t = NEU_JSON_INT; + tag_elems[9].v.val_int = + p_tag->precision > 0 ? p_tag->precision : 1; + } + } + + for (int k = 0; k < p_tag->n_meta; k++) { + tag_elems[if_precision + 9 + k].name = p_tag->metas[k].name; + tag_elems[if_precision + 9 + k].t = p_tag->metas[k].t; + tag_elems[if_precision + 9 + k].v = p_tag->metas[k].value; + } + + tag_array = neu_json_encode_array(tag_array, tag_elems, + 9 + if_precision + p_tag->n_meta); + + free(p_tag->datatag.name); + free(p_tag->datatag.address); + free(p_tag->datatag.description); + + p_tag++; + } + + void * meta_object = neu_json_encode_new(); + neu_json_elem_t meta_elems[] = { + { .name = "currentPage", + .t = NEU_JSON_INT, + .v.val_int = resp->meta.current_page }, + { .name = "pageSize", + .t = NEU_JSON_INT, + .v.val_int = resp->meta.page_size }, + { .name = "total", .t = NEU_JSON_INT, .v.val_int = resp->meta.total } + }; + neu_json_encode_field(meta_object, meta_elems, 3); + + neu_json_elem_t resp_elems[] = { + { .name = "meta", .t = NEU_JSON_OBJECT, .v.val_object = meta_object }, + { .name = "items", .t = NEU_JSON_OBJECT, .v.val_object = tag_array }, + }; + ret = neu_json_encode_field(json_object, resp_elems, 2); + + return ret; +} + int neu_json_encode_read_resp1(void *json_object, void *param) { int ret = 0; @@ -519,6 +620,123 @@ void neu_json_decode_read_req_free(neu_json_read_req_t *req) free(req); } +int neu_json_decode_read_paginate_req(char * buf, + neu_json_read_paginate_req_t **result) +{ + int ret = 0; + void *json_obj = NULL; + + json_obj = neu_json_decode_new(buf); + if (NULL == json_obj) { + return -1; + } + + neu_json_read_paginate_req_t *req = + calloc(1, sizeof(neu_json_read_paginate_req_t)); + if (req == NULL) { + neu_json_decode_free(json_obj); + return -1; + } + + neu_json_elem_t req_elems[] = { + { + .name = "node", + .t = NEU_JSON_STR, + }, + { + .name = "group", + .t = NEU_JSON_STR, + }, + { + .name = "sync", + .t = NEU_JSON_BOOL, + .attribute = NEU_JSON_ATTRIBUTE_OPTIONAL, + }, + { + .name = "query", + .t = NEU_JSON_OBJECT, + .attribute = NEU_JSON_ATTRIBUTE_OPTIONAL, + }, + }; + + neu_json_elem_t query_elems[] = { + { + .name = "name", + .t = NEU_JSON_STR, + .attribute = NEU_JSON_ATTRIBUTE_OPTIONAL, + }, + { + .name = "description", + .t = NEU_JSON_STR, + .attribute = NEU_JSON_ATTRIBUTE_OPTIONAL, + }, + { + .name = "currentPage", + .t = NEU_JSON_INT, + .attribute = NEU_JSON_ATTRIBUTE_OPTIONAL, + }, + { + .name = "pageSize", + .t = NEU_JSON_INT, + .attribute = NEU_JSON_ATTRIBUTE_OPTIONAL, + }, + { + .name = "isError", + .t = NEU_JSON_BOOL, + .attribute = NEU_JSON_ATTRIBUTE_OPTIONAL, + }, + }; + + ret = neu_json_decode_by_json(json_obj, NEU_JSON_ELEM_SIZE(req_elems), + req_elems); + + if (ret != 0) { + goto error; + } + + req->node = req_elems[0].v.val_str; + req->group = req_elems[1].v.val_str; + req->sync = req_elems[2].v.val_bool; + + if (req_elems[3].v.val_object) { + ret = neu_json_decode_by_json(req_elems[3].v.val_object, + NEU_JSON_ELEM_SIZE(query_elems), + query_elems); + + if (ret != 0) { + goto error; + } + req->name = query_elems[0].v.val_str; + req->desc = query_elems[1].v.val_str; + req->current_page = query_elems[2].v.val_int; + req->page_size = query_elems[3].v.val_int; + req->is_error = query_elems[4].v.val_bool; + } + + *result = req; + neu_json_decode_free(json_obj); + return ret; + +error: + free(query_elems[0].v.val_str); + free(query_elems[1].v.val_str); + free(req_elems[0].v.val_str); + free(req_elems[1].v.val_str); + free(req); + if (json_obj != NULL) { + neu_json_decode_free(json_obj); + } + return ret; +} + +void neu_json_decode_read_paginate_req_free(neu_json_read_paginate_req_t *req) +{ + free(req->group); + free(req->node); + + free(req); +} + int neu_json_encode_read_periodic_resp(void *json_object, void *param) { int ret = 0; @@ -619,6 +837,81 @@ void neu_json_metas_to_json(neu_tag_meta_t *metas, int n_meta, } } +void neu_json_metas_to_json_paginate( + neu_tag_meta_t *metas, int n_meta, + neu_json_read_paginate_resp_tag_t *json_tag) +{ + for (int k = 0; k < n_meta; k++) { + if (strlen(metas[k].name) > 0) { + json_tag->metas[k].name = metas[k].name; + switch (metas[k].value.type) { + case NEU_TYPE_UINT8: + json_tag->metas[k].t = NEU_JSON_INT; + json_tag->metas[k].value.val_int = metas[k].value.value.u8; + break; + case NEU_TYPE_INT8: + json_tag->metas[k].t = NEU_JSON_INT; + json_tag->metas[k].value.val_int = metas[k].value.value.i8; + break; + case NEU_TYPE_INT16: + json_tag->metas[k].t = NEU_JSON_INT; + json_tag->metas[k].value.val_int = metas[k].value.value.i16; + break; + case NEU_TYPE_INT32: + json_tag->metas[k].t = NEU_JSON_INT; + json_tag->metas[k].value.val_int = metas[k].value.value.i32; + break; + case NEU_TYPE_INT64: + json_tag->metas[k].t = NEU_JSON_INT; + json_tag->metas[k].value.val_int = metas[k].value.value.i64; + break; + case NEU_TYPE_WORD: + case NEU_TYPE_UINT16: + json_tag->metas[k].t = NEU_JSON_INT; + json_tag->metas[k].value.val_int = metas[k].value.value.u16; + break; + case NEU_TYPE_DWORD: + case NEU_TYPE_UINT32: + json_tag->metas[k].t = NEU_JSON_INT; + json_tag->metas[k].value.val_int = metas[k].value.value.u32; + break; + case NEU_TYPE_LWORD: + case NEU_TYPE_UINT64: + json_tag->metas[k].t = NEU_JSON_INT; + json_tag->metas[k].value.val_int = metas[k].value.value.u64; + break; + case NEU_TYPE_FLOAT: + json_tag->metas[k].t = NEU_JSON_FLOAT; + json_tag->metas[k].value.val_float = metas[k].value.value.f32; + break; + case NEU_TYPE_DOUBLE: + json_tag->metas[k].t = NEU_JSON_DOUBLE; + json_tag->metas[k].value.val_double = metas[k].value.value.d64; + break; + case NEU_TYPE_BOOL: + json_tag->metas[k].t = NEU_JSON_BOOL; + json_tag->metas[k].value.val_bool = + metas[k].value.value.boolean; + break; + case NEU_TYPE_BIT: + json_tag->metas[k].t = NEU_JSON_BIT; + json_tag->metas[k].value.val_bit = metas[k].value.value.u8; + break; + case NEU_TYPE_STRING: + case NEU_TYPE_TIME: + case NEU_TYPE_DATA_AND_TIME: + json_tag->metas[k].t = NEU_JSON_STR; + json_tag->metas[k].value.val_str = metas[k].value.value.str; + break; + default: + break; + } + } else { + break; + } + } +} + int neu_json_decode_write_gtags_req(char * buf, neu_json_write_gtags_req_t **result) { diff --git a/tests/ft/http_api/test_read_paginate.py b/tests/ft/http_api/test_read_paginate.py new file mode 100644 index 00000000..a299f11b --- /dev/null +++ b/tests/ft/http_api/test_read_paginate.py @@ -0,0 +1,293 @@ +import neuron.api as api +import neuron.error as error +import neuron.config as config +from neuron.common import * + +tcp_port = random_port() + + +@pytest.fixture(params=[('modbus-tcp', config.PLUGIN_MODBUS_TCP)], scope='class') +def param(request): + return request.param + + +@pytest.fixture(autouse=True, scope='class') +def simulator_setup_teardown(param): + p = process.start_simulator( + ['./modbus_simulator', 'tcp', f'{tcp_port}', 'ip_v4']) + + response = api.add_node(node=param[0], plugin=param[1]) + assert 200 == response.status_code + + response = api.add_group(node=param[0], group='group') + assert 200 == response.status_code + + response = api.modbus_tcp_node_setting( + node=param[0], interval=1, port=tcp_port) + + yield + process.stop_simulator(p) + + +hold_bit = [{"name": "hold_bit", "address": "1!400001.15", + "attribute": config.NEU_TAG_ATTRIBUTE_READ_SUBSCRIBE, "type": config.NEU_TYPE_BIT, "description": "123"}] +hold_int16 = [{"name": "hold_int16", "address": "1!400001", + "attribute": config.NEU_TAG_ATTRIBUTE_RW_SUBSCRIBE, "type": config.NEU_TYPE_INT16, "description": "123"}] +hold_uint16 = [{"name": "hold_uint16", "address": "1!400002", + "attribute": config.NEU_TAG_ATTRIBUTE_RW_SUBSCRIBE, "type": config.NEU_TYPE_UINT16, "description": "456"}] +hold_int32 = [{"name": "hold_int32", "address": "1!400003", + "attribute": config.NEU_TAG_ATTRIBUTE_RW_SUBSCRIBE, "type": config.NEU_TYPE_INT32, "description": "456"}] +hold_uint32 = [{"name": "hold_uint32", "address": "1!400015", + "attribute": config.NEU_TAG_ATTRIBUTE_RW_SUBSCRIBE, "type": config.NEU_TYPE_UINT32}] +hold_float = [{"name": "hold_float", "address": "1!400017", + "attribute": config.NEU_TAG_ATTRIBUTE_RW_SUBSCRIBE, "type": config.NEU_TYPE_FLOAT}] +hold_string = [{"name": "hold_string", "address": "1!400020.10", + "attribute": config.NEU_TAG_ATTRIBUTE_RW_SUBSCRIBE, "type": config.NEU_TYPE_STRING}] +hold_bytes = [{"name": "hold_bytes", "address": "1!400040.10", + "attribute": config.NEU_TAG_ATTRIBUTE_RW_SUBSCRIBE, "type": config.NEU_TYPE_BYTES}] + + +class TestReadPaginate: + + @description(given="created modbus node", when="add multiple tags", then="add success") + def test_add_tags(self, param): + api.add_tags_check(node=param[0], group='group', tags=hold_bit) + api.add_tags_check(node=param[0], group='group', tags=hold_int16) + api.add_tags_check(node=param[0], group='group', tags=hold_uint16) + api.add_tags_check(node=param[0], group='group', tags=hold_int32) + api.add_tags_check(node=param[0], group='group', tags=hold_uint32) + api.add_tags_check(node=param[0], group='group', tags=hold_float) + api.add_tags_check(node=param[0], group='group', tags=hold_string) + api.add_tags_check(node=param[0], group='group', tags=hold_bytes) + + + @description(given="created modbus node and tags", when="write/read tags without filter", then="write/read success") + def test_write_read_tags(self, param): + api.write_tag_check( + node=param[0], group='group', tag=hold_int16[0]['name'], value=-123) + api.write_tag_check( + node=param[0], group='group', tag=hold_uint16[0]['name'], value=123) + api.write_tag_check( + node=param[0], group='group', tag=hold_int32[0]['name'], value=-1234) + api.write_tag_check( + node=param[0], group='group', tag=hold_uint32[0]['name'], value=1234) + api.write_tag_check( + node=param[0], group='group', tag=hold_float[0]['name'], value=13.4121) + api.write_tag_check( + node=param[0], group='group', tag=hold_string[0]['name'], value='hello') + api.write_tag_check( + node=param[0], group='group', tag=hold_bytes[0]['name'], value=[0x1, 0x2, 0x3, 0x4]) + + time.sleep(0.3) + response = api.read_tags_paginate(node=param[0], group='group') + + assert 0 == response.json()["meta"]["currentPage"] + assert 0 == response.json()["meta"]["pageSize"] + assert 8 == response.json()["meta"]["total"] + + assert 8 == len(response.json()["items"]) + + assert "hold_bit" == response.json()["items"][0]["name"] + assert 1 == response.json()["items"][0]["value"] + + assert "hold_int16" == response.json()["items"][1]["name"] + assert -123 == response.json()["items"][1]["value"] + + assert "hold_uint16" == response.json()["items"][2]["name"] + assert 123 == response.json()["items"][2]["value"] + + assert "hold_int32" == response.json()["items"][3]["name"] + assert -1234 == response.json()["items"][3]["value"] + + assert "hold_uint32" == response.json()["items"][4]["name"] + assert 1234 == response.json()["items"][4]["value"] + + assert "hold_float" == response.json()["items"][5]["name"] + assert compare_float(13.4121, response.json()["items"][5]["value"]) + + assert "hold_string" == response.json()["items"][6]["name"] + assert 'hello' == response.json()["items"][6]["value"] + + assert "hold_bytes" == response.json()["items"][7]["name"] + assert [0x1, 0x2, 0x3, 0x4, 0, 0, 0, 0, 0, 0] == response.json()["items"][7]["value"] + + @description(given="created modbus node and tags", when="read tags with filter(name)", then="read success") + def test_read_tags_filter_name(self, param): + response = api.read_tags_paginate(node=param[0], group='group', query= {"name": "int16"}) + + assert 0 == response.json()["meta"]["currentPage"] + assert 0 == response.json()["meta"]["pageSize"] + assert 2 == response.json()["meta"]["total"] + + assert 2 == len(response.json()["items"]) + + assert "hold_int16" == response.json()["items"][0]["name"] + assert -123 == response.json()["items"][0]["value"] + assert "hold_uint16" == response.json()["items"][1]["name"] + assert 123 == response.json()["items"][1]["value"] + + @description(given="created modbus node and tags", when="read tags with filter(desc)", then="read success") + def test_read_tags_filter_desc(self, param): + response = api.read_tags_paginate(node=param[0], group='group', query= {"description": "12"}) + + assert 0 == response.json()["meta"]["currentPage"] + assert 0 == response.json()["meta"]["pageSize"] + assert 2 == response.json()["meta"]["total"] + + assert 2 == len(response.json()["items"]) + + assert "hold_bit" == response.json()["items"][0]["name"] + assert 1 == response.json()["items"][0]["value"] + assert "hold_int16" == response.json()["items"][1]["name"] + assert -123 == response.json()["items"][1]["value"] + + @description(given="created modbus node and tags", when="read tags with paginate", then="read success") + def test_read_tags_paginate(self, param): + response = api.read_tags_paginate(node=param[0], group='group', query= {"currentPage": 1, "pageSize": 5}) + assert 1 == response.json()["meta"]["currentPage"] + assert 5 == response.json()["meta"]["pageSize"] + assert 8 == response.json()["meta"]["total"] + assert 5 == len(response.json()["items"]) + + response = api.read_tags_paginate(node=param[0], group='group', query= {"currentPage": 2, "pageSize": 5}) + assert 2 == response.json()["meta"]["currentPage"] + assert 5 == response.json()["meta"]["pageSize"] + assert 8 == response.json()["meta"]["total"] + assert 3 == len(response.json()["items"]) + + response = api.read_tags_paginate(node=param[0], group='group', query= {"currentPage": 3, "pageSize": 5}) + assert 3 == response.json()["meta"]["currentPage"] + assert 5 == response.json()["meta"]["pageSize"] + assert 8 == response.json()["meta"]["total"] + assert 0 == len(response.json()["items"]) + + response = api.read_tags_paginate(node=param[0], group='group', query= {"currentPage": 6, "pageSize": 5}) + assert 6 == response.json()["meta"]["currentPage"] + assert 5 == response.json()["meta"]["pageSize"] + assert 8 == response.json()["meta"]["total"] + assert 0 == len(response.json()["items"]) + + response = api.read_tags_paginate(node=param[0], group='group', query= {"currentPage": 0, "pageSize": 5}) + assert 0 == response.json()["meta"]["currentPage"] + assert 5 == response.json()["meta"]["pageSize"] + assert 8 == response.json()["meta"]["total"] + assert 8 == len(response.json()["items"]) + + @description(given="created modbus node and tags", when="read tags with filter(isError)", then="read success") + def test_read_tags_filter_isError(self, param): + response = api.read_tags_paginate(node=param[0], group='group', query= {"isError": True}) + + assert 0 == response.json()["meta"]["currentPage"] + assert 0 == response.json()["meta"]["pageSize"] + assert 0 == response.json()["meta"]["total"] + assert 0 == len(response.json()["items"]) + + api.node_ctl_check(node='modbus-tcp', ctl=config.NEU_CTL_STOP) + + response = api.read_tags_paginate(node=param[0], group='group', query= {"isError": True}) + + assert 0 == response.json()["meta"]["currentPage"] + assert 0 == response.json()["meta"]["pageSize"] + assert 8 == response.json()["meta"]["total"] + assert 8 == len(response.json()["items"]) + + api.node_ctl_check(node='modbus-tcp', ctl=config.NEU_CTL_START) + time.sleep(0.3) + + @description(given="created modbus node and tags", when="read tags with filter(name&desc)", then="read success") + def test_read_tags_filter_name_desc(self, param): + response = api.read_tags_paginate(node=param[0], group='group', query= {"name": "16", "description": "123"}) + + assert 0 == response.json()["meta"]["currentPage"] + assert 0 == response.json()["meta"]["pageSize"] + assert 1 == response.json()["meta"]["total"] + assert 1 == len(response.json()["items"]) + assert "hold_int16" == response.json()["items"][0]["name"] + assert -123 == response.json()["items"][0]["value"] + + @description(given="created modbus node and tags", when="read tags with filter(isError&name)", then="read success") + def test_read_tags_filter_isError_name(self, param): + api.node_ctl_check(node='modbus-tcp', ctl=config.NEU_CTL_STOP) + + response = api.read_tags_paginate(node=param[0], group='group', query= {"isError": True, "name": "16"}) + + assert 0 == response.json()["meta"]["currentPage"] + assert 0 == response.json()["meta"]["pageSize"] + assert 2 == response.json()["meta"]["total"] + assert 2 == len(response.json()["items"]) + assert "hold_int16" == response.json()["items"][0]["name"] + assert error.NEU_ERR_PLUGIN_NOT_RUNNING == response.json()["items"][0]["error"] + assert "hold_uint16" == response.json()["items"][1]["name"] + assert error.NEU_ERR_PLUGIN_NOT_RUNNING == response.json()["items"][1]["error"] + + api.node_ctl_check(node='modbus-tcp', ctl=config.NEU_CTL_START) + time.sleep(0.3) + + @description(given="created modbus node and tags", when="read tags with filter(isError&desc)", then="read success") + def test_read_tags_filter_isError_desc(self, param): + api.node_ctl_check(node='modbus-tcp', ctl=config.NEU_CTL_STOP) + + response = api.read_tags_paginate(node=param[0], group='group', query= {"isError": True, "description": "12"}) + + assert 0 == response.json()["meta"]["currentPage"] + assert 0 == response.json()["meta"]["pageSize"] + assert 2 == response.json()["meta"]["total"] + assert 2 == len(response.json()["items"]) + assert "hold_bit" == response.json()["items"][0]["name"] + assert error.NEU_ERR_PLUGIN_NOT_RUNNING == response.json()["items"][0]["error"] + assert "hold_int16" == response.json()["items"][1]["name"] + assert error.NEU_ERR_PLUGIN_NOT_RUNNING == response.json()["items"][1]["error"] + + api.node_ctl_check(node='modbus-tcp', ctl=config.NEU_CTL_START) + time.sleep(0.3) + + @description(given="created modbus node and tags", when="read tags with filter(isError&name&desc)", then="read success") + def test_read_tags_filter_isError_name_desc(self, param): + api.node_ctl_check(node='modbus-tcp', ctl=config.NEU_CTL_STOP) + + response = api.read_tags_paginate(node=param[0], group='group', query= {"isError": True, "name":"16", "description": "12"}) + + assert 0 == response.json()["meta"]["currentPage"] + assert 0 == response.json()["meta"]["pageSize"] + assert 1 == response.json()["meta"]["total"] + assert 1 == len(response.json()["items"]) + assert "hold_int16" == response.json()["items"][0]["name"] + assert error.NEU_ERR_PLUGIN_NOT_RUNNING == response.json()["items"][0]["error"] + + api.node_ctl_check(node='modbus-tcp', ctl=config.NEU_CTL_START) + time.sleep(0.3) + + @description(given="created modbus node and tags", when="read tags with paginate & filter(isError)", then="read success") + def test_read_tags_paginate_filter_isError(self, param): + api.node_ctl_check(node='modbus-tcp', ctl=config.NEU_CTL_STOP) + + response = api.read_tags_paginate(node=param[0], group='group', query= {"currentPage": 1, "pageSize": 5, "isError": True}) + assert 1 == response.json()["meta"]["currentPage"] + assert 5 == response.json()["meta"]["pageSize"] + assert 8 == response.json()["meta"]["total"] + assert 5 == len(response.json()["items"]) + assert "hold_bit" == response.json()["items"][0]["name"] + assert error.NEU_ERR_PLUGIN_NOT_RUNNING == response.json()["items"][0]["error"] + assert "hold_int16" == response.json()["items"][1]["name"] + assert error.NEU_ERR_PLUGIN_NOT_RUNNING == response.json()["items"][1]["error"] + assert "hold_uint16" == response.json()["items"][2]["name"] + assert error.NEU_ERR_PLUGIN_NOT_RUNNING == response.json()["items"][2]["error"] + assert "hold_int32" == response.json()["items"][3]["name"] + assert error.NEU_ERR_PLUGIN_NOT_RUNNING == response.json()["items"][3]["error"] + assert "hold_uint32" == response.json()["items"][4]["name"] + assert error.NEU_ERR_PLUGIN_NOT_RUNNING == response.json()["items"][4]["error"] + + response = api.read_tags_paginate(node=param[0], group='group', query= {"currentPage": 2, "pageSize": 5, "isError": True}) + assert 2 == response.json()["meta"]["currentPage"] + assert 5 == response.json()["meta"]["pageSize"] + assert 8 == response.json()["meta"]["total"] + assert 3 == len(response.json()["items"]) + assert "hold_float" == response.json()["items"][0]["name"] + assert error.NEU_ERR_PLUGIN_NOT_RUNNING == response.json()["items"][0]["error"] + assert "hold_string" == response.json()["items"][1]["name"] + assert error.NEU_ERR_PLUGIN_NOT_RUNNING == response.json()["items"][1]["error"] + assert "hold_bytes" == response.json()["items"][2]["name"] + assert error.NEU_ERR_PLUGIN_NOT_RUNNING == response.json()["items"][2]["error"] + + api.node_ctl_check(node='modbus-tcp', ctl=config.NEU_CTL_START) + time.sleep(0.3) \ No newline at end of file diff --git a/tests/ft/neuron/api.py b/tests/ft/neuron/api.py index bf38e193..137b2b20 100644 --- a/tests/ft/neuron/api.py +++ b/tests/ft/neuron/api.py @@ -157,6 +157,11 @@ def read_tags(node, group, sync=False, query=None): body["query"] = query return requests.post(url=config.BASE_URL + "/api/v2/read", headers={"Authorization": config.default_jwt}, json=body) +def read_tags_paginate(node, group, sync=False, query=None): + body = {"node": node, "group": group, "sync": sync} + if query: + body["query"] = query + return requests.post(url=config.BASE_URL + "/api/v2/read/paginate", headers={"Authorization": config.default_jwt}, json=body) def read_tag(node, group, tag, sync=False): response = read_tags(node, group, sync, query={"name": tag})