Find cleanest 2.4 GHz and 5 GHz channels, hop to them

{EDIT 1: Fixed 5GHz scanning }

The script evaluates the 3 non-overlapping channels of 2.4 GHz and all 28 channels of the 5 GHz spectrum. It automatically filters out weak distant WiFi noise, penalizes overlapping non-standard neighbor channels, blends 40 MHz channel boundaries seamlessly, and hops to the cleanest 2.4 GHz and 5 GHz channels.

cat << 'EOF' > /etc/wifi_optimize.sh
#!/bin/sh

IFACE_2G="phy0-ap0"
IFACE_5G="phy1-ap0"

PHY_2G="phy0"
PHY_5G="phy1"

CHANNELS_2G="1 6 11"

# -------------------------------------------------------------
# Scan 3 non-overlapping 2.4 GHz channels
# -------------------------------------------------------------
get_best_2g_channel() {
    local iface="$1"
    local channel_list="$2"
    local best_chan="1"
    local min_score=999999999999

    local survey_data
    survey_data=$(iw dev "$iface" survey dump 2>/dev/null)

    local scan_json
    scan_json=$(ubus call iwinfo scan '{"device":"'"$iface"'"}' 2>/dev/null)

    for chan in $channel_list; do
        local freq=""
        case "$chan" in
            1)  freq="2412" ;;
            6)  freq="2437" ;;
            11) freq="2462" ;;
        esac

        local base_busy=0
        if [ -n "$survey_data" ]; then
            base_busy=$(echo "$survey_data" | awk -v f="$freq" '
                $0 ~ "frequency:" && $2 == f {found=1; next}
                found && /channel time busy:/ {print $4; exit}
                found && /frequency:/ {exit}
            ')
        fi
        : "${base_busy:=0}"

        local bleeding_penalty=0
        if [ -n "$scan_json" ]; then
            local neighbors
            neighbors=$(echo "$scan_json" | awk '
                /"channel":/ { ch=$2; gsub(/[^0-9]/, "", ch) }
                /"signal":/  { 
                    sig=$2; gsub(/[^0-9-]/, "", sig);
                    if (sig > -80 && ch != "") { print ch }
                }
            ')

# -------------------------------------------------------------
# Penalize overlapping channels used
# -------------------------------------------------------------
            for neighbor_chan in $neighbors; do
                [ "$neighbor_chan" -eq "$chan" ] && continue
                local distance=$((neighbor_chan - chan))
                [ $distance -lt 0 ] && distance=$(( -distance ))

                if [ $distance -le 4 ]; then
                    case "$distance" in
                        1) bleeding_penalty=$((bleeding_penalty + 15000000)) ;;
                        2) bleeding_penalty=$((bleeding_penalty + 10000000)) ;;
                        3) bleeding_penalty=$((bleeding_penalty + 5000000))  ;;
                        4) bleeding_penalty=$((bleeding_penalty + 2000000))  ;;
                    esac
                fi
            done
        fi

        local total_score=$((base_busy + bleeding_penalty))
        logger -t wifi_optimize "WiFi Optimize: 2.4GHz check - Ch $chan: Base=$base_busy, Bleed=$bleeding_penalty, Total=$total_score"

        if [ "$total_score" -lt "$min_score" ]; then
            min_score=$total_score
            best_chan=$chan
        fi
    done
    logger -t wifi_optimize "WiFi Optimize: 2.4GHz: $best_chan"
    echo "$best_chan"
}

