#!/bin/sh
# === RSSI threshold definition ===
RSSI_THRESHOLD="-65"
# === Sort mode: rssi / uptime / none ===
SORT_MODE="rssi"
interfaces="wl0.1 wl1.1"
# === Auto-detect all Wi-Fi interfaces ===
get_wifi_interfaces() {
for iface in $interfaces; do
if wl -i "$iface" assoclist >/dev/null 2>&1; then
echo "$iface"
fi
done
}
# === Get device name corresponding to MAC from custom_clientlist ===
get_device_name() {
MAC="$1"
nvram get custom_clientlist | tr '>' '\n' | awk -v mac="$MAC" '
{
if (tolower($0) == tolower(mac)) {
gsub(/^<|>$/, "", prev)
print prev
exit
}
prev = $0
}'
}
# === Get IP corresponding to MAC from ARP table ===
get_device_ip() {
MAC="$1"
ip neigh | grep -i "$MAC" | awk '{print $1}' | head -n 1
}
# === Decode chanspec into human readable format ===
decode_chanspec() {
iface="$1"
chanspec_output=$(wl -i "$iface" chanspec 2>/dev/null)
[ -z "$chanspec_output" ] && echo "Channel info unavailable" && return
# <<< ULTIMATE FIX START >>>
# Handle different output formats from 'wl chanspec'
# Format 1 (wide channels): "149/80 (0xe09b)"
# Format 2 (narrow channels): "10 (0x100a)"
if echo "$chanspec_output" | grep -q '/'; then
# Handle format 1
channel=$(echo "$chanspec_output" | awk -F'/' '{print $1}')
bandwidth_raw=$(echo "$chanspec_output" | awk -F'[/ ]' '{print $2}')
bandwidth="${bandwidth_raw} MHz"
else
# Handle format 2
channel=$(echo "$chanspec_output" | awk '{print $1}')
# Extract hex code to decode bandwidth accurately
raw=$(echo "$chanspec_output" | awk -F'[()]' '{print $2}')
if [ -n "$raw" ]; then
val=$((raw))
bw_code=$(( (val >> 10) & 0x3 ))
case $bw_code in
0) bandwidth="20 MHz" ;;
1) bandwidth="40 MHz" ;;
2) bandwidth="80 MHz" ;;
3) bandwidth="160 MHz" ;;
*) bandwidth="Unknown" ;;
esac
else
bandwidth="20 MHz" # Fallback for 2.4GHz
fi
fi
if [ "$channel" -le 14 ]; then
bandname="2.4 GHz"
elif [ "$channel" -ge 36 ] && [ "$channel" -le 177 ]; then
bandname="5 GHz"
elif [ "$channel" -ge 1 ] && [ "$channel" -le 233 ]; then
bandname="6 GHz"
else
bandname="Unknown Band"
fi
echo "$channel ($bandname, $bandwidth)"
# <<< ULTIMATE FIX END >>>
}
# === Format uptime seconds ===
format_time() {
SECS=$1
[ -z "$SECS" ] && echo "N/A" && return
DAYS=$((SECS/86400))
HOURS=$(( (SECS%86400)/3600 ))
MINS=$(( (SECS%3600)/60 ))
SECS_REST=$((SECS%60))
if [ $DAYS -gt 0 ]; then
printf "%dd %dh %dm %ds" $DAYS $HOURS $MINS $SECS_REST
elif [ $HOURS -gt 0 ]; then
printf "%dh %dm %ds" $HOURS $MINS $SECS_REST
elif [ $MINS -gt 0 ]; then
printf "%dm %ds" $MINS $SECS_REST
else
printf "%ds" $SECS_REST
fi
}
# === Convert bytes to MB/GB ===
format_bytes() {
BYTES=$1
if [ -z "$BYTES" ] || [ "$BYTES" -eq 0 ]; then
echo "0 MB"
elif [ "$BYTES" -ge 1073741824 ]; then
echo "$((BYTES / 1024 / 1024 / 1024)) GB"
else
echo "$((BYTES / 1024 / 1024)) MB"
fi
}
echo "📡 Checking RSSI, uptime & traffic usage for all connected Wi-Fi devices..."
echo "RSSI threshold is set to ${RSSI_THRESHOLD} dBm"
echo "==========================================================="
RESULTS_24G=""
RESULTS_5G=""
for WIFI_IF in $(get_wifi_interfaces); do
CH_INFO=$(decode_chanspec "$WIFI_IF")
BAND=$(echo "$CH_INFO" | sed -n 's/.*(\(.*\),.*/\1/p' | tr -d ' ')
for MAC in $(wl -i "$WIFI_IF" assoclist | grep -Eo '([0-9A-Fa-f]{2}:){5}[0-9A-Fa-f]{2}'); do
RSSI=$(wl -i "$WIFI_IF" rssi "$MAC" 2>/dev/null)
[ -z "$RSSI" ] && RSSI=0
NAME=$(get_device_name "$MAC")
[ -z "$NAME" ] && NAME="Unknown Device"
IPADDR=$(get_device_ip "$MAC")
[ -z "$IPADDR" ] && IPADDR="Unavailable"
STAINFO=$(wl -i "$WIFI_IF" sta_info "$MAC" 2>/dev/null)
SECS=$(echo "$STAINFO" | awk '/in network/ {print $3}')
CONN_TIME=$(format_time "$SECS")
TX_BYTES=$(echo "$STAINFO" | awk '/tx total bytes/ {print $4}')
RX_BYTES=$(echo "$STAINFO" | awk '/rx data bytes/ {print $4}')
TOTAL_BYTES=$((TX_BYTES + RX_BYTES))
TX_HUMAN=$(format_bytes $TX_BYTES)
RX_HUMAN=$(format_bytes $RX_BYTES)
TOTAL_HUMAN=$(format_bytes $TOTAL_BYTES)
[ "$RSSI" -lt "$RSSI_THRESHOLD" ] && STATUS="⚠️ Roaming recommended" || STATUS="✅ No roaming needed"
ENTRY="📱 ${NAME} ($MAC) - IP: ${IPADDR} - RSSI: ${RSSI} dBm - Uptime: ${CONN_TIME} - Channel: ${CH_INFO} - Traffic: TX=${TX_HUMAN}, RX=${RX_HUMAN}, TOTAL=${TOTAL_HUMAN} ... ${STATUS}"
if echo "$BAND" | grep -q "2.4"; then
RESULTS_24G="${RESULTS_24G}\n$RSSI|$SECS|$ENTRY"
elif echo "$BAND" | grep -q "5"; then
RESULTS_5G="${RESULTS_5G}\n$RSSI|$SECS|$ENTRY"
fi
done
done
# === Sorting & display ===
echo "=== 2.4 GHz Devices ==="
if [ -z "$RESULTS_24G" ]; then
echo "No devices connected"
else
case "$SORT_MODE" in
rssi) echo -e "$RESULTS_24G" | tail -n +2 | sort -t"|" -k1 -n | cut -d"|" -f3- ;;
uptime) echo -e "$RESULTS_24G" | tail -n +2 | sort -t"|" -k2 -n | cut -d"|" -f3- ;;
*) echo -e "$RESULTS_24G" | tail -n +2 | cut -d"|" -f3- ;;
esac
fi
echo ""
echo "=== 5 GHz Devices ==="
if [ -z "$RESULTS_5G" ]; then
echo "No devices connected"
else
case "$SORT_MODE" in
rssi) echo -e "$RESULTS_5G" | tail -n +2 | sort -t"|" -k1 -n | cut -d"|" -f3- ;;
uptime) echo -e "$RESULTS_5G" | tail -n +2 | sort -t"|" -k2 -n | cut -d"|" -f3- ;;
*) echo -e "$RESULTS_5G" | tail -n +2 | cut -d"|" -f3- ;;
esac
fi
# === Wi-Fi channel utilization ===
echo ""
echo "=== WiFi Channel Utilization Report ==="
for iface in $interfaces; do
if wl -i "$iface" assoclist >/dev/null 2>&1; then
echo ""
echo "Interface: $iface"
CH_INFO_FOOTER=$(decode_chanspec $iface)
echo "Channel: $CH_INFO_FOOTER"
stats=$(wl -i $iface chanim_stats | grep -v version)
[ -z "$stats" ] && { echo " No chanim_stats available for $iface"; continue; }
idle=$(echo "$stats" | awk '{print $14}' | grep -Eo '^[0-9]+$')
busy=$(echo "$stats" | awk '{print $15}' | grep -Eo '^[0-9]+$')
idle=${idle:-0}
busy=${busy:-0}
total=$((idle + busy))
[ "$total" -eq 0 ] && { echo " Busy Ratio: N/A (idle + busy = 0)"; continue; }
busy_percent=$(awk "BEGIN { printf \"%.2f\", ($busy / $total) * 100 }")
clients=$(wl -i $iface assoclist | grep -Eo '([0-9A-Fa-f]{2}:){5}[0-9A-Fa-f]{2}' | wc -l)
echo " Busy Ratio: ${busy_percent}%"
if [ "$clients" -eq 0 ]; then
echo " Connected Devices: 0"
else
avg_load=$(awk "BEGIN { printf \"%.2f\", $busy_percent / $clients }")
est_max=$(awk "BEGIN { if ($avg_load > 0) printf \"%d\", 100 / $avg_load; else print \"N/A\" }")
echo " Average Load per Device: ${avg_load}%"
echo " Connected Devices: $clients (Est. Max Capacity: $est_max)"
fi
fi
done
#!/bin/bash
nvram set custom_clientlist="$(ssh [email protected] 'nvram get custom_clientlist')" ; nvram commit
nvram get custom_clientlist
echo Done!
Too funny, I asked the bot to do something similar for both stock and Merlin nodes to troubleshoot RSSI connectivity. My current versions have gone beyind the ones there.Its main purpose is to display the dBm signal strength of each device so I can fine-tune the roaming assistant threshold for smoother roaming.
We use essential cookies to make this site work, and optional cookies to enhance your experience.