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
23 changes: 13 additions & 10 deletions src/kestrel/RtlKestrelDevice.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -372,16 +372,19 @@ RxEnergy RtlKestrelDevice::GetRxEnergy() {
* env-monitor. Frame-free, BB-driven, no clock-stop -> no wedge.
* ~mntr_time (100 ms) of control-thread wait; opt-in. Guard to a plausible
* idle-floor band so a not-ready/garbage report never emits a fake dBm.
* C8852B only: the NHM triggers on the C8852C but its nhm_pwr reads ~23 dB
* high (on-air ch36 the NHM reads -70 dBm while the passive floor and the
* 8852B NHM both read -93). The offset is in the NHM's raw HW gain reference,
* not the SW path (halbb_env_mntr treats the 8852C identically to the 8852B)
* and not the front-end gain (the physts RSSI is correct — the passive floor
* cross-matches the 8852B NHM within ~1 dB). The vendor has no 8852C NHM
* offset; it is a deeper RF-cal reference. The C8852C's absolute floor is met
* by the passive rssi-snr floor (GetRxQuality), so the frame-free NHM stays
* 8852B-only rather than emit the wrong -70. */
if (_cfg.rx.abs_noise_floor && _variant == kestrel::ChipVariant::C8852B) {
*
* The C8852C NHM is band-limited to 2.4 GHz. On-air it reads correctly there
* (cross-matches its passive floor within ~2 dB, stable), but on 5 GHz the
* AGC drifts ~1.5 s after a channel tune and LOCKS the NHM ~24 dB high (a
* bimodal gain-state lock, not a fixed offset the halbb SW path handles — it
* treats the 8852C identically to the 8852B, and the physts RSSI is correct;
* only a full set_channel re-tune resets it, and RX traffic re-drives it). So
* the 8852C emits the NHM on 2.4 GHz and falls back to the passive rssi-snr
* floor (GetRxQuality) on 5 GHz. The C8852B NHM is correct on both bands. */
const bool nhm_supported =
_variant == kestrel::ChipVariant::C8852B ||
(_variant == kestrel::ChipVariant::C8852C && _channel.Channel <= 14);
if (_cfg.rx.abs_noise_floor && nhm_supported) {
int8_t nf = 0;
if (_hal.nhm_noise_floor(nf) && nf <= -60 && nf >= -105) {
e.abs_noise_floor_dbm = nf;
Expand Down
64 changes: 64 additions & 0 deletions tests/kestrel_nhm_2g_onair.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
#!/usr/bin/env bash
# Validate the band-gated Kestrel active NHM floor (DEVOURER_RX_NOISE_FLOOR ->
# RxEnergy.abs_noise_floor_dbm, emitted in rx.quality as abs_noise_floor_dbm):
# - C8852C: NHM emitted on 2.4 GHz (validated correct), NULL on 5 GHz (the AGC
# gain-lock reads ~24 dB high there, so it falls back to the passive floor).
# - C8852B: NHM emitted on both bands (unchanged).
#
# Usage: sudo -v && tests/kestrel_nhm_2g_onair.sh
set -u
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
OUT="${NHM2G_OUT:-/tmp/devourer-nhm-2g}"; mkdir -p "$OUT"
CH_24="${CH_24:-6}" CH_5="${CH_5:-36}" DUR="${DUR:-8}"
VID=0x35bc; C_PID=0x0101; B_PID=0x0108

plugged() { lsusb -d "$(printf '%04x:%04x' "$1" "$2")" >/dev/null 2>&1; }
unbind() { local pid="$1" d p i
for d in /sys/bus/usb/devices/*/idProduct; do p=$(cat "$d" 2>/dev/null) || continue
[ "$p" = "${pid#0x}" ] || continue
for i in "$(dirname "$d")":*; do
[ -e "$i/driver" ] && sudo sh -c "echo '$(basename "$i")' > '$i/driver/unbind'" 2>/dev/null || true
done; done; }

cat >"$OUT/med.py" <<'PYEOF'
import json,sys,statistics as st
v=[]
for l in sys.stdin:
try: x=json.loads(l).get("abs_noise_floor_dbm")
except: continue
if isinstance(x,(int,float)): v.append(x)
print(f"{int(st.median(v))}" if v else "null")
PYEOF

nhm() { # $1=pid $2=ch -> median abs_noise_floor_dbm or "null"
unbind "$1"
sudo env DEVOURER_VID="$VID" DEVOURER_PID="$1" DEVOURER_CHANNEL="$2" \
DEVOURER_RXQUALITY=1 DEVOURER_RX_NOISE_FLOOR=1 DEVOURER_RX_ENERGY_MS=500 \
timeout "$DUR" "$ROOT/build/rxdemo" 2>/dev/null | grep '"ev":"rx.quality"' \
| python3 "$OUT/med.py"; }

echo "== building rxdemo =="; cmake --build "$ROOT/build" -j --target rxdemo >/dev/null || exit 1
printf "\n%-8s %-16s %-16s\n" DIE "2.4G(ch$CH_24)" "5G(ch$CH_5)"
c24="skip"; c5="skip"; b24="skip"; b5="skip"
if plugged "$VID" "$C_PID"; then c24=$(nhm "$C_PID" "$CH_24"); c5=$(nhm "$C_PID" "$CH_5"); fi
if plugged "$VID" "$B_PID"; then b24=$(nhm "$B_PID" "$CH_24"); b5=$(nhm "$B_PID" "$CH_5"); fi
printf "%-8s %-16s %-16s\n" C8852C "$c24" "$c5"
printf "%-8s %-16s %-16s\n" C8852B "$b24" "$b5"

echo
python3 - "$c24" "$c5" "$b24" "$b5" <<'PYEOF'
import sys
c24,c5,b24,b5=sys.argv[1:5]
def num(x):
try: return int(x)
except: return None
ok=True
n=num(c24)
if n is not None and -100<=n<=-80: print(f" C8852C 2.4G NHM {c24} dBm -> PASS (emitted, plausible)")
else: print(f" C8852C 2.4G NHM {c24} -> FAIL (expected a plausible -80..-100)"); ok=False
if c5=="null": print(f" C8852C 5G NHM null -> PASS (gated off, uses passive floor)")
else: print(f" C8852C 5G NHM {c5} -> FAIL (expected null; the 5G gain-lock)"); ok=False
if b24!="skip":
print(f" C8852B 2.4G {b24} / 5G {b5} (reference; expect both emitted)")
print(" =>", "ALL PASS" if ok else "CHECK ABOVE")
PYEOF
Loading