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

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
    echo "$best_chan"
}

# -------------------------------------------------------------
# Scan all 28 channels & calculate optimal 40MHz pair
# -------------------------------------------------------------
get_best_5g_all_channels() {
    local iface="$1"
    local survey_data
    survey_data=$(iw dev "$iface" survey dump 2>/dev/null)
    local logs
    logs=$(logread)

    local chan_map="36:5180:0 40:5200:0 44:5220:0 48:5240:0 52:5260:1 56:5280:1 60:5300:1 64:5320:1 100:5500:1 104:5520:1 108:5540:1 112:5560:1 116:5580:1 120:5600:1 124:5620:1 128:5640:1 132:5660:1 136:5680:1 140:5700:1 144:5720:1 149:5745:0 153:5765:0 157:5785:0 161:5805:0 165:5825:0 169:5845:0 173:5865:0 177:5885:0"

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

    for item in $chan_map; do
        local chan=$(echo "$item" | cut -d: -f1)
        local freq=$(echo "$item" | cut -d: -f2)
        local is_dfs=$(echo "$item" | cut -d: -f3)

        local busy=0
        if [ -n "$survey_data" ]; then
            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
        : "${busy:=0}"

        if [ "$is_dfs" -eq 1 ] && echo "$logs" | grep -iq "radar detected on channel $chan"; then
            busy=$((busy + 100000000))
        fi

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

    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="36"
    local best_offset="1"
    local min_pair_score=9999999999999

    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))

        if [ "$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"
    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"
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."
/etc/init.d/network 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