// 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 #include #include #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(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 Station::test_drain(bool& overflow, std::size_t budget) { std::deque 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(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(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