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 <andreas@sandberg.pp.se>
This commit is contained in:
parent
75b5359d75
commit
d8de33e966
3 changed files with 344 additions and 0 deletions
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
337
drivers/lora/shell.c
Normal file
337
drivers/lora/shell.c
Normal file
|
@ -0,0 +1,337 @@
|
|||
/*
|
||||
* Copyright (c) 2020 Andreas Sandberg
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <drivers/lora.h>
|
||||
#include <inttypes.h>
|
||||
#include <shell/shell.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
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 <Hz>] [tx-power <dBm>] [bw <kHz>] "
|
||||
"[sf <int>] [cr <int>] [pre-len <int>]\n",
|
||||
cmd_lora_conf),
|
||||
SHELL_CMD_ARG(send, NULL,
|
||||
"Send LoRa packet\n"
|
||||
" Usage: send <data>",
|
||||
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 <freq (Hz)> <power (dBm)> <duration (s)>",
|
||||
cmd_lora_test_cw, 4, 0),
|
||||
SHELL_SUBCMD_SET_END /* Array terminated. */
|
||||
);
|
||||
|
||||
SHELL_CMD_REGISTER(lora, &sub_lora, "LoRa commands", NULL);
|
Loading…
Add table
Add a link
Reference in a new issue