1 /*
2 * Copyright (c) 2020 PHYTEC Messtechnik GmbH
3 * Copyright (c) 2021 Nordic Semiconductor ASA
4 *
5 * SPDX-License-Identifier: Apache-2.0
6 */
7
8 #include <zephyr/kernel.h>
9 #include <zephyr/sys/util.h>
10 #include <zephyr/drivers/gpio.h>
11 #include <zephyr/modbus/modbus.h>
12
13 #include <zephyr/net/socket.h>
14
15 #include <zephyr/logging/log.h>
16 LOG_MODULE_REGISTER(tcp_modbus, LOG_LEVEL_INF);
17
18 #define MODBUS_TCP_PORT 502
19
20 static uint16_t holding_reg[8];
21 static uint8_t coils_state;
22
23 static const struct gpio_dt_spec led_dev[] = {
24 GPIO_DT_SPEC_GET(DT_ALIAS(led0), gpios),
25 GPIO_DT_SPEC_GET(DT_ALIAS(led1), gpios),
26 GPIO_DT_SPEC_GET(DT_ALIAS(led2), gpios),
27 };
28
29 static int custom_read_count;
30
custom_handler(const int iface,const struct modbus_adu * rx_adu,struct modbus_adu * tx_adu,uint8_t * const excep_code,void * const user_data)31 static bool custom_handler(const int iface,
32 const struct modbus_adu *rx_adu,
33 struct modbus_adu *tx_adu,
34 uint8_t *const excep_code,
35 void *const user_data)
36 {
37 const uint8_t request_len = 2;
38 const uint8_t response_len = 6;
39 int *read_counter = (int *)user_data;
40 uint8_t subfunc;
41 uint8_t data_len;
42
43 LOG_INF("Custom Modbus handler called");
44
45 if (rx_adu->length != request_len) {
46 LOG_WRN("Custom request length doesn't match");
47 *excep_code = MODBUS_EXC_ILLEGAL_DATA_VAL;
48 return true;
49 }
50
51 subfunc = rx_adu->data[0];
52 data_len = rx_adu->data[1];
53
54 LOG_INF("Custom function called with subfunc=%u, data_len=%u", subfunc, data_len);
55 (*read_counter)++;
56 sys_put_be16(0x5555, tx_adu->data);
57 sys_put_be16(0xAAAA, &tx_adu->data[2]);
58 sys_put_be16(*read_counter, &tx_adu->data[4]);
59 tx_adu->length = response_len;
60
61 return true;
62 }
63
64 MODBUS_CUSTOM_FC_DEFINE(custom, custom_handler, 101, &custom_read_count);
65
init_leds(void)66 static int init_leds(void)
67 {
68 int err;
69
70 for (int i = 0; i < ARRAY_SIZE(led_dev); i++) {
71 if (!gpio_is_ready_dt(&led_dev[i])) {
72 LOG_ERR("LED%u GPIO device not ready", i);
73 return -ENODEV;
74 }
75
76 err = gpio_pin_configure_dt(&led_dev[i], GPIO_OUTPUT_INACTIVE);
77 if (err != 0) {
78 LOG_ERR("Failed to configure LED%u pin", i);
79 return err;
80 }
81 }
82
83 return 0;
84 }
85
coil_rd(uint16_t addr,bool * state)86 static int coil_rd(uint16_t addr, bool *state)
87 {
88 if (addr >= ARRAY_SIZE(led_dev)) {
89 return -ENOTSUP;
90 }
91
92 if (coils_state & BIT(addr)) {
93 *state = true;
94 } else {
95 *state = false;
96 }
97
98 LOG_INF("Coil read, addr %u, %d", addr, (int)*state);
99
100 return 0;
101 }
102
coil_wr(uint16_t addr,bool state)103 static int coil_wr(uint16_t addr, bool state)
104 {
105 bool on;
106
107 if (addr >= ARRAY_SIZE(led_dev)) {
108 return -ENOTSUP;
109 }
110
111 if (state == true) {
112 coils_state |= BIT(addr);
113 on = true;
114 } else {
115 coils_state &= ~BIT(addr);
116 on = false;
117 }
118
119 gpio_pin_set(led_dev[addr].port, led_dev[addr].pin, (int)on);
120
121 LOG_INF("Coil write, addr %u, %d", addr, (int)state);
122
123 return 0;
124 }
125
holding_reg_rd(uint16_t addr,uint16_t * reg)126 static int holding_reg_rd(uint16_t addr, uint16_t *reg)
127 {
128 if (addr >= ARRAY_SIZE(holding_reg)) {
129 return -ENOTSUP;
130 }
131
132 *reg = holding_reg[addr];
133
134 LOG_INF("Holding register read, addr %u", addr);
135
136 return 0;
137 }
138
holding_reg_wr(uint16_t addr,uint16_t reg)139 static int holding_reg_wr(uint16_t addr, uint16_t reg)
140 {
141 if (addr >= ARRAY_SIZE(holding_reg)) {
142 return -ENOTSUP;
143 }
144
145 holding_reg[addr] = reg;
146
147 LOG_INF("Holding register write, addr %u", addr);
148
149 return 0;
150 }
151
152 static struct modbus_user_callbacks mbs_cbs = {
153 .coil_rd = coil_rd,
154 .coil_wr = coil_wr,
155 .holding_reg_rd = holding_reg_rd,
156 .holding_reg_wr = holding_reg_wr,
157 };
158
159 static struct modbus_adu tmp_adu;
160 K_SEM_DEFINE(received, 0, 1);
161 static int server_iface;
162
server_raw_cb(const int iface,const struct modbus_adu * adu,void * user_data)163 static int server_raw_cb(const int iface, const struct modbus_adu *adu,
164 void *user_data)
165 {
166 LOG_DBG("Server raw callback from interface %d", iface);
167
168 tmp_adu.trans_id = adu->trans_id;
169 tmp_adu.proto_id = adu->proto_id;
170 tmp_adu.length = adu->length;
171 tmp_adu.unit_id = adu->unit_id;
172 tmp_adu.fc = adu->fc;
173 memcpy(tmp_adu.data, adu->data,
174 MIN(adu->length, CONFIG_MODBUS_BUFFER_SIZE));
175
176 LOG_HEXDUMP_DBG(tmp_adu.data, tmp_adu.length, "resp");
177 k_sem_give(&received);
178
179 return 0;
180 }
181
182 const static struct modbus_iface_param server_param = {
183 .mode = MODBUS_MODE_RAW,
184 .server = {
185 .user_cb = &mbs_cbs,
186 .unit_id = 1,
187 },
188 .rawcb.raw_tx_cb = server_raw_cb,
189 .rawcb.user_data = NULL
190 };
191
init_modbus_server(void)192 static int init_modbus_server(void)
193 {
194 char iface_name[] = "RAW_0";
195 int err;
196
197 server_iface = modbus_iface_get_by_name(iface_name);
198
199 if (server_iface < 0) {
200 LOG_ERR("Failed to get iface index for %s",
201 iface_name);
202 return -ENODEV;
203 }
204
205 err = modbus_init_server(server_iface, server_param);
206
207 if (err < 0) {
208 return err;
209 }
210
211 return modbus_register_user_fc(server_iface, &modbus_cfg_custom);
212 }
213
modbus_tcp_reply(int client,struct modbus_adu * adu)214 static int modbus_tcp_reply(int client, struct modbus_adu *adu)
215 {
216 uint8_t header[MODBUS_MBAP_AND_FC_LENGTH];
217
218 modbus_raw_put_header(adu, header);
219 if (send(client, header, sizeof(header), 0) < 0) {
220 return -errno;
221 }
222
223 if (send(client, adu->data, adu->length, 0) < 0) {
224 return -errno;
225 }
226
227 return 0;
228 }
229
modbus_tcp_connection(int client)230 static int modbus_tcp_connection(int client)
231 {
232 uint8_t header[MODBUS_MBAP_AND_FC_LENGTH];
233 int rc;
234 int data_len;
235
236 rc = recv(client, header, sizeof(header), MSG_WAITALL);
237 if (rc <= 0) {
238 return rc == 0 ? -ENOTCONN : -errno;
239 }
240
241 LOG_HEXDUMP_DBG(header, sizeof(header), "h:>");
242 modbus_raw_get_header(&tmp_adu, header);
243 data_len = tmp_adu.length;
244
245 rc = recv(client, tmp_adu.data, data_len, MSG_WAITALL);
246 if (rc <= 0) {
247 return rc == 0 ? -ENOTCONN : -errno;
248 }
249
250 LOG_HEXDUMP_DBG(tmp_adu.data, tmp_adu.length, "d:>");
251 if (modbus_raw_submit_rx(server_iface, &tmp_adu)) {
252 LOG_ERR("Failed to submit raw ADU");
253 return -EIO;
254 }
255
256 if (k_sem_take(&received, K_MSEC(1000)) != 0) {
257 LOG_ERR("MODBUS RAW wait time expired");
258 modbus_raw_set_server_failure(&tmp_adu);
259 }
260
261 return modbus_tcp_reply(client, &tmp_adu);
262 }
263
main(void)264 int main(void)
265 {
266 int serv;
267 struct sockaddr_in bind_addr;
268 static int counter;
269
270 if (init_modbus_server()) {
271 LOG_ERR("Modbus TCP server initialization failed");
272 return 0;
273 }
274
275 if (init_leds()) {
276 LOG_ERR("Modbus TCP server initialization failed");
277 return 0;
278 }
279
280 serv = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
281
282 if (serv < 0) {
283 LOG_ERR("error: socket: %d", errno);
284 return 0;
285 }
286
287 bind_addr.sin_family = AF_INET;
288 bind_addr.sin_addr.s_addr = htonl(INADDR_ANY);
289 bind_addr.sin_port = htons(MODBUS_TCP_PORT);
290
291 if (bind(serv, (struct sockaddr *)&bind_addr, sizeof(bind_addr)) < 0) {
292 LOG_ERR("error: bind: %d", errno);
293 return 0;
294 }
295
296 if (listen(serv, 5) < 0) {
297 LOG_ERR("error: listen: %d", errno);
298 return 0;
299 }
300
301 LOG_INF("Started MODBUS TCP server example on port %d", MODBUS_TCP_PORT);
302
303 while (1) {
304 struct sockaddr_in client_addr;
305 socklen_t client_addr_len = sizeof(client_addr);
306 char addr_str[INET_ADDRSTRLEN];
307 int client;
308 int rc;
309
310 client = accept(serv, (struct sockaddr *)&client_addr,
311 &client_addr_len);
312
313 if (client < 0) {
314 LOG_ERR("error: accept: %d", errno);
315 continue;
316 }
317
318 inet_ntop(client_addr.sin_family, &client_addr.sin_addr,
319 addr_str, sizeof(addr_str));
320 LOG_INF("Connection #%d from %s",
321 counter++, addr_str);
322
323 do {
324 rc = modbus_tcp_connection(client);
325 } while (!rc);
326
327 close(client);
328 LOG_INF("Connection from %s closed, errno %d",
329 addr_str, rc);
330 }
331 return 0;
332 }
333