From 8d8c694baecccd3cff52c95cae8a7d2afae615d7 Mon Sep 17 00:00:00 2001 From: Josh Blum Date: Sat, 26 Mar 2011 23:03:04 -0700 Subject: usrp2: created net burner gui wrapper for N series The gui looks and works much like the card burner gui. However, the possible devices are not enumerated. Handles errors, resetting, and showing progress. Added hooks to net burner for progress callbacks (progress meter). Changed --ip option to --addr (any resolvable address will work). Fixes issue with "timeout" on reset, by catching socket.timeout. Updated the manual to reflect using the gui app and --addr option. --- host/docs/usrp2.rst | 11 +- host/utils/usrp_n2xx_net_burner.py | 54 ++++++---- host/utils/usrp_n2xx_net_burner_gui.py | 188 +++++++++++++++++++++++++++++++++ 3 files changed, 226 insertions(+), 27 deletions(-) create mode 100644 host/utils/usrp_n2xx_net_burner_gui.py diff --git a/host/docs/usrp2.rst b/host/docs/usrp2.rst index 70101bd87..912f7d2bd 100644 --- a/host/docs/usrp2.rst +++ b/host/docs/usrp2.rst @@ -58,17 +58,20 @@ Use the net burner tool (unix) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ :: + sudo /share/uhd/utils/usrp_n2xx_net_burner_gui.py + + -- OR -- + cd /share/uhd/utils - ./usrp_n2xx_net_burner.py --ip= --fw= - ./usrp_n2xx_net_burner.py --ip= --fpga= + ./usrp_n2xx_net_burner.py --addr= --fw= + ./usrp_n2xx_net_burner.py --addr= --fpga= ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Use the net burner tool (Windows) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ :: - /share/uhd/utils/usrp_n2xx_net_burner.py --ip= --fw= - /share/uhd/utils/usrp_n2xx_net_burner.py --ip= --fpga= + /share/uhd/utils/usrp_n2xx_net_burner_gui.py ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Device recovery and bricking diff --git a/host/utils/usrp_n2xx_net_burner.py b/host/utils/usrp_n2xx_net_burner.py index 6fdc9df20..c715f3364 100755 --- a/host/utils/usrp_n2xx_net_burner.py +++ b/host/utils/usrp_n2xx_net_burner.py @@ -118,31 +118,29 @@ def is_valid_fw_image(fw_image): # Burner class, holds a socket and send/recv routines ######################################################################## class burner_socket(object): - def __init__(self, ip): + def __init__(self, addr): self._sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) self._sock.settimeout(UDP_TIMEOUT) - self._sock.connect((ip, UDP_FW_UPDATE_PORT)) + self._sock.connect((addr, UDP_FW_UPDATE_PORT)) + self.set_callbacks(lambda *a: None, lambda *a: None) + self.init_update() #check that the device is there - def send_and_recv(self, pkt): - try: self._sock.send(pkt) - except Exception, e: - print e - sys.exit(1) - - try: recv_pkt = self._sock.recv(UDP_MAX_XFER_BYTES) - except Exception, e: - print e - sys.exit(1) + def set_callbacks(self, progress_cb, status_cb): + self._progress_cb = progress_cb + self._status_cb = status_cb - return recv_pkt + def send_and_recv(self, pkt): + self._sock.send(pkt) + return self._sock.recv(UDP_MAX_XFER_BYTES) #just here to validate comms def init_update(self): out_pkt = pack_flash_args_fmt(USRP2_FW_PROTO_VERSION, update_id_t.USRP2_FW_UPDATE_ID_OHAI_LOL, seq(), 0, 0, "") - in_pkt = self.send_and_recv(out_pkt) + try: in_pkt = self.send_and_recv(out_pkt) + except socket.timeout: raise Exception, "No response from device" (proto_ver, pktid, rxseq, ip_addr) = unpack_flash_ip_fmt(in_pkt) if pktid == update_id_t.USRP2_FW_UPDATE_ID_OHAI_OMG: - print "USRP2P found." + print "USRP-N2XX found." else: raise Exception, "Invalid reply received from device." @@ -214,9 +212,11 @@ class burner_socket(object): def write_image(self, image, addr): print "Writing image" + self._status_cb("Writing") + writedata = image #we split the image into smaller (256B) bits and send them down the wire - while image: - out_pkt = pack_flash_args_fmt(USRP2_FW_PROTO_VERSION, update_id_t.USRP2_FW_UPDATE_ID_WRITE_TEH_FLASHES_LOL, seq(), addr, FLASH_DATA_PACKET_SIZE, image[:FLASH_DATA_PACKET_SIZE]) + while writedata: + out_pkt = pack_flash_args_fmt(USRP2_FW_PROTO_VERSION, update_id_t.USRP2_FW_UPDATE_ID_WRITE_TEH_FLASHES_LOL, seq(), addr, FLASH_DATA_PACKET_SIZE, writedata[:FLASH_DATA_PACKET_SIZE]) in_pkt = self.send_and_recv(out_pkt) (proto_ver, pktid, rxseq, flash_addr, rxlength, data) = unpack_flash_args_fmt(in_pkt) @@ -224,11 +224,13 @@ class burner_socket(object): if pktid != update_id_t.USRP2_FW_UPDATE_ID_WROTE_TEH_FLASHES_OMG: raise Exception, "Invalid reply %c from device." % (chr(pktid)) - image = image[FLASH_DATA_PACKET_SIZE:] + writedata = writedata[FLASH_DATA_PACKET_SIZE:] addr += FLASH_DATA_PACKET_SIZE + self._progress_cb(float(len(image)-len(writedata))/len(image)) def verify_image(self, image, addr): print "Verifying data" + self._status_cb("Verifying") readsize = len(image) readdata = str() while readsize > 0: @@ -245,6 +247,7 @@ class burner_socket(object): readdata += data[:thisreadsize] readsize -= FLASH_DATA_PACKET_SIZE addr += FLASH_DATA_PACKET_SIZE + self._progress_cb(float(len(readdata))/len(image)) print "Read back %i bytes" % len(readdata) # print readdata @@ -253,7 +256,7 @@ class burner_socket(object): # print "out: %i in: %i" % (ord(image[i]), ord(readdata[i])) if readdata != image: - print "Verify failed. Image did not write correctly." + raise Exception, "Verify failed. Image did not write correctly." else: print "Success." @@ -285,13 +288,15 @@ class burner_socket(object): def reset_usrp(self): out_pkt = pack_flash_args_fmt(USRP2_FW_PROTO_VERSION, update_id_t.USRP2_FW_UPDATE_ID_RESET_MAH_COMPUTORZ_LOL, seq(), 0, 0, "") - in_pkt = self.send_and_recv(out_pkt) + try: in_pkt = self.send_and_recv(out_pkt) + except socket.timeout: return (proto_ver, pktid, rxseq, flash_addr, rxlength, data) = unpack_flash_args_fmt(in_pkt) if pktid == update_id_t.USRP2_FW_UPDATE_ID_RESETTIN_TEH_COMPUTORZ_OMG: raise Exception, "Device failed to reset." def erase_image(self, addr, length): + self._status_cb("Erasing") #get flash info first out_pkt = pack_flash_args_fmt(USRP2_FW_PROTO_VERSION, update_id_t.USRP2_FW_UPDATE_ID_ERASE_TEH_FLASHES_LOL, seq(), addr, length, "") in_pkt = self.send_and_recv(out_pkt) @@ -302,6 +307,7 @@ class burner_socket(object): raise Exception, "Invalid reply %c from device." % (chr(pktid)) print "Erasing %i bytes at %i" % (length, addr) + start_time = time.time() #now wait for it to finish while(True): @@ -313,6 +319,8 @@ class burner_socket(object): if pktid == update_id_t.USRP2_FW_UPDATE_ID_IM_DONE_ERASING_OMG: break elif pktid != update_id_t.USRP2_FW_UPDATE_ID_NOPE_NOT_DONE_ERASING_OMG: raise Exception, "Invalid reply %c from device." % (chr(pktid)) + time.sleep(0.01) #decrease network overhead by waiting a bit before polling + self._progress_cb(min(1.0, (time.time() - start_time)/(length/80e3))) ######################################################################## @@ -320,7 +328,7 @@ class burner_socket(object): ######################################################################## def get_options(): parser = optparse.OptionParser() - parser.add_option("--ip", type="string", help="USRP2P firmware address", default='') + parser.add_option("--addr", type="string", help="USRP-N2XX device address", default='') parser.add_option("--fw", type="string", help="firmware image path (optional)", default='') parser.add_option("--fpga", type="string", help="fpga image path (optional)", default='') parser.add_option("--reset", action="store_true", help="reset the device after writing", default=False) @@ -335,7 +343,7 @@ def get_options(): ######################################################################## if __name__=='__main__': options = get_options() - if not options.ip: raise Exception, 'no ip address specified' + if not options.addr: raise Exception, 'no address specified' if not options.fpga and not options.fw and not options.reset: raise Exception, 'Must specify either a firmware image or FPGA image, and/or reset.' @@ -345,7 +353,7 @@ if __name__=='__main__': response = raw_input("""Type "yes" to continue, or anything else to quit: """) if response != "yes": sys.exit(0) - burner = burner_socket(ip=options.ip) + burner = burner_socket(addr=options.addr) if options.read: if options.fw: diff --git a/host/utils/usrp_n2xx_net_burner_gui.py b/host/utils/usrp_n2xx_net_burner_gui.py new file mode 100644 index 000000000..7fcb7d121 --- /dev/null +++ b/host/utils/usrp_n2xx_net_burner_gui.py @@ -0,0 +1,188 @@ +#!/usr/bin/env python +# +# Copyright 2011 Ettus Research LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# + +import threading +import usrp_n2xx_net_burner #import implementation +import Tkinter, tkFileDialog, tkFont, tkMessageBox +import os + +class BinFileEntry(Tkinter.Frame): + """ + Simple file entry widget for getting the file path of bin files. + Combines a label, entry, and button with file dialog callback. + """ + + def __init__(self, root, what, def_path=''): + self._what = what + Tkinter.Frame.__init__(self, root) + Tkinter.Label(self, text=what+":").pack(side=Tkinter.LEFT) + self._entry = Tkinter.Entry(self, width=50) + self._entry.insert(Tkinter.END, def_path) + self._entry.pack(side=Tkinter.LEFT) + Tkinter.Button(self, text="...", command=self._button_cb).pack(side=Tkinter.LEFT) + + def _button_cb(self): + filename = tkFileDialog.askopenfilename( + parent=self, + filetypes=[('bin files', '*.bin'), ('all files', '*.*')], + title="Select bin file for %s"%self._what, + initialdir=os.path.dirname(self.get_filename()), + ) + + # open file on your own + if filename: + self._entry.delete(0, Tkinter.END) + self._entry.insert(0, filename) + + def get_filename(self): + return self._entry.get() + +class ProgressBar(Tkinter.Canvas): + """ + A simple implementation of a progress bar. + Draws rectangle that fills from left to right. + """ + + def __init__(self, root, width=500, height=20): + self._width = width + self._height = height + Tkinter.Canvas.__init__(self, root, relief="sunken", borderwidth=2, width=self._width-2, height=self._height-2) + self._last_fill_pixels = None + self.set(0.0) + + def set(self, frac): + """ + Update the progress where fraction is between 0.0 and 1.0 + """ + #determine the number of pixels to draw + fill_pixels = int(round(self._width*frac)) + if fill_pixels == self._last_fill_pixels: return + self._last_fill_pixels = fill_pixels + + #draw a rectangle representing the progress + if frac: self.create_rectangle(0, 0, fill_pixels, self._height, fill="#357EC7") + else: self.create_rectangle(0, 0, self._width, self._height, fill="#E8E8E8") + +class SectionLabel(Tkinter.Label): + """ + Make a text label with bold font. + """ + + def __init__(self, root, text): + Tkinter.Label.__init__(self, root, text=text) + + #set the font bold + f = tkFont.Font(font=self['font']) + f['weight'] = 'bold' + self['font'] = f.name + +class USRPN2XXNetBurnerApp(Tkinter.Frame): + """ + The top level gui application for the usrp-n2xx network burner. + Creates entry widgets and button with callback to write images. + """ + + def __init__(self, root, addr, fw, fpga): + + Tkinter.Frame.__init__(self, root) + + #pack the file entry widgets + SectionLabel(self, text="Select Images").pack(pady=5) + self._fw_img_entry = BinFileEntry(self, "Firmware Image", def_path=fw) + self._fw_img_entry.pack() + self._fpga_img_entry = BinFileEntry(self, "FPGA Image", def_path=fpga) + self._fpga_img_entry.pack() + + #pack the destination entry widget + SectionLabel(self, text="Select Address").pack(pady=5) + self._addr_entry = Tkinter.Entry(self, width=30) + self._addr_entry.insert(Tkinter.END, addr) + self._addr_entry.pack() + + #the do it button + SectionLabel(self, text="").pack(pady=5) + button = Tkinter.Button(self, text="Burn Images", command=self._burn) + self._enable_input = lambda: button.configure(state=Tkinter.NORMAL) + self._disable_input = lambda: button.configure(state=Tkinter.DISABLED) + button.pack() + + #a progress bar to monitor the status + progress_frame = Tkinter.Frame(self) + progress_frame.pack() + self._status = Tkinter.StringVar() + Tkinter.Label(progress_frame, textvariable=self._status).pack(side=Tkinter.LEFT) + self._pbar = ProgressBar(progress_frame) + self._pbar.pack(side=Tkinter.RIGHT, expand=True) + + def _burn(self): + self._disable_input() + threading.Thread(target=self._burn_bg).start() + + def _burn_bg(self): + #grab strings from the gui + fw = self._fw_img_entry.get_filename() + fpga = self._fpga_img_entry.get_filename() + addr = self._addr_entry.get() + + #check input + if not addr: + tkMessageBox.showerror('Error:', 'No address specified!') + return + if not fw and not fpga: + tkMessageBox.showerror('Error:', 'No images specified!') + return + if fw and not os.path.exists(fw): + tkMessageBox.showerror('Error:', 'Firmware image not found!') + return + if fpga and not os.path.exists(fpga): + tkMessageBox.showerror('Error:', 'FPGA image not found!') + return + + try: + #make a new burner object and attempt the burner operation + burner = usrp_n2xx_net_burner.burner_socket(addr=addr) + + for (image_type, fw_img, fpga_img) in (('FPGA', '', fpga), ('Firmware', fw, '')): + #setup callbacks that update the gui + def status_cb(status): + self._pbar.set(0.0) #status change, reset the progress + self._status.set("%s %s "%(status.title(), image_type)) + burner.set_callbacks(progress_cb=self._pbar.set, status_cb=status_cb) + burner.burn_fw(fw=fw_img, fpga=fpga_img, reset=False, safe=False) + + if tkMessageBox.askyesno("Burn was successful!", "Reset the device?"): + burner.reset_usrp() + + except Exception, e: + tkMessageBox.showerror('Verbose:', 'Error: %s'%str(e)) + + #reset the progress bar + self._pbar.set(0.0) + self._status.set("") + self._enable_input() + +######################################################################## +# main +######################################################################## +if __name__=='__main__': + options = usrp_n2xx_net_burner.get_options() + root = Tkinter.Tk() + root.title('USRP-N2XX Net Burner') + USRPN2XXNetBurnerApp(root, addr=options.addr, fw=options.fw, fpga=options.fpga).pack() + root.mainloop() + exit() -- cgit v1.2.3 From ae8fd1009794cd80ee79fcf56a6bddb609bb32b5 Mon Sep 17 00:00:00 2001 From: Josh Blum Date: Sat, 26 Mar 2011 23:10:47 -0700 Subject: usrp2: added usrp_n2xx_net_burner_gui to build system installation --- host/utils/CMakeLists.txt | 1 + host/utils/usrp_n2xx_net_burner_gui.py | 0 2 files changed, 1 insertion(+) mode change 100644 => 100755 host/utils/usrp_n2xx_net_burner_gui.py diff --git a/host/utils/CMakeLists.txt b/host/utils/CMakeLists.txt index 3c18324a5..98b5d41fb 100644 --- a/host/utils/CMakeLists.txt +++ b/host/utils/CMakeLists.txt @@ -59,6 +59,7 @@ IF(ENABLE_USRP2) usrp2_card_burner.py usrp2_card_burner_gui.py usrp_n2xx_net_burner.py + usrp_n2xx_net_burner_gui.py DESTINATION ${PKG_DATA_DIR}/utils COMPONENT utilities ) diff --git a/host/utils/usrp_n2xx_net_burner_gui.py b/host/utils/usrp_n2xx_net_burner_gui.py old mode 100644 new mode 100755 -- cgit v1.2.3