Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 73 additions & 0 deletions tests/stbc_sanity.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
#!/usr/bin/env bash
# STBC on-air sanity gate: does the chip actually EMIT decodable STBC?
#
# Sends canonical-SA beacons at MCS1/STBC from an RTL8814AU (which encodes
# Alamouti across two TX antennas internally, sidestepping the SDR 2-channel
# problem), and receives on an RTL8812AU — the 8812/8821 RX descriptor exposes
# the received HT-SIG STBC bit, which the 8814 RX does NOT (it reports defaults).
# A pass = frames arrive with stbc=1 in the <devourer-stream> line.
#
# Hardware (all on ONE host, adapters inches apart for a STRONG link — a clean
# yes/no needs the frame to decode easily; range confounds a zero result):
# TX = RTL8814AU (0bda:8813), selected by USB_BUS
# RX = RTL8812AU (0bda:8812) ← must be an 8812/8821 to read the STBC bit
#
# Env:
# TX_BUS=N [TX_PORT=a.b.c] which 8814 transmits (topology select)
# CHANNEL=6 test channel
# MCS=MCS1 HT rate to carry STBC (STBC needs HT/VHT, not legacy)
# DURATION=20 RX capture seconds
#
# Usage: sudo TX_BUS=4 ./tests/stbc_sanity.sh
set -euo pipefail

HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
ROOT="$(cd "$HERE/.." && pwd)"
CHANNEL="${CHANNEL:-6}"; MCS="${MCS:-MCS1}"; DURATION="${DURATION:-20}"
RXLOG="$(mktemp -t devourer-stbc-rx.XXXXXX.log)"
TXLOG="$(mktemp -t devourer-stbc-tx.XXXXXX.log)"

cleanup() {
for comm in WiFiDriverDemo WiFiDriverTxDem; do pkill -x "$comm" 2>/dev/null || true; done
rm -f "$RXLOG" "$TXLOG" 2>/dev/null || true
}
trap cleanup EXIT INT TERM

echo "== building =="; cmake --build "$ROOT/build" -j >/dev/null
DEMO="$ROOT/build/WiFiDriverDemo"; TXDEMO="$ROOT/build/WiFiDriverTxDemo"

# Preflight: both chips present.
lsusb | grep -qi "0bda:8812" || { echo "no RTL8812AU (RX) present — plug it into this host" >&2; exit 1; }
lsusb | grep -qi "0bda:8813" || { echo "no RTL8814AU (TX) present" >&2; exit 1; }

echo "== TX: 8814 (bus ${TX_BUS:-first}) beacon at ${MCS}/STBC, ch$CHANNEL =="
TX_ENV=(DEVOURER_PID=0x8813 DEVOURER_CHANNEL="$CHANNEL" DEVOURER_TX_RATE="${MCS}/STBC")
[ -n "${TX_BUS:-}" ] && TX_ENV+=(DEVOURER_USB_BUS="$TX_BUS")
[ -n "${TX_PORT:-}" ] && TX_ENV+=(DEVOURER_USB_PORT="$TX_PORT")
stdbuf -oL -eL env "${TX_ENV[@]}" "$TXDEMO" >"$TXLOG" 2>&1 &
for _ in $(seq 1 30); do grep -q 'TX #.* rc=1' "$TXLOG" 2>/dev/null && break; sleep 1; done
grep -q 'TX #.* rc=1' "$TXLOG" || { echo "TX beacon never injected — check $TXLOG" >&2; exit 3; }
echo " TX injecting ($(grep -m1 'fixed rate' "$TXLOG" 2>/dev/null || echo '?'))"

echo "== RX: 8812 for ${DURATION}s, reading the STBC bit =="
timeout "$((DURATION+2))" env DEVOURER_PID=0x8812 DEVOURER_STREAM_OUT=1 \
DEVOURER_CHANNEL="$CHANNEL" "$DEMO" >"$RXLOG" 2>/dev/null &
rxpid=$!
sleep "$DURATION"
kill "$rxpid" 2>/dev/null || true
wait "$rxpid" 2>/dev/null || true # only the RX — the TX beacon runs on

