What's new

[Script] 160mhz channel recovery for wifi 6 only routers

soul4kills

Occasional Visitor
Sharing this script that helps to maintain and recover the 160mhz connection for wifi 6 routers . There are other scripts in this forum that do something similar, but it disconnects the clients. This script does not disconnect the clients as it uses the built in broadcom command to trigger a pre-ism CAC check. It took a while to find that command. Before this, i was using the csa command to migrate the width of the channel.

It's set to run every 30 minutes, that's how long a DFS block lasts, with an offset of 1 minute to account for the routers own cac check and not interrupt it. It checks if you're already on 160hz, if so early exit. If not, it will trigger the CAC check for your current control channel, if clear, gets upgraded to 160mhz channel. If it fails, it will fallback to the other 160mhz block and do another cac check. Doesn't matter if it fails at this point as only 2 blocks are available for 160mhz in the US. 36/160 & 100/160. Then after 30 minutes, it will try again from the cron job.

It avoids the upper channels that are not 160mhz capable. The reason why is, when you are kicked off of the DFS channels, and you end up in the 149-165 block. The routers auto mode stops trying to connect to 160mhz because it's not capable in this range of channels. So you're just stuck there until you manually restart your radio. For the most part, my script allows the router to do it's own thing to get you back to 160mhz channel, that's the reason for the offset. It's weird that asus hasn't considered this. What's the point of auto if it gets stuck and doesn't wrap around again. It would be such a simple fix.

That's pretty much what it does. AI definitely helped me with this. I'm capable of doing on my own but, it would've taken me longer than a day to figure out. It's expected to run in the scripts folder. It has 3 levels of logging, none, events, verbose saved in the scripts folder. It will automatically add/remove a cron job and create/add/remove init-start depending on the flags you set for the script. You can set a preferred channel depending on which block is more congested in your area. 100/160 for me is clear as most routers default to channel 36.

So far, it seems to work without disconnecting the clients from my own testing when it comes to upgrading to 160mhz, but when moving channels it still seemed like it didn't disconnect, even though i though it might. Maybe it happens so quick that it's not noticeable.

Edit: Reasons it may not work.


make sure you set your 5ghz interface mapping. on my rtax86s, it's eth6. These ssh commands will help. "nvram get sta_phy_ifnames" use this to see what the interfaces are. "wl -i "eth5" chanspec" use this to find out which channel they are operating at and use that to identify the 5ghz interface. the output will be "channel/width", (ex. 100/160).

Then there is the timing. Change CAC_WAIT to something longer if it's logging as failed even though it's clear and 160mhz capable. I originally had it at 60 seconds but it was failing, then I added a second and it worked.

Also if someone wants to make this into a plugin for amtm, please feel free because I don't recommend anyone not familiar with ssh trying to use this if they don't know what they're doing. A plugin would be nice for them.

Update:
This was made for a "single" 5ghz radio in mind as that is what I have. RT-AX86s. A triband router is not something I considered or can test for.

CLIENT DISCONNECTS. When only upgrading to 160mhz, it does not disconnect. When moving channels, sometimes it does disconnect, but it happens so fast it is not noticeable. Most likely clients that will disconnect momentarily are the non ax clients.
 

Attachments

Last edited:
thanks when i set startup to 36/160 the logs retrun its on 40 ? is this normal due to the extension channel
i am on a gtaxe16000

admin@GT-AXE16000-Upstairs:/jffs/scripts# cat fix_160A.log
Sat Mar 21 23:15:18 EDT 2026: [__START] Script triggered by cron
Sat Mar 21 23:15:18 EDT 2026: [ACTION] Added cron job 'fix_160A'.
Sat Mar 21 23:15:18 EDT 2026: [ACTION] Added entry to existing '/jffs/scripts/init-start'.
Sat Mar 21 23:15:18 EDT 2026: [ONLINE] Radio eth7 is [UP]. Proceeding.
Sat Mar 21 23:15:18 EDT 2026: [INFO] Current state [CHANSPEC=40/160]
Sat Mar 21 23:15:18 EDT 2026: [OK] Already on 160MHz [40/160]. No action needed.
Sat Mar 21 23:15:18 EDT 2026: [__END] Script completed successfully
 
i also dont know about yhour only 2 channels avail comment as my downstairs router currently shows on channel 120 or 124 depedning on how you interpret it.. with 160

ie
admin@GT-AXE16000downstairs:/tmp/home/root# wl -i "eth8" chanspec
124/160 (0xee72)
 
It's because even if your control channel is 124, the channels used still spans 100-128, and the 36 block is 36-64. So these 2 blocks are the only blocks capable of 160mhz width. So if you're on the lower block with a control channel of 52, the channels used are 36-64. Also having channel 36 or 100 as the control channels are more stable.
 
makes sense thanks..
hopfully this helps the issue i had where when the dfs was detected and it changed the config. my 2 wifi ethernet bridges would drop off and never come back. Changed to 80mhz fixed it but i am trying your script now with fingers crossed.
 
thanks when i set startup to 36/160 the logs retrun its on 40 ? is this normal due to the extension channel
i am on a gtaxe16000

admin@GT-AXE16000-Upstairs:/jffs/scripts# cat fix_160A.log
Sat Mar 21 23:15:18 EDT 2026: [__START] Script triggered by cron
Sat Mar 21 23:15:18 EDT 2026: [ACTION] Added cron job 'fix_160A'.
Sat Mar 21 23:15:18 EDT 2026: [ACTION] Added entry to existing '/jffs/scripts/init-start'.
Sat Mar 21 23:15:18 EDT 2026: [ONLINE] Radio eth7 is [UP]. Proceeding.
Sat Mar 21 23:15:18 EDT 2026: [INFO] Current state [CHANSPEC=40/160]
Sat Mar 21 23:15:18 EDT 2026: [OK] Already on 160MHz [40/160]. No action needed.
Sat Mar 21 23:15:18 EDT 2026: [__END] Script completed successfully
Yeah, it's normal, it could be that there's a lot of interference on channel 36, so it picked something else. My script only forces the firmware's normal auto routine to re-establish a 160mhz connection. It will only try to get to your preferred channel.

