Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,5 @@ neuron-sdk
log.html
output.xml
report.html
*.cache
*.cache
.claude
22 changes: 21 additions & 1 deletion docs/api/cn/http.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,19 @@ Neuron 将为 IIoT 平台提供一系列 API 服务,用于查询基本信息

AUTO 必须用于可读点位;仅写 AUTO 点位返回 `2204`。识别后,单点及批量写入均按具体类型检查输入类型和数值范围,再交给驱动。例如 INT16 可接受整数 `123`,但拒绝 `40000` 和字符串 `"123"`。已排队写入若因点位或组 cache 重建失效,返回 `3011`,不会按另一类型重新解释。

配置查询、导出和持久化存储始终保留 `type: 0`,不会回写识别结果,也不新增 `resolved_type` 字段。读取和上报值使用已识别的具体类型,并应用该类型适用的 precision、缩放及地址选项。非数值类型不进行数值缩放,缩放后的输出类型也不会改变设备侧已识别类型。
配置查询、导出和持久化存储始终保留 `type: 0`,不会回写识别结果。读取和上报值使用已识别的具体类型,并应用该类型适用的 precision、缩放及地址选项。非数值类型不进行数值缩放,缩放后的输出类型也不会改变设备侧已识别类型。

### 点位运行时类型

`GET /api/v2/tags`、`POST /api/v2/read`、`POST /api/v2/read/paginate` 的每个点位对象顶层均返回整数 `resolved_type`:

- 显式类型点位:等于配置 `type`,即使尚无样本、只写或读取报错。
- AUTO 点位:未识别、cache 不存在或新配置尚待应用时为 `0`;识别后为设备侧实际类型。STRING/BYTES 的内部 PTR 样本返回 STRING/BYTES,不返回 PTR。
- 已识别后的通信错误、类型不匹配、值过期或停用节点不清除识别结果;cache/配置重建或进程重启后重新识别。
- `resolved_type` 不是缩放后的输出类型。例如 AUTO 识别为 INT16,`decimal: 0.1` 时可返回 `"resolved_type": 3, "value": 12.3`。前端写入校验还需结合 decimal、bias,并以后端结果为准。
- 已识别类型不表示当前值有效、节点正在运行或具有写权限,应分别检查 error、节点状态和 attribute。

原有 `type` 仍表示配置类型;普通 `/read` 不因此新增配置 `type` 字段。`resolved_type` 不放入 `attributes`,普通读取存在同名 metas 时由该权威字段覆盖。配置导出 `/global/config`、`/global/drivers`、SQLite 和 MQTT 不新增此字段。

### 波特率

Expand Down Expand Up @@ -1050,6 +1062,8 @@ HTTP 200 响应:

*GET* /api/v2/tags

每个点位返回 `resolved_type`,含义见“点位运行时类型”;`type` 保持配置值。

### 请求参数

