mirror of
https://github.com/torlando-tech/pyxis.git
synced 2026-08-17 16:20:01 +00:00
147 lines
5.8 KiB
C++
147 lines
5.8 KiB
C++
#include <cstdint>
|
|
#include <iostream>
|
|
|
|
#include "RadioActivityHistory.h"
|
|
|
|
using RadioActivity::Event;
|
|
using RadioActivity::History;
|
|
using RadioActivity::has_event;
|
|
|
|
int main() {
|
|
int passed = 0;
|
|
int failed = 0;
|
|
|
|
auto check = [&](const char* name, bool condition) {
|
|
if (condition) {
|
|
++passed;
|
|
} else {
|
|
++failed;
|
|
std::cerr << "FAIL: " << name << "\n";
|
|
}
|
|
};
|
|
|
|
History history;
|
|
for (std::size_t i = 0; i < History::CAPACITY + 5; ++i) {
|
|
history.record(-130 + static_cast<int16_t>(i % 3));
|
|
}
|
|
auto bounded = history.snapshot();
|
|
check("history has fixed capacity", bounded.count == History::CAPACITY);
|
|
check("snapshot is chronological after wrap",
|
|
bounded.samples[0].sequence == 5 &&
|
|
bounded.samples[bounded.count - 1].sequence == History::CAPACITY + 4);
|
|
|
|
History baseline;
|
|
baseline.record(-121);
|
|
baseline.record(-120);
|
|
baseline.record(-119);
|
|
baseline.record(-120);
|
|
auto learned = baseline.snapshot();
|
|
check("noise floor becomes ready from accepted samples", learned.noise_floor_ready);
|
|
check("noise floor learns a stable baseline", learned.noise_floor == -120);
|
|
|
|
baseline.record(-95);
|
|
auto interference = baseline.snapshot();
|
|
check("sample above noise threshold is interference",
|
|
has_event(interference.samples[interference.count - 1], Event::Interference));
|
|
check("interference does not raise learned noise floor", interference.noise_floor == -120);
|
|
|
|
baseline.record(-112);
|
|
auto accepted = baseline.snapshot();
|
|
check("sample within eleven dB is accepted as noise",
|
|
accepted.samples[accepted.count - 1].events == 0);
|
|
check("accepted sample updates bounded noise estimator", accepted.noise_floor > -120);
|
|
|
|
History events;
|
|
events.mark_event(Event::Tx);
|
|
events.mark_event(Event::Rx);
|
|
check("events do not append off-cadence samples", events.snapshot().count == 0);
|
|
events.record(-118);
|
|
auto marked = events.snapshot();
|
|
check("events share one fixed-cadence bucket", marked.count == 1);
|
|
check("transmit metadata survives until the next bucket", has_event(marked.samples[0], Event::Tx));
|
|
check("receive metadata shares the next bucket", has_event(marked.samples[0], Event::Rx));
|
|
check("channel load counts activity", marked.channel_load_percent == 100);
|
|
|
|
History skipped;
|
|
skipped.mark_event(Event::Tx, 3);
|
|
skipped.record_gap();
|
|
skipped.record_gap();
|
|
skipped.record_gap();
|
|
auto tx_gap = skipped.snapshot();
|
|
check("skipped cadence buckets are represented", tx_gap.count == 3);
|
|
check("TX duration spans every queued bucket",
|
|
has_event(tx_gap.samples[0], Event::Tx) &&
|
|
has_event(tx_gap.samples[1], Event::Tx) &&
|
|
has_event(tx_gap.samples[2], Event::Tx));
|
|
check("gap buckets do not invent RSSI", !tx_gap.samples[0].rssi_valid);
|
|
check("TX gap buckets contribute to load", tx_gap.channel_load_percent == 100);
|
|
|
|
History aligned;
|
|
aligned.mark_event(Event::Tx, 2);
|
|
aligned.record_gap(false);
|
|
aligned.record_gap();
|
|
aligned.record(-100);
|
|
auto right_aligned = aligned.snapshot();
|
|
check("pending duration does not mark older catch-up gaps",
|
|
!has_event(right_aligned.samples[0], Event::Tx));
|
|
check("pending duration is right-aligned to current time",
|
|
has_event(right_aligned.samples[1], Event::Tx) &&
|
|
has_event(right_aligned.samples[2], Event::Tx));
|
|
|
|
History rx_burst;
|
|
rx_burst.mark_event(Event::Rx);
|
|
rx_burst.mark_event(Event::Rx);
|
|
rx_burst.record(-110);
|
|
rx_burst.record(-120);
|
|
auto rx_bucket = rx_burst.snapshot();
|
|
check("multiple RX events coalesce within one cadence bucket",
|
|
has_event(rx_bucket.samples[0], Event::Rx));
|
|
check("coalesced RX does not spill into a future bucket",
|
|
!has_event(rx_bucket.samples[1], Event::Rx));
|
|
|
|
History unknown_gap;
|
|
unknown_gap.record_gap();
|
|
auto unknown = unknown_gap.snapshot();
|
|
check("unknown contention gap advances time without activity",
|
|
unknown.count == 1 && unknown.channel_load_percent == 0);
|
|
check("newest gap makes current RSSI unavailable", !unknown.current_rssi_valid);
|
|
|
|
unknown_gap.record(-101);
|
|
auto current = unknown_gap.snapshot();
|
|
check("newest measured bucket makes current RSSI valid", current.current_rssi_valid);
|
|
check("current RSSI comes from the newest measured bucket", current.current_rssi == -101);
|
|
|
|
History clamped;
|
|
clamped.record(-200);
|
|
clamped.record(20);
|
|
auto limits = clamped.snapshot();
|
|
check("RSSI is clamped to graph bounds",
|
|
limits.samples[0].rssi_dbm == History::MIN_RSSI_DBM &&
|
|
limits.samples[1].rssi_dbm == History::MAX_RSSI_DBM);
|
|
check("channel load is bounded", limits.channel_load_percent <= 100);
|
|
|
|
History resettable;
|
|
resettable.record(-121);
|
|
resettable.record(-120);
|
|
resettable.record(-119);
|
|
resettable.record(-120);
|
|
resettable.mark_event(Event::Tx);
|
|
const uint32_t prior_generation = resettable.generation();
|
|
resettable.reset();
|
|
auto cleared = resettable.snapshot();
|
|
check("reset clears samples", cleared.count == 0);
|
|
check("reset invalidates current RSSI", !cleared.current_rssi_valid);
|
|
check("reset clears learned noise floor", !cleared.noise_floor_ready);
|
|
check("reset clears channel load", cleared.channel_load_percent == 0);
|
|
check("reset advances the current-channel generation",
|
|
resettable.generation() != prior_generation);
|
|
resettable.record(-100);
|
|
auto after_reset = resettable.snapshot();
|
|
check("reset clears pending events and restarts sequence",
|
|
after_reset.samples[0].events == 0 &&
|
|
after_reset.samples[0].sequence == 0);
|
|
|
|
std::cout << passed << " passed, " << failed << " failed\n";
|
|
return failed == 0 ? 0 : 1;
|
|
}
|