blob: 7d3775c9d5c352ad2891e81dab09e4e28889dd0f [file] [log] [blame]
/*
* Copyright (c) 2020 Andreas Sandberg
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/drivers/lora.h>
#include <inttypes.h>
#include <zephyr/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_NODE_HAS_STATUS_OKAY(DEFAULT_RADIO_NODE),
"No default LoRa radio specified in DT");
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 *sh, const char *arg)
{
char *eptr;
long lval;
lval = strtol(arg, &eptr, 0);
if (*eptr != '\0') {
shell_error(sh, "'%s' is not an integer", arg);
return -EINVAL;
}
*out = lval;
return 0;
}
static int parse_long_range(long *out, const struct shell *sh,
const char *arg, const char *name, long min,
long max)
{
int ret;
ret = parse_long(out, sh, arg);
if (ret < 0) {
return ret;
}
if (*out < min || *out > max) {
shell_error(sh, "Parameter '%s' is out of range. "
"Valid range is %li -- %li.",
name, min, max);
return -EINVAL;
}
return 0;
}
static int parse_freq(uint32_t *out, const struct shell *sh, const char *arg)
{
char *eptr;
unsigned long val;
val = strtoul(arg, &eptr, 0);
if (*eptr != '\0') {
shell_error(sh, "Invalid frequency, '%s' is not an integer",
arg);
return -EINVAL;
}
if (val == ULONG_MAX) {
shell_error(sh, "Frequency %s out of range", arg);
return -EINVAL;
}
*out = (uint32_t)val;
return 0;
}
static const struct device *get_modem(const struct shell *sh)
{
const struct device *dev;
dev = DEVICE_DT_GET(DEFAULT_RADIO_NODE);
if (!device_is_ready(dev)) {
shell_error(sh, "LORA Radio device not ready");
return NULL;
}
return dev;
}
static const struct device *get_configured_modem(const struct shell *sh)
{
int ret;
const struct device *dev;
dev = get_modem(sh);
if (!dev) {
return NULL;
}
if (modem_config.frequency == 0) {
shell_error(sh, "No frequency specified.");
return NULL;
}
ret = lora_config(dev, &modem_config);
if (ret < 0) {
shell_error(sh, "LoRa config failed");
return NULL;
}
return dev;
}
static int lora_conf_dump(const struct shell *sh)
{
shell_print(sh, " Frequency: %" PRIu32 " Hz",
modem_config.frequency);
shell_print(sh, " TX power: %" PRIi8 " dBm",
modem_config.tx_power);
shell_print(sh, " Bandwidth: %i kHz",
bw_table[modem_config.bandwidth]);
shell_print(sh, " Spreading factor: SF%i",
(int)modem_config.datarate);
shell_print(sh, " Coding rate: 4/%i",
(int)modem_config.coding_rate + 4);
shell_print(sh, " Preamble length: %" PRIu16,
modem_config.preamble_len);
return 0;
}
static int lora_conf_set(const struct shell *sh, const char *param,
const char *value)
{
long lval;
if (!strcmp("freq", param)) {
if (parse_freq(&modem_config.frequency, sh, value) < 0) {
return -EINVAL;
}
} else if (!strcmp("tx-power", param)) {
if (parse_long_range(&lval, sh, 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, sh, value,
"bw", 0, INT16_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(sh, "Invalid bandwidth: %ld", lval);
return -EINVAL;
}
} else if (!strcmp("sf", param)) {
if (parse_long_range(&lval, sh, 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, sh, 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, sh, value,
"pre-len", 0, UINT16_MAX) < 0) {
return -EINVAL;
}
modem_config.preamble_len = lval;
} else {
shell_error(sh, "Unknown parameter '%s'", param);
return -EINVAL;
}
return 0;
}
static int cmd_lora_conf(const struct shell *sh, size_t argc, char **argv)
{
int i;
int ret;
if (argc < 2) {
return lora_conf_dump(sh);
}
for (i = 1; i < argc; i += 2) {
if (i + 1 >= argc) {
shell_error(sh, "'%s' expects an argument",
argv[i]);
return -EINVAL;
}
ret = lora_conf_set(sh, argv[i], argv[i + 1]);
if (ret != 0) {
return ret;
}
}
return 0;
}
static int cmd_lora_send(const struct shell *sh,
size_t argc, char **argv)
{
int ret;
const struct device *dev;
modem_config.tx = true;
dev = get_configured_modem(sh);
if (!dev) {
return -ENODEV;
}
ret = lora_send(dev, argv[1], strlen(argv[1]));
if (ret < 0) {
shell_error(sh, "LoRa send failed: %i", ret);
return ret;
}
return 0;
}
static int cmd_lora_recv(const struct shell *sh, size_t argc, char **argv)
{
static char buf[0xff];
const struct device *dev;
long timeout = 0;
int ret;
int16_t rssi;
int8_t snr;
modem_config.tx = false;
dev = get_configured_modem(sh);
if (!dev) {
return -ENODEV;
}
if (argc >= 2 && parse_long_range(&timeout, sh, 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(sh, "LoRa recv failed: %i", ret);
return ret;
}
shell_hexdump(sh, buf, ret);
shell_print(sh, "RSSI: %" PRIi16 " dBm, SNR:%" PRIi8 " dBm",
rssi, snr);
return 0;
}
static int cmd_lora_test_cw(const struct shell *sh,
size_t argc, char **argv)
{
const struct device *dev;
int ret;
uint32_t freq;
long power, duration;
dev = get_modem(sh);
if (!dev) {
return -ENODEV;
}
if (parse_freq(&freq, sh, argv[1]) < 0 ||
parse_long_range(&power, sh, argv[2],
"power", INT8_MIN, INT8_MAX) < 0 ||
parse_long_range(&duration, sh, argv[3],
"duration", 0, UINT16_MAX) < 0) {
return -EINVAL;
}
ret = lora_test_cw(dev, (uint32_t)freq, (int8_t)power, (uint16_t)duration);
if (ret < 0) {
shell_error(sh, "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);