diff --git a/meson.build b/meson.build index a1a0164c..ec3e861b 100644 --- a/meson.build +++ b/meson.build @@ -385,8 +385,8 @@ targets = [ objcopy = find_program('objcopy', required:false, disabler:true) radio_tool = find_program('radio_tool', required:false, disabler:true) -bin2sgl = find_program('bin2sgl.Linux', required:false, disabler:true) -gd77_loader = find_program('gd-77_firmware_loader.py', required:false, disabler:true) +bin2sgl = find_program('scripts/bin2sgl.Linux', required:false, disabler:true) +gd77_loader = find_program('scripts/gd-77_firmware_loader.py', required:false, disabler:true) foreach t : targets diff --git a/scripts/bin2sgl.Linux b/scripts/bin2sgl.Linux new file mode 100755 index 00000000..5554caae Binary files /dev/null and b/scripts/bin2sgl.Linux differ diff --git a/scripts/gd-77_firmware_loader.py b/scripts/gd-77_firmware_loader.py new file mode 100644 index 00000000..dbe06d6f --- /dev/null +++ b/scripts/gd-77_firmware_loader.py @@ -0,0 +1,619 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +################################################################################################################################################ +# +# GD-77 Firmware uploader. By Roger VK3KYY +# +# +# This script has only been tested on Windows and Linux, it may or may not work on OSX +# +# On Windows,.. +# the driver the system installs for the GD-77, which is the HID driver, needs to be replaced by the LibUSB-win32 using Zadig +# for USB device with idVendor=0x15a2, idProduct=0x0073 +# Once this driver is installed the CPS and official firmware loader will no longer work as they can't find the device +# To use the CPS etc again, use the DeviceManager to uninstall the driver associated with idVendor=0x15a2, idProduct=0x0073 (this will appear as a libusb-win32 device) +# Then unplug the GD-77 and reconnect, and the HID driver will be re-installed +# +# +# On Linux, depending of you distro, you need to install a special udev rule to automatically unbind the USB HID device to usbhid driver. +# +# +# You also need python3-usb, enum34 and urllib3 +# +################################################################################################################################################ + +######################### Error codes ######################### +# +# -1: Missing firmware file +# -2: Wrong SGL file format +# -3: Unencrypted firmware +# -4: Firmware file is too large +# -5: Unknown HT model type +# -6: Command line parsing error +# -7: Online download firmware location error +# -8: Online download firmware binary error +# -9: Online firmware download failure +# -10: Firmare/HT mismatch +# -99: Unsupported GD-77S (will be removed in the futur) +# +############################################################### + +import usb +import getopt, sys +import ntpath +import os.path +from array import array +import enum +import urllib3 +import re +import tempfile + +class SGLFormatOutput(enum.Enum): + GD_77 = 0 + GD_77S = 1 + DM_1801 = 2 + RD_5R = 3 + UNKNOWN = 4 + + def __int__(self): + return self.value + + +# Globals +responseOK = [0x41] +outputModes = ["GD-77", "GD-77S", "DM-1801", "RD-5R", "Unknown"] +outputFormat = SGLFormatOutput.GD_77 +downloadedFW = "" + +######################################################################## +# Utilities to dump hex for testing +######################################################################## +def hexdump(buf): + cbuf = "" + for b in buf: + cbuf = cbuf + "0x%0.2X " % ord(b) + return cbuf + +def hexdumpArray(buf): + cbuf = "" + for b in buf: + cbuf = cbuf + "0x%0.2X " % b + return cbuf + +def hexdumpArray2(buf): + cbuf = "" + for b in buf: + cbuf = cbuf + "%0.2X-" % b + return cbuf[:-1] + +def strdumpArray(buf): + cbuf = "" + for b in buf: + cbuf = cbuf + chr(b) + return cbuf + +def downloadFirmware(downloadStable): + url = "https://github.com/rogerclarkmelbourne/OpenGD77/releases" + urlBase = "http://github.com" + httpPool = urllib3.PoolManager() + pattern = "" + fwVersion = "UNKNOWN" + fwVersionPatternFormat = r'/{}([0-9\.]+)/' + urlFW = "" + webContent = "" + + print(" - " + "Try to download the firmware for your {} from the project page".format(outputModes[int(outputFormat)])) + print(" - " + "Retrieve firmware location"); + + try: + response = httpPool.request('GET', url) + except urllib3.URLError as e: + print("".format(e.reason)) + sys.exit(-7) + + webContent = str(response.data) + + if (outputFormat == SGLFormatOutput.GD_77): + patternFormat = r'/rogerclarkmelbourne/OpenGD77/releases/download/{}([0-9\.]+)/OpenGD77\.sgl' + elif (outputFormat == SGLFormatOutput.GD_77S): + patternFormat = r'/rogerclarkmelbourne/OpenGD77/releases/download/{}([0-9\.]+)/OpenGD77S\.sgl' + elif (outputFormat == SGLFormatOutput.DM_1801): + patternFormat = r'/rogerclarkmelbourne/OpenGD77/releases/download/{}([0-9\.]+)/OpenDM1801\.sgl' + elif (outputFormat == SGLFormatOutput.RD_5R): + patternFormat = r'/rogerclarkmelbourne/OpenGD77/releases/download/{}([0-9\.]+)/OpenDM5R\.sgl' + + pattern = patternFormat.format("R" if downloadStable == True else "D") + fwVersionPattern = fwVersionPatternFormat.format("R" if downloadStable == True else "D") + contentArray = webContent.split("\n") + + for l in contentArray: + m = re.search(pattern, l) + if (m != None): + urlFW = urlBase + m.group(0) + + m = re.search(fwVersionPattern, urlFW) + if (m != None): + fwVersion = m.group(0).strip('/') + + break + + if (len(urlFW)): + global downloadedFW + downloadedFW = os.path.join(tempfile.gettempdir(), next(tempfile._get_candidate_names()) + '.sgl') + + print(" - " + "Downloading the firmware version {}, please wait".format(fwVersion)); + + try: + response = httpPool.request('GET', urlFW, preload_content=False) + except urllib3.URLError as e: + print("".format(e.reason)) + sys.exit(-8) + + length = response.getheader('content-length') + + if (length != None): + length = int(length) + blocksize = max(4096, (length//100)) + else: + blocksize = 4096 + + # Download data + with open(downloadedFW, "w+b") as f: + while True: + data = response.read(blocksize) + + if not data: + break + + f.write(data) + f.close() + return True + + return False + +######################################################################## +# Send the data packet to the GD-77 and return response +######################################################################## +def sendAndGetResponse(dev, cmd): + USB_WRITE_ENDPOINT = 0x02 + USB_READ_ENDPOINT = 0x81 + TRANSFER_LENGTH = 38 + headerData = [0x0] * 4 + + headerData[0] = 1 + headerData[1] = 0 + headerData[2] = ((len(cmd) >> 0) & 0xff) + headerData[3] = ((len(cmd) >> 8) & 0xff) + + cmd = headerData + cmd + + #print("TX: " + hexdumpArray2(cmd)) + #print("TX: '{}'".format(strdumpArray(cmd[4:]))) + + ret = dev.write(USB_WRITE_ENDPOINT, cmd) + ret = dev.read(USB_READ_ENDPOINT, TRANSFER_LENGTH + 4, 5000) + + #print("RX: " + hexdumpArray2(ret[4:])) + #print("RX: '{}'".format(strdumpArray(ret[4:]))) + + return ret[4:] + +######################################################################## +# Send the data packet to the GD-77 and confirm the response is correct +######################################################################## +def sendAndCheckResponse(dev, cmd, resp): + USB_WRITE_ENDPOINT = 0x02 + USB_READ_ENDPOINT = 0x81 + TRANSFER_LENGTH = 38 + zeroPad = [0x0] * TRANSFER_LENGTH + headerData = [0x0] * 4 + + headerData[0] = 1 + headerData[1] = 0 + headerData[2] = ((len(cmd) >> 0) & 0xff) + headerData[3] = ((len(cmd) >> 8) & 0xff) + + if (len(resp) < TRANSFER_LENGTH): + resp = resp + zeroPad[0:TRANSFER_LENGTH - len(resp)] + + cmd = headerData + cmd + + #print("TX: " + hexdumpArray2(cmd)) + #print("TX: '{}'".format(strdumpArray(cmd[4:]))) + + ret = dev.write(USB_WRITE_ENDPOINT, cmd) + ret = dev.read(USB_READ_ENDPOINT, TRANSFER_LENGTH + 4, 5000) + expected = array("B", resp) + + #print("RX: " + hexdumpArray2(ret[4:])) + #print("RX: '{}'".format(strdumpArray(ret[4:]))) + + if (expected == ret[4:]): + return True + else: + print("Error. Read returned: " + str(ret)) + return False + + +############################## +# Create checksum data packet +############################## +def createChecksumData(buf, startAddress, endAddress): + #checksum data starts with a small header, followed by the 32 bit checksum value, least significant byte first + checkSumData = [ 0x45, 0x4e, 0x44, 0xff, 0xDE, 0xAD, 0xBE, 0xEF ] + cs = 0 + + for i in range(startAddress, endAddress): + cs = cs + buf[i] + + checkSumData[4] = (cs % 256) & 0xff + checkSumData[5] = ((cs >> 8) % 256) & 0xff + checkSumData[6] = ((cs >> 16) % 256) & 0xff + checkSumData[7] = ((cs >> 24) % 256) & 0xff + return checkSumData + + +def updateBlockAddressAndLength(buf, address, length): + buf[5] = ((length) % 256) & 0xff + buf[4] = ((length >> 8) % 256) & 0xff + buf[3] = ((address) % 256) & 0xff + buf[2] = ((address >> 8) % 256) & 0xff + buf[1] = ((address >> 16) % 256) & 0xff + buf[0] = ((address >> 24) % 256) & 0xff + return buf + + +##################################################### +# Open firmware file on disk and sent it to the GD-77 +###########################################b########## +def sendFileData(fileBuf, dev): + dataHeader = [0x00] * (0x20 + 0x06) + BLOCK_LENGTH = 1024 #1k + DATA_TRANSFER_SIZE = 0x20 + checksumStartAddress = 0 + address = 0 + + fileLength = len(fileBuf) + totalBlocks = (fileLength // BLOCK_LENGTH) + 1 + + while address < fileLength: + if ((address % BLOCK_LENGTH) == 0): + checksumStartAddress = address + + dataHeader = updateBlockAddressAndLength(dataHeader, address, DATA_TRANSFER_SIZE) + + if ((address + DATA_TRANSFER_SIZE) < fileLength): + + for i in range(DATA_TRANSFER_SIZE): + dataHeader[6 + i] = fileBuf[address + i] + + if (sendAndCheckResponse(dev, dataHeader, responseOK) == False): + print("Error sending data") + return False + break + + address = address + DATA_TRANSFER_SIZE + + if ((address % 0x400) == 0): + print("\r - Sent block " + str(address // BLOCK_LENGTH) + " of "+ str(totalBlocks), end='') + sys.stdout.flush() + + if (sendAndCheckResponse(dev, createChecksumData(fileBuf, checksumStartAddress, address), responseOK) == False): + print("Error sending checksum") + return False + break + + else: + print("\r - Sending last block ", end='') + sys.stdout.flush() + + DATA_TRANSFER_SIZE = fileLength - address + + dataHeader = updateBlockAddressAndLength(dataHeader, address, DATA_TRANSFER_SIZE) + + for i in range(DATA_TRANSFER_SIZE): + dataHeader[6 + i] = fileBuf[address + i] + + if (sendAndCheckResponse(dev, dataHeader, responseOK) == False): + print("Error sending data") + return False + break + + address = address + DATA_TRANSFER_SIZE + + if (sendAndCheckResponse(dev, createChecksumData(fileBuf, checksumStartAddress, address), responseOK) == False): + print("Error sending checksum") + return False + break + + print("") + return True + +##################################################### +# Probe connected model +###########################################b########## +def probeModel(dev): + commandLetterA = [ 0x41 ] # 'A' + command0 = [[ 0x44, 0x4f, 0x57, 0x4e, 0x4c, 0x4f, 0x41, 0x44 ], [ 0x23, 0x55, 0x50, 0x44, 0x41, 0x54, 0x45, 0x3f ]] # 'DOWNLOAD' + command1 = [ commandLetterA, responseOK ] + commandDummy = [ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF ] + #commandMOD = [ 0x46, 0x2D, 0x4D, 0x4F, 0x44, 0xff, 0xff, 0xff ] # F-MOD... + #commandEND = [ 0x45, 0x4E, 0x44, 0xFF ] # END. + commandID = [ command0, command1 ] + models = [[ 'DV01', SGLFormatOutput.GD_77 ], [ 'DV02', SGLFormatOutput.GD_77S ], [ 'DV03', SGLFormatOutput.DM_1801 ]] + # RD-5R also have "DV02" id + + commandNumber = 0 + while commandNumber < len(commandID): + if sendAndCheckResponse(dev, commandID[commandNumber][0], commandID[commandNumber][1]) == False: + return SGLFormatOutput.UNKNOWN + commandNumber = commandNumber + 1 + + resp = sendAndGetResponse(dev, commandDummy) + ##dummy = sendAndGetResponse(dev, command0[0]) + + for x in models: + if (x[0] == str(resp[:4].tobytes().decode("ascii"))): + return x[1] + + return SGLFormatOutput.UNKNOWN + +########################################################################################################################################### +# Send commands to the GD-77 to verify we are the updater, prepare to program including erasing the internal program flash memory +########################################################################################################################################### +def sendInitialCommands(dev, encodeKey): + commandLetterA =[ 0x41] #A + command0 =[[0x44,0x4f,0x57,0x4e,0x4c,0x4f,0x41,0x44],[0x23,0x55,0x50,0x44,0x41,0x54,0x45,0x3f]] # DOWNLOAD + command1 =[commandLetterA,responseOK] + command3 =[[0x46, 0x2d, 0x50, 0x52, 0x4f, 0x47, 0xff, 0xff],responseOK] #... F-PROG.. + + if (outputFormat == SGLFormatOutput.GD_77): + command2 =[[0x44, 0x56, 0x30, 0x31, (0x61 + 0x00), (0x61 + 0x0C), (0x61 + 0x0D), (0x61 + 0x01)],[0x44, 0x56, 0x30, 0x31]] #.... DV01enhi (DV01enhi comes from deobfuscated sgl file) + command4 =[[0x53, 0x47, 0x2d, 0x4d, 0x44, 0x2d, 0x37, 0x36, 0x30, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff],responseOK] #SG-MD-760 + command5 =[[0x4d, 0x44, 0x2d, 0x37, 0x36, 0x30, 0xff, 0xff],responseOK] #MD-760.. + elif (outputFormat == SGLFormatOutput.GD_77S): + command2 =[[0x44, 0x56, 0x30, 0x32, 0x6D, 0x40, 0x7D, 0x63],[0x44, 0x56, 0x30, 0x32]] #.... DV02Gpmj (thanks Wireshark) + command4 =[[0x53, 0x47, 0x2d, 0x4d, 0x44, 0x2d, 0x37, 0x33, 0x30, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff],responseOK] #SG-MD-730 + command5 =[[0x4d, 0x44, 0x2d, 0x37, 0x33, 0x30, 0xff, 0xff],responseOK] #MD-730.. + elif (outputFormat == SGLFormatOutput.DM_1801): + command2 =[[0x44, 0x56, 0x30, 0x33, 0x74, 0x21, 0x44, 0x39],[0x44, 0x56, 0x30, 0x33]] #.... last 4 bytes of the command are the offset encoded as letters a - p (hard coded fr + command4 =[[0x42, 0x46, 0x2d, 0x44, 0x4d, 0x52, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff],responseOK] #BF-DMR + command5 =[[0x31, 0x38, 0x30, 0x31, 0xff, 0xff, 0xff, 0xff],responseOK] # MD-1801 + elif (outputFormat == SGLFormatOutput.RD_5R): + command2 =[[0x44, 0x56, 0x30, 0x32, 0x53, 0x36, 0x37, 0x62],[0x44, 0x56, 0x30, 0x32]] #.... last 4 bytes of the command are the offset encoded as letters a - p (hard coded fr + command4 =[[0x42, 0x46, 0x2D, 0x35, 0x52, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff],responseOK] #BF-5R + command5 =[[0x42, 0x46, 0x2D, 0x35, 0x52, 0xff, 0xff, 0xff],responseOK] # BF-5R + + command6 =[[0x56, 0x31, 0x2e, 0x30, 0x30, 0x2e, 0x30, 0x31],responseOK] #V1.00.01 + commandErase =[[0x46, 0x2d, 0x45, 0x52, 0x41, 0x53, 0x45, 0xff],responseOK] #F-ERASE + commandPostErase =[commandLetterA,responseOK] + commandProgram =[[0x50, 0x52, 0x4f, 0x47, 0x52, 0x41, 0x4d, 0xf],responseOK] #PROGRAM + commands =[command0,command1,command2,command3,command4,command5,command6,commandErase,commandPostErase,commandProgram] + commandNames =["Sending Download command", "Sending ACK", "Sending encryption key", "Sending F-PROG command", "Sending radio modem number", "Sending radio modem number 2", "Sending version", "Sending erase command", "Send post erase command", "Sending Program command"] + commandNumber = 0 + + # Buffer.BlockCopy(encodeKey, 0, command2[0], 4, 4); + command2[0] = command2[0][0:4] + encodeKey + + # Send the commands which the GD-77 expects before the start of the data + while commandNumber < len(commands): + print(" - " + commandNames[commandNumber]) + + if sendAndCheckResponse(dev, commands[commandNumber][0], commands[commandNumber][1]) == False: + print("Error sending command") + return False + break + commandNumber = commandNumber + 1 + return True + +########################################################################################################################################### +# +########################################################################################################################################### +def checkForSGLAndReturnEncryptedData(fileBuf): + header_tag = list("SGL!") + headerModel = [] + + buf_in_4 = list("".join(map(chr, fileBuf[0:4]))) + headerModel.append(fileBuf[11]) + + if buf_in_4 == header_tag: + # read and decode offset and xor tag + buf_in_4 = list(fileBuf[0x000C : 0x000C + 4]) + + for i in range(0, 4): + buf_in_4[i] = buf_in_4[i] ^ ord(header_tag[i]) + + offset = buf_in_4[0] + 256 * buf_in_4[1] + xor_data = [ buf_in_4[2], buf_in_4[3] ] + + # read and decode part of the header + buf_in_512 = list(fileBuf[offset + 0x0006 : offset + 0x0006 + 512]) + + xor_idx = 0; + for i in range(0, 512): + buf_in_512[i] = buf_in_512[i] ^ xor_data[xor_idx] + + xor_idx += 1 + if xor_idx == 2: + xor_idx = 0 + + # + encodeKey = buf_in_512[0x005D : 0x005D + 4] + + # extract length + length1 = buf_in_512[0x0000] + length2 = buf_in_512[0x0001] + length3 = buf_in_512[0x0002] + length4 = buf_in_512[0x0003] + length = (length4 << 24) + (length3 << 16) + (length2 << 8) + length1 + + # extract encoded raw firmware + retBuf = [0x00] * length; + retBuf = fileBuf[len(fileBuf) - length : len(fileBuf) - length + len(retBuf) ] + + return retBuf, encodeKey, headerModel + + print("ERROR: SGL! header is missing.") + return None, None, None + +########################################################################################################################################### +# +########################################################################################################################################### +def usage(): + print("Usage:") + print(" " + ntpath.basename(sys.argv[0]) + " [OPTION]") + print("") + print(" -h, --help : Display this help text") + print(" -f, --firmware= : Flash instead of default file \"firmware.sgl\"") + print(" -m, --model= : Select transceiver model. Models are: {}".format(", ".join(str(x) for x in outputModes[:-1])) + ".") + print(" -d, --download : Download firmware from the project website") + print(" -S, --stable : Select the stable version while downloading from the project page") + print(" -U, --unstable : Select the development version while downloading from the project page") + print("") + +##################################################### +# Main function. +##################################################### +def main(): + global outputFormat + sglFile = "firmware.sgl" + downloadStable = True + doDownload = False + doForce = False + + # Command line argument parsing + try: + opts, args = getopt.getopt(sys.argv[1:], "hf:m:dSUF", ["help", "firmware=", "model=", "download", "stable", "unstable", "force"]) + except getopt.GetoptError as err: + print(str(err)) + usage() + sys.exit(-6) + + for opt, arg in opts: + if opt in ("-h", "--help"): + usage() + sys.exit(0) + elif opt in ("-f", "--firmware"): + sglFile = arg + elif opt in ("-m", "--model"): + try: + index = outputModes.index(arg) + except ValueError as e: + print("Model \"{}\" is unknown".format(arg)) + sys.exit(-5) + + outputFormat = SGLFormatOutput(index) + + if (outputFormat == SGLFormatOutput.UNKNOWN): + print("Unsupported model") + sys.exit(-5) + + elif opt in ["-d", "--download"]: + doDownload = True + elif opt in ["-S", "--stable"]: + downloadStable = True + elif opt in ["-U", "--unstable"]: + downloadStable = False + elif opt in ["-F", "--force"]: + doForce = True + else: + assert False, "Unhandled option" + + # Try to connect USB device + dev = usb.core.find(idVendor=0x15a2, idProduct=0x0073) + if (dev): + # Needed on Linux + try: + if dev.is_kernel_driver_active(0): + dev.detach_kernel_driver(0) + except NotImplementedError as e: + pass + + #seems to be needed for the usb to work ! + dev.set_configuration() + + if (outputFormat == SGLFormatOutput.UNKNOWN): + outputFormat = probeModel(dev) + if (outputFormat == SGLFormatOutput.UNKNOWN): + print("Error. Failed to detect you transceiver model.") + sys.exit(-5) + print(" - Detected model: {}".format(outputModes[int(outputFormat)])) + + # Try to download the firmware + if (doDownload == True): + if (downloadFirmware(downloadStable) == True): + sglFile = downloadedFW + else: + print("Firmware download failed") + sys.exit(-9) + + if (os.path.isfile(sglFile) == False): + print("Firmware file \"" + sglFile + "\" is missing.") + sys.exit(-1) + + print(" - " + "Now flashing your {} with \"{}\"".format(outputModes[int(outputFormat)], sglFile)) + + with open(sglFile, 'rb') as f: + fileBuf = f.read() + + # Check firmware + filename, file_extension = os.path.splitext(sglFile) + + # Define encodeKey according to HT model + if (outputFormat == SGLFormatOutput.GD_77): + encodeKey = [ (0x61 + 0x00), (0x61 + 0x0C), (0x61 + 0x0D), (0x61 + 0x01) ] + elif (outputFormat == SGLFormatOutput.GD_77S): + encodeKey = [ (0x6D), (0x40), (0x7D), (0x63) ] ## Original header (smaller filelength): was (0x47), (0x70), (0x6d), (0x4a) + elif (outputFormat == SGLFormatOutput.DM_1801): + encodeKey = [ (0x74), (0x21), (0x44), (0x39) ] + elif (outputFormat == SGLFormatOutput.RD_5R): + encodeKey = [ (0x53), (0x36), (0x37), (0x62) ] + + if (file_extension == ".sgl"): + firmwareModelTag = { SGLFormatOutput.GD_77: 0x1B , SGLFormatOutput.GD_77S: 0x70, SGLFormatOutput.DM_1801: 0x4F, SGLFormatOutput.RD_5R: 0x5C} + + ## Could be a SGL file ! + fileBuf, encodeKey, headerModel = checkForSGLAndReturnEncryptedData(fileBuf) + + if (fileBuf == None): + print("Error. Missing SGL in .sgl file header") + sys.exit(-2) + + print(" - " + "Firmware file confirmed as SGL") + + if (doForce == False): + # Check if the firmware matches the transceiver model + if (headerModel[0] != firmwareModelTag[outputFormat]): + print("Error. The firmware doesn't match the transceiver model.") + sys.exit(-10) + + else: + print("Firmware file is an unencrypted binary. Exiting") + sys.exit(-3) + + if len(fileBuf) > 0x7b000: + print("Error. Firmware file too large.") + sys.exit(-4) + + if (sendInitialCommands(dev, encodeKey) == True): + if (sendFileData(fileBuf, dev) == True): + print("Firmware update complete. Please reboot the {}.".format(outputModes[int(outputFormat)])) + else: + print("Error while sending data") + else: + print("Error while sending initial commands") + + usb.util.dispose_resources(dev) #free up the USB device + + else: + print("Error. Can't find your transceiver.") + + # Remove downloaded firmware, if any + if (len(downloadedFW)): + if (os.path.isfile(downloadedFW)): + os.remove(downloadedFW) + + +## Run the program +main() +sys.exit(0)