What's new

Wireless Log for AIMesh Nodes

JB_1366

Occasional Visitor
has anyone written a script to mimick the Wireless Log tab for AIMesh Nodes? cant believe its not default with Asus software.
 
Previously, I asked the bot to help me write a script. 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. It also shows whether the 5 GHz bandwidth is maintaining 160 MHz.

1770301803819.png


An additional feature calculates how many devices the 2.4 GHz and 5 GHz bands can still accommodate under nearby neighbor Wi-Fi interference. All of this information is then pushed from the AiMesh nodes to the main router via the Scribe add-on for easier monitoring.

1770301872230.png


addon_show_dbm_qbss.sh
Bash:
#!/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

Additionally, I noticed that device names are only stored on the main router. Whenever a new device is added, the device list needs to be manually synchronized from the main router to the node units, making it easier to identify and manage devices when viewing the information.

Please note that the “[email protected]” shown in this script is only an example. Make sure to replace it with your own main router’s username and IP address.

addon_update_custom_clientlist.sh
Bash:
#!/bin/bash

nvram set custom_clientlist="$(ssh [email protected] 'nvram get custom_clientlist')" ; nvram commit
nvram get custom_clientlist
echo Done!

If you run into any issues or need further modifications, you may need to paste the script back into the bot for assistance, as I’m not very familiar with the script’s technical details myself.
 
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.
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.

https___www.snbforums.com_attachments_kitchen-jpg.70115_.jpeg


Note that the RTRMON Addon by @Viktor Jaep, screen seven, has some functionality on that for MerlinFW Nodes too.
 

Support SNBForums w/ Amazon

If you'd like to support SNBForums, just use this link and buy anything on Amazon. Thanks!

Sign Up For SNBForums Daily Digest

Get an update of what's new every day delivered to your mailbox. Sign up here!
Back
Top