mirror of
https://github.com/meshtastic/firmware.git
synced 2026-11-07 23:04:49 -05:00
Merge branch 'develop' into ccr-e331c35d-ls1qhv
This commit is contained in:
6 files changed
+579
-17
No files matched your search
+1
-1
@@ -134,7 +134,7 @@ lib_deps =
|
||||
[radiolib_base]
|
||||
lib_deps =
|
||||
# renovate: datasource=github-tags depName=RadioLib packageName=jgromes/RadioLib
|
||||
https://github.com/jgromes/RadioLib/archive/510e00cfb05bbc3c2b7b524262785454944adb6e.zip
|
||||
https://github.com/jgromes/RadioLib/archive/5878f990bdb4c95c75494a21562c0c5a49659c6f.zip
|
||||
|
||||
[device-ui_base]
|
||||
lib_deps =
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
#include <functional>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
class RecordingHal : public RadioLibHal
|
||||
@@ -15,7 +17,8 @@ class RecordingHal : public RadioLibHal
|
||||
// A scripted answer, chosen by the leading bytes (the opcode) of what the driver sent.
|
||||
struct Reply {
|
||||
std::vector<uint8_t> prefix;
|
||||
bool nextTransaction; // answer the transaction after the match: LRxxxx reads reply in a second one
|
||||
uint8_t after; // answer the after-th transaction following the match, 0 = the match itself.
|
||||
// LRxxxx reads reply in a second transaction; prefix-less ones (IRQ status) need more
|
||||
std::vector<uint8_t> head;
|
||||
uint8_t fill; // every byte after head
|
||||
};
|
||||
@@ -32,7 +35,9 @@ class RecordingHal : public RadioLibHal
|
||||
registerEcho = false;
|
||||
memset(registers, 0, sizeof(registers));
|
||||
defaultFill = fill;
|
||||
pending = nullptr;
|
||||
pending.clear();
|
||||
checks.clear();
|
||||
irqPin = RADIOLIB_NC;
|
||||
nowUs = 0;
|
||||
}
|
||||
|
||||
@@ -44,11 +49,22 @@ class RecordingHal : public RadioLibHal
|
||||
bool registerEcho = false;
|
||||
uint8_t registers[128] = {};
|
||||
|
||||
void reply(std::vector<uint8_t> prefix, uint8_t fill, std::vector<uint8_t> head = {}, bool nextTransaction = false)
|
||||
void reply(std::vector<uint8_t> prefix, uint8_t fill, std::vector<uint8_t> head = {}, uint8_t after = 0)
|
||||
{
|
||||
replies.push_back({std::move(prefix), nextTransaction, std::move(head), fill});
|
||||
replies.push_back({std::move(prefix), after, std::move(head), fill});
|
||||
}
|
||||
|
||||
// Chip model: a command matching `prefix` whose bytes `accepts` refuses gets `status` in the first
|
||||
// byte of the next transaction, where LRxxxx paranoid SPI reads a verified write's status.
|
||||
using Accepts = std::function<bool(const std::vector<uint8_t> &)>;
|
||||
void rejectUnless(std::vector<uint8_t> prefix, Accepts accepts, uint8_t status)
|
||||
{
|
||||
checks.push_back({std::move(prefix), std::move(accepts), status});
|
||||
}
|
||||
|
||||
// Reads high, so RadioLib's blocking scanChannel() sees its IRQ and returns; every other pin is low.
|
||||
uint32_t irqPin = RADIOLIB_NC;
|
||||
|
||||
// Transactions whose leading bytes are `prefix`.
|
||||
size_t count(const std::vector<uint8_t> &prefix) const
|
||||
{
|
||||
@@ -76,7 +92,7 @@ class RecordingHal : public RadioLibHal
|
||||
|
||||
void pinMode(uint32_t, uint32_t) override {}
|
||||
void digitalWrite(uint32_t, uint32_t) override {}
|
||||
uint32_t digitalRead(uint32_t) override { return 0; } // BUSY low
|
||||
uint32_t digitalRead(uint32_t pin) override { return irqPin != RADIOLIB_NC && pin == irqPin; } // BUSY low, irqPin high
|
||||
void attachInterrupt(uint32_t, void (*)(void), uint32_t) override {}
|
||||
void detachInterrupt(uint32_t) override {}
|
||||
void delay(RadioLibTime_t ms) override { nowUs += ms * 1000; }
|
||||
@@ -97,23 +113,43 @@ class RecordingHal : public RadioLibHal
|
||||
echoRegisters(out, len, in);
|
||||
return;
|
||||
}
|
||||
const Reply *r = pending;
|
||||
pending = nullptr;
|
||||
// A due deferred reply wins over a direct match, and the first due one wins over any other: each
|
||||
// targets one transaction, so two due together is a test bug. Copies, so reply() cannot invalidate one.
|
||||
Reply due{};
|
||||
const Reply *r = nullptr;
|
||||
for (auto it = pending.begin(); it != pending.end();) {
|
||||
if (--it->second == 0) {
|
||||
if (!r)
|
||||
due = it->first, r = &due;
|
||||
it = pending.erase(it);
|
||||
} else {
|
||||
++it;
|
||||
}
|
||||
}
|
||||
for (const auto &c : replies) {
|
||||
if (!startsWith(out, len, c.prefix))
|
||||
continue;
|
||||
if (c.nextTransaction)
|
||||
pending = &c;
|
||||
if (c.after)
|
||||
pending.emplace_back(c, c.after);
|
||||
else if (!r)
|
||||
r = &c;
|
||||
}
|
||||
for (const auto &c : checks)
|
||||
if (startsWith(out, len, c.prefix) && !c.accepts(transactions.back()))
|
||||
pending.emplace_back(Reply{{}, 1, {c.status}, defaultFill}, 1);
|
||||
for (size_t i = 0; i < len; i++)
|
||||
in[i] = !r ? defaultFill : (i < r->head.size() ? r->head[i] : r->fill);
|
||||
}
|
||||
|
||||
private:
|
||||
uint8_t defaultFill;
|
||||
const Reply *pending = nullptr;
|
||||
std::vector<std::pair<Reply, uint8_t>> pending; // deferred replies, transactions still to wait
|
||||
struct Check {
|
||||
std::vector<uint8_t> prefix;
|
||||
Accepts accepts;
|
||||
uint8_t status;
|
||||
};
|
||||
std::vector<Check> checks;
|
||||
RadioLibTime_t nowUs = 0;
|
||||
|
||||
static bool startsWith(const uint8_t *data, size_t len, const std::vector<uint8_t> &prefix)
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
// so it goes in the data byte only.
|
||||
#define LR11X0_RADIO(hal) \
|
||||
RecordingHal &hal = freshHal(); \
|
||||
hal.reply(op16(RADIOLIB_LR11X0_CMD_GET_PACKET_TYPE), 0x04, {0x04, RADIOLIB_LR11X0_PACKET_TYPE_LORA}, true); \
|
||||
hal.reply(op16(RADIOLIB_LR11X0_CMD_GET_PACKET_TYPE), 0x04, {0x04, RADIOLIB_LR11X0_PACKET_TYPE_LORA}, 1); \
|
||||
Module mod(&hal, 1, RADIOLIB_NC, RADIOLIB_NC, 2); \
|
||||
LR1121 radio(&mod)
|
||||
|
||||
@@ -70,6 +70,82 @@ static void test_lr11x0_other_setters_and_modes_succeed()
|
||||
TEST_ASSERT_TRUE(hal.count(op16(RADIOLIB_LR11X0_CMD_SET_RX)) >= 1);
|
||||
}
|
||||
|
||||
// --- CAD: LR11x0Interface::isChannelActive() -> startChannelScan(cfg) -> startCad() ---
|
||||
//
|
||||
// SetCadParams (0x020D), 9 bytes: [0-1] opcode [2] symNum [3] detPeak [4] detMin [5] exit mode
|
||||
// [6-8] timeout, 24-bit big-endian. Unlike the LR2021 (lr2021_tests.h), RadioLib's LR11x0 exit
|
||||
// defines are already the chip's values (STBY_RC 0x00, RX 0x01, LBT 0x10), so these pin them against
|
||||
// a regression, e.g. an LR2021 "fix" copied across the LRxxxx family the wrong way round.
|
||||
|
||||
static ChannelScanConfig_t lr11x0CadConfig(uint8_t exitMode = RADIOLIB_LR11X0_CAD_PARAM_DEFAULT)
|
||||
{
|
||||
return {.cad = {.symNum = RADIOLIB_LR11X0_CAD_PARAM_DEFAULT,
|
||||
.detPeak = RADIOLIB_LR11X0_CAD_PARAM_DEFAULT,
|
||||
.detMin = RADIOLIB_LR11X0_CAD_PARAM_DEFAULT,
|
||||
.exitMode = exitMode,
|
||||
.timeout = 0,
|
||||
.irqFlags = RADIOLIB_IRQ_CAD_DEFAULT_FLAGS,
|
||||
.irqMask = RADIOLIB_IRQ_CAD_DEFAULT_MASK}};
|
||||
}
|
||||
|
||||
// As on the LR2021, startCad() indexes det_peak by SF - 5 and the SF starts at 0 without begin().
|
||||
static void lr11x0SetSf(RecordingHal &hal, LR1121 &radio, uint8_t sf)
|
||||
{
|
||||
TEST_ASSERT_EQUAL_INT16(RADIOLIB_ERR_NONE, radio.setSpreadingFactor(sf));
|
||||
hal.transactions.clear();
|
||||
}
|
||||
|
||||
static const std::vector<uint8_t> &lr11x0CadParams(const RecordingHal &hal)
|
||||
{
|
||||
const auto *t = hal.last(op16(RADIOLIB_LR11X0_CMD_SET_CAD_PARAMS));
|
||||
TEST_ASSERT_NOT_NULL_MESSAGE(t, "no SetCadParams sent");
|
||||
TEST_ASSERT_EQUAL_UINT32(9, t->size());
|
||||
return *t;
|
||||
}
|
||||
|
||||
static void test_lr11x0_cad_exit_defines_match_datasheet()
|
||||
{
|
||||
TEST_ASSERT_EQUAL_UINT8(0x00, RADIOLIB_LR11X0_CAD_EXIT_MODE_STBY_RC);
|
||||
TEST_ASSERT_EQUAL_UINT8(0x01, RADIOLIB_LR11X0_CAD_EXIT_MODE_RX);
|
||||
TEST_ASSERT_EQUAL_UINT8(0x10, RADIOLIB_LR11X0_CAD_EXIT_MODE_LBT);
|
||||
}
|
||||
|
||||
// develop's config at SF9: the default exit is standby (0x00), the byte LR11x0Interface sends today.
|
||||
static void test_lr11x0_cad_default_config_frame()
|
||||
{
|
||||
LR11X0_RADIO(hal);
|
||||
lr11x0SetSf(hal, radio, 9);
|
||||
TEST_ASSERT_EQUAL_INT16(RADIOLIB_ERR_NONE, radio.startChannelScan(lr11x0CadConfig()));
|
||||
const auto &t = lr11x0CadParams(hal);
|
||||
TEST_ASSERT_EQUAL_UINT8(2, t[2]); // symNum: sentinel -> 2
|
||||
TEST_ASSERT_EQUAL_UINT8(55, t[3]); // detPeak, SF9 entry of RadioLib's table
|
||||
TEST_ASSERT_EQUAL_UINT8(10, t[4]); // detMin
|
||||
TEST_ASSERT_EQUAL_UINT8(0x00, t[5]); // STBY_RC
|
||||
const uint8_t noTimeout[] = {0, 0, 0};
|
||||
TEST_ASSERT_EQUAL_UINT8_ARRAY(noTimeout, t.data() + 6, 3);
|
||||
TEST_ASSERT_EQUAL_UINT32(1, hal.count(op16(RADIOLIB_LR11X0_CMD_SET_CAD)));
|
||||
}
|
||||
|
||||
// The RX exit define reaches the chip as 0x01, CAD-to-RX, the byte a CAD-to-RX handoff relies on.
|
||||
static void test_lr11x0_cad_rx_exit_byte_sent()
|
||||
{
|
||||
LR11X0_RADIO(hal);
|
||||
lr11x0SetSf(hal, radio, 9);
|
||||
TEST_ASSERT_EQUAL_INT16(RADIOLIB_ERR_NONE, radio.startChannelScan(lr11x0CadConfig(RADIOLIB_LR11X0_CAD_EXIT_MODE_RX)));
|
||||
TEST_ASSERT_EQUAL_UINT8(0x01, lr11x0CadParams(hal)[5]);
|
||||
}
|
||||
|
||||
// A refused SetCadParams (CMD_PERR) returns -706 and starts no CAD, the same contract as the LR2021:
|
||||
// LR11x0Interface::isChannelActive() also reads any error but WRONG_MODEM as a free channel.
|
||||
static void test_lr11x0_cad_params_rejected_returns_spi_cmd_invalid()
|
||||
{
|
||||
LR11X0_RADIO(hal);
|
||||
lr11x0SetSf(hal, radio, 9);
|
||||
hal.reply(op16(RADIOLIB_LR11X0_CMD_SET_CAD_PARAMS), 0x04, {0x02}, 1);
|
||||
TEST_ASSERT_EQUAL_INT16(RADIOLIB_ERR_SPI_CMD_INVALID, radio.startChannelScan(lr11x0CadConfig()));
|
||||
TEST_ASSERT_EQUAL_UINT32(0, hal.count(op16(RADIOLIB_LR11X0_CMD_SET_CAD)));
|
||||
}
|
||||
|
||||
// Grows here - each needs replies scripted per opcode:
|
||||
// begin(): getVersion() device-type check
|
||||
// updateFirmware(): enter bootloader, report RADIOLIB_LR11X0_DEVICE_BOOT, then normal. Pins every
|
||||
@@ -77,7 +153,6 @@ static void test_lr11x0_other_setters_and_modes_succeed()
|
||||
// chunk in 7.8.0) and a failed chunk being reported (its result is currently dropped)
|
||||
// readData() / getPacketLength(): a chip-reported length longer than the caller's buffer
|
||||
// getRSSI() / getSNR() / getPacketStatus(): decoding of known reply bytes
|
||||
// scanChannel(): the CAD parameters sent
|
||||
|
||||
static void runLr11x0Tests()
|
||||
{
|
||||
@@ -86,4 +161,8 @@ static void runLr11x0Tests()
|
||||
RUN_TEST(test_lr11x0_coding_rate_long_interleaves_except_4_7);
|
||||
RUN_TEST(test_lr11x0_packet_setters_send_packet_params);
|
||||
RUN_TEST(test_lr11x0_other_setters_and_modes_succeed);
|
||||
RUN_TEST(test_lr11x0_cad_exit_defines_match_datasheet);
|
||||
RUN_TEST(test_lr11x0_cad_default_config_frame);
|
||||
RUN_TEST(test_lr11x0_cad_rx_exit_byte_sent);
|
||||
RUN_TEST(test_lr11x0_cad_params_rejected_returns_spi_cmd_invalid);
|
||||
}
|
||||
@@ -29,7 +29,7 @@ static void lr2021Tune(RecordingHal &hal, LR2021 &radio)
|
||||
// As LR11x0: the packet type comes back in the data byte of the next transaction. 0x00 is LoRa.
|
||||
#define LR2021_RADIO(hal) \
|
||||
RecordingHal &hal = freshHal(); \
|
||||
hal.reply(op16(RADIOLIB_LR2021_CMD_GET_PACKET_TYPE), 0x04, {0x04, RADIOLIB_LR2021_PACKET_TYPE_LORA}, true); \
|
||||
hal.reply(op16(RADIOLIB_LR2021_CMD_GET_PACKET_TYPE), 0x04, {0x04, RADIOLIB_LR2021_PACKET_TYPE_LORA}, 1); \
|
||||
Module mod(&hal, 1, RADIOLIB_NC, RADIOLIB_NC, 2); \
|
||||
LR2021 radio(&mod); \
|
||||
lr2021Tune(hal, radio)
|
||||
@@ -135,13 +135,301 @@ static void test_lr2021_other_setters_and_modes_succeed()
|
||||
TEST_ASSERT_TRUE(hal.count(op16(RADIOLIB_LR2021_CMD_SET_RX)) >= 1);
|
||||
}
|
||||
|
||||
// --- CAD: LR20x0Interface::isChannelActive() -> startChannelScan(cfg) -> startCad() ---
|
||||
//
|
||||
// LoRa CAD on the LR2021 is SetLoraCadParams (0x0227) + SetLoraCad (0x0228), 9 bytes:
|
||||
// [0-1] opcode [2] nb_symbols [3] rfu(7:5) pbl_any(4) pnr_delta(3:0) [4] exit mode
|
||||
// [5-7] timeout, 24-bit big-endian [8] det_peak & 0x7F (LR20xx DS 2.2, Table 6-17)
|
||||
// Exit mode for this command is CAD_ONLY 0x00, CAD_RX 0x01, CAD_LBT 0x10 (Table 6-18). RadioLib's
|
||||
// RADIOLIB_LR2021_CAD_EXIT_MODE_* hold the SetCadParams (0x021B) values instead, so its _RX is 0x02.
|
||||
// On hardware the chip rejects 0x02 with CMD_PERR, scanChannel() returns -706, and
|
||||
// LR20x0Interface::isChannelActive() reads that as a free channel, so a node sending it would transmit over
|
||||
// live frames (16/16 trials on the bench). develop does not: it sends 0x01 from its own local
|
||||
// CAD_EXIT_MODE_RX, which is what these tests keep it doing.
|
||||
//
|
||||
// Never call scanChannel() here: it spins on digitalRead(irq), which RecordingHal holds at 0.
|
||||
|
||||
// Every field at its default sentinel, as LR20x0Interface::isChannelActive() builds it.
|
||||
static ChannelScanConfig_t lr2021CadConfig(uint8_t exitMode = RADIOLIB_LR2021_CAD_PARAM_DEFAULT,
|
||||
uint8_t detPeak = RADIOLIB_LR2021_CAD_PARAM_DEFAULT,
|
||||
uint8_t symNum = RADIOLIB_LR2021_CAD_PARAM_DEFAULT, RadioLibTime_t timeout = 0)
|
||||
{
|
||||
return {.cad = {.symNum = symNum,
|
||||
.detPeak = detPeak,
|
||||
.detMin = RADIOLIB_LR2021_CAD_PARAM_DEFAULT,
|
||||
.exitMode = exitMode,
|
||||
.timeout = timeout,
|
||||
.irqFlags = RADIOLIB_IRQ_CAD_DEFAULT_FLAGS,
|
||||
.irqMask = RADIOLIB_IRQ_CAD_DEFAULT_MASK}};
|
||||
}
|
||||
|
||||
// LRxxxx::spreadingFactor starts at 0 and startCad() indexes its det_peak table by SF - 5, so a
|
||||
// default-detPeak scan before any setSpreadingFactor() reads out of bounds. begin() would set it.
|
||||
static void lr2021SetSf(RecordingHal &hal, LR2021 &radio, uint8_t sf)
|
||||
{
|
||||
TEST_ASSERT_EQUAL_INT16(RADIOLIB_ERR_NONE, radio.setSpreadingFactor(sf));
|
||||
hal.transactions.clear();
|
||||
}
|
||||
|
||||
// The SetLoraCadParams frame of a scan that must have reached the chip.
|
||||
static const std::vector<uint8_t> &lr2021CadParams(const RecordingHal &hal)
|
||||
{
|
||||
const auto *t = hal.last(op16(RADIOLIB_LR2021_CMD_SET_LORA_CAD_PARAMS));
|
||||
TEST_ASSERT_NOT_NULL_MESSAGE(t, "no SetLoraCadParams sent");
|
||||
TEST_ASSERT_EQUAL_UINT32(9, t->size());
|
||||
return *t;
|
||||
}
|
||||
|
||||
static void test_lr2021_cad_exit_defines_are_setcadparams_values()
|
||||
{
|
||||
// These are the SetCadParams (0x021B) values and correct for that command; they must not be
|
||||
// "fixed" to the LoRa ones. The bug is startCad() sending them to 0x0227.
|
||||
TEST_ASSERT_EQUAL_UINT8(0x00, RADIOLIB_LR2021_CAD_EXIT_MODE_FALLBACK);
|
||||
TEST_ASSERT_EQUAL_UINT8(0x01, RADIOLIB_LR2021_CAD_EXIT_MODE_TX);
|
||||
TEST_ASSERT_EQUAL_UINT8(0x02, RADIOLIB_LR2021_CAD_EXIT_MODE_RX);
|
||||
}
|
||||
|
||||
// develop's config, SF9. A default exit mode must reach the chip as CAD_ONLY (0x00) on every pin:
|
||||
// it is the only exit byte develop sends, and the one that works on hardware today.
|
||||
static void test_lr2021_cad_default_config_frame()
|
||||
{
|
||||
LR2021_RADIO(hal);
|
||||
lr2021SetSf(hal, radio, 9);
|
||||
TEST_ASSERT_EQUAL_INT16(RADIOLIB_ERR_NONE, radio.startChannelScan(lr2021CadConfig()));
|
||||
const auto &t = lr2021CadParams(hal);
|
||||
TEST_ASSERT_EQUAL_UINT8(2, t[2]); // nb_symbols: sentinel -> 2
|
||||
TEST_ASSERT_EQUAL_UINT8(0x00, t[3]); // pbl_any 0, pnr_delta 0
|
||||
TEST_ASSERT_EQUAL_UINT8(0x00, t[4]); // CAD_ONLY
|
||||
const uint8_t noTimeout[] = {0, 0, 0};
|
||||
TEST_ASSERT_EQUAL_UINT8_ARRAY(noTimeout, t.data() + 5, 3);
|
||||
TEST_ASSERT_EQUAL_UINT8(58, t[8]); // det_peak, SF9 row
|
||||
}
|
||||
|
||||
// The exit byte is passed through unmasked and unmapped, whatever the caller asks for. 0x02 is the
|
||||
// byte the LBT branches send today via RADIOLIB_LR2021_CAD_EXIT_MODE_RX, and the one the chip rejects.
|
||||
static void test_lr2021_cad_exit_byte_sent_verbatim()
|
||||
{
|
||||
const uint8_t modes[] = {0x00, 0x01, 0x10, RADIOLIB_LR2021_CAD_EXIT_MODE_RX};
|
||||
for (uint8_t mode : modes) {
|
||||
LR2021_RADIO(hal);
|
||||
lr2021SetSf(hal, radio, 9);
|
||||
TEST_ASSERT_EQUAL_INT16(RADIOLIB_ERR_NONE, radio.startChannelScan(lr2021CadConfig(mode)));
|
||||
TEST_ASSERT_EQUAL_UINT8(mode, lr2021CadParams(hal)[4]);
|
||||
}
|
||||
}
|
||||
|
||||
// RadioLib's 2-symbol det_peak row (DS Table 6-19), used when the caller passes the sentinel.
|
||||
static void test_lr2021_cad_default_det_peak_per_sf()
|
||||
{
|
||||
const uint8_t expected[] = {56, 56, 56, 58, 58, 60, 64, 68};
|
||||
for (uint8_t sf = 5; sf <= 12; sf++) {
|
||||
LR2021_RADIO(hal);
|
||||
lr2021SetSf(hal, radio, sf);
|
||||
TEST_ASSERT_EQUAL_INT16(RADIOLIB_ERR_NONE, radio.startChannelScan(lr2021CadConfig()));
|
||||
TEST_ASSERT_EQUAL_UINT8_MESSAGE(expected[sf - 5], lr2021CadParams(hal)[8], "det_peak for this SF");
|
||||
}
|
||||
}
|
||||
|
||||
// Pins today: the default det_peak is the 2-symbol value whatever nb_symbols is. Table 6-19 gives
|
||||
// 51 for 4 symbols at SF7, so a caller scanning more than 2 symbols must pass its own det_peak.
|
||||
static void test_lr2021_cad_default_det_peak_ignores_symNum()
|
||||
{
|
||||
LR2021_RADIO(hal);
|
||||
lr2021SetSf(hal, radio, 7);
|
||||
TEST_ASSERT_EQUAL_INT16(RADIOLIB_ERR_NONE, radio.startChannelScan(lr2021CadConfig(RADIOLIB_LR2021_CAD_PARAM_DEFAULT,
|
||||
RADIOLIB_LR2021_CAD_PARAM_DEFAULT, 4)));
|
||||
const auto &t = lr2021CadParams(hal);
|
||||
TEST_ASSERT_EQUAL_UINT8(4, t[2]);
|
||||
TEST_ASSERT_EQUAL_UINT8(56, t[8]);
|
||||
}
|
||||
|
||||
// det_peak is 7 bits; 0xFF is the sentinel, so it can never be sent as itself.
|
||||
static void test_lr2021_cad_explicit_det_peak_sent_masked()
|
||||
{
|
||||
const struct {
|
||||
uint8_t in, sent;
|
||||
} cases[] = {{60, 60}, {0x85, 0x05}, {RADIOLIB_LR2021_CAD_PARAM_DEFAULT, 56}};
|
||||
for (const auto &c : cases) {
|
||||
LR2021_RADIO(hal);
|
||||
lr2021SetSf(hal, radio, 7);
|
||||
TEST_ASSERT_EQUAL_INT16(RADIOLIB_ERR_NONE,
|
||||
radio.startChannelScan(lr2021CadConfig(RADIOLIB_LR2021_CAD_PARAM_DEFAULT, c.in)));
|
||||
TEST_ASSERT_EQUAL_UINT8(c.sent, lr2021CadParams(hal)[8]);
|
||||
}
|
||||
}
|
||||
|
||||
// fastCad sets pnr_delta 8; pbl_any (bit 4, "preamble only") stays off either way, which RadioLib
|
||||
// does on purpose: the datasheet calls it unreliable.
|
||||
static void test_lr2021_fast_cad_sets_pnr_delta_only()
|
||||
{
|
||||
LR2021_RADIO(hal);
|
||||
lr2021SetSf(hal, radio, 9);
|
||||
radio.fastCad = true;
|
||||
TEST_ASSERT_EQUAL_INT16(RADIOLIB_ERR_NONE, radio.startChannelScan(lr2021CadConfig()));
|
||||
TEST_ASSERT_EQUAL_UINT8(RADIOLIB_LR2021_LORA_CAD_PNR_DELTA_FAST, lr2021CadParams(hal)[3]);
|
||||
}
|
||||
|
||||
// Pins RadioLib's arithmetic: timeout_us / 30.52 (32.768 kHz ticks). The timeout only matters under
|
||||
// CAD_RX / CAD_LBT. DS 2.2 6.3.11 states the unit as "periods of 32 MHz crystal oscillator", which
|
||||
// this does not obviously match; if this test breaks, check the new unit on hardware before
|
||||
// updating it.
|
||||
static void test_lr2021_cad_timeout_in_30_52_us_steps()
|
||||
{
|
||||
LR2021_RADIO(hal);
|
||||
lr2021SetSf(hal, radio, 9);
|
||||
TEST_ASSERT_EQUAL_INT16(RADIOLIB_ERR_NONE,
|
||||
radio.startChannelScan(lr2021CadConfig(0x01, RADIOLIB_LR2021_CAD_PARAM_DEFAULT,
|
||||
RADIOLIB_LR2021_CAD_PARAM_DEFAULT, 100000)));
|
||||
const uint8_t ticks[] = {0x00, 0x0C, 0xCC}; // 3276
|
||||
TEST_ASSERT_EQUAL_UINT8_ARRAY(ticks, lr2021CadParams(hal).data() + 5, 3);
|
||||
}
|
||||
|
||||
// The LoRa CAD pair, never the RSSI-based SetCadParams/SetCad (0x021B/0x021C), in this order.
|
||||
// The packet-type reads and status checks are opcode-less (NOP) transactions and are skipped.
|
||||
static void test_lr2021_cad_command_sequence()
|
||||
{
|
||||
LR2021_RADIO(hal);
|
||||
lr2021SetSf(hal, radio, 9);
|
||||
TEST_ASSERT_EQUAL_INT16(RADIOLIB_ERR_NONE, radio.startChannelScan(lr2021CadConfig()));
|
||||
std::vector<uint16_t> ops;
|
||||
for (const auto &t : hal.transactions)
|
||||
if (t.size() >= 2 && (t[0] | t[1]))
|
||||
ops.push_back(static_cast<uint16_t>(t[0] << 8 | t[1]));
|
||||
const uint16_t expected[] = {RADIOLIB_LR2021_CMD_GET_PACKET_TYPE, RADIOLIB_LR2021_CMD_SET_STANDBY,
|
||||
RADIOLIB_LR2021_CMD_SET_DIO_IRQ_CONFIG, RADIOLIB_LR2021_CMD_CLEAR_IRQ,
|
||||
RADIOLIB_LR2021_CMD_GET_PACKET_TYPE, RADIOLIB_LR2021_CMD_SET_LORA_CAD_PARAMS,
|
||||
RADIOLIB_LR2021_CMD_SET_LORA_CAD};
|
||||
TEST_ASSERT_EQUAL_UINT32(sizeof(expected) / sizeof(expected[0]), ops.size());
|
||||
TEST_ASSERT_EQUAL_HEX16_ARRAY(expected, ops.data(), ops.size());
|
||||
TEST_ASSERT_EQUAL_UINT32(0, hal.count(op16(RADIOLIB_LR2021_CMD_SET_CAD_PARAMS)));
|
||||
TEST_ASSERT_EQUAL_UINT32(0, hal.count(op16(RADIOLIB_LR2021_CMD_SET_CAD)));
|
||||
}
|
||||
|
||||
// CAD_DONE and CAD_DETECTED routed to DIO5, which is LR2021::irqDioNum's default in RadioLib. The firmware
|
||||
// sets irqDioNum per variant (LR20x0Interface::init()), so this pins RadioLib's default, not that line.
|
||||
static void test_lr2021_cad_irq_routed_to_dio()
|
||||
{
|
||||
LR2021_RADIO(hal);
|
||||
lr2021SetSf(hal, radio, 9);
|
||||
TEST_ASSERT_EQUAL_INT16(RADIOLIB_ERR_NONE, radio.startChannelScan(lr2021CadConfig()));
|
||||
const auto *t = hal.first(op16(RADIOLIB_LR2021_CMD_SET_DIO_IRQ_CONFIG));
|
||||
TEST_ASSERT_NOT_NULL(t);
|
||||
TEST_ASSERT_EQUAL_UINT32(7, t->size()); // opcode(2) + dio + irq(4)
|
||||
TEST_ASSERT_EQUAL_UINT8(5, (*t)[2]);
|
||||
const uint32_t irq = (uint32_t)(*t)[3] << 24 | (uint32_t)(*t)[4] << 16 | (uint32_t)(*t)[5] << 8 | (*t)[6];
|
||||
TEST_ASSERT_EQUAL_HEX32(RADIOLIB_LR2021_IRQ_CAD_DONE | RADIOLIB_LR2021_IRQ_CAD_DETECTED, irq);
|
||||
}
|
||||
|
||||
static void test_lr2021_cad_wrong_modem_sends_no_cad()
|
||||
{
|
||||
LR2021_RADIO(hal);
|
||||
lr2021SetSf(hal, radio, 9);
|
||||
hal.replies.clear();
|
||||
hal.reply(op16(RADIOLIB_LR2021_CMD_GET_PACKET_TYPE), 0x04, {0x04, RADIOLIB_LR2021_PACKET_TYPE_GFSK}, 1);
|
||||
TEST_ASSERT_EQUAL_INT16(RADIOLIB_ERR_WRONG_MODEM, radio.startChannelScan(lr2021CadConfig()));
|
||||
TEST_ASSERT_EQUAL_UINT32(0, hal.count(op16(RADIOLIB_LR2021_CMD_SET_LORA_CAD_PARAMS)));
|
||||
TEST_ASSERT_EQUAL_UINT32(0, hal.count(op16(RADIOLIB_LR2021_CMD_SET_LORA_CAD)));
|
||||
}
|
||||
|
||||
// Answers getChannelScanResult()'s IRQ read with `irq`. That read is an opcode-less 6-byte transfer
|
||||
// (status(2), irq(4) big-endian), the second after a packet-type request. The packet type is also
|
||||
// asked before standby and before SetLoraCadParams; those writes get the same bytes, which as a
|
||||
// status byte (0x04) read as CMD_OK and leave them unaffected.
|
||||
static void lr2021AnswerIrq(RecordingHal &hal, uint32_t irq)
|
||||
{
|
||||
hal.reply(op16(RADIOLIB_LR2021_CMD_GET_PACKET_TYPE), 0x04,
|
||||
{0x04, 0x04, (uint8_t)(irq >> 24), (uint8_t)(irq >> 16), (uint8_t)(irq >> 8), (uint8_t)irq}, 2);
|
||||
}
|
||||
|
||||
// The LR2021 as measured on the bench: SetLoraCadParams takes exit byte 0x00, 0x01 or 0x10 and
|
||||
// refuses anything else with CMD_PERR (stat1 0x02), which RadioLib reports as -706.
|
||||
static bool lr2021AcceptsLoraCadExit(uint8_t exitMode)
|
||||
{
|
||||
return exitMode == 0x00 || exitMode == 0x01 || exitMode == 0x10;
|
||||
}
|
||||
|
||||
static void lr2021ModelCadExitCheck(RecordingHal &hal)
|
||||
{
|
||||
hal.rejectUnless(
|
||||
op16(RADIOLIB_LR2021_CMD_SET_LORA_CAD_PARAMS),
|
||||
[](const std::vector<uint8_t> &t) { return t.size() > 4 && lr2021AcceptsLoraCadExit(t[4]); }, 0x02);
|
||||
}
|
||||
|
||||
// A started scan, then getChannelScanResult() with `irq` as the chip's IRQ word.
|
||||
static int16_t lr2021CadResultFor(uint32_t irq)
|
||||
{
|
||||
LR2021_RADIO(hal);
|
||||
lr2021SetSf(hal, radio, 9);
|
||||
lr2021AnswerIrq(hal, irq);
|
||||
TEST_ASSERT_EQUAL_INT16(RADIOLIB_ERR_NONE, radio.startChannelScan(lr2021CadConfig()));
|
||||
return radio.getChannelScanResult();
|
||||
}
|
||||
|
||||
// Only CAD_DONE without CAD_DETECTED is a free channel; no CAD_DONE at all is an error, not "free".
|
||||
static void test_lr2021_cad_result_decoding()
|
||||
{
|
||||
TEST_ASSERT_EQUAL_INT16(RADIOLIB_LORA_DETECTED, lr2021CadResultFor(RADIOLIB_LR2021_IRQ_CAD_DETECTED));
|
||||
TEST_ASSERT_EQUAL_INT16(RADIOLIB_LORA_DETECTED,
|
||||
lr2021CadResultFor(RADIOLIB_LR2021_IRQ_CAD_DETECTED | RADIOLIB_LR2021_IRQ_CAD_DONE));
|
||||
TEST_ASSERT_EQUAL_INT16(RADIOLIB_CHANNEL_FREE, lr2021CadResultFor(RADIOLIB_LR2021_IRQ_CAD_DONE));
|
||||
TEST_ASSERT_EQUAL_INT16(RADIOLIB_ERR_UNKNOWN, lr2021CadResultFor(0));
|
||||
}
|
||||
|
||||
// The bench failure at the SPI level: against the modelled chip, RadioLib's own RX exit define is
|
||||
// refused and the scan returns -706. That is neither LORA_DETECTED nor CHANNEL_FREE, so the
|
||||
// interface has an error to refuse to transmit on. If this starts passing a scan through, RadioLib
|
||||
// has changed CAD_EXIT_MODE_RX or what startCad() sends; check against DS 2.2 Table 6-18.
|
||||
static void test_lr2021_cad_rx_exit_define_rejected_returns_spi_cmd_invalid()
|
||||
{
|
||||
LR2021_RADIO(hal);
|
||||
lr2021SetSf(hal, radio, 9);
|
||||
lr2021ModelCadExitCheck(hal);
|
||||
TEST_ASSERT_EQUAL_INT16(RADIOLIB_ERR_SPI_CMD_INVALID,
|
||||
radio.startChannelScan(lr2021CadConfig(RADIOLIB_LR2021_CAD_EXIT_MODE_RX)));
|
||||
}
|
||||
|
||||
// A CAD must not start with parameters the chip refused.
|
||||
static void test_lr2021_cad_not_started_after_rejected_params()
|
||||
{
|
||||
LR2021_RADIO(hal);
|
||||
lr2021SetSf(hal, radio, 9);
|
||||
lr2021ModelCadExitCheck(hal);
|
||||
(void)radio.startChannelScan(lr2021CadConfig(RADIOLIB_LR2021_CAD_EXIT_MODE_RX));
|
||||
TEST_ASSERT_EQUAL_UINT32(1, hal.count(op16(RADIOLIB_LR2021_CMD_SET_LORA_CAD_PARAMS)));
|
||||
TEST_ASSERT_EQUAL_UINT32(0, hal.count(op16(RADIOLIB_LR2021_CMD_SET_LORA_CAD)));
|
||||
}
|
||||
|
||||
// The exit bytes the chip accepts start a CAD; guards the model itself, so the rejection tests
|
||||
// above cannot pass because the model refuses everything.
|
||||
static void test_lr2021_cad_accepted_exit_bytes_start_a_cad()
|
||||
{
|
||||
const uint8_t modes[] = {0x00, 0x01, 0x10};
|
||||
for (uint8_t mode : modes) {
|
||||
LR2021_RADIO(hal);
|
||||
lr2021SetSf(hal, radio, 9);
|
||||
lr2021ModelCadExitCheck(hal);
|
||||
TEST_ASSERT_EQUAL_INT16(RADIOLIB_ERR_NONE, radio.startChannelScan(lr2021CadConfig(mode)));
|
||||
TEST_ASSERT_EQUAL_UINT32(1, hal.count(op16(RADIOLIB_LR2021_CMD_SET_LORA_CAD)));
|
||||
}
|
||||
}
|
||||
|
||||
// The same path with the other failure status: (stat1 & 0x0E) == 0 is CMD_FAIL, -707.
|
||||
static void test_lr2021_cad_params_failed_returns_spi_cmd_failed()
|
||||
{
|
||||
LR2021_RADIO(hal);
|
||||
lr2021SetSf(hal, radio, 9);
|
||||
hal.reply(op16(RADIOLIB_LR2021_CMD_SET_LORA_CAD_PARAMS), 0x04, {0x01}, 1);
|
||||
TEST_ASSERT_EQUAL_INT16(RADIOLIB_ERR_SPI_CMD_FAILED, radio.startChannelScan(lr2021CadConfig()));
|
||||
TEST_ASSERT_EQUAL_UINT32(0, hal.count(op16(RADIOLIB_LR2021_CMD_SET_LORA_CAD)));
|
||||
}
|
||||
|
||||
// Grows here - each needs replies scripted per opcode:
|
||||
// begin(): getVersion() check and the calibration sequence
|
||||
// resetDCDCworkaround() via setPacketType(): only begin() reaches it; same one-word write as above
|
||||
// setPaTable(): custom LF table entry chosen per output power (cf. meshtastic/firmware#11980)
|
||||
// readData() / getPacketLength(): a chip-reported length longer than the caller's buffer
|
||||
// getRSSI() / getSNR() / getPacketStatus(): decoding of known reply bytes
|
||||
// scanChannel(): the CAD parameters sent (LR2021 has two CAD commands)
|
||||
// LR20x0Interface::isChannelActive() is driven in lr20x0_interface_tests.h
|
||||
|
||||
static void runLr2021Tests()
|
||||
{
|
||||
@@ -155,4 +443,20 @@ static void runLr2021Tests()
|
||||
RUN_TEST(test_lr2021_coding_rate_long_interleaves_except_4_7);
|
||||
RUN_TEST(test_lr2021_packet_setters_send_packet_params);
|
||||
RUN_TEST(test_lr2021_other_setters_and_modes_succeed);
|
||||
RUN_TEST(test_lr2021_cad_exit_defines_are_setcadparams_values);
|
||||
RUN_TEST(test_lr2021_cad_default_config_frame);
|
||||
RUN_TEST(test_lr2021_cad_exit_byte_sent_verbatim);
|
||||
RUN_TEST(test_lr2021_cad_default_det_peak_per_sf);
|
||||
RUN_TEST(test_lr2021_cad_default_det_peak_ignores_symNum);
|
||||
RUN_TEST(test_lr2021_cad_explicit_det_peak_sent_masked);
|
||||
RUN_TEST(test_lr2021_fast_cad_sets_pnr_delta_only);
|
||||
RUN_TEST(test_lr2021_cad_timeout_in_30_52_us_steps);
|
||||
RUN_TEST(test_lr2021_cad_command_sequence);
|
||||
RUN_TEST(test_lr2021_cad_irq_routed_to_dio);
|
||||
RUN_TEST(test_lr2021_cad_wrong_modem_sends_no_cad);
|
||||
RUN_TEST(test_lr2021_cad_result_decoding);
|
||||
RUN_TEST(test_lr2021_cad_rx_exit_define_rejected_returns_spi_cmd_invalid);
|
||||
RUN_TEST(test_lr2021_cad_not_started_after_rejected_params);
|
||||
RUN_TEST(test_lr2021_cad_accepted_exit_bytes_start_a_cad);
|
||||
RUN_TEST(test_lr2021_cad_params_failed_returns_spi_cmd_failed);
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
#pragma once
|
||||
|
||||
// LR20x0Interface::isChannelActive() in src/mesh/LR20x0Interface.cpp, run against the modelled LR2021
|
||||
// in lr2021_tests.h. The tests there pin what RadioLib sends for a given config; these pin the config
|
||||
// the firmware builds. A detected preamble must be reported busy, and that needs a CAD the chip
|
||||
// accepts. develop sends exit byte 0x01 from its own local CAD_EXIT_MODE_RX and is fine; an interface
|
||||
// that sent RadioLib's RADIOLIB_LR2021_CAD_EXIT_MODE_RX (0x02) instead would have its scan refused
|
||||
// (-706), read the error as a free channel, and transmit over live frames - 16/16 on the bench, and
|
||||
// 539 scans without a busy verdict on a Mesh-Tracker X1. These tests fail on that change.
|
||||
|
||||
#include "RecordingHal.h"
|
||||
#include "TestUtil.h"
|
||||
#include "lr2021_tests.h"
|
||||
#include "mesh/LR2021Interface.h"
|
||||
#include "mesh/MeshRadio.h"
|
||||
#include "mesh/NodeDB.h"
|
||||
|
||||
// A LockingArduinoHal, as the interface requires, that forwards everything to the suite's RecordingHal.
|
||||
// spiBeginTransaction() skips the base class, which takes spiLock: nothing here shares the bus.
|
||||
class RecordingLockingHal : public LockingArduinoHal
|
||||
{
|
||||
public:
|
||||
explicit RecordingLockingHal(RecordingHal &rec) : LockingArduinoHal(SPI, SPISettings()), rec(rec) {}
|
||||
|
||||
void init() override {}
|
||||
void term() override {}
|
||||
void pinMode(uint32_t pin, uint32_t mode) override { rec.pinMode(pin, mode); }
|
||||
void digitalWrite(uint32_t pin, uint32_t value) override { rec.digitalWrite(pin, value); }
|
||||
uint32_t digitalRead(uint32_t pin) override { return rec.digitalRead(pin); }
|
||||
void attachInterrupt(uint32_t num, void (*cb)(void), uint32_t mode) override { rec.attachInterrupt(num, cb, mode); }
|
||||
void detachInterrupt(uint32_t num) override { rec.detachInterrupt(num); }
|
||||
void delay(RadioLibTime_t ms) override { rec.delay(ms); }
|
||||
void delayMicroseconds(RadioLibTime_t us) override { rec.delayMicroseconds(us); }
|
||||
RadioLibTime_t millis() override { return rec.millis(); }
|
||||
RadioLibTime_t micros() override { return rec.micros(); }
|
||||
long pulseIn(uint32_t pin, uint32_t state, RadioLibTime_t timeout) override { return rec.pulseIn(pin, state, timeout); }
|
||||
void spiBegin() override {}
|
||||
void spiBeginTransaction() override {}
|
||||
void spiTransfer(uint8_t *out, size_t len, uint8_t *in) override { rec.spiTransfer(out, len, in); }
|
||||
void spiEndTransaction() override {}
|
||||
void spiEnd() override {}
|
||||
void yield() override {}
|
||||
uint32_t pinToInterrupt(uint32_t pin) override { return pin; }
|
||||
|
||||
private:
|
||||
RecordingHal &rec;
|
||||
};
|
||||
|
||||
static constexpr uint32_t LR20X0_TEST_IRQ_PIN = 7;
|
||||
|
||||
class TestLr2021Interface : public LR2021Interface
|
||||
{
|
||||
public:
|
||||
explicit TestLr2021Interface(RecordingLockingHal *hal)
|
||||
: LR2021Interface(hal, 1, LR20X0_TEST_IRQ_PIN, RADIOLIB_NC, RADIOLIB_NC)
|
||||
{
|
||||
}
|
||||
|
||||
using LR20x0Interface<LR2021>::isChannelActive;
|
||||
|
||||
// init() would set the SF via begin(); RadioLib's default det_peak lookup needs one (see lr2021SetSf).
|
||||
void setSpreadingFactor(uint8_t sf) { TEST_ASSERT_EQUAL_INT16(RADIOLIB_ERR_NONE, lora.setSpreadingFactor(sf)); }
|
||||
|
||||
// begin() also tunes before it writes the SF, and that ordering is load-bearing: the SF write ends
|
||||
// in the DC-DC workaround's closing setFrequency(freqMHz), so without a frequency it fails with
|
||||
// INVALID_FREQUENCY (see lr2021Tune).
|
||||
void tune(RecordingHal &hal) { lr2021Tune(hal, lora); }
|
||||
};
|
||||
|
||||
// One interface for the whole suite, never destroyed: the constructor registers a worker thread and
|
||||
// sets RadioLibInterface::instance, and a failed assertion would skip a test-local destructor anyway.
|
||||
// Each test gets the shared HAL reset, the chip modelled, and its IRQ line high so scanChannel()
|
||||
// returns.
|
||||
static TestLr2021Interface &lr20x0Interface(RecordingHal &hal)
|
||||
{
|
||||
static RecordingLockingHal lockingHal(hal);
|
||||
// RadioInterface's constructor computes the slot time from myRegion, which nothing else here sets.
|
||||
static TestLr2021Interface *iface = [] {
|
||||
config.lora.region = meshtastic_Config_LoRaConfig_RegionCode_US;
|
||||
initRegion();
|
||||
return new TestLr2021Interface(&lockingHal);
|
||||
}();
|
||||
hal.reply(op16(RADIOLIB_LR2021_CMD_GET_PACKET_TYPE), 0x04, {0x04, RADIOLIB_LR2021_PACKET_TYPE_LORA}, 1);
|
||||
lr2021ModelCadExitCheck(hal);
|
||||
hal.irqPin = LR20X0_TEST_IRQ_PIN;
|
||||
iface->tune(hal);
|
||||
iface->setSpreadingFactor(9);
|
||||
hal.transactions.clear();
|
||||
return *iface;
|
||||
}
|
||||
|
||||
// The exit byte the interface sends is one the chip takes, and the CAD it asks for actually starts.
|
||||
static void test_lr20x0_isChannelActive_starts_a_cad_the_chip_accepts()
|
||||
{
|
||||
RecordingHal &hal = freshHal();
|
||||
auto &iface = lr20x0Interface(hal);
|
||||
lr2021AnswerIrq(hal, RADIOLIB_LR2021_IRQ_CAD_DONE);
|
||||
(void)iface.isChannelActive();
|
||||
const auto &t = lr2021CadParams(hal);
|
||||
TEST_ASSERT_TRUE_MESSAGE(lr2021AcceptsLoraCadExit(t[4]), "SetLoraCadParams exit byte the LR2021 refuses (0x02 -> -706)");
|
||||
TEST_ASSERT_EQUAL_UINT32(1, hal.count(op16(RADIOLIB_LR2021_CMD_SET_LORA_CAD)));
|
||||
TEST_ASSERT_EQUAL_UINT32(0, hal.count(op16(RADIOLIB_LR2021_CMD_SET_CAD)));
|
||||
}
|
||||
|
||||
// The bench failure as a test: a preamble on the air must read as busy, so the caller defers.
|
||||
static void test_lr20x0_isChannelActive_reports_a_detected_preamble_busy()
|
||||
{
|
||||
RecordingHal &hal = freshHal();
|
||||
auto &iface = lr20x0Interface(hal);
|
||||
lr2021AnswerIrq(hal, RADIOLIB_LR2021_IRQ_CAD_DETECTED | RADIOLIB_LR2021_IRQ_CAD_DONE);
|
||||
TEST_ASSERT_TRUE_MESSAGE(iface.isChannelActive(), "CAD detected a preamble, interface reported the channel free");
|
||||
}
|
||||
|
||||
static void test_lr20x0_isChannelActive_reports_cad_done_free()
|
||||
{
|
||||
RecordingHal &hal = freshHal();
|
||||
auto &iface = lr20x0Interface(hal);
|
||||
lr2021AnswerIrq(hal, RADIOLIB_LR2021_IRQ_CAD_DONE);
|
||||
TEST_ASSERT_FALSE(iface.isChannelActive());
|
||||
}
|
||||
|
||||
// Grows here:
|
||||
// a scan that returns an error (-706, -707, -705) must not read as a free channel. Today every
|
||||
// driver's isChannelActive() returns false on it; which verdict replaces that is undecided.
|
||||
|
||||
static void runLr20x0InterfaceTests()
|
||||
{
|
||||
RUN_TEST(test_lr20x0_isChannelActive_starts_a_cad_the_chip_accepts);
|
||||
RUN_TEST(test_lr20x0_isChannelActive_reports_a_detected_preamble_busy);
|
||||
RUN_TEST(test_lr20x0_isChannelActive_reports_cad_done_free);
|
||||
}
|
||||
@@ -14,7 +14,17 @@
|
||||
// the LR2021 DC-DC workaround passes sizeof(uint32_t) as a word count, overrunning the stack on
|
||||
// every setRxPath() and LoRa modulation change. The pin here, 7.7.1, predates that workaround, so
|
||||
// the LR2021 DC-DC tests register only on a RadioLib that has it (see lr2021_tests.h). On one that
|
||||
// also carries the overrun (7.8.0) they abort, so the LR2021 set runs last and the rest report first.
|
||||
// also carries the overrun (7.8.0) they abort, so the LR2021 set and the LR20x0Interface set, whose
|
||||
// setup runs the same workaround, run last and the rest report first.
|
||||
//
|
||||
// CAD (channel activity detection, the listen-before-talk check) is pinned per family: the command
|
||||
// bytes, and that a command the chip refuses comes back as an error, never CHANNEL_FREE. The LR2021
|
||||
// refuses SetLoraCadParams exit mode 0x02, the value of RadioLib's RADIOLIB_LR2021_CAD_EXIT_MODE_RX
|
||||
// (a SetCadParams value; jgromes/RadioLib#1882 adds the LoRa ones), and LR20x0Interface reads the
|
||||
// error as a free channel, so a node sending that define would transmit over live frames. develop
|
||||
// sends 0x01 from a local constant and does not, which is what is pinned here. lr2021_tests.h
|
||||
// models that refusal; lr20x0_interface_tests.h runs LR20x0Interface::isChannelActive() against it,
|
||||
// so a firmware change that sends a refused exit byte fails here.
|
||||
//
|
||||
// Anything that decodes a chip reply (begin(), readData(), getRSSI(), updateFirmware()) needs replies
|
||||
// scripted per opcode; each family's header lists those under "Grows here".
|
||||
@@ -24,6 +34,7 @@
|
||||
|
||||
#include "lr11x0_tests.h"
|
||||
#include "lr2021_tests.h"
|
||||
#include "lr20x0_interface_tests.h"
|
||||
#include "sx126x_tests.h"
|
||||
#include "sx127x_tests.h"
|
||||
#include "sx128x_tests.h"
|
||||
@@ -39,7 +50,8 @@ void setup()
|
||||
runSx127xTests();
|
||||
runSx128xTests();
|
||||
runLr11x0Tests();
|
||||
runLr2021Tests(); // last: its DC-DC tests abort under ASan on a RadioLib with the #1864 overrun
|
||||
runLr2021Tests(); // after the rest: its DC-DC tests abort under ASan on a RadioLib with the #1864 overrun
|
||||
runLr20x0InterfaceTests(); // its setSpreadingFactor() runs that same DC-DC workaround
|
||||
exit(UNITY_END());
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user