**node** 必需
Expand Down Expand Up @@ -1078,6 +1092,7 @@ HTTP 200 响应:
"name": "tag1",
//tag type
"type": 4,
"resolved_type": 4,
//tag address
"address": "1!400001",
//tag attribute
Expand All @@ -1094,6 +1109,7 @@ HTTP 200 响应:
{
"name": "tag2",
"type": 14,
"resolved_type": 14,
"address": "1!00001",
"attribute": 3,
"description": "",
Expand All @@ -1103,6 +1119,7 @@ HTTP 200 响应:
{
"name": "tag3",
"type": 11,
"resolved_type": 11,
"address": "1!400009",
"attribute": 3,
"description": "",
Expand Down Expand Up @@ -1264,6 +1281,7 @@ HTTP 200 响应:
"tags": [
{
"name": "temperature",
"resolved_type": 10,
"value": 25.5,
"transferPrecision": 2,
"timestamp": 1700000000000,
Expand All @@ -1273,6 +1291,7 @@ HTTP 200 响应:
},
{
"name": "pressure",
"resolved_type": 3,
"error": 3002,
"quality": 0
}
Expand Down Expand Up @@ -1328,6 +1347,7 @@ HTTP 200 响应:
"items": [
{
"name": "temperature",
"resolved_type": 10,
"type": 10,
"address": "1!400001",
"attribute": 1,
Expand Down
22 changes: 21 additions & 1 deletion docs/api/english/http.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,19 @@ The first valid, successful sample fixes the concrete runtime type for that tag'

AUTO requires a readable tag; write-only AUTO is rejected with `2204`. After detection, single and batch writes are checked against the concrete type, including input type and numeric range, before the driver receives them. For example, an INT16 tag accepts integer `123`, but rejects `40000` and string `"123"`. Writes invalidated by a tag/group cache rebuild return `3011` rather than being reinterpreted with another type.

Configuration queries, exports and persistent storage continue to show `type: 0`; detection does not rewrite metadata or add a `resolved_type` field. Read/report values use the detected concrete type and its applicable precision, scaling and address options. Numeric scaling does not apply to nonnumeric types, and a scaled output value does not change the detected device type.
Configuration queries, exports and persistent storage continue to show `type: 0`; detection does not rewrite the configured type. Read/report values use the detected concrete type and its applicable precision, scaling and address options. Numeric scaling does not apply to nonnumeric types, and a scaled output value does not change the detected device type.

### Runtime tag type

Every tag object returned by `GET /api/v2/tags`, `POST /api/v2/read` and `POST /api/v2/read/paginate` includes a top-level integer `resolved_type`:

- For explicitly typed tags it equals configured `type`, including tags without samples, write-only tags and read errors.
- For AUTO it is `0` while unresolved, without a cache entry, or while configuration changes await application. Once identified it is the device-side semantic type. Internally PTR-backed STRING/BYTES samples report STRING/BYTES, not PTR.
- Communication errors, type mismatches, value expiration and stopping a node do not themselves clear an identified type. Cache/configuration rebuilding or process restart requires identification again.
- It is not the scaled output type. For example, AUTO resolved as INT16 with `decimal: 0.1` can return `"resolved_type": 3, "value": 12.3`. Frontend write validation must also consider decimal and bias; backend validation remains authoritative.
- An identified type does not imply a valid current value, a running node or write permission. Check error, node state and attribute separately.

Existing `type` remains the configured type. Ordinary `/read` does not gain a configured `type` field. `resolved_type` is separate from `attributes` and replaces any colliding ordinary-read metadata key. Configuration exports through `/global/config` and `/global/drivers`, SQLite and MQTT do not gain this field.

### Baud

Expand Down Expand Up @@ -1031,6 +1043,8 @@ To update both group name and interval:

*GET* /api/v2/tags

Each tag includes `resolved_type` as described in "Runtime tag type"; `type` remains the configured value.

### Request Params

**node** required
Expand Down Expand Up @@ -1059,6 +1073,7 @@ To update both group name and interval:
"name": "tag1",
//tag type
"type": 4,
"resolved_type": 4,
//tag address
"address": "1!400001",
//tag attribute
Expand All @@ -1075,6 +1090,7 @@ To update both group name and interval:
{
"name": "tag2",
"type": 14,
"resolved_type": 14,
"address": "1!00001",
"attribute": 3,
"description": "",
Expand All @@ -1084,6 +1100,7 @@ To update both group name and interval:
{
"name": "tag3",
"type": 11,
"resolved_type": 11,
"address": "1!400009",
"attribute": 3,
"description": "",
Expand Down Expand Up @@ -1245,6 +1262,7 @@ To update both group name and interval:
"tags": [
{
"name": "temperature",
"resolved_type": 10,
"value": 25.5,
"transferPrecision": 2,
"timestamp": 1700000000000,
Expand All @@ -1254,6 +1272,7 @@ To update both group name and interval:
},
{
"name": "pressure",
"resolved_type": 3,
"error": 3002,
"quality": 0
}
Expand Down Expand Up @@ -1309,6 +1328,7 @@ Request headers are the same as for reading tag values.
"items": [
{
"name": "temperature",
"resolved_type": 10,
"type": 10,
"address": "1!400001",
"attribute": 1,
Expand Down
29 changes: 28 additions & 1 deletion include/neuron/msg.h
Original file line number Diff line number Diff line change
Expand Up @@ -848,8 +848,33 @@ typedef struct neu_req_get_tag {
char name[NEU_TAG_NAME_LEN];
} neu_req_get_tag_t;

typedef struct {
neu_datatag_t tag;
neu_type_e resolved_type;
} neu_resp_tag_info_t;

static inline void neu_resp_tag_info_copy(void *dst, const void *src)
{
neu_resp_tag_info_t * to = (neu_resp_tag_info_t *) dst;
const neu_resp_tag_info_t *from = (const neu_resp_tag_info_t *) src;
neu_tag_get_icd()->copy(&to->tag, &from->tag);
to->resolved_type = from->resolved_type;
}

static inline void neu_resp_tag_info_fini(void *item)
{
neu_tag_fini(&((neu_resp_tag_info_t *) item)->tag);
}

static inline UT_icd *neu_resp_tag_info_icd(void)
{
static UT_icd icd = { sizeof(neu_resp_tag_info_t), NULL,
neu_resp_tag_info_copy, neu_resp_tag_info_fini };
return &icd;
}

typedef struct neu_resp_get_tag {
UT_array *tags; // array neu_datatag_t
UT_array *tags; // array neu_resp_tag_info_t
} neu_resp_get_tag_t;

typedef struct {
Expand Down Expand Up @@ -1126,6 +1151,7 @@ typedef struct neu_resp_tag_value_meta {
neu_tag_meta_t *metas;
int n_meta;
neu_datatag_t datatag;
neu_type_e resolved_type;
} neu_resp_tag_value_meta_t;

static inline UT_icd *neu_resp_tag_value_meta_icd()
Expand Down Expand Up @@ -1229,6 +1255,7 @@ typedef struct neu_resp_tag_value_meta_paginate {
neu_tag_meta_t *metas;
int n_meta;
neu_datatag_t datatag;
neu_type_e resolved_type;
} neu_resp_tag_value_meta_paginate_t;

static inline UT_icd *neu_resp_tag_value_meta_paginate_icd()
Expand Down
34 changes: 25 additions & 9 deletions plugins/restful/datatag_handle.c
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
#include "utils/http.h"

#include "datatag_handle.h"
#include "read_response.h"

void handle_add_tags(nng_aio *aio)
{
Expand Down Expand Up @@ -548,6 +549,7 @@ void handle_get_tags_resp(nng_aio *aio, neu_resp_get_tag_t *tags)
neu_json_get_tags_resp_t tags_res = { 0 };
char * result = NULL;
char sort_by[32] = { 0 };
int error = NEU_ERR_EINTERNAL;

ssize_t sort_len =
neu_http_get_param_str(aio, "sort_by", sort_by, sizeof(sort_by));
Expand All @@ -557,21 +559,24 @@ void handle_get_tags_resp(nng_aio *aio, neu_resp_get_tag_t *tags)
? neu_tag_get_sort_cmp(sort_by)
: NULL;
if (!cmp) {
NEU_JSON_RESPONSE_ERROR(NEU_ERR_PARAM_IS_WRONG, {
neu_http_response(aio, NEU_ERR_PARAM_IS_WRONG, result_error);
});
utarray_free(tags->tags);
return;
error = NEU_ERR_PARAM_IS_WRONG;
goto error;
}
utarray_sort(tags->tags, cmp);
}

tags_res.n_tag = utarray_len(tags->tags);
tags_res.tags = calloc(tags_res.n_tag, sizeof(neu_json_tag_t));
if (tags_res.n_tag > 0) {
tags_res.tags = calloc(tags_res.n_tag, sizeof(neu_json_tag_t));
if (tags_res.tags == NULL) {
goto error;
}
}

utarray_foreach(tags->tags, neu_datatag_t *, tag)
utarray_foreach(tags->tags, neu_resp_tag_info_t *, item)
{
int index = utarray_eltidx(tags->tags, tag);
int index = utarray_eltidx(tags->tags, item);
neu_datatag_t *tag = &item->tag;

tags_res.tags[index].name = tag->name;
tags_res.tags[index].address = tag->address;
Expand All @@ -585,10 +590,21 @@ void handle_get_tags_resp(nng_aio *aio, neu_resp_get_tag_t *tags)
tags_res.tags[index].unit = tag->unit;
}

neu_json_encode_by_fn(&tags_res, neu_json_encode_get_tags_resp, &result);
neu_http_get_tags_response_t context = { .response = &tags_res,
.tags = tags->tags };
if (neu_json_encode_by_fn(&context, neu_http_encode_get_tags_response,
&result) != 0 ||
result == NULL) {
goto error;
}

neu_http_ok(aio, result);
goto end;

error:
NEU_JSON_RESPONSE_ERROR(error,
{ neu_http_response(aio, error, result_error); });
end:
free(result);
free(tags_res.tags);
utarray_free(tags->tags);
Expand Down
10 changes: 6 additions & 4 deletions plugins/restful/global_config_handle.c
Original file line number Diff line number Diff line change
Expand Up @@ -1136,9 +1136,10 @@ static int get_tags_resp(context_t *ctx, neu_resp_get_tag_t *tags)
goto end;
}

utarray_foreach(tags->tags, neu_datatag_t *, tag)
utarray_foreach(tags->tags, neu_resp_tag_info_t *, item)
{
int index = utarray_eltidx(tags->tags, tag);
int index = utarray_eltidx(tags->tags, item);
neu_datatag_t *tag = &item->tag;

tags_res.tags[index].name = tag->name;
tags_res.tags[index].address = tag->address;
Expand Down Expand Up @@ -1217,9 +1218,10 @@ static int get_driver_tags_resp(context_t *ctx, neu_resp_get_tag_t *tags)
goto end;
}

utarray_foreach(tags->tags, neu_datatag_t *, tag)
utarray_foreach(tags->tags, neu_resp_tag_info_t *, item)
{
int index = utarray_eltidx(tags->tags, tag);
int index = utarray_eltidx(tags->tags, item);
neu_datatag_t *tag = &item->tag;

gtag->tags[index].name = tag->name;
gtag->tags[index].address = tag->address;
Expand Down
48 changes: 46 additions & 2 deletions plugins/restful/read_response.c
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#include "msg.h"
#include "parser/neu_json_tag.h"

#include "read_response.h"

Expand Down Expand Up @@ -38,10 +39,13 @@ int neu_http_encode_read_response(void *json_object, void *param)
int count = ctx->paginate
? ((neu_json_read_paginate_resp_t *) ctx->response)->n_tag
: ((neu_json_read_resp_t *) ctx->response)->n_tag;
void *output_tags = ctx->paginate
? (void *) ((neu_json_read_paginate_resp_t *) ctx->response)->tags
: (void *) ((neu_json_read_resp_t *) ctx->response)->tags;
unsigned int length = ctx->tags ? utarray_len(ctx->tags) : 0;
if (!json_is_object(object) || count < 0 ||
(count > 0 &&
(ctx->offset > length ||
(output_tags == NULL || ctx->offset > length ||
(unsigned int) count > length - ctx->offset))) {
if (ctx->paginate) {
neu_json_read_paginate_resp_t *response = ctx->response;
Expand Down Expand Up @@ -72,7 +76,47 @@ int neu_http_encode_read_response(void *json_object, void *param)
const neu_tag_sample_attrs_t *attrs = ctx->paginate
? &((neu_resp_tag_value_meta_paginate_t *) tag)->value.attrs
: &((neu_resp_tag_value_meta_t *) tag)->value.attrs;
if (encode_attrs(json_array_get(items, i), attrs) != 0) {
neu_type_e resolved_type = ctx->paginate
? ((neu_resp_tag_value_meta_paginate_t *) tag)->resolved_type
: ((neu_resp_tag_value_meta_t *) tag)->resolved_type;
json_t *item = json_array_get(items, i);
if (encode_attrs(item, attrs) != 0 ||
json_object_set_new(item, "resolved_type",
json_integer(resolved_type)) != 0) {
return -1;
}
}
return 0;
}

int neu_http_encode_get_tags_response(void *json_object, void *param)
{
neu_http_get_tags_response_t *ctx = param;
json_t * object = json_object;
if (ctx == NULL || ctx->response == NULL || !json_is_object(object)) {
return -1;
}

neu_json_get_tags_resp_t *response = ctx->response;
unsigned int length = ctx->tags ? utarray_len(ctx->tags) : 0;
if (response->n_tag < 0 || (unsigned int) response->n_tag != length ||
(length > 0 && response->tags == NULL)) {
return -1;
}

int ret = neu_json_encode_get_tags_resp(json_object, response);
if (ret != 0) {
return ret;
}

json_t *items = json_object_get(json_object, "tags");
if (!json_is_array(items) || json_array_size(items) != length) {
return -1;
}
for (unsigned int i = 0; i < length; i++) {
const neu_resp_tag_info_t *item = utarray_eltptr(ctx->tags, i);
if (json_object_set_new(json_array_get(items, i), "resolved_type",
json_integer(item->resolved_type)) != 0) {
return -1;
}
}
Expand Down
6 changes: 6 additions & 0 deletions plugins/restful/read_response.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@ typedef struct {
bool paginate;
} neu_http_read_response_t;

typedef struct {
void * response;
UT_array *tags;
} neu_http_get_tags_response_t;

int neu_http_encode_read_response(void *json_object, void *param);
int neu_http_encode_get_tags_response(void *json_object, void *param);

#endif
Loading
Loading