TOTAL=$(grep -c '<devourer-stream>' "$RXLOG" || true)
STBC1=$(grep -c '<devourer-stream>.*stbc=1' "$RXLOG" || true)
echo "== result: canonical frames=$TOTAL with stbc=1: $STBC1 =="
grep -m3 '<devourer-stream>' "$RXLOG" | sed -E 's/body=.*//'
if [ "${STBC1:-0}" -gt 0 ]; then
echo "PASS — the chip emits decodable STBC on-air (stbc=1 seen). Proceed to the"
echo " TX-diversity mobility measurement (STBC vs single-stream, moving TX)."
elif [ "${TOTAL:-0}" -gt 0 ]; then
echo "FAIL — frames decode but stbc=0: the 8814 is NOT emitting STBC for a"
echo " descriptor STBC bit. TX diversity would need CSD instead (#128)."
else
echo "INCONCLUSIVE — no canonical frames. Move TX/RX closer (MCS1 needs a strong"
echo " link) or confirm both adapters are on ch$CHANNEL."
fi
69 changes: 65 additions & 4 deletions txdemo/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -111,9 +111,18 @@ void usb_event_loop(Logger_t _logger, libusb_context *ctx) {
while (!g_devourer_should_stop) {
struct timeval tv {0, 100000};
int r = libusb_handle_events_timeout_completed(ctx, &tv, nullptr);
if (r < 0 && r != LIBUSB_ERROR_TIMEOUT) {
_logger->error("Error handling events: {}", r);
break;
/* TIMEOUT is normal poll-expiry; INTERRUPTED (EINTR) is a transient wakeup
* (a signal hit the underlying poll, more likely under concurrent USB load).
* Neither is fatal. Do NOT break on them: this thread services async TX
* completions, so if it exits, the completion callbacks stop firing,
* submitted URBs are never freed, and within a few frames every
* libusb_submit_transfer fails — the "TX works briefly then every send
* fails" wedge (seen intermittently while an RX ran concurrently). Keep
* polling; only log a genuinely unexpected error and carry on. */
if (r < 0 && r != LIBUSB_ERROR_TIMEOUT && r != LIBUSB_ERROR_INTERRUPTED) {
static int logged = 0;
if (logged++ < 5)
_logger->error("libusb_handle_events: {} (continuing)", r);
}
}
}
Expand Down Expand Up @@ -161,7 +170,44 @@ int main(int argc, char **argv) {
target_vid = static_cast<uint16_t>(std::strtoul(vid_env, nullptr, 0));
logger->info("DEVOURER_VID={:04x} (overriding default VID)", target_vid);
}
/* DEVOURER_USB_BUS (+ optional DEVOURER_USB_PORT) select a device by USB
* topology when several share one VID:PID and even the serial — e.g. two
* identical RTL8814AU dongles. DEVOURER_USB_PORT is the dotted libusb port
* path (sysfs `devpath` / `lsusb -t`). Unset = the VID:PID open loop below.
* Mirrors the RX demo (demo/main.cpp). */
if (const char *bus_env = std::getenv("DEVOURER_USB_BUS")) {
const auto want_bus =
static_cast<uint8_t>(std::strtoul(bus_env, nullptr, 0));
const char *port_env = std::getenv("DEVOURER_USB_PORT");
libusb_device **list = nullptr;
ssize_t n = libusb_get_device_list(context, &list);
for (ssize_t i = 0; i < n && handle == NULL; ++i) {
libusb_device_descriptor dd{};
if (libusb_get_device_descriptor(list[i], &dd) != 0) continue;
if (dd.idVendor != target_vid) continue;
if (target_pid != 0 && dd.idProduct != target_pid) continue;
if (libusb_get_bus_number(list[i]) != want_bus) continue;
if (port_env != nullptr) {
uint8_t ports[8];
int pc = libusb_get_port_numbers(list[i], ports, sizeof(ports));
std::string path;
for (int p = 0; p < pc; ++p)
path += (path.empty() ? "" : ".") + std::to_string(ports[p]);
if (path != port_env) continue;
}
if (libusb_open(list[i], &handle) == 0)
logger->info("Opened device {:04x}:{:04x} on bus {} port {}",
dd.idVendor, dd.idProduct, want_bus,
port_env ? port_env : "(any)");
}
if (list != nullptr) libusb_free_device_list(list, 1);
if (handle == NULL)
logger->error("DEVOURER_USB_BUS={} PORT={} matched no device", want_bus,
port_env ? port_env : "(any)");
}

for (uint16_t pid : kRealtekProductIds) {
if (handle != NULL) break;
if (target_pid != 0 && pid != target_pid) continue;
handle = libusb_open_device_with_vid_pid(context, target_vid, pid);
if (handle != NULL) {
Expand Down Expand Up @@ -433,7 +479,17 @@ int main(int argc, char **argv) {
* it applies to Jaguar3 (8822CU/EU) too. The demo's beacon is rate-less, so
* without this its Jaguar3 TX fell back to MGN_1M (1 Mbps) regardless of
* DEVOURER_TX_RATE. Per-packet radiotap still overrides. */
rtlDevice->SetTxMode(devourer::parse_tx_mode_env());
const devourer::TxMode tx_mode_base = devourer::parse_tx_mode_env();
rtlDevice->SetTxMode(tx_mode_base);

/* DEVOURER_TX_STBC_TOGGLE=1 alternates the STBC bit every frame (keeping the
* rate from DEVOURER_TX_RATE, which must be HT/VHT for STBC to apply). The RX
* reports the received STBC bit per frame, so an alternating TX lets one
* moving capture compare STBC vs single-stream delivery on the same fading —
* the transmit-side mobility measurement, tagged for free by the receiver. */
const bool stbc_toggle = std::getenv("DEVOURER_TX_STBC_TOGGLE") != nullptr;
if (stbc_toggle)
logger->info("DEVOURER_TX_STBC_TOGGLE: alternating STBC on/off per frame");

std::vector<uint8_t> tx_buf(beacon_frame, beacon_frame + sizeof(beacon_frame));

Expand Down Expand Up @@ -673,6 +729,11 @@ int main(int argc, char **argv) {
tx_count, switch_us, ms_since_start(), mode);
fflush(stdout);
}
if (stbc_toggle) {
devourer::TxMode m = tx_mode_base;
m.stbc = static_cast<uint8_t>(tx_count & 1); /* 0,1,0,1,… per frame */
rtlDevice->SetTxMode(m);
}
rc = rtlDevice->send_packet(tx_buf.data(), tx_buf.size());
++tx_count;
if (!hop_channels.empty() && ++frames_in_dwell >= hop_dwell)
Expand Down
Loading