1 /*
2  * Copyright (c) 2020 Andreas Sandberg
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 
7 #include <zephyr/drivers/lora.h>
8 #include <inttypes.h>
9 #include <zephyr/shell/shell.h>
10 #include <stdlib.h>
11 #include <string.h>
12 
13 LOG_MODULE_REGISTER(lora_shell, CONFIG_LORA_LOG_LEVEL);
14 
15 #define DEFAULT_RADIO_NODE DT_ALIAS(lora0)
16 BUILD_ASSERT(DT_NODE_HAS_STATUS_OKAY(DEFAULT_RADIO_NODE),
17 	     "No default LoRa radio specified in DT");
18 
19 static struct lora_modem_config modem_config = {
20 	.frequency = 0,
21 	.bandwidth = BW_125_KHZ,
22 	.datarate = SF_10,
23 	.coding_rate = CR_4_5,
24 	.preamble_len = 8,
25 	.tx_power = 4,
26 };
27 
28 static const int bw_table[] = {
29 	[BW_125_KHZ] = 125,
30 	[BW_250_KHZ] = 250,
31 	[BW_500_KHZ] = 500,
32 };
33 
parse_long(long * out,const struct shell * sh,const char * arg)34 static int parse_long(long *out, const struct shell *sh, const char *arg)
35 {
36 	char *eptr;
37 	long lval;
38 
39 	lval = strtol(arg, &eptr, 0);
40 	if (*eptr != '\0') {
41 		shell_error(sh, "'%s' is not an integer", arg);
42 		return -EINVAL;
43 	}
44 
45 	*out = lval;
46 	return 0;
47 }
48 
parse_long_range(long * out,const struct shell * sh,const char * arg,const char * name,long min,long max)49 static int parse_long_range(long *out, const struct shell *sh,
50 			    const char *arg, const char *name, long min,
51 			    long max)
52 {
53 	int ret;
54 
55 	ret = parse_long(out, sh, arg);
56 	if (ret < 0) {
57 		return ret;
58 	}
59 
60 	if (*out < min || *out > max) {
61 		shell_error(sh, "Parameter '%s' is out of range. "
62 			    "Valid range is %li -- %li.",
63 			    name, min, max);
64 		return -EINVAL;
65 	}
66 
67 	return 0;
68 }
69 
parse_freq(uint32_t * out,const struct shell * sh,const char * arg)70 static int parse_freq(uint32_t *out, const struct shell *sh, const char *arg)
71 {
72 	char *eptr;
73 	unsigned long val;
74 
75 	val = strtoul(arg, &eptr, 0);
76 	if (*eptr != '\0') {
77 		shell_error(sh, "Invalid frequency, '%s' is not an integer",
78 			    arg);
79 		return -EINVAL;
80 	}
81 
82 	if (val == ULONG_MAX) {
83 		shell_error(sh, "Frequency %s out of range", arg);
84 		return -EINVAL;
85 	}
86 
87 	*out = (uint32_t)val;
88 	return 0;
89 }
90 
get_modem(const struct shell * sh)91 static const struct device *get_modem(const struct shell *sh)
92 {
93 	const struct device *dev;
94 
95 	dev = DEVICE_DT_GET(DEFAULT_RADIO_NODE);
96 
97 	if (!device_is_ready(dev)) {
98 		shell_error(sh, "LORA Radio device not ready");
99 		return NULL;
100 	}
101 
102 	return dev;
103 }
104 
get_configured_modem(const struct shell * sh)105 static const struct device *get_configured_modem(const struct shell *sh)
106 {
107 	int ret;
108 	const struct device *dev;
109 
110 	dev = get_modem(sh);
111 	if (!dev) {
112 		return NULL;
113 	}
114 
115 	if (modem_config.frequency == 0) {
116 		shell_error(sh, "No frequency specified.");
117 		return NULL;
118 	}
119 
120 	ret = lora_config(dev, &modem_config);
121 	if (ret < 0) {
122 		shell_error(sh, "LoRa config failed");
123 		return NULL;
124 	}
125 
126 	return dev;
127 }
128 
lora_conf_dump(const struct shell * sh)129 static int lora_conf_dump(const struct shell *sh)
130 {
131 	shell_print(sh, "  Frequency: %" PRIu32 " Hz",
132 		    modem_config.frequency);
133 	shell_print(sh, "  TX power: %" PRIi8 " dBm",
134 		    modem_config.tx_power);
135 	shell_print(sh, "  Bandwidth: %i kHz",
136 		    bw_table[modem_config.bandwidth]);
137 	shell_print(sh, "  Spreading factor: SF%i",
138 		    (int)modem_config.datarate);
139 	shell_print(sh, "  Coding rate: 4/%i",
140 		    (int)modem_config.coding_rate + 4);
141 	shell_print(sh, "  Preamble length: %" PRIu16,
142 		    modem_config.preamble_len);
143 
144 	return 0;
145 }
146 
lora_conf_set(const struct shell * sh,const char * param,const char * value)147 static int lora_conf_set(const struct shell *sh, const char *param,
148 			 const char *value)
149 {
150 	long lval;
151 
152 	if (!strcmp("freq", param)) {
153 		if (parse_freq(&modem_config.frequency, sh, value) < 0) {
154 			return -EINVAL;
155 		}
156 	} else if (!strcmp("tx-power", param)) {
157 		if (parse_long_range(&lval, sh, value,
158 				     "tx-power", INT8_MIN, INT8_MAX) < 0) {
159 			return -EINVAL;
160 		}
161 		modem_config.tx_power = lval;
162 	} else if (!strcmp("bw", param)) {
163 		if (parse_long_range(&lval, sh, value,
164 				     "bw", 0, INT16_MAX) < 0) {
165 			return -EINVAL;
166 		}
167 		switch (lval) {
168 		case 125:
169 			modem_config.bandwidth = BW_125_KHZ;
170 			break;
171 		case 250:
172 			modem_config.bandwidth = BW_250_KHZ;
173 			break;
174 		case 500:
175 			modem_config.bandwidth = BW_500_KHZ;
176 			break;
177 		default:
178 			shell_error(sh, "Invalid bandwidth: %ld", lval);
179 			return -EINVAL;
180 		}
181 	} else if (!strcmp("sf", param)) {
182 		if (parse_long_range(&lval, sh, value, "sf", 6, 12) < 0) {
183 			return -EINVAL;
184 		}
185 		modem_config.datarate = SF_6 + (unsigned int)lval - 6;
186 	} else if (!strcmp("cr", param)) {
187 		if (parse_long_range(&lval, sh, value, "cr", 5, 8) < 0) {
188 			return -EINVAL;
189 		}
190 		modem_config.coding_rate = CR_4_5 + (unsigned int)lval - 5;
191 	} else if (!strcmp("pre-len", param)) {
192 		if (parse_long_range(&lval, sh, value,
193 				     "pre-len", 0, UINT16_MAX) < 0) {
194 			return -EINVAL;
195 		}
196 		modem_config.preamble_len = lval;
197 	} else {
198 		shell_error(sh, "Unknown parameter '%s'", param);
199 		return -EINVAL;
200 	}
201 
202 	return 0;
203 }
204 
cmd_lora_conf(const struct shell * sh,size_t argc,char ** argv)205 static int cmd_lora_conf(const struct shell *sh, size_t argc, char **argv)
206 {
207 	int i;
208 	int ret;
209 
210 	if (argc < 2) {
211 		return lora_conf_dump(sh);
212 	}
213 
214 	for (i = 1; i < argc; i += 2) {
215 		if (i + 1 >= argc) {
216 			shell_error(sh, "'%s' expects an argument",
217 				    argv[i]);
218 			return -EINVAL;
219 		}
220 
221 		ret = lora_conf_set(sh, argv[i], argv[i + 1]);
222 		if (ret != 0) {
223 			return ret;
224 		}
225 	}
226 
227 	return 0;
228 }
229 
cmd_lora_send(const struct shell * sh,size_t argc,char ** argv)230 static int cmd_lora_send(const struct shell *sh,
231 			size_t argc, char **argv)
232 {
233 	int ret;
234 	const struct device *dev;
235 
236 	modem_config.tx = true;
237 	dev = get_configured_modem(sh);
238 	if (!dev) {
239 		return -ENODEV;
240 	}
241 
242 	ret = lora_send(dev, argv[1], strlen(argv[1]));
243 	if (ret < 0) {
244 		shell_error(sh, "LoRa send failed: %i", ret);
245 		return ret;
246 	}
247 
248 	return 0;
249 }
250 
cmd_lora_recv(const struct shell * sh,size_t argc,char ** argv)251 static int cmd_lora_recv(const struct shell *sh, size_t argc, char **argv)
252 {
253 	static char buf[0xff];
254 	const struct device *dev;
255 	long timeout = 0;
256 	int ret;
257 	int16_t rssi;
258 	int8_t snr;
259 
260 	modem_config.tx = false;
261 	dev = get_configured_modem(sh);
262 	if (!dev) {
263 		return -ENODEV;
264 	}
265 
266 	if (argc >= 2 && parse_long_range(&timeout, sh, argv[1],
267 					  "timeout", 0, INT_MAX) < 0) {
268 		return -EINVAL;
269 	}
270 
271 	ret = lora_recv(dev, buf, sizeof(buf),
272 			timeout ? K_MSEC(timeout) : K_FOREVER, &rssi, &snr);
273 	if (ret < 0) {
274 		shell_error(sh, "LoRa recv failed: %i", ret);
275 		return ret;
276 	}
277 
278 	shell_hexdump(sh, buf, ret);
279 	shell_print(sh, "RSSI: %" PRIi16 " dBm, SNR:%" PRIi8 " dBm",
280 		    rssi, snr);
281 
282 	return 0;
283 }
284 
cmd_lora_test_cw(const struct shell * sh,size_t argc,char ** argv)285 static int cmd_lora_test_cw(const struct shell *sh,
286 			    size_t argc, char **argv)
287 {
288 	const struct device *dev;
289 	int ret;
290 	uint32_t freq;
291 	long power, duration;
292 
293 	dev = get_modem(sh);
294 	if (!dev) {
295 		return -ENODEV;
296 	}
297 
298 	if (parse_freq(&freq, sh, argv[1]) < 0 ||
299 	    parse_long_range(&power, sh, argv[2],
300 			     "power", INT8_MIN, INT8_MAX) < 0 ||
301 	    parse_long_range(&duration, sh, argv[3],
302 			     "duration", 0, UINT16_MAX) < 0) {
303 		return -EINVAL;
304 	}
305 
306 	ret = lora_test_cw(dev, (uint32_t)freq, (int8_t)power, (uint16_t)duration);
307 	if (ret < 0) {
308 		shell_error(sh, "LoRa test CW failed: %i", ret);
309 		return ret;
310 	}
311 
312 	return 0;
313 }
314 
315 SHELL_STATIC_SUBCMD_SET_CREATE(sub_lora,
316 	SHELL_CMD(config, NULL,
317 		  "Configure the LoRa radio\n"
318 		  " Usage: config [freq <Hz>] [tx-power <dBm>] [bw <kHz>] "
319 		  "[sf <int>] [cr <int>] [pre-len <int>]\n",
320 		  cmd_lora_conf),
321 	SHELL_CMD_ARG(send, NULL,
322 		      "Send LoRa packet\n"
323 		      " Usage: send <data>",
324 		      cmd_lora_send, 2, 0),
325 	SHELL_CMD_ARG(recv, NULL,
326 		      "Receive LoRa packet\n"
327 		      " Usage: recv [timeout (ms)]",
328 		      cmd_lora_recv, 1, 1),
329 	SHELL_CMD_ARG(test_cw, NULL,
330 		  "Send a continuous wave\n"
331 		  " Usage: test_cw <freq (Hz)> <power (dBm)> <duration (s)>",
332 		  cmd_lora_test_cw, 4, 0),
333 	SHELL_SUBCMD_SET_END /* Array terminated. */
334 );
335 
336 SHELL_CMD_REGISTER(lora, &sub_lora, "LoRa commands", NULL);
337