# -------------------------------------------------------------
# Scan all 28 channels & calculate optimal 40MHz pair
# -------------------------------------------------------------
get_best_5g_all_channels() {
    local iface="$1"
    local scan_data
    # Capture the raw 5GHz text-wall scan info
    scan_data=$(iw dev "$iface" scan passive 2>/dev/null)
    local logs
    logs=$(logread)

    local chan_map="36 40 44 48 52 56 60 64 100 104 108 112 116 120 124 128 132 136 140 144 149 153 157 161 165 169 173 177"

    local tmp_scores="/tmp/wifi_chan_scores.txt"
    echo -n "" > "$tmp_scores"

    # Step 1: Parse the text wall to count neighboring APs per channel
    for chan in $chan_map; do
        local busy=0

        if [ -n "$scan_data" ]; then
            # In 5GHz, iw reports channels as "primary channel: X" or "Current channel: X"
            busy=$(echo "$scan_data" | grep -Ei "(primary|current) channel: $chan$" | wc -l)
        fi
        : "${busy:=0}"

        # Step 2: Check for regulatory radar blocks in logread
        # If a channel has a radar history, give it a massive penalty
        if echo "$logs" | grep -iq "radar detected on channel $chan"; then
            busy=$((busy + 1000))
        fi

        echo "$chan:$busy" >> "$tmp_scores"
    done

    # Step 3: Evaluate 40MHz channel pairs to find the absolute cleanest space
    local pairs_40mhz="36:1:40 44:1:48 52:1:56 60:1:64 100:1:104 108:1:112 116:1:120 124:1:128 132:1:136 140:1:144 149:1:153 157:1:161 165:1:169 173:1:177"

    local best_primary=""
    local best_offset="1"
    local min_pair_score=999999

    for pair in $pairs_40mhz; do
        local p_chan=$(echo "$pair" | cut -d: -f1)
        local offset=$(echo "$pair" | cut -d: -f2)
        local s_chan=$(echo "$pair" | cut -d: -f3)

        local p_score=$(grep "^$p_chan:" "$tmp_scores" | cut -d: -f2)
        local s_score=$(grep "^$s_chan:" "$tmp_scores" | cut -d: -f2)

        : "${p_score:=0}"
        : "${s_score:=0}"

        local total_pair_score=$((p_score + s_score))

        # First match or a lower congestion score wins
        if [ -z "$best_primary" ] || [ "$total_pair_score" -lt "$min_pair_score" ]; then
            min_pair_score=$total_pair_score
            best_primary=$p_chan
            best_offset=$offset
        fi
    done

    rm -f "$tmp_scores"
    logger -t wifi_optimize "WiFi Optimize: 5GHz: $best_primary:$best_offset"
    echo "$best_primary:$best_offset"
}

# -------------------------------------------------------------
# Main Execution Logic
# -------------------------------------------------------------
BEST_2G=$(get_best_2g_channel "$IFACE_2G" "$CHANNELS_2G")
RESULT_5G=$(get_best_5g_all_channels "$IFACE_5G")

BEST_5G=$(echo "$RESULT_5G" | cut -d: -f1)
SEC_OFFSET_5G=$(echo "$RESULT_5G" | cut -d: -f2)

case "$BEST_2G" in
    1)  FREQ_2G=2412 ;;
    6)  FREQ_2G=2437 ;;
    11) FREQ_2G=2462 ;;
esac

# Update configuration file values for both radios
uci set wireless.radio0.channel="$BEST_2G"
uci set wireless.radio1.channel="$BEST_5G"

# --- ADDED MEDIATEK DRIVER OVERRIDES FOR DFS PATIENCE ---
uci set wireless.radio1.bgscan='1'
uci set wireless.radio1.acs_work_around='1'
uci set wireless.radio1.acs_exclude_dfs='0'

uci commit wireless

# Trigger system network reload to push channel transformations cleanly across both radios
logger -t wifi_optimize "WiFi Optimize: Setting 2G->Ch $BEST_2G, 5G->Ch $BEST_5G."
wifi reload
EOF

Because the WiFi has to go down momentarily, it is recommended to set this as a cron job running in the wee hours:

In System >> Scheduled Tasks

# Find the best 2.4 GHz and 5 GHz channels (DAILY at 0300)
0 3 * * * /bin/sh /etc/wifi_optimize.sh

Finally, set permissions for the file:

chmod +x /etc/wifi_optimize.sh

You can monitor it via:

logread | grep wifi_optimize

You can manually run it via:

sh /etc/wifi_optimize.sh
1 Like