From d8de33e966312c278daa390fd17f50100852e6a0 Mon Sep 17 00:00:00 2001 From: Andreas Sandberg Date: Thu, 16 Apr 2020 00:35:04 +0100 Subject: [PATCH] drivers: lora: Add a shell for LoRa testing Provide basic commands that are useful when testing a LoRa radio. Currently, the shell supports: > lora conf ... > lora send ... > lora recv ... > lora test_cw ... Signed-off-by: Andreas Sandberg --- drivers/lora/CMakeLists.txt | 1 + drivers/lora/Kconfig | 6 + drivers/lora/shell.c | 337 ++++++++++++++++++++++++++++++++++++ 3 files changed, 344 insertions(+) create mode 100644 drivers/lora/shell.c diff --git a/drivers/lora/CMakeLists.txt b/drivers/lora/CMakeLists.txt index 4686147fa24..1a359072b4f 100644 --- a/drivers/lora/CMakeLists.txt +++ b/drivers/lora/CMakeLists.txt @@ -2,4 +2,5 @@ zephyr_library_named(loramac-node) +zephyr_library_sources_ifdef(CONFIG_LORA_SHELL shell.c) zephyr_library_sources_ifdef(CONFIG_LORA_SX1276 sx1276.c) diff --git a/drivers/lora/Kconfig b/drivers/lora/Kconfig index 83b2f6ad01c..c0d24f653d2 100644 --- a/drivers/lora/Kconfig +++ b/drivers/lora/Kconfig @@ -18,6 +18,12 @@ module = LORA module-str = lora source "subsys/logging/Kconfig.template.log_config" +config LORA_SHELL + bool "Enable LoRa Shell" + depends on SHELL + help + Enable LoRa Shell for testing. + config LORA_INIT_PRIORITY int "LoRa initialization priority" default 90 diff --git a/drivers/lora/shell.c b/drivers/lora/shell.c new file mode 100644 index 00000000000..acff4c740fd --- /dev/null +++ b/drivers/lora/shell.c @@ -0,0 +1,337 @@ +/* + * Copyright (c) 2020 Andreas Sandberg + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include + +LOG_MODULE_REGISTER(lora_shell, CONFIG_LORA_LOG_LEVEL); + +#define DEFAULT_RADIO_NODE DT_ALIAS(lora0) +BUILD_ASSERT(DT_HAS_NODE_STATUS_OKAY(DEFAULT_RADIO_NODE), + "No default LoRa radio specified in DT"); +#define DEFAULT_RADIO DT_LABEL(DEFAULT_RADIO_NODE) + +static struct lora_modem_config modem_config = { + .frequency = 0, + .bandwidth = BW_125_KHZ, + .datarate = SF_10, + .coding_rate = CR_4_5, + .preamble_len = 8, + .tx_power = 4, +}; + +static const int bw_table[] = { + [BW_125_KHZ] = 125, + [BW_250_KHZ] = 250, + [BW_500_KHZ] = 500, +}; + +static int parse_long(long *out, const struct shell *shell, const char *arg) +{ + char *eptr; + long lval; + + lval = strtol(arg, &eptr, 0); + if (*eptr != '\0') { + shell_error(shell, "'%s' is not an integer", arg); + return -EINVAL; + } + + *out = lval; + return 0; +} + +static int parse_long_range(long *out, const struct shell *shell, + const char *arg, const char *name, long min, + long max) +{ + int ret; + + ret = parse_long(out, shell, arg); + if (ret < 0) { + return ret; + } + + if (*out < min || *out > max) { + shell_error(shell, "Parameter '%s' is out of range. " + "Valid range is %li -- %li.", + name, min, max); + return -EINVAL; + } + + return 0; +} + +static int parse_freq(u32_t *out, const struct shell *shell, const char *arg) +{ + char *eptr; + long long llval; + + llval = strtoll(arg, &eptr, 0); + if (*eptr != '\0') { + shell_error(shell, "Invalid frequency, '%s' is not an integer", + arg); + return -EINVAL; + } + + if (llval < 0 || llval > UINT32_MAX) { + shell_error(shell, "Frequency %lli out of range", llval); + return -EINVAL; + } + + *out = (u32_t)llval; + return 0; +} + +static struct device *get_modem(const struct shell *shell) +{ + struct device *dev; + + dev = device_get_binding(DEFAULT_RADIO); + if (!dev) { + shell_error(shell, "%s Device not found", DEFAULT_RADIO); + return NULL; + } + + return dev; +} + +static struct device *get_configured_modem(const struct shell *shell) +{ + int ret; + struct device *dev; + + dev = get_modem(shell); + if (!dev) { + return NULL; + } + + if (modem_config.frequency == 0) { + shell_error(shell, "No frequency specified."); + return NULL; + } + + ret = lora_config(dev, &modem_config); + if (ret < 0) { + shell_error(shell, "LoRa config failed"); + return NULL; + } + + return dev; +} + +static int lora_conf_dump(const struct shell *shell) +{ + shell_print(shell, DEFAULT_RADIO ":"); + shell_print(shell, " Frequency: %" PRIu32 " Hz", + modem_config.frequency); + shell_print(shell, " TX power: %" PRIi8 " dBm", + modem_config.tx_power); + shell_print(shell, " Bandwidth: %i kHz", + bw_table[modem_config.bandwidth]); + shell_print(shell, " Spreading factor: SF%i", + (int)modem_config.datarate); + shell_print(shell, " Coding rate: 4/%i", + (int)modem_config.coding_rate + 4); + shell_print(shell, " Preamble length: %" PRIu16, + modem_config.preamble_len); + + return 0; +} + +static int lora_conf_set(const struct shell *shell, const char *param, + const char *value) +{ + long lval; + + if (!strcmp("freq", param)) { + if (parse_freq(&modem_config.frequency, shell, value) < 0) { + return -EINVAL; + } + } else if (!strcmp("tx-power", param)) { + if (parse_long_range(&lval, shell, value, + "tx-power", INT8_MIN, INT8_MAX) < 0) { + return -EINVAL; + } + modem_config.tx_power = lval; + } else if (!strcmp("bw", param)) { + if (parse_long_range(&lval, shell, value, + "bw", 0, INT8_MAX) < 0) { + return -EINVAL; + } + switch (lval) { + case 125: + modem_config.bandwidth = BW_125_KHZ; + break; + case 250: + modem_config.bandwidth = BW_250_KHZ; + break; + case 500: + modem_config.bandwidth = BW_500_KHZ; + break; + default: + shell_error(shell, "Invalid bandwidth: %s", lval); + return -EINVAL; + } + } else if (!strcmp("sf", param)) { + if (parse_long_range(&lval, shell, value, "sf", 6, 12) < 0) { + return -EINVAL; + } + modem_config.datarate = SF_6 + (unsigned int)lval - 6; + } else if (!strcmp("cr", param)) { + if (parse_long_range(&lval, shell, value, "cr", 5, 8) < 0) { + return -EINVAL; + } + modem_config.coding_rate = CR_4_5 + (unsigned int)lval - 5; + } else if (!strcmp("pre-len", param)) { + if (parse_long_range(&lval, shell, value, + "pre-len", 0, UINT16_MAX) < 0) { + return -EINVAL; + } + modem_config.preamble_len = lval; + } else { + shell_error(shell, "Unknown parameter '%s'", param); + return -EINVAL; + } + + return 0; +} + +static int cmd_lora_conf(const struct shell *shell, size_t argc, char **argv) +{ + int i; + int ret; + + if (argc < 2) { + return lora_conf_dump(shell); + } + + for (i = 1; i < argc; i += 2) { + if (i + 1 >= argc) { + shell_error(shell, "'%s' expects an argument", + argv[i]); + return -EINVAL; + } + + ret = lora_conf_set(shell, argv[i], argv[i + 1]); + if (ret != 0) { + return ret; + } + } + + return 0; +} + +static int cmd_lora_send(const struct shell *shell, + size_t argc, char **argv) +{ + int ret; + struct device *dev; + + modem_config.tx = true; + dev = get_configured_modem(shell); + if (!dev) { + return -ENODEV; + } + + ret = lora_send(dev, argv[1], strlen(argv[1])); + if (ret < 0) { + shell_error(shell, "LoRa send failed: %i", ret); + return ret; + } + + return 0; +} + +static int cmd_lora_recv(const struct shell *shell, size_t argc, char **argv) +{ + static char buf[0xff]; + struct device *dev; + long timeout = 0; + int ret; + s16_t rssi; + s8_t snr; + + modem_config.tx = false; + dev = get_configured_modem(shell); + if (!dev) { + return -ENODEV; + } + + if (argc >= 2 && parse_long_range(&timeout, shell, argv[1], + "timeout", 0, INT_MAX) < 0) { + return -EINVAL; + } + + ret = lora_recv(dev, buf, sizeof(buf), + timeout ? K_MSEC(timeout) : K_FOREVER, &rssi, &snr); + if (ret < 0) { + shell_error(shell, "LoRa recv failed: %i", ret); + return ret; + } + + shell_hexdump(shell, buf, ret); + shell_print(shell, "RSSI: %" PRIi16 " dBm, SNR:%" PRIi8 " dBm", + rssi, snr); + + return 0; +} + +static int cmd_lora_test_cw(const struct shell *shell, + size_t argc, char **argv) +{ + struct device *dev; + int ret; + u32_t freq; + long power, duration; + + dev = get_modem(shell); + if (!dev) { + return -ENODEV; + } + + if (parse_freq(&freq, shell, argv[1]) < 0 || + parse_long_range(&power, shell, argv[2], + "power", INT8_MIN, INT8_MAX) < 0 || + parse_long_range(&duration, shell, argv[3], + "duration", 0, UINT16_MAX) < 0) { + return -EINVAL; + } + + ret = lora_test_cw(dev, (u32_t)freq, (s8_t)power, (u16_t)duration); + if (ret < 0) { + shell_error(shell, "LoRa test CW failed: %i", ret); + return ret; + } + + return 0; +} + +SHELL_STATIC_SUBCMD_SET_CREATE(sub_lora, + SHELL_CMD(config, NULL, + "Configure the LoRa radio\n" + " Usage: config [freq ] [tx-power ] [bw ] " + "[sf ] [cr ] [pre-len ]\n", + cmd_lora_conf), + SHELL_CMD_ARG(send, NULL, + "Send LoRa packet\n" + " Usage: send ", + cmd_lora_send, 2, 0), + SHELL_CMD_ARG(recv, NULL, + "Receive LoRa packet\n" + " Usage: recv [timeout (ms)]", + cmd_lora_recv, 1, 1), + SHELL_CMD_ARG(test_cw, NULL, + "Send a continuous wave\n" + " Usage: test_cw ", + cmd_lora_test_cw, 4, 0), + SHELL_SUBCMD_SET_END /* Array terminated. */ +); + +SHELL_CMD_REGISTER(lora, &sub_lora, "LoRa commands", NULL);