1 /*
2  * Copyright (c) 2020 PHYTEC Messtechnik GmbH
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 
7 #include "test_modbus.h"
8 
9 #include <zephyr/sys/util.h>
10 #include <zephyr/logging/log.h>
11 LOG_MODULE_REGISTER(mbs_test, LOG_LEVEL_INF);
12 
13 const static uint16_t fp_offset = MB_TEST_FP_OFFSET;
14 static uint16_t coils;
15 static uint16_t holding_reg[8];
16 static float holding_fp[4];
17 
18 uint8_t server_iface;
19 
test_get_server_iface(void)20 uint8_t test_get_server_iface(void)
21 {
22 	return server_iface;
23 }
24 
coil_rd(uint16_t addr,bool * state)25 static int coil_rd(uint16_t addr, bool *state)
26 {
27 	if (addr >= (sizeof(coils) * 8)) {
28 		return -ENOTSUP;
29 	}
30 
31 	if (coils & BIT(addr)) {
32 		*state = true;
33 	} else {
34 		*state = false;
35 	}
36 
37 	LOG_DBG("Coil read, addr %u, %d", addr, (int)*state);
38 
39 	return 0;
40 }
41 
coil_wr(uint16_t addr,bool state)42 static int coil_wr(uint16_t addr, bool state)
43 {
44 	if (addr >= (sizeof(coils) * 8)) {
45 		return -ENOTSUP;
46 	}
47 
48 	if (state == true) {
49 		coils |= BIT(addr);
50 	} else {
51 		coils &= ~BIT(addr);
52 	}
53 
54 	LOG_DBG("Coil write, addr %u, %d", addr, (int)state);
55 
56 	return 0;
57 }
58 
discrete_input_rd(uint16_t addr,bool * state)59 static int discrete_input_rd(uint16_t addr, bool *state)
60 {
61 	if (addr >= (sizeof(coils) * 8)) {
62 		return -ENOTSUP;
63 	}
64 
65 	if (coils & BIT(addr)) {
66 		*state = true;
67 	} else {
68 		*state = false;
69 	}
70 
71 	LOG_DBG("Discrete input read, addr %u, %d", addr, (int)*state);
72 
73 	return 0;
74 }
75 
input_reg_rd(uint16_t addr,uint16_t * reg)76 static int input_reg_rd(uint16_t addr, uint16_t *reg)
77 {
78 	if (addr >= ARRAY_SIZE(holding_reg)) {
79 		return -ENOTSUP;
80 	}
81 
82 	*reg = holding_reg[addr];
83 
84 	LOG_DBG("Input register read, addr %u, 0x%04x", addr, *reg);
85 
86 	return 0;
87 }
88 
input_reg_rd_fp(uint16_t addr,float * reg)89 static int input_reg_rd_fp(uint16_t addr, float *reg)
90 {
91 	if (!IN_RANGE(addr, fp_offset, sizeof(holding_fp) / 2 + fp_offset)) {
92 		return -ENOTSUP;
93 	}
94 
95 	*reg = holding_fp[(addr - fp_offset) / 2];
96 
97 	LOG_DBG("FP input register read, addr %u", addr);
98 
99 	return 0;
100 }
101 
holding_reg_rd(uint16_t addr,uint16_t * reg)102 static int holding_reg_rd(uint16_t addr, uint16_t *reg)
103 {
104 	if (addr >= ARRAY_SIZE(holding_reg)) {
105 		return -ENOTSUP;
106 	}
107 
108 	*reg = holding_reg[addr];
109 
110 	LOG_DBG("Holding register read, addr %u", addr);
111 
112 	return 0;
113 }
114 
holding_reg_wr(uint16_t addr,uint16_t reg)115 static int holding_reg_wr(uint16_t addr, uint16_t reg)
116 {
117 	if (addr >= ARRAY_SIZE(holding_reg)) {
118 		return -ENOTSUP;
119 	}
120 
121 	holding_reg[addr] = reg;
122 
123 	LOG_DBG("Holding register write, addr %u", addr);
124 
125 	return 0;
126 }
127 
holding_reg_rd_fp(uint16_t addr,float * reg)128 static int holding_reg_rd_fp(uint16_t addr, float *reg)
129 {
130 	if (!IN_RANGE(addr, fp_offset, sizeof(holding_fp) / 2 + fp_offset)) {
131 		return -ENOTSUP;
132 	}
133 
134 	*reg = holding_fp[(addr - fp_offset) / 2];
135 
136 	LOG_DBG("FP holding register read, addr %u", addr);
137 
138 	return 0;
139 }
140 
holding_reg_wr_fp(uint16_t addr,float reg)141 static int holding_reg_wr_fp(uint16_t addr, float reg)
142 {
143 	if (!IN_RANGE(addr, fp_offset, sizeof(holding_fp) / 2 + fp_offset)) {
144 		return -ENOTSUP;
145 	}
146 
147 	holding_fp[(addr - fp_offset) / 2] = reg;
148 
149 	LOG_DBG("FP holding register write, addr %u", addr);
150 
151 	return 0;
152 }
153 
154 static struct modbus_user_callbacks mbs_cbs = {
155 	/** Coil read/write callback */
156 	.coil_rd = coil_rd,
157 	.coil_wr = coil_wr,
158 	/* Discrete Input read callback */
159 	.discrete_input_rd = discrete_input_rd,
160 	/* Input Register read callback */
161 	.input_reg_rd = input_reg_rd,
162 	/* Floating Point Input Register read callback */
163 	.input_reg_rd_fp = input_reg_rd_fp,
164 	/* Holding Register read/write callback */
165 	.holding_reg_rd = holding_reg_rd,
166 	.holding_reg_wr = holding_reg_wr,
167 	/* Floating Point Holding Register read/write callback */
168 	.holding_reg_rd_fp = holding_reg_rd_fp,
169 	.holding_reg_wr_fp = holding_reg_wr_fp,
170 };
171 
172 static struct modbus_iface_param server_param = {
173 	.mode = MODBUS_MODE_RTU,
174 	.server = {
175 		.user_cb = &mbs_cbs,
176 		.unit_id = MB_TEST_NODE_ADDR,
177 	},
178 	.serial = {
179 		.baud = MB_TEST_BAUDRATE_LOW,
180 		.parity = UART_CFG_PARITY_ODD,
181 	},
182 };
183 
184 /*
185  * This test performed on hardware requires two UART controllers
186  * on the board (with RX/TX lines connected crosswise).
187  * The exact mapping is not required, we assume that both controllers
188  * have similar capabilities and use the instance with index 1
189  * as interface for the server.
190  */
191 #if DT_NODE_EXISTS(DT_INST(1, zephyr_modbus_serial))
192 static const char rtu_iface_name[] = {DEVICE_DT_NAME(DT_INST(1, zephyr_modbus_serial))};
193 #else
194 static const char rtu_iface_name[] = "";
195 #endif
196 
test_server_setup_low_odd(void)197 void test_server_setup_low_odd(void)
198 {
199 	int err;
200 
201 	server_iface = modbus_iface_get_by_name(rtu_iface_name);
202 	server_param.mode = MODBUS_MODE_RTU;
203 	server_param.serial.baud = MB_TEST_BAUDRATE_LOW;
204 	server_param.serial.parity = UART_CFG_PARITY_ODD;
205 
206 	if (IS_ENABLED(CONFIG_MODBUS_SERVER)) {
207 		err = modbus_init_server(server_iface, server_param);
208 		zassert_equal(err, 0, "Failed to configure RTU server");
209 	} else {
210 		ztest_test_skip();
211 	}
212 }
213 
test_server_setup_low_none(void)214 void test_server_setup_low_none(void)
215 {
216 	int err;
217 
218 	server_iface = modbus_iface_get_by_name(rtu_iface_name);
219 	server_param.mode = MODBUS_MODE_RTU;
220 	server_param.serial.baud = MB_TEST_BAUDRATE_LOW;
221 	server_param.serial.parity = UART_CFG_PARITY_NONE;
222 
223 	if (IS_ENABLED(CONFIG_MODBUS_SERVER)) {
224 		err = modbus_init_server(server_iface, server_param);
225 		zassert_equal(err, 0, "Failed to configure RTU server");
226 	} else {
227 		ztest_test_skip();
228 	}
229 }
230 
test_server_setup_high_even(void)231 void test_server_setup_high_even(void)
232 {
233 	int err;
234 
235 	server_iface = modbus_iface_get_by_name(rtu_iface_name);
236 	server_param.mode = MODBUS_MODE_RTU;
237 	server_param.serial.baud = MB_TEST_BAUDRATE_HIGH;
238 	server_param.serial.parity = UART_CFG_PARITY_EVEN;
239 
240 	if (IS_ENABLED(CONFIG_MODBUS_SERVER)) {
241 		err = modbus_init_server(server_iface, server_param);
242 		zassert_equal(err, 0, "Failed to configure RTU server");
243 	} else {
244 		ztest_test_skip();
245 	}
246 }
247 
test_server_setup_ascii(void)248 void test_server_setup_ascii(void)
249 {
250 	int err;
251 
252 	server_iface = modbus_iface_get_by_name(rtu_iface_name);
253 	server_param.mode = MODBUS_MODE_ASCII;
254 	server_param.serial.baud = MB_TEST_BAUDRATE_HIGH;
255 	server_param.serial.parity = UART_CFG_PARITY_EVEN;
256 
257 	if (IS_ENABLED(CONFIG_MODBUS_SERVER)) {
258 		err = modbus_init_server(server_iface, server_param);
259 		zassert_equal(err, 0, "Failed to configure RTU server");
260 	} else {
261 		ztest_test_skip();
262 	}
263 }
264 
test_server_setup_raw(void)265 void test_server_setup_raw(void)
266 {
267 	char iface_name[] = "RAW_1";
268 	int err;
269 
270 	server_iface = modbus_iface_get_by_name(iface_name);
271 	server_param.mode = MODBUS_MODE_RAW;
272 	server_param.rawcb.raw_tx_cb = server_raw_cb;
273 
274 	if (IS_ENABLED(CONFIG_MODBUS_SERVER)) {
275 		err = modbus_init_server(server_iface, server_param);
276 		zassert_equal(err, 0, "Failed to configure RAW server");
277 	} else {
278 		ztest_test_skip();
279 	}
280 }
281 
test_server_disable(void)282 void test_server_disable(void)
283 {
284 	int err;
285 
286 	if (IS_ENABLED(CONFIG_MODBUS_SERVER)) {
287 		err = modbus_disable(server_iface);
288 		zassert_equal(err, 0, "Failed to disable RTU server");
289 	} else {
290 		ztest_test_skip();
291 	}
292 }
293