OpenWrt Forum Archive

Topic: HOWTO: Use Expect/TCL on Linux to flash your router

The content of this topic has been archived on 24 Mar 2018. There are no obvious gaps in this topic, but there may still be some posts missing at the end.

Getting tired of trying to hit the enter key at just the right time?  Or of typing the same prompts into tftp time after time?

Let's use Expect/TCL to automate the process.

Cut and paste the following into your favorite editor:

------------------ Cut here ----------------------------------------
#!/usr/bin/expect -f

# read the input parameters
set ipaddr [lindex $argv 0]
set binfile [lindex $argv 1]
set retry_timeout [lindex $argv 2]

# Display input
puts "ip address: $ipaddr"
puts "binary file: $binfile"
puts "retry timeout: $retry_timeout"

# set our script timeout
set timeout [expr $retry_timeout + 20]
# check if all were provided
if { $ipaddr == "" || $binfile == "" }  {
   puts "Usage: <ip> <binary_file> <timeout (seconds)>\n"
   exit 1
}


spawn tftp $ipaddr
# mode
expect "tftp>"
send "mode binary\r"
# rexmt
expect "tftp>"
send "rexmt 1\r"
# timeout
expect "tftp>"
send "timeout $retry_timeout\r"
# trace
expect "tftp>"
send "trace\r"
# attempt to flash
expect "tftp>"
send "put $binfile\r"

# wait
set timer 0
#after $timeout { set timer 1 }
while { !$timer } {
expect {
    "tftp>" {
        send "quit\r"
    } "Transfer timed out." {
        exit
    } "Transfer" {
        exit
        } timeout {
                exit
        } eof {
        exit
        }
    }
}

unset timer
------------------- Stop cutting here ----------------------------

Now save the file as "tftp-flash.sh". Remember to "chmod a+x tftp-flash.sh" to make it executable.

Use the script like this from a terminal (assuming that you are in the same directory):

./tft-flash.sh <ipaddress> <binary_file_name> <timeout (seconds>

For example, to flash my Wrt at 192.168.1.1 with openwrt.trx and wait 20 seconds:

./tft-flash.sh 192.168.1.1 openwrt.trx 20

PREREQUISITES and ERRATA:

* Make sure that you have expect/tcl installed.  Enter "expect" and hit the return at a terminal console.  If you do not, use your distro's package management tool to download and install "expect" (yum, apt-get, etc)
* Change the first line in the script to point to the path where "expect" is installed on your system.  On some systems it's "/usr/bin/expect", on others "/usr/local/bin/expect", etc.

Final hint:  One thing that is not always obvious and that costs me time and frustration every time I forget is that the IP address for flashing is NOT necessarily the same default IP address when nvram is cleared.  The flash IP is set by the manufacturer (usually in the CFE).  After control is passed to the OS (OpenWrt, DD-WRT, manufacturer's firmware, etc) a different IP may be used.  Some personal examples:

Buffalo WLI-TX4-G54HP
   Flash IP: 192.168.11.1
   Original firmware default IP: 1.1.1.1
   OpenWrt default IP: 192.168.1.1

We always want to flash to 192.168.11.1, regardless of what firmware is loaded.

Belkin F58230-4
   Flash IP: 192.168.2.1
   Original firmware default IP: 192.168.2.1
   OpenWrt default IP: 192.168.1.1

This example is more common.  The original firmware IP and flash IP are at least the same.

The lesson: if you are going nuts trying to tftp flash your device, verify the flash IP!

Oops!  One mistake in the script in the previous post.  While debugging, I forgot to uncomment the line:

#after $timeout { set timer 1 }

It should be

after $timeout { set timer 1 }

Here is the corrected script:

-------------------------------- Cut here ----------------------------------------
#!/usr/bin/expect -f

# read the input parameters
set ipaddr [lindex $argv 0]
set binfile [lindex $argv 1]
set retry_timeout [lindex $argv 2]

# Display input
puts "ip address: $ipaddr"
puts "binary file: $binfile"
puts "retry timeout: $retry_timeout"

# set our script timeout
set timeout [expr $retry_timeout + 20]
# check if all were provided
if { $ipaddr == "" || $binfile == "" }  {
   puts "Usage: <ip> <binary_file> <timeout (seconds)>\n"
   exit 1
}


spawn tftp $ipaddr
# mode
expect "tftp>"
send "mode binary\r"
# rexmt
expect "tftp>"
send "rexmt 1\r"
# timeout
expect "tftp>"
send "timeout $retry_timeout\r"
# trace
expect "tftp>"
send "trace\r"
# attempt to flash
expect "tftp>"
send "put $binfile\r"

# wait
set timer 0
after $timeout { set timer 1 }
while { !$timer } {
expect {
    "tftp>" {
        send "quit\r"
    } "Transfer timed out." {
        exit
    } "Transfer" {
        exit
        } timeout {
                exit
        } eof {
        exit
        }
    }
}

unset timer

---------------------------------- Uncut here --------------------------------

Once again, I am self posting - sorry.

Found a nasty bug where I forgot to close the process in the event of a timeout.  New script:

--------------------------- Cut --------------------------------
#!/usr/bin/expect -f

# read the input parameters
set ipaddr [lindex $argv 0]
set binfile [lindex $argv 1]
set retry_timeout [lindex $argv 2]

# Display input
puts "\nCommand line arguments entered:"
puts "\tIP address: $ipaddr"
puts "\tBinary file: $binfile"
puts "\tRetry timeout: $retry_timeout\n"

# set our script timeout
set timeout [expr $retry_timeout + 20]
# check if all were provided
if { $ipaddr == "" || $binfile == "" }  {
   puts "Usage: <ip> <binary_file> <timeout (seconds)>\n"
   exit 1
}


spawn tftp $ipaddr
set tftp_id $spawn_id
#save spawn_id - note that you can switch expect and send between processes by "set spawn_id $tftp_id"

# mode
expect "tftp>"
send "mode binary\r"
# rexmt
expect "tftp>"
send "rexmt 1\r"
# timeout
expect "tftp>"
send "timeout $retry_timeout\r"
# trace
expect "tftp>"
send "trace\r"
# attempt to flash
expect "tftp>"
send "put $binfile\r"

# wait
set timer 0
after $timeout { set timer 1 }
while { !$timer } {
expect {
    "tftp>" {
        send "quit\r"
        } timeout {
                close -i $tftp_id
        } eof {
        exit
        }
    }
}

unset timer

--------------------------------------------------------------------

The discussion might have continued from here.