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