131 lines
5.0 KiB
C++
131 lines
5.0 KiB
C++
// Station's test-channel API (software lower tester; not part of the phone interface, see
|
|||
|
|
// test_channel.hpp). Kept in its own translation unit so the productive station.cpp stays free
|
||
|
|
// of test-only code; compiles to nothing when CONFIG_MICROBU_TEST_CHANNEL is off.
|
||
|
|
#include "station.hpp"
|
||
|
|
#if CONFIG_MICROBU_TEST_CHANNEL
|
||
|
|
#include <vanetza_idf/nf_sap.hpp>
|
||
|
|
#include <vanetza_idf/sf_sap.hpp>
|
||
|
|
#include <esp_log.h>
|
||
|
|
#include "c5_radio.hpp"
|
||
|
|
|
||
|
|
namespace microbu {
|
||
|
|
using namespace vanetza_idf;
|
||
|
|
namespace gn = vanetza::geonet;
|
||
|
|
namespace {
|
||
|
|
const char* TAG = "station";
|
||
|
|
link::Code code(Result result) { return static_cast<link::Code>(result); }
|
||
|
|
}
|
||
|
|
|
||
|
|
void Station::test_mirror(Mirror mode) { mirror_ = mode; }
|
||
|
|
|
||
|
|
link::Code Station::test_inject(const vanetza::MacAddress& source, const vanetza::MacAddress& destination, link::Bytes gnpdu) {
|
||
|
|
if (!stack_) return link::Code::not_configured;
|
||
|
|
tick();
|
||
|
|
AlDataIndication indication;
|
||
|
|
indication.source = source;
|
||
|
|
indication.destination = destination;
|
||
|
|
indication.channel_number = radio_parameters_.channel_number;
|
||
|
|
indication.data = std::move(gnpdu);
|
||
|
|
return code(stack_->indicate(std::move(indication)));
|
||
|
|
}
|
||
|
|
|
||
|
|
link::Code Station::test_gn_request(std::uint8_t traffic_class, link::Bytes payload) {
|
||
|
|
if (!stack_) return link::Code::not_configured;
|
||
|
|
if (!clock_.synchronised || !have_fix_) return link::Code::rejected;
|
||
|
|
tick();
|
||
|
|
GnRequest request;
|
||
|
|
request.traffic_class = gn::TrafficClass(traffic_class);
|
||
|
|
request.data = std::move(payload);
|
||
|
|
return code(stack_->request(std::move(request)));
|
||
|
|
}
|
||
|
|
|
||
|
|
std::deque<TestRecord> Station::test_drain(bool& overflow, std::size_t budget) {
|
||
|
|
std::deque<TestRecord> out;
|
||
|
|
std::size_t used = 0;
|
||
|
|
while (!records_.empty() && used + records_.front().bytes.size() + 3 <= budget) {
|
||
|
|
used += records_.front().bytes.size() + 3;
|
||
|
|
record_bytes_ -= records_.front().bytes.size();
|
||
|
|
out.push_back(std::move(records_.front()));
|
||
|
|
records_.pop_front();
|
||
|
|
}
|
||
|
|
overflow = overflow_ && records_.empty();
|
||
|
|
if (records_.empty()) overflow_ = false;
|
||
|
|
return out;
|
||
|
|
}
|
||
|
|
|
||
|
|
void Station::test_reset() {
|
||
|
|
mirror_ = Mirror::off;
|
||
|
|
records_.clear();
|
||
|
|
record_bytes_ = 0;
|
||
|
|
overflow_ = false;
|
||
|
|
}
|
||
|
|
|
||
|
|
link::Code Station::test_radio_burst(std::uint16_t channel, double power_dbm, unsigned mcs,
|
||
|
|
unsigned count, unsigned interval_ms, std::size_t payload_len) {
|
||
|
|
if (!radio_) {
|
||
|
|
// Automatically start the radio for testing if not yet started
|
||
|
|
C5RadioConfig rc;
|
||
|
|
rc.channel_number = channel;
|
||
|
|
rc.transmit_power_dbm = power_dbm;
|
||
|
|
rc.laboratory_transmission = true;
|
||
|
|
radio_ = std::make_unique<C5Radio>(rc);
|
||
|
|
const auto error = radio_->start();
|
||
|
|
if (error != ESP_OK) {
|
||
|
|
ESP_LOGE(TAG, "radio start failed for test burst: %s", esp_err_to_name(error));
|
||
|
|
radio_.reset();
|
||
|
|
return link::Code::rejected;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
const auto err = radio_->transmit_burst(channel, power_dbm, mcs, count, interval_ms, payload_len);
|
||
|
|
if (err == ESP_OK) {
|
||
|
|
counters_.radio_submitted += count;
|
||
|
|
if (disseminated_) disseminated_();
|
||
|
|
return link::Code::accepted;
|
||
|
|
}
|
||
|
|
if (err == ESP_ERR_INVALID_ARG) return link::Code::invalid_argument;
|
||
|
|
if (err == ESP_ERR_NO_MEM) return link::Code::resource_limit;
|
||
|
|
return link::Code::rejected;
|
||
|
|
}
|
||
|
|
|
||
|
|
link::Code Station::test_cca_sample(unsigned duration_ms, link::Bytes& detail) {
|
||
|
|
// Capped: this busy-loops with interrupts otherwise free, so keep it short enough that
|
||
|
|
// the task watchdog and the WiFi/BLE stack's own housekeeping are not starved for long.
|
||
|
|
duration_ms = std::min(duration_ms, 500u);
|
||
|
|
if (!radio_) {
|
||
|
|
C5RadioConfig rc; // default channel 180 / 10 dBm is fine: this probes PHY polling
|
||
|
|
rc.laboratory_transmission = true; // capability, not a specific channel's RF content
|
||
|
|
radio_ = std::make_unique<C5Radio>(rc);
|
||
|
|
const auto error = radio_->start();
|
||
|
|
if (error != ESP_OK) {
|
||
|
|
ESP_LOGE(TAG, "radio start failed for CCA sample: %s", esp_err_to_name(error));
|
||
|
|
radio_.reset();
|
||
|
|
return link::Code::rejected;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
const auto result = radio_->sample_cca(duration_ms);
|
||
|
|
link::Writer w;
|
||
|
|
w.u32(result.samples);
|
||
|
|
w.u32(result.duration_us);
|
||
|
|
w.u32(result.min_delta_us);
|
||
|
|
w.u32(result.max_delta_us);
|
||
|
|
w.u32(result.busy_count);
|
||
|
|
w.i32(result.first_cca);
|
||
|
|
w.i32(result.last_cca);
|
||
|
|
w.i32(result.noise_floor_dbm);
|
||
|
|
w.i32(result.cca_total_cycles_delta);
|
||
|
|
w.i32(result.cca_busy_cycles_delta);
|
||
|
|
w.i32(result.cca_status);
|
||
|
|
detail = w.out;
|
||
|
|
return result.samples > 0 ? link::Code::accepted : link::Code::rejected;
|
||
|
|
}
|
||
|
|
|
||
|
|
void Station::record(std::uint8_t kind, link::Bytes bytes) {
|
||
|
|
if (mirror_ == Mirror::off) return;
|
||
|
|
if (record_bytes_ + bytes.size() > 8192 || records_.size() >= 32) { overflow_ = true; return; }
|
||
|
|
record_bytes_ += bytes.size();
|
||
|
|
records_.push_back(TestRecord {kind, std::move(bytes)});
|
||
|
|
}
|
||
|
|
|
||
|
|
} // namespace microbu
|
||
|
|
#endif // CONFIG_MICROBU_TEST_CHANNEL
|