Edit: sorry, i misread, if you're already on 160mhz width, it won't make any attempts to change to your preferred channel. If you want to force it yourself, use "wl -i eth7 dfs_ap_move 36/160" or whichever eth# you're using.
 
Last edited:
i forsee a problem on the gtaxe16000 as the 5ghz band is split and either can be the low or high areas depending on if using radio 1 or radio 2.. and the jump to the other range will not work.

is there a way to disable the attempt to jump to the other band range? in this case probably should be an option? maybe it if just fails no bigee?
 
@Adamm would be proud of this version, except the name of the script is too gentle ;-)
 
i forsee a problem on the gtaxe16000 as the 5ghz band is split and either can be the low or high areas depending on if using radio 1 or radio 2.. and the jump to the other range will not work.

is there a way to disable the attempt to jump to the other band range? in this case probably should be an option? maybe it if just fails no bigee?
ooof, i did not consider a triband router. I don't have one myself and won't be able to test or figure out how a triband handles it's radios. So i made a simpler version where you can set a preferred range for your radios to stay on, but you have to create 2 files for each radio and modify 1 for a seperate cron job/init-start.

The logic is, you set a preferred range for it to stay in, and a preferred channel for it to jump back to if it falls outside your range. If it's already in it's preferrred range but it is 80mhz, it will trigger a CAC check. took out any logging, as it's more straight forward than the other version. No fallbacks because of the overlap problem with a triband router.


Bash:
#!/bin/sh
# sticky_160.sh - Keep 5GHz radio on preferred range at 160MHz
# Runs via cron every 30 minutes (1,31 * * * *)

SCRIPT_NAME="sticky_160"   # Script name — must match the actual filename without .sh
IFACE="eth6"
PREFERRED_CHAN="100/160"    # Preferred chanspec to force back to if outside range
PREFERRED_RANGE="100-128"  # Channel range considered "preferred" (e.g. 100-128 or 36-64)
MANAGE_CRON=1               # Set to 1/0 to add/remove cron job
INIT_START="/jffs/scripts/init-start"

# Derived from SCRIPT_NAME — do not edit
SCRIPT_PATH="/jffs/scripts/${SCRIPT_NAME}.sh"
JOB_ID="$SCRIPT_NAME"
CRON_SCHEDULE="1,31 * * * * $SCRIPT_PATH"
INIT_ENTRY="cru a \"$SCRIPT_NAME\" \"1,31 * * * * $SCRIPT_PATH\""

# Parse preferred range bounds
RANGE_LOW="${PREFERRED_RANGE%-*}"
RANGE_HIGH="${PREFERRED_RANGE#*-}"

# -----------------------------------------
# 1. Cron and init-start self-registration
# -----------------------------------------
if [ "$MANAGE_CRON" = "1" ]; then
    cru l 2>/dev/null | grep -q "$JOB_ID" || cru a "$JOB_ID" "$CRON_SCHEDULE"
    if [ ! -f "$INIT_START" ]; then
        printf '#!/bin/sh\n%s\n' "$INIT_ENTRY" > "$INIT_START"
        chmod +x "$INIT_START"
    elif ! grep -qF "$INIT_ENTRY" "$INIT_START"; then
        echo "$INIT_ENTRY" >> "$INIT_START"
    fi
else
    cru l 2>/dev/null | grep -q "$JOB_ID" && cru d "$JOB_ID"
    grep -qF "$INIT_ENTRY" "$INIT_START" 2>/dev/null && sed -i "\|$SCRIPT_PATH|d" "$INIT_START"
fi

# -----------------------------------------
# 2. Radio up check
# -----------------------------------------
[ "$(wl -i "$IFACE" isup 2>/dev/null)" = "1" ] || exit 0

# -----------------------------------------
# 3. Read current state
# -----------------------------------------
CURRENT_SPEC=$(wl -i "$IFACE" chanspec 2>/dev/null | awk '{print $1}')
CURRENT_CHAN="${CURRENT_SPEC%%/*}"
CURRENT_WIDTH="${CURRENT_SPEC#*/}"

# -----------------------------------------
# 4. Outside preferred range or in range at 80MHz — move to preferred channel
# -----------------------------------------
if [ "$CURRENT_CHAN" -lt "$RANGE_LOW" ] || [ "$CURRENT_CHAN" -gt "$RANGE_HIGH" ] || [ "$CURRENT_WIDTH" = "80" ]; then
    wl -i "$IFACE" dfs_ap_move "$PREFERRED_CHAN" 2>/dev/null
fi

exit 0

One point of failure to consider is whether or not the "dfs_ap_move" command can run in parallel. So you may need to offset the cron jobs by a minute or 2. I don't have a triband router to test this.

To test, use "wl -i "eth6" dfs_ap_move 36/160" & "wl -i "eth7" dfs_ap_move 100/160". Assuming those are your interface id's for your 2 5ghz radios, run the second command right after the first. If the second command returns as busy, it means it can't run in parallel and you have to offset your cron jobs.

Change this line here. from 1,31 to 2,32 for an offset of 1 minute. or 3,33 for an offset of 2 minutes to just 1 of the scripts.
Code:
CRON_SCHEDULE="1,31 * * * * $SCRIPT_PATH"
 
Last edited:

Latest threads

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!

Members online

Back
Top