diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index dc616f122..be3dc447e 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -9,7 +9,7 @@ on: jobs: clang_format_check: - runs-on: ubuntu-20.04 + runs-on: ubuntu-24.04 steps: # checkout diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5f8e4f7d3..76a21dad7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,7 +14,7 @@ env: jobs: cross-build: - runs-on: ubuntu-20.04 + runs-on: ubuntu-24.04 strategy: matrix: arch: @@ -52,14 +52,14 @@ jobs: run: | ./package-sdk.sh -p ${{ matrix.arch[2] }} -n neuron-sdk-$VERSION - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 with: - name: neuron-sdk + name: neuron-sdk-${{ matrix.arch[2] }} path: | neuron-sdk-*.tar.gz build-debug-sdk: - runs-on: ubuntu-20.04 + runs-on: ubuntu-24.04 strategy: matrix: arch: @@ -83,7 +83,7 @@ jobs: run: | ./package-sdk.sh -p ${{ matrix.arch[2] }} -n neuron-sdk-debug-$VERSION - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 with: name: neuron-sdk path: | @@ -102,7 +102,7 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - uses: actions/download-artifact@v3 + - uses: actions/download-artifact@v4 with: name: neuron-sdk diff --git a/include/neuron/tag.h b/include/neuron/tag.h index 9fb5d71a0..f26591893 100644 --- a/include/neuron/tag.h +++ b/include/neuron/tag.h @@ -44,6 +44,10 @@ typedef enum { NEU_DATATAG_ENDIAN_BL32 = 5, // #BL 2,1,4,3 NEU_DATATAG_ENDIAN_L64 = 6, // #L 8,7,6,5,4,3,2,1 NEU_DATATAG_ENDIAN_B64 = 7, // #B 1,2,3,4,5,6,7,8 + NEU_DATATAG_ENDIAN_LL64 = 8, + NEU_DATATAG_ENDIAN_LB64 = 9, + NEU_DATATAG_ENDIAN_BB64 = 10, + NEU_DATATAG_ENDIAN_BL64 = 11, } neu_datatag_endian_e; typedef enum { @@ -63,6 +67,7 @@ typedef union { } value32; struct { neu_datatag_endian_e endian; + bool is_default; } value64; struct { uint16_t length; diff --git a/include/neuron/type.h b/include/neuron/type.h index c38c4fcfb..2a7b69b1e 100644 --- a/include/neuron/type.h +++ b/include/neuron/type.h @@ -738,6 +738,42 @@ static inline uint64_t neu_htonll(uint64_t u) return ret; } +static inline uint64_t neu_htonlb(uint64_t u) +{ + uint8_t *in = (uint8_t *) &u; + uint64_t out = 0; + uint8_t *out_bytes = (uint8_t *) &out; + + out_bytes[0] = in[1]; + out_bytes[1] = in[0]; + out_bytes[2] = in[3]; + out_bytes[3] = in[2]; + out_bytes[4] = in[5]; + out_bytes[5] = in[4]; + out_bytes[6] = in[7]; + out_bytes[7] = in[6]; + + return out; +} + +static inline uint64_t neu_htonbl(uint64_t u) +{ + uint8_t *in = (uint8_t *) &u; + uint64_t out = 0; + uint8_t *out_bytes = (uint8_t *) &out; + + out_bytes[0] = in[6]; + out_bytes[1] = in[7]; + out_bytes[2] = in[4]; + out_bytes[3] = in[5]; + out_bytes[4] = in[2]; + out_bytes[5] = in[3]; + out_bytes[6] = in[0]; + out_bytes[7] = in[1]; + + 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 413054c1b..bf464a1bf 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|)$" + "regex": "^[0-9]+![3-4][0-9]+(#B|#L|#BB|#BL|#LL|#LB|)$" }, { "type": 8, - "regex": "^[0-9]+![3-4][0-9]+(#B|#L|)$" + "regex": "^[0-9]+![3-4][0-9]+(#B|#L|#BB|#BL|#LL|#LB|)$" }, { "type": 9, @@ -30,7 +30,7 @@ }, { "type": 10, - "regex": "^[0-9]+![3-4][0-9]+(#B|#L|)$" + "regex": "^[0-9]+![3-4][0-9]+(#B|#L|#BB|#BL|#LL|#LB|)$" }, { "type": 11, @@ -106,6 +106,35 @@ "max": 10000 } }, + "endianess_64": { + "name": "Endianess of 8-Byte Data", + "name_zh": "8 字节数据字节序", + "description": "Tag byte order, applicable to int64, uint64 and double types", + "description_zh": "点位字节序,适用于 int64,uint64 和 double 类型", + "attribute": "required", + "type": "map", + "default": 1, + "valid": { + "map": [ + { + "key": "12 34 56 78", + "value": 1 + }, + { + "key": "21 43 65 87", + "value": 2 + }, + { + "key": "87 65 43 21", + "value": 3 + }, + { + "key": "78 56 34 12", + "value": 4 + } + ] + } + }, "interval": { "name": "Send Interval", "name_zh": "指令发送间隔", diff --git a/plugins/modbus/modbus-tcp.json b/plugins/modbus/modbus-tcp.json index 1085044ee..5ea61cd4f 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|)$" + "regex": "^[0-9]+![3-4][0-9]+(#B|#L|#BB|#BL|#LL|#LB|)$" }, { "type": 8, - "regex": "^[0-9]+![3-4][0-9]+(#B|#L|)$" + "regex": "^[0-9]+![3-4][0-9]+(#B|#L|#BB|#BL|#LL|#LB|)$" }, { "type": 9, @@ -30,7 +30,7 @@ }, { "type": 10, - "regex": "^[0-9]+![3-4][0-9]+(#B|#L|)$" + "regex": "^[0-9]+![3-4][0-9]+(#B|#L|#BB|#BL|#LL|#LB|)$" }, { "type": 11, @@ -112,6 +112,35 @@ "max": 10000 } }, + "endianess_64": { + "name": "Endianess of 8-Byte Data", + "name_zh": "8 字节数据字节序", + "description": "Tag byte order, applicable to int64, uint64 and double types", + "description_zh": "点位字节序,适用于 int64,uint64 和 double 类型", + "attribute": "required", + "type": "map", + "default": 1, + "valid": { + "map": [ + { + "key": "12 34 56 78", + "value": 1 + }, + { + "key": "21 43 65 87", + "value": 2 + }, + { + "key": "87 65 43 21", + "value": 3 + }, + { + "key": "78 56 34 12", + "value": 4 + } + ] + } + }, "interval": { "name": "Send Interval", "name_zh": "指令发送间隔", diff --git a/plugins/modbus/modbus.h b/plugins/modbus/modbus.h index a512ee220..dc7c957af 100644 --- a/plugins/modbus/modbus.h +++ b/plugins/modbus/modbus.h @@ -66,6 +66,13 @@ typedef enum modbus_area { MODBUS_AREA_HOLD_REGISTER = 4, } modbus_area_e; +typedef enum modbus_endianess_64 { + MODBUS_LL = 1, // 12 34 56 78 + MODBUS_LB = 2, // 21 43 65 87 + MODBUS_BB = 3, // 87 65 43 21 + MODBUS_BL = 4, // 78 56 34 12 +} modbus_endianess_64; + struct modbus_header { uint16_t seq; uint16_t protocol; diff --git a/plugins/modbus/modbus_point.c b/plugins/modbus/modbus_point.c index 1556a15d3..0af14232b 100644 --- a/plugins/modbus/modbus_point.c +++ b/plugins/modbus/modbus_point.c @@ -230,7 +230,8 @@ modbus_read_cmd_sort_t *modbus_tag_sort(UT_array *tags, uint16_t max_byte) return sort_result; } -int cal_n_byte(int type, neu_value_u *value, neu_datatag_addr_option_u option) +int cal_n_byte(int type, neu_value_u *value, neu_datatag_addr_option_u option, + modbus_endianess_64 endianess_64, bool default_tag_endian_64) { int n = 0; switch (type) { @@ -249,6 +250,9 @@ int cal_n_byte(int type, neu_value_u *value, neu_datatag_addr_option_u option) case NEU_TYPE_DOUBLE: case NEU_TYPE_INT64: case NEU_TYPE_UINT64: + if (default_tag_endian_64) { + modbus_convert_endianess_64(value, endianess_64); + } n = sizeof(uint64_t); value->u64 = neu_htonll(value->u64); break; @@ -282,7 +286,8 @@ int cal_n_byte(int type, neu_value_u *value, neu_datatag_addr_option_u option) return n; } -modbus_write_cmd_sort_t *modbus_write_tags_sort(UT_array *tags) +modbus_write_cmd_sort_t * +modbus_write_tags_sort(UT_array *tags, modbus_endianess_64 endianess_64) { neu_tag_sort_result_t *result = neu_tag_sort(tags, tag_sort_write, tag_cmp_write); @@ -304,14 +309,17 @@ modbus_write_cmd_sort_t *modbus_write_tags_sort(UT_array *tags) utarray_foreach(result->sorts[i].tags, modbus_point_write_t **, tag_s) { if ((*tag_s)->point.area == MODBUS_AREA_COIL) { - n_byte_tag = cal_n_byte((*tag_s)->point.type, &(*tag_s)->value, - (*tag_s)->point.option); + n_byte_tag = + cal_n_byte((*tag_s)->point.type, &(*tag_s)->value, + (*tag_s)->point.option, endianess_64, true); data_bit[k / 8] += ((*tag_s)->value.i8) << k % 8; n_byte += n_byte_tag; k++; } else { - n_byte_tag = cal_n_byte((*tag_s)->point.type, &(*tag_s)->value, - (*tag_s)->point.option); + n_byte_tag = + cal_n_byte((*tag_s)->point.type, &(*tag_s)->value, + (*tag_s)->point.option, endianess_64, + (*tag_s)->point.option.value64.is_default); memcpy(sort_result->cmd[i].bytes + 2 * ((*tag_s)->point.start_address - @@ -521,4 +529,24 @@ static bool tag_sort_write(neu_tag_sort_t *sort, void *tag, } return true; +} + +void modbus_convert_endianess_64(neu_value_u * value, + modbus_endianess_64 endianess_64) +{ + switch (endianess_64) { + case MODBUS_LL: + break; + case MODBUS_BB: + value->u64 = neu_htonll(value->u64); + break; + case MODBUS_LB: + value->u64 = neu_htonlb(value->u64); + break; + case MODBUS_BL: + value->u64 = neu_htonbl(value->u64); + break; + default: + break; + } } \ No newline at end of file diff --git a/plugins/modbus/modbus_point.h b/plugins/modbus/modbus_point.h index 0e48155df..5f0497b4e 100644 --- a/plugins/modbus/modbus_point.h +++ b/plugins/modbus/modbus_point.h @@ -79,9 +79,13 @@ typedef struct modbus_write_cmd_sort { modbus_write_cmd_t *cmd; } modbus_write_cmd_sort_t; -modbus_read_cmd_sort_t * modbus_tag_sort(UT_array *tags, uint16_t max_byte); -modbus_write_cmd_sort_t *modbus_write_tags_sort(UT_array *tags); -void modbus_tag_sort_free(modbus_read_cmd_sort_t *cs); +modbus_read_cmd_sort_t *modbus_tag_sort(UT_array *tags, uint16_t max_byte); +modbus_write_cmd_sort_t * + modbus_write_tags_sort(UT_array *tags, modbus_endianess_64 endianess_64); +void modbus_tag_sort_free(modbus_read_cmd_sort_t *cs); + +void modbus_convert_endianess_64(neu_value_u * value, + modbus_endianess_64 endianess_64); #ifdef __cplusplus } diff --git a/plugins/modbus/modbus_req.c b/plugins/modbus/modbus_req.c index 55367a421..f6f0919b5 100644 --- a/plugins/modbus/modbus_req.c +++ b/plugins/modbus/modbus_req.c @@ -414,6 +414,10 @@ int modbus_value_handle(void *ctx, uint8_t slave_id, uint16_t n_byte, case NEU_TYPE_DOUBLE: case NEU_TYPE_INT64: case NEU_TYPE_UINT64: + if ((*p_tag)->option.value64.is_default) { + modbus_convert_endianess_64(&dvalue.value, + plugin->endianess_64); + } dvalue.value.u64 = neu_ntohll(dvalue.value.u64); break; case NEU_TYPE_BIT: { @@ -677,6 +681,9 @@ int modbus_write(neu_plugin_t *plugin, void *req, neu_datatag_t *tag, case NEU_TYPE_DOUBLE: case NEU_TYPE_INT64: case NEU_TYPE_UINT64: + if (tag->option.value64.is_default) { + modbus_convert_endianess_64(&value, plugin->endianess_64); + } value.u64 = neu_htonll(value.u64); n_byte = sizeof(uint64_t); break; @@ -744,7 +751,7 @@ int modbus_write_tags(neu_plugin_t *plugin, void *req, UT_array *tags) utarray_push_back(gtags->tags, &p); } - gtags->cmd_sort = modbus_write_tags_sort(gtags->tags); + gtags->cmd_sort = modbus_write_tags_sort(gtags->tags, plugin->endianess_64); for (uint16_t i = 0; i < gtags->cmd_sort->n_cmd; i++) { uint16_t response_size = 0; diff --git a/plugins/modbus/modbus_req.h b/plugins/modbus/modbus_req.h index 838f4a610..7e26a8f67 100644 --- a/plugins/modbus/modbus_req.h +++ b/plugins/modbus/modbus_req.h @@ -43,7 +43,9 @@ struct neu_plugin { uint16_t interval; uint16_t retry_interval; uint16_t max_retries; - uint16_t check_header; + + modbus_endianess_64 endianess_64; + uint16_t check_header; }; void modbus_conn_connected(void *data, int fd); diff --git a/plugins/modbus/modbus_rtu.c b/plugins/modbus/modbus_rtu.c index bd1eaeac0..184335db8 100644 --- a/plugins/modbus/modbus_rtu.c +++ b/plugins/modbus/modbus_rtu.c @@ -167,6 +167,9 @@ static int driver_config(neu_plugin_t *plugin, const char *config) neu_json_elem_t retry_interval = { .name = "retry_interval", .t = NEU_JSON_INT }; + neu_json_elem_t endianess_64 = { .name = "endianess_64", + .t = NEU_JSON_INT }; + ret = neu_parse_param((char *) config, &err_param, 3, &link, &timeout, &interval); if (ret != 0) { @@ -206,9 +209,16 @@ static int driver_config(neu_plugin_t *plugin, const char *config) retry_interval.v.val_int = 0; } + ret = neu_parse_param((char *) config, &err_param, 1, &endianess_64); + if (ret != 0) { + free(err_param); + endianess_64.v.val_int = MODBUS_LL; + } + param.log = plugin->common.log; plugin->max_retries = max_retries.v.val_int; plugin->retry_interval = retry_interval.v.val_int; + plugin->endianess_64 = endianess_64.v.val_int; if (link.v.val_int == 0) { param.type = NEU_CONN_TTY_CLIENT; diff --git a/plugins/modbus/modbus_tcp.c b/plugins/modbus/modbus_tcp.c index 7fa60f256..f432af75a 100644 --- a/plugins/modbus/modbus_tcp.c +++ b/plugins/modbus/modbus_tcp.c @@ -163,6 +163,8 @@ static int driver_config(neu_plugin_t *plugin, const char *config) .t = NEU_JSON_INT }; neu_json_elem_t check_header = { .name = "check_header", .t = NEU_JSON_INT }; + neu_json_elem_t endianess_64 = { .name = "endianess_64", + .t = NEU_JSON_INT }; ret = neu_parse_param((char *) config, &err_param, 5, &port, &host, &mode, &timeout, &interval); @@ -191,6 +193,12 @@ static int driver_config(neu_plugin_t *plugin, const char *config) retry_interval.v.val_int = 0; } + ret = neu_parse_param((char *) config, &err_param, 1, &endianess_64); + if (ret != 0) { + free(err_param); + endianess_64.v.val_int = MODBUS_LL; + } + ret = neu_parse_param((char *) config, &err_param, 1, &check_header); if (ret != 0) { free(err_param); @@ -201,6 +209,7 @@ static int driver_config(neu_plugin_t *plugin, const char *config) plugin->interval = interval.v.val_int; plugin->max_retries = max_retries.v.val_int; plugin->retry_interval = retry_interval.v.val_int; + plugin->endianess_64 = endianess_64.v.val_int; plugin->check_header = check_header.v.val_int; if (mode.v.val_int == 1) { diff --git a/src/adapter/driver/driver.c b/src/adapter/driver/driver.c index 1d1fd2fc3..271053918 100644 --- a/src/adapter/driver/driver.c +++ b/src/adapter/driver/driver.c @@ -756,9 +756,17 @@ static void fix_value(neu_datatag_t *tag, neu_type_e value_type, case NEU_TYPE_LWORD: switch (tag->option.value64.endian) { case NEU_DATATAG_ENDIAN_B64: + case NEU_DATATAG_ENDIAN_BB64: value->value.u64 = neu_ntohll(value->value.u64); break; + case NEU_DATATAG_ENDIAN_LB64: + value->value.u64 = neu_htonlb(value->value.u64); + break; + case NEU_DATATAG_ENDIAN_BL64: + value->value.u64 = neu_htonbl(value->value.u64); + break; case NEU_DATATAG_ENDIAN_L64: + case NEU_DATATAG_ENDIAN_LL64: default: break; } @@ -2178,9 +2186,17 @@ static void read_report_group(int64_t timestamp, int64_t timeout, case NEU_TYPE_UINT64: switch (tag->option.value64.endian) { case NEU_DATATAG_ENDIAN_B64: + case NEU_DATATAG_ENDIAN_BB64: value.value.value.u64 = neu_htonll(value.value.value.u64); break; + case NEU_DATATAG_ENDIAN_LB64: + value.value.value.u64 = neu_htonlb(value.value.value.u64); + break; + case NEU_DATATAG_ENDIAN_BL64: + value.value.value.u64 = neu_htonbl(value.value.value.u64); + break; case NEU_DATATAG_ENDIAN_L64: + case NEU_DATATAG_ENDIAN_LL64: default: break; } @@ -2343,9 +2359,17 @@ static void read_group(int64_t timestamp, int64_t timeout, case NEU_TYPE_UINT64: switch (tag->option.value64.endian) { case NEU_DATATAG_ENDIAN_B64: + case NEU_DATATAG_ENDIAN_BB64: value.value.value.u64 = neu_htonll(value.value.value.u64); break; + case NEU_DATATAG_ENDIAN_LB64: + value.value.value.u64 = neu_htonlb(value.value.value.u64); + break; + case NEU_DATATAG_ENDIAN_BL64: + value.value.value.u64 = neu_htonbl(value.value.value.u64); + break; case NEU_DATATAG_ENDIAN_L64: + case NEU_DATATAG_ENDIAN_LL64: default: break; } @@ -2522,9 +2546,17 @@ static void read_group_paginate(int64_t timestamp, int64_t timeout, case NEU_TYPE_UINT64: switch (tag->option.value64.endian) { case NEU_DATATAG_ENDIAN_B64: + case NEU_DATATAG_ENDIAN_BB64: value.value.value.u64 = neu_htonll(value.value.value.u64); break; + case NEU_DATATAG_ENDIAN_LB64: + value.value.value.u64 = neu_htonlb(value.value.value.u64); + break; + case NEU_DATATAG_ENDIAN_BL64: + value.value.value.u64 = neu_htonbl(value.value.value.u64); + break; case NEU_DATATAG_ENDIAN_L64: + case NEU_DATATAG_ENDIAN_LL64: default: break; } diff --git a/src/base/tag.c b/src/base/tag.c index 97662f14c..b1247f63b 100644 --- a/src/base/tag.c +++ b/src/base/tag.c @@ -255,21 +255,35 @@ int neu_datatag_parse_addr_option(const neu_datatag_t * datatag, case NEU_TYPE_UINT64: { char *op = find_last_character(datatag->address, '#'); - option->value64.endian = NEU_DATATAG_ENDIAN_L64; + option->value64.endian = NEU_DATATAG_ENDIAN_L64; + option->value64.is_default = true; if (op != NULL) { - char e = 0; - sscanf(op, "#%c", &e); + char e1 = 0; + char e2 = 0; + int n = sscanf(op, "#%c%c", &e1, &e2); - switch (e) { - case 'B': - option->value64.endian = NEU_DATATAG_ENDIAN_B64; - break; - case 'L': - option->value64.endian = NEU_DATATAG_ENDIAN_L64; - break; - default: - option->value64.endian = NEU_DATATAG_ENDIAN_L64; - break; + if (n == 2) { + if (e1 == 'B' && e2 == 'B') { + option->value64.endian = NEU_DATATAG_ENDIAN_BB64; + option->value64.is_default = false; + } else if (e1 == 'B' && e2 == 'L') { + option->value64.endian = NEU_DATATAG_ENDIAN_BL64; + option->value64.is_default = false; + } else if (e1 == 'L' && e2 == 'L') { + option->value64.endian = NEU_DATATAG_ENDIAN_LL64; + option->value64.is_default = false; + } else if (e1 == 'L' && e2 == 'B') { + option->value64.endian = NEU_DATATAG_ENDIAN_LB64; + option->value64.is_default = false; + } + } else if (n == 1) { + if (e1 == 'B') { + option->value64.endian = NEU_DATATAG_ENDIAN_B64; + option->value64.is_default = false; + } else if (e1 == 'L') { + option->value64.endian = NEU_DATATAG_ENDIAN_L64; + option->value64.is_default = false; + } } }