You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
334 lines
7.1 KiB
334 lines
7.1 KiB
/* |
|
* 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, |
|
SHELL_HELP("Configure the LoRa radio", |
|
"[freq <Hz>] [tx-power <dBm>] [bw <kHz>] [sf <int>] [cr <int>] " |
|
"[pre-len <int>]"), |
|
cmd_lora_conf), |
|
SHELL_CMD_ARG(send, NULL, |
|
SHELL_HELP("Send a LoRa packet", "<data>"), |
|
cmd_lora_send, 2, 0), |
|
SHELL_CMD_ARG(recv, NULL, |
|
SHELL_HELP("Receive a LoRa packet", "[timeout (ms)]"), |
|
cmd_lora_recv, 1, 1), |
|
SHELL_CMD_ARG(test_cw, NULL, |
|
SHELL_HELP("Send a continuous wave", |
|
"<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);
|
|
|