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