2026-10-12 20:29:29 +02:00

227 lines
7.9 KiB
C++

#include "doctest.h"
#include <tactility/device.h>
#include <tactility/driver.h>
#include <tactility/drivers/radio.h>
#include <cstring>
namespace {
struct FakeRadio {
bool enabled = false;
int32_t frequency = 868000000;
int32_t bandwidth = 125000;
int32_t power = 14;
};
FakeRadio fake_radio;
error_t fake_get_radio_state(Device*, RadioState* state) {
*state = fake_radio.enabled ? RADIO_STATE_ON : RADIO_STATE_OFF;
return ERROR_NONE;
}
error_t fake_set_enabled(Device*, bool enabled) {
fake_radio.enabled = enabled;
return ERROR_NONE;
}
error_t fake_set_modulation(Device*, RadioModulation) { return ERROR_NONE; }
error_t fake_get_modulation(Device*, RadioModulation* modulation) {
*modulation = RADIO_MODULATION_LORA;
return ERROR_NONE;
}
bool fake_can_transmit(Device*, RadioModulation) { return true; }
bool fake_can_receive(Device*, RadioModulation) { return true; }
error_t fake_set_parameter(Device*, RadioParameter parameter, int32_t value) {
switch (parameter) {
case RADIO_PARAMETER_FREQUENCY:
fake_radio.frequency = value;
return ERROR_NONE;
case RADIO_PARAMETER_BANDWIDTH:
fake_radio.bandwidth = value;
return ERROR_NONE;
case RADIO_PARAMETER_POWER:
fake_radio.power = value;
return ERROR_NONE;
default:
return ERROR_NOT_SUPPORTED;
}
}
error_t fake_get_parameter(Device*, RadioParameter parameter, int32_t* value) {
switch (parameter) {
case RADIO_PARAMETER_FREQUENCY:
*value = fake_radio.frequency;
return ERROR_NONE;
case RADIO_PARAMETER_BANDWIDTH:
*value = fake_radio.bandwidth;
return ERROR_NONE;
case RADIO_PARAMETER_POWER:
*value = fake_radio.power;
return ERROR_NONE;
default:
return ERROR_NOT_SUPPORTED;
}
}
error_t fake_transmit(Device*, const uint8_t*, size_t, RadioTxId*) { return ERROR_NONE; }
const RadioApi fake_radio_api = {
.get_radio_state = fake_get_radio_state,
.set_enabled = fake_set_enabled,
.set_modulation = fake_set_modulation,
.get_modulation = fake_get_modulation,
.can_transmit = fake_can_transmit,
.can_receive = fake_can_receive,
.set_parameter = fake_set_parameter,
.get_parameter = fake_get_parameter,
.transmit = fake_transmit,
.get_rssi = nullptr,
.is_receiving = nullptr,
};
Driver fake_radio_driver = {
.name = "fake_radio_driver",
.compatible = (const char*[]) { "radio_test,fake", nullptr },
.start_device = nullptr,
.stop_device = nullptr,
.api = &fake_radio_api,
.device_type = &RADIO_TYPE,
.internal = nullptr,
};
/** Constructs, adds and starts a fake radio for the duration of a test case */
struct FakeRadioFixture {
Device device {
.name = "fake_radio",
.config = nullptr,
.parent = nullptr,
};
FakeRadioFixture() {
fake_radio = {};
CHECK_EQ(driver_construct_add(&fake_radio_driver), ERROR_NONE);
CHECK_EQ(device_construct_add(&device, "radio_test,fake"), ERROR_NONE);
CHECK_EQ(device_start(&device), ERROR_NONE);
}
~FakeRadioFixture() {
radio_release(&device);
radio_set_region(&device, RADIO_REGION_UNSET);
CHECK_EQ(device_stop(&device), ERROR_NONE);
CHECK_EQ(device_remove(&device), ERROR_NONE);
CHECK_EQ(device_destruct(&device), ERROR_NONE);
CHECK_EQ(driver_remove_destruct(&fake_radio_driver), ERROR_NONE);
}
};
} // namespace
TEST_CASE("radio_acquire grants one owner at a time") {
FakeRadioFixture fixture;
Device* device = &fixture.device;
char owner[RADIO_OWNER_NAME_MAX_LENGTH + 1];
CHECK_EQ(radio_get_owner(device, owner, sizeof(owner)), ERROR_NOT_FOUND);
CHECK_EQ(radio_acquire(device, "mesh"), ERROR_NONE);
CHECK_EQ(radio_acquire(device, "mesh"), ERROR_NONE);
CHECK_EQ(radio_acquire(device, "sniffer"), ERROR_RESOURCE_BUSY);
CHECK_EQ(radio_get_owner(device, owner, sizeof(owner)), ERROR_NONE);
CHECK_EQ(std::strcmp(owner, "mesh"), 0);
CHECK_EQ(radio_release(device), ERROR_NONE);
CHECK_EQ(radio_release(device), ERROR_INVALID_STATE);
CHECK_EQ(radio_acquire(device, "sniffer"), ERROR_NONE);
}
TEST_CASE("radio_get_owner truncates to the buffer size") {
FakeRadioFixture fixture;
Device* device = &fixture.device;
CHECK_EQ(radio_acquire(device, "a-rather-long-owner"), ERROR_NONE);
char owner[5];
CHECK_EQ(radio_get_owner(device, owner, sizeof(owner)), ERROR_NONE);
CHECK_EQ(std::strcmp(owner, "a-ra"), 0);
}
TEST_CASE("radio region gates frequency and power") {
FakeRadioFixture fixture;
Device* device = &fixture.device;
// Without a region nothing is enforced
CHECK_EQ(radio_get_region(device), RADIO_REGION_UNSET);
CHECK_EQ(radio_set_parameter(device, RADIO_PARAMETER_FREQUENCY, 915000000), ERROR_NONE);
CHECK_EQ(radio_set_parameter(device, RADIO_PARAMETER_POWER, 30), ERROR_NONE);
CHECK_EQ(radio_set_region(device, RADIO_REGION_EU_868), ERROR_NONE);
CHECK_EQ(radio_get_region(device), RADIO_REGION_EU_868);
CHECK_EQ(radio_set_parameter(device, RADIO_PARAMETER_FREQUENCY, 915000000), ERROR_OUT_OF_RANGE);
CHECK_EQ(radio_set_parameter(device, RADIO_PARAMETER_FREQUENCY, 869525000), ERROR_NONE);
CHECK_EQ(radio_set_parameter(device, RADIO_PARAMETER_POWER, 28), ERROR_OUT_OF_RANGE);
CHECK_EQ(radio_set_parameter(device, RADIO_PARAMETER_POWER, 27), ERROR_NONE);
// Unknown parameters pass through to the driver
CHECK_EQ(radio_set_parameter(device, RADIO_PARAMETER_SPREADING_FACTOR, 9), ERROR_NOT_SUPPORTED);
}
TEST_CASE("radio_set_enabled checks the configuration against a single band") {
FakeRadioFixture fixture;
Device* device = &fixture.device;
CHECK_EQ(radio_set_region(device, RADIO_REGION_EU_868), ERROR_NONE);
CHECK_EQ(radio_set_parameter(device, RADIO_PARAMETER_BANDWIDTH, 250000), ERROR_NONE);
// 869.525 MHz with 250 kHz fits the 869.4-869.65 MHz band at 27 dBm
CHECK_EQ(radio_set_parameter(device, RADIO_PARAMETER_FREQUENCY, 869525000), ERROR_NONE);
CHECK_EQ(radio_set_parameter(device, RADIO_PARAMETER_POWER, 27), ERROR_NONE);
CHECK_EQ(radio_set_enabled(device, true), ERROR_NONE);
CHECK(fake_radio.enabled);
CHECK_EQ(radio_set_enabled(device, false), ERROR_NONE);
// 868.1 MHz only allows 14 dBm
CHECK_EQ(radio_set_parameter(device, RADIO_PARAMETER_FREQUENCY, 868100000), ERROR_NONE);
CHECK_EQ(radio_set_enabled(device, true), ERROR_OUT_OF_RANGE);
CHECK_FALSE(fake_radio.enabled);
CHECK_EQ(radio_set_parameter(device, RADIO_PARAMETER_POWER, 14), ERROR_NONE);
CHECK_EQ(radio_set_enabled(device, true), ERROR_NONE);
CHECK_EQ(radio_set_enabled(device, false), ERROR_NONE);
// The occupied bandwidth must stay inside the band
CHECK_EQ(radio_set_parameter(device, RADIO_PARAMETER_FREQUENCY, 869950000), ERROR_NONE);
CHECK_EQ(radio_set_enabled(device, true), ERROR_OUT_OF_RANGE);
// Disabling is never blocked
CHECK_EQ(radio_set_enabled(device, false), ERROR_NONE);
}
TEST_CASE("radio_set_region rejects unknown regions") {
FakeRadioFixture fixture;
CHECK_EQ(radio_set_region(&fixture.device, RADIO_REGION_COUNT), ERROR_INVALID_ARGUMENT);
}
TEST_CASE("radio optional API returns ERROR_NOT_SUPPORTED when not implemented") {
FakeRadioFixture fixture;
float rssi;
bool receiving;
CHECK_EQ(radio_get_rssi(&fixture.device, &rssi), ERROR_NOT_SUPPORTED);
CHECK_EQ(radio_is_receiving(&fixture.device, &receiving), ERROR_NOT_SUPPORTED);
}
TEST_CASE("radio region bands") {
const RadioRegionBand* bands;
CHECK_EQ(radio_get_region_bands(RADIO_REGION_UNSET, &bands), 0);
CHECK_EQ(radio_get_region_bands(RADIO_REGION_EU_868, &bands), 3);
CHECK_EQ(radio_get_region_bands(RADIO_REGION_US_915, &bands), 1);
CHECK_EQ(bands[0].frequency_min, 902000000);
CHECK_EQ(std::strcmp(radio_region_to_string(RADIO_REGION_US_915), "US_915"), 0);
}