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