Wi‑Fi Neighborhood Counters Script

1. Introduction

When diagnosing Wi‑Fi congestion or planning coverage, a quick snapshot of nearby networks by band (2.4/5/6 GHz) is invaluable.
This script scans the air and reports visible BSSIDs total, a breakdown per 2.4 GHz / 5 GHz / 6 GHz, and the count of unique channels observed.

Example Use Cases:

  • Detect neighborhood density before picking channels
  • Track Wi‑Fi “noisiness” over time per band
  • Validate 6 GHz availability/visibility on upgraded sites

2. Overview of the Script

This Bash script:

  1. Attempts an iw scan with multiple fallbacks (best‑practice order):

    • scan ap-force passivescan passive → plain scan (active)
  2. Parses the scan results with portable awk to:

    • Count visible BSSIDs overall and per band (2.4/5/6 GHz)
    • Compute unique channel count across all bands
  3. Emits zeroes if scanning isn’t possible (no interface/permissions/driver support)

Environment/Overrides:

  • IF (default wlan0) – Wi‑Fi interface
  • IW (default /sbin/iw) – path to iw
  • SUDO (default sudo) – privilege escalation (can set to empty if not needed)

Prerequisites:

  • Linux-based NetBeez agent with a Wi‑Fi NIC
  • iw installed and permission to scan (may require sudo)
  • Bash + awk; locale forced to C for stable parsing

Expected Output:

  • Five key metrics in key=value lines:

    • scan_visible_bssids_total
    • scan_visible_bssids_24g
    • scan_visible_bssids_5g
    • scan_visible_bssids_6g
    • scan_visible_channels_total

3. Script Code

#!/usr/bin/env bash
# Wi‑Fi neighborhood counters (POSIX sh, BusyBox-friendly)
# Emits:
#   scan_visible_bssids_total=
#   scan_visible_bssids_24g=
#   scan_visible_bssids_5g=
#   scan_visible_bssids_6g=
#   scan_visible_channels_total=

IF="${IF:-wlan0}"
IW="${IW:-/sbin/iw}"
SUDO="${SUDO:-sudo}"
export LC_ALL=C

run_scan() {
  # Try correct ordering first, then fallbacks. Return scan text or empty.
  # 1) ap-force passive (correct order)
  out="$($SUDO "$IW" dev "$IF" scan ap-force passive 2>/dev/null)"; rc=$?
  case "$rc:$out" in
    0:*Usage*) rc=1;;  # usage banner means bad combo on this build
  esac
  [ $rc -eq 0 ] && { printf '%s\n' "$out"; return; }

  # 2) passive only
  out="$($SUDO "$IW" dev "$IF" scan passive 2>/dev/null)"; rc=$?
  case "$rc:$out" in
    0:*Usage*) rc=1;;
  esac
  [ $rc -eq 0 ] && { printf '%s\n' "$out"; return; }

  # 3) plain scan (active)
  out="$($SUDO "$IW" dev "$IF" scan 2>/dev/null)"; rc=$?
  case "$rc:$out" in
    0:*Usage*) rc=1;;
  esac
  [ $rc -eq 0 ] && { printf '%s\n' "$out"; return; }

  # 4) give up
  printf '%s' ""
}

SCAN_OUT="$(run_scan)"

# If scan failed or returned nothing, emit zeros and exit cleanly
if [ -z "$SCAN_OUT" ]; then
  echo "scan_visible_bssids_total=0"
  echo "scan_visible_bssids_24g=0"
  echo "scan_visible_bssids_5g=0"
  echo "scan_visible_bssids_6g=0"
  echo "scan_visible_channels_total=0"
  exit 0
fi

# Parse with portable awk
printf '%s\n' "$SCAN_OUT" | awk '
BEGIN{
  vb=0; v24=0; v5=0; v6=0
}
function band_of_freq(f){
  if (f>=2400 && f<2500) return 24
  if (f>=5000 && f<6000) return 5
  if (f>=5925 && f<7125) return 6
  return 0
}
function ch2g_from_freq(f){
  if (f==2484) return 14
  if (f>=2412 && f<=2472) return int((f-2412)/5)+1
  return 0
}

/^BSS[[:space:]]/{
  vb++; freq_seen=0; next
}

/^[[:space:]]+freq:[[:space:]]*/{
  if (freq_seen) next
  freq_seen=1
  f=$0; gsub(/[^0-9]/,"",f); f+=0
  b=band_of_freq(f)
  if (b==24){
    v24++
    ch=ch2g_from_freq(f); if (ch>0) ch24[ch]=1
  } else if (b==5){
    v5++; ch5[f]=1       # freq key for 5 GHz
  } else if (b==6){
    v6++; ch6[f]=1       # freq key for 6 GHz
  }
  next
}

END{
  visible_channels_total=0
  for (k in ch24) visible_channels_total++
  for (k in ch5)  visible_channels_total++
  for (k in ch6)  visible_channels_total++

  print "scan_visible_bssids_total=" vb
  print "scan_visible_bssids_24g=" v24
  print "scan_visible_bssids_5g=" v5
  print "scan_visible_bssids_6g=" v6
  print "scan_visible_channels_total=" visible_channels_total
}
'

4. Sample Output

scan_visible_bssids_total=42
scan_visible_bssids_24g=18
scan_visible_bssids_5g=23
scan_visible_bssids_6g=1
scan_visible_channels_total=21

5. Closing Remarks

Use this script to baseline Wi‑Fi density and band adoption around agents, or to verify improvements after channel/Tx‑power changes.
It’s resilient across differing iw builds and cleanly degrades to zeroed metrics if scanning isn’t possible.

Extensions you could try:

  • Emit unique channel counts per band (e.g., visible_channels_24g)
  • Add RSSI/quality buckets (e.g., >-70 dBm) for interference severity
  • Output JSON for easier ingestion and historical trending
  • Accept interface via IF env var: IF=wlp2s0 SUDO= ./wifi_scan.sh (skip sudo when not needed)