Skip to content

Commit 8c11f83

Browse files
authored
Merge pull request #2621 from hxy7yx/main-1
feat:add modbus-tcp simulator
2 parents ae959ac + c9bf291 commit 8c11f83

File tree

15 files changed

+1733
-7
lines changed

15 files changed

+1733
-7
lines changed

CMakeLists.txt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,9 @@ set(NEURON_SOURCES
169169
plugins/restful/system_handle.c
170170
plugins/restful/datalayers_handle.c
171171
plugins/restful/rest.c
172-
plugins/restful/user.c)
172+
plugins/restful/user.c
173+
plugins/restful/simulator_handle.c
174+
simulator/modbus_tcp_simulator.c)
173175

174176
set(CMAKE_BUILD_RPATH ./)
175177
add_executable(neuron)

include/neuron/errcodes.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ typedef enum {
5353
NEU_ERR_USER_NOT_EXISTS = 1020,
5454
NEU_ERR_INVALID_USER_LEN = 1021,
5555
NEU_ERR_USER_NO_PERMISSION = 1022,
56+
NEU_ERR_PORT_IN_USE = 1023,
5657

5758
NEU_ERR_NODE_EXIST = 2002,
5859
NEU_ERR_NODE_NOT_EXIST = 2003,
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
/**
2+
* NEURON IIoT System for Industry 4.0
3+
* Copyright (C) 2020-2025 EMQ Technologies Co., Ltd All rights reserved.
4+
*
5+
* This program is free software; you can redistribute it and/or
6+
* modify it under the terms of the GNU Lesser General Public
7+
* License as published by the Free Software Foundation; either
8+
* version 3 of the License, or (at your option) any later version.
9+
*
10+
* This program is distributed in the hope that it will be useful,
11+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13+
* Lesser General Public License for more details.
14+
*
15+
* You should have received a copy of the GNU Lesser General Public License
16+
* along with this program; if not, write to the Free Software Foundation,
17+
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18+
**/
19+
#ifndef NEU_MODBUS_TCP_SIMULATOR_H
20+
#define NEU_MODBUS_TCP_SIMULATOR_H
21+
22+
#include <stdbool.h>
23+
#include <stdint.h>
24+
25+
int neu_modbus_simulator_init(const char *config_dir);
26+
void neu_modbus_simulator_fini(void);
27+
28+
int neu_modbus_simulator_start(const char *ip, uint16_t port);
29+
void neu_modbus_simulator_stop(void);
30+
void neu_modbus_simulator_apply_persist(void);
31+
32+
typedef enum {
33+
NEU_SIM_TAG_NONE = 0,
34+
NEU_SIM_TAG_SINE = 1, // float, [-100,100], period 60s
35+
NEU_SIM_TAG_SAW = 2, // int16, [0,100], period 100s
36+
NEU_SIM_TAG_SQUARE = 3, // int16, [-10,10], period 10s
37+
NEU_SIM_TAG_RANDOM = 4 // int16, [0,100]
38+
} neu_sim_tag_type_e;
39+
40+
int neu_modbus_simulator_config_tags(const char **names, const char **addr_strs,
41+
const uint16_t * addresses,
42+
const neu_sim_tag_type_e *types,
43+
int tag_count);
44+
45+
char *neu_modbus_simulator_export_drivers_json(void);
46+
char *neu_modbus_simulator_list_tags_json(void);
47+
48+
typedef struct {
49+
bool running;
50+
char ip[64];
51+
uint16_t port;
52+
int tag_count;
53+
int error;
54+
} neu_modbus_simulator_status_t;
55+
56+
void neu_modbus_simulator_get_status(neu_modbus_simulator_status_t *out);
57+
58+
#endif

neuron.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,6 @@
44
"disable_auth": 0,
55
"syslog_host": "",
66
"syslog_port": 541,
7-
"sub_filter_error": 0
8-
}
7+
"sub_filter_error": 0,
8+
"modbus_simulator": { "port":502 }
9+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
CREATE TABLE IF NOT EXISTS modbus_tcp_simulator (
2+
id INTEGER PRIMARY KEY CHECK (id = 1),
3+
enabled INTEGER NOT NULL DEFAULT 0,
4+
tags_json TEXT NOT NULL DEFAULT '[]',
5+
updated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
6+
);

plugins/restful/handle.c

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
#include "version_handle.h"
4545

4646
#include "handle.h"
47+
#include "simulator_handle.h"
4748

4849
struct neu_rest_handle_ctx {
4950
void *plugin;
@@ -190,6 +191,24 @@ static struct neu_http_handler cors_handler[] = {
190191
{
191192
.url = "/api/v2/auth/basic/security_policies",
192193
},
194+
{
195+
.url = "/api/v2/simulator/status",
196+
},
197+
{
198+
.url = "/api/v2/simulator/start",
199+
},
200+
{
201+
.url = "/api/v2/simulator/stop",
202+
},
203+
{
204+
.url = "/api/v2/simulator/config",
205+
},
206+
{
207+
.url = "/api/v2/simulator/tags",
208+
},
209+
{
210+
.url = "/api/v2/simulator/export",
211+
},
193212
};
194213

195214
static struct neu_http_handler rest_handlers[] = {
@@ -686,6 +705,42 @@ static struct neu_http_handler rest_handlers[] = {
686705
.url = "/api/v2/auth/basic/security_policies",
687706
.value.handler = handle_server_security_policy,
688707
},
708+
{
709+
.method = NEU_HTTP_METHOD_GET,
710+
.type = NEU_HTTP_HANDLER_FUNCTION,
711+
.url = "/api/v2/simulator/status",
712+
.value.handler = handle_simulator_status,
713+
},
714+
{
715+
.method = NEU_HTTP_METHOD_GET,
716+
.type = NEU_HTTP_HANDLER_FUNCTION,
717+
.url = "/api/v2/simulator/tags",
718+
.value.handler = handle_simulator_list_tags,
719+
},
720+
{
721+
.method = NEU_HTTP_METHOD_POST,
722+
.type = NEU_HTTP_HANDLER_FUNCTION,
723+
.url = "/api/v2/simulator/start",
724+
.value.handler = handle_simulator_start,
725+
},
726+
{
727+
.method = NEU_HTTP_METHOD_POST,
728+
.type = NEU_HTTP_HANDLER_FUNCTION,
729+
.url = "/api/v2/simulator/stop",
730+
.value.handler = handle_simulator_stop,
731+
},
732+
{
733+
.method = NEU_HTTP_METHOD_POST,
734+
.type = NEU_HTTP_HANDLER_FUNCTION,
735+
.url = "/api/v2/simulator/config",
736+
.value.handler = handle_simulator_set_config,
737+
},
738+
{
739+
.method = NEU_HTTP_METHOD_GET,
740+
.type = NEU_HTTP_HANDLER_FUNCTION,
741+
.url = "/api/v2/simulator/export",
742+
.value.handler = handle_simulator_export,
743+
},
689744
};
690745

691746
void neu_rest_handler(const struct neu_http_handler **handlers, uint32_t *size)

plugins/restful/simulator_handle.c

Lines changed: 217 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,217 @@
1+
#include <ctype.h>
2+
#include <jansson.h>
3+
#include <stdlib.h>
4+
#include <string.h>
5+
6+
#include "handle.h"
7+
#include "modbus_tcp_simulator.h"
8+
#include "utils/http.h"
9+
#include "utils/log.h"
10+
#include "json/neu_json_fn.h"
11+
12+
static int extract_hold_start_from_addr(const char *addr_str,
13+
uint16_t * out_idx0)
14+
{
15+
if (!addr_str || !out_idx0)
16+
return -1;
17+
const char *p = strstr(addr_str, "!4");
18+
if (!p)
19+
return -1;
20+
p += 2;
21+
if (!isdigit((unsigned char) *p))
22+
return -1;
23+
long v = strtol(p, NULL, 10);
24+
if (v <= 0 || v > 65535)
25+
return -1;
26+
*out_idx0 = (uint16_t)(v - 1);
27+
return 0;
28+
}
29+
30+
void handle_simulator_status(nng_aio *aio)
31+
{
32+
NEU_VALIDATE_JWT(aio);
33+
neu_modbus_simulator_status_t st = { 0 };
34+
neu_modbus_simulator_get_status(&st);
35+
char buf[256] = { 0 };
36+
snprintf(buf, sizeof(buf),
37+
"{\"running\":%d,\"port\":%u,\"tag_count\":%d,\"error\":%d}",
38+
st.running ? 1 : 0, st.port, st.tag_count, st.error);
39+
neu_http_ok(aio, buf);
40+
}
41+
42+
void handle_simulator_start(nng_aio *aio)
43+
{
44+
NEU_VALIDATE_JWT(aio);
45+
int rc = neu_modbus_simulator_start(NULL, 0);
46+
47+
neu_modbus_simulator_status_t st = { 0 };
48+
neu_modbus_simulator_get_status(&st);
49+
if (rc != 0 || (!st.running && st.error != 0)) {
50+
char buf[64] = { 0 };
51+
snprintf(buf, sizeof(buf), "{\"error\":%d}", st.error);
52+
neu_http_ok(aio, buf);
53+
return;
54+
}
55+
neu_http_ok(aio, "{\"error\":0}");
56+
}
57+
58+
void handle_simulator_list_tags(nng_aio *aio)
59+
{
60+
NEU_VALIDATE_JWT(aio);
61+
char *out = neu_modbus_simulator_list_tags_json();
62+
if (!out) {
63+
NEU_JSON_RESPONSE_ERROR(NEU_ERR_EINTERNAL, {
64+
neu_http_response(aio, NEU_ERR_EINTERNAL, result_error);
65+
});
66+
return;
67+
}
68+
neu_http_ok(aio, out);
69+
free(out);
70+
}
71+
72+
void handle_simulator_stop(nng_aio *aio)
73+
{
74+
NEU_VALIDATE_JWT(aio);
75+
neu_modbus_simulator_stop();
76+
neu_http_ok(aio, "{\"error\":0}");
77+
}
78+
79+
void handle_simulator_set_config(nng_aio *aio)
80+
{
81+
NEU_VALIDATE_JWT(aio);
82+
char * body = NULL;
83+
size_t sz = 0;
84+
if (neu_http_get_body(aio, (void **) &body, &sz) != 0 || !body) {
85+
NEU_JSON_RESPONSE_ERROR(NEU_ERR_PARAM_IS_WRONG, {
86+
neu_http_response(aio, NEU_ERR_PARAM_IS_WRONG, result_error);
87+
});
88+
return;
89+
}
90+
91+
json_error_t jerr;
92+
json_t * root = json_loads(body, 0, &jerr);
93+
free(body);
94+
if (!root) {
95+
NEU_JSON_RESPONSE_ERROR(NEU_ERR_PARAM_IS_WRONG, {
96+
neu_http_response(aio, NEU_ERR_PARAM_IS_WRONG, result_error);
97+
});
98+
return;
99+
}
100+
json_t *tags = json_object_get(root, "tags");
101+
if (!tags || !json_is_array(tags)) {
102+
json_decref(root);
103+
NEU_JSON_RESPONSE_ERROR(NEU_ERR_PARAM_IS_WRONG, {
104+
neu_http_response(aio, NEU_ERR_PARAM_IS_WRONG, result_error);
105+
});
106+
return;
107+
}
108+
109+
int count = 0;
110+
for (size_t i = 0; i < json_array_size(tags); ++i) {
111+
json_t *obj = json_array_get(tags, i);
112+
if (!obj || !json_is_object(obj))
113+
continue;
114+
json_t *jaddr = json_object_get(obj, "address");
115+
json_t *jtype = json_object_get(obj, "type");
116+
if (!jaddr || !json_is_string(jaddr) || !jtype)
117+
continue;
118+
const char *addr_str = json_string_value(jaddr);
119+
uint16_t idx0 = 0;
120+
if (extract_hold_start_from_addr(addr_str, &idx0) != 0)
121+
continue;
122+
count++;
123+
}
124+
125+
char ** names = NULL;
126+
char ** addresses = NULL;
127+
uint16_t * indices = NULL;
128+
neu_sim_tag_type_e *types = NULL;
129+
if (count > 0) {
130+
names = calloc((size_t) count, sizeof(char *));
131+
addresses = calloc((size_t) count, sizeof(char *));
132+
indices = calloc((size_t) count, sizeof(uint16_t));
133+
types = calloc((size_t) count, sizeof(neu_sim_tag_type_e));
134+
if (!names || !addresses || !indices || !types) {
135+
free(names);
136+
free(addresses);
137+
free(indices);
138+
free(types);
139+
json_decref(root);
140+
NEU_JSON_RESPONSE_ERROR(NEU_ERR_EINTERNAL, {
141+
neu_http_response(aio, NEU_ERR_EINTERNAL, result_error);
142+
});
143+
return;
144+
}
145+
}
146+
147+
int m = 0;
148+
for (size_t i = 0; i < json_array_size(tags); ++i) {
149+
json_t *obj = json_array_get(tags, i);
150+
if (!obj || !json_is_object(obj))
151+
continue;
152+
json_t *jname = json_object_get(obj, "name");
153+
json_t *jaddr = json_object_get(obj, "address");
154+
json_t *jtype = json_object_get(obj, "type");
155+
if (!jaddr || !json_is_string(jaddr) || !jtype)
156+
continue;
157+
const char *addr_str = json_string_value(jaddr);
158+
uint16_t idx0 = 0;
159+
if (extract_hold_start_from_addr(addr_str, &idx0) != 0)
160+
continue;
161+
neu_sim_tag_type_e tp = NEU_SIM_TAG_NONE;
162+
if (json_is_string(jtype)) {
163+
const char *ts = json_string_value(jtype);
164+
if (ts) {
165+
if (strcmp(ts, "sine") == 0)
166+
tp = NEU_SIM_TAG_SINE;
167+
else if (strcmp(ts, "saw") == 0)
168+
tp = NEU_SIM_TAG_SAW;
169+
else if (strcmp(ts, "square") == 0)
170+
tp = NEU_SIM_TAG_SQUARE;
171+
else if (strcmp(ts, "random") == 0)
172+
tp = NEU_SIM_TAG_RANDOM;
173+
}
174+
} else if (json_is_integer(jtype)) {
175+
tp = (neu_sim_tag_type_e) json_integer_value(jtype);
176+
}
177+
if (tp == NEU_SIM_TAG_NONE || idx0 >= 1000)
178+
continue;
179+
indices[m] = idx0;
180+
types[m] = tp;
181+
names[m] = jname && json_is_string(jname)
182+
? strdup(json_string_value(jname))
183+
: strdup("");
184+
addresses[m] = strdup(addr_str);
185+
m++;
186+
}
187+
json_decref(root);
188+
189+
neu_modbus_simulator_config_tags(
190+
(const char **) names, (const char **) addresses, indices, types, m);
191+
192+
for (int i = 0; i < m; ++i) {
193+
free(names[i]);
194+
free(addresses[i]);
195+
}
196+
free(names);
197+
free(addresses);
198+
free(indices);
199+
free(types);
200+
neu_http_ok(aio, "{\"error\":0}");
201+
}
202+
203+
void handle_simulator_export(nng_aio *aio)
204+
{
205+
NEU_VALIDATE_JWT(aio);
206+
char *drivers = neu_modbus_simulator_export_drivers_json();
207+
if (!drivers) {
208+
NEU_JSON_RESPONSE_ERROR(NEU_ERR_EINTERNAL, {
209+
neu_http_response(aio, NEU_ERR_EINTERNAL, result_error);
210+
});
211+
return;
212+
}
213+
neu_http_response_file(
214+
aio, drivers, strlen(drivers),
215+
"attachment; filename=\"simulator_modbus_south.json\"");
216+
free(drivers);
217+
}

0 commit comments

Comments
 (0)