From a1b6711cbd7325c6081230aa40bf46b973352a06 Mon Sep 17 00:00:00 2001 From: zigakerzan Date: Wed, 23 Feb 2022 18:18:31 +0100 Subject: [PATCH 01/26] Created GUI for bootloader Moved GUI from Flash boot speedup branch to develop branch. --- examples/bootloader/gui.py | 123 +++++++++++++++ examples/bootloader/guiFunctions.py | 228 ++++++++++++++++++++++++++++ 2 files changed, 351 insertions(+) create mode 100644 examples/bootloader/gui.py create mode 100644 examples/bootloader/guiFunctions.py diff --git a/examples/bootloader/gui.py b/examples/bootloader/gui.py new file mode 100644 index 000000000..9248a6443 --- /dev/null +++ b/examples/bootloader/gui.py @@ -0,0 +1,123 @@ +#!/usr/bin/env python3 + +from guiFunctions import * + + +usbSpeeds = ["UNKNOWN", "LOW", "FULL", "HIGH", "SUPER", "SUPER_PLUS"] + +sg.theme('DarkGrey4') + +aboutDeviceLayout = [ + [sg.Text("About device", size=(20, 1), font=('Arial', 30, 'bold'), text_color="black")], + [sg.HSeparator()], + [ + sg.Text("Select device: ", size=(15, 1), font=('Arial', 10, 'bold'), text_color="black"), + sg.Combo([], "Search for devices", size=(30, 5), key="devices", enable_events=True), + sg.Button("Select"), sg.Button("Search") + ], + [sg.Text("Name of connected device:", size=(30, 1), font=('Arial', 10, 'bold'), text_color="black")], + [sg.Text("-name-", key="devName", size=(30, 1))], + [sg.Text("Version of newest bootloader:", size=(30, 1), font=('Arial', 10, 'bold'), text_color="black")], + [sg.Text("-version-", key="newBoot", size=(20, 1))], + [sg.Text("Current bootloader version:", size=(30, 1), font=('Arial', 10, 'bold'), text_color="black")], + [sg.Text("-version-", key="currBoot", size=(20, 1))], + [sg.HSeparator()], + [ + sg.Text("", size=(5, 2)), + sg.Button("Flash newest version", size=(20, 2), font=('Arial', 10, 'bold'), disabled=True, + button_color='#FFEA00'), + sg.Button("Factory reset", size=(20, 2), font=('Arial', 10, 'bold'), disabled=True, button_color='#FFEA00')] +] + +deviceConfigLayout = [ + [sg.Text("Configuration settings", size=(20, 1), font=('Arial', 30, 'bold'), text_color="black")], + [sg.HSeparator()], + [ + sg.Text("IPv4 type:", size=(30, 1), font=('Arial', 10, 'bold'), text_color="black"), + sg.Radio('Static', "ipType", default=True, font=('Arial', 10, 'bold'), text_color="black", + key="staticBut", enable_events=True, disabled=True), + sg.Radio('Dynamic', "ipType", default=False, font=('Arial', 10, 'bold'), text_color="black", + key="dynamicBut", enable_events=True, disabled=True) + ], + [ + sg.Text("IPv4:", size=(12, 1), font=('Arial', 10, 'bold'), text_color="black"), + sg.InputText(key="staticIp", size=(16, 2), disabled=True), + sg.Text("Mask:", size=(5, 1), font=('Arial', 10, 'bold'), text_color="black"), + sg.InputText(key="staticMask", size=(16, 2), disabled=True), + sg.Text("Gateway:", size=(8, 1), font=('Arial', 10, 'bold'), text_color="black"), + sg.InputText(key="staticGateway", size=(16, 2), disabled=True) + ], + [ + sg.Text("DNS name:", size=(30, 1), font=('Arial', 10, 'bold'), text_color="black"), + sg.InputText(key="dns", size=(30, 2), disabled=True) + ], + [ + sg.Text("Alt DNS name:", size=(30, 1), font=('Arial', 10, 'bold'), text_color="black"), + sg.InputText(key="dnsAlt", size=(30, 2), disabled=True) + ], + [ + sg.Text("USB timeout:", size=(30, 1), font=('Arial', 10, 'bold'), text_color="black"), + sg.InputText(key="usbTimeout", size=(30, 2), disabled=True) + ], + [ + sg.Text("Network timeout:", size=(30, 1), font=('Arial', 10, 'bold'), text_color="black"), + sg.InputText(key="networkTimeout", size=(30, 2), disabled=True) + ], + [ + sg.Text("MAC address:", size=(30, 1), font=('Arial', 10, 'bold'), text_color="black"), + sg.InputText(key="mac", size=(30, 2), disabled=True) + ], + [ + sg.Text("USB max speed:", size=(30, 1), font=('Arial', 10, 'bold'), text_color="black"), + sg.Combo(usbSpeeds, "Select speed", key="usbSpeed", size=(30, 6), disabled=True) + ], + [sg.HSeparator()], + [ + sg.Text("", size=(20, 2)), + sg.Button("Flash configuration", size=(20, 2), font=('Arial', 10, 'bold'), disabled=True, + button_color='#FFEA00'), + sg.Button("Clear flash", size=(20, 2), font=('Arial', 10, 'bold'), disabled=True, + button_color='#FFEA00') + ] +] + +layout = [ + [sg.VSeparator(), + sg.Column(aboutDeviceLayout), + sg.VSeparator(), + sg.Column(deviceConfigLayout), + sg.VSeparator()] +] + +devices = dict() +devType = "" +window = sg.Window(title="Bootloader GUI", layout=layout) + +while True: + event, values = window.read() + if event == sg.WIN_CLOSED: + break + dev = values['devices'] + if event == "Select": + if dev != "Select device": + devType = getDeviceType(devices[values['devices']]) + getConfigs(window, devices[values['devices']], devType) + unlockConfig(window, devType) + else: + window.Element('progress').update("No device selected.") + if event == "Search": + getDevices(window, devices) + if event == "Flash newest version": + flashBootloader(window, devices[values['devices']]) + if event == "Flash configuration": + flashConfig(values, window, devices[values['devices']], devType, values['StaticBut']) + getConfigs(window, devices[values['devices']], devType) + lockConfig(window) + if devType != "Poe": + unlockConfig(window, devType) + else: + devices.clear() + window.Element('devices').update("Search for devices", values=[]) + if event == "Factory reset": + factoryReset(devices[values['devices']]) +window.close() diff --git a/examples/bootloader/guiFunctions.py b/examples/bootloader/guiFunctions.py new file mode 100644 index 000000000..046f02054 --- /dev/null +++ b/examples/bootloader/guiFunctions.py @@ -0,0 +1,228 @@ +from datetime import timedelta +import depthai as dai +import tempfile +import PySimpleGUI as sg + + +def check_ip(s: str): + spl = s.split(".") + if len(spl) != 4: + sg.Popup("Wrong IP format.\nValue should be similar to 255.255.255.255") + return False + for num in spl: + if 255 < int(num): + sg.Popup("Wrong IP format.\nValues can not be above 255!") + return False + return True + + +def check_mac(s): + if s.count(":") != 5: + sg.Popup("Wrong MAC format.\nValue should be similar to FF:FF:FF:FF:FF:FF") + return False + for i in s.split(":"): + for j in i: + if j > "F" or (j < "A" and not j.isdigit()) or len(i) != 2: + sg.Popup("Wrong MAC format.\nValue should be similar to FF:FF:FF:FF:FF:FF") + return False + return True + + +def usbSpeed(s): + if s == "UNKNOWN": + return dai.UsbSpeed.UNKNOWN + elif s == "LOW": + return dai.UsbSpeed.LOW + elif s == "FULL": + return dai.UsbSpeed.FULL + elif s == "HIGH": + return dai.UsbSpeed.HIGH + elif s == "SUPER": + return dai.UsbSpeed.SUPER + else: + return dai.UsbSpeed.SUPER_PLUS + + +def usbSpeedCorrection(s): + if s == 0: + return "UNKNOWN" + elif s == 1: + return "LOW" + elif s == 2: + return "FULL" + elif s == 3: + return "HIGH" + elif s == 4: + return "SUPER" + else: + return "SUPER_PLUS" + + +def macCorrectFormat(s): + ret = "" + for x in s: + ret += str(hex(x)).strip("0x").upper() + ":" + return ret[:-1] + + +def unlockConfig(window, devType): + if devType == "Poe": + window['staticIp'].update(disabled=False) + window['staticMask'].update(disabled=False) + window['staticGateway'].update(disabled=False) + window['dns'].update(disabled=False) + window['dnsAlt'].update(disabled=False) + window['networkTimeout'].update(disabled=False) + window['mac'].update(disabled=False) + window['staticBut'].update(disabled=False) + window['dynamicBut'].update(disabled=False) + else: + window['usbTimeout'].update(disabled=False) + window['usbSpeed'].update(disabled=False) + window['Flash newest version'].update(disabled=False) + window['Flash configuration'].update(disabled=False) + window['Factory reset'].update(disabled=False) + # window['Reset configuration'].update(disabled=False) + + +def lockConfig(window): + window['staticIp'].update(disabled=True) + window['staticMask'].update(disabled=True) + window['staticGateway'].update(disabled=True) + window['dns'].update(disabled=True) + window['dnsAlt'].update(disabled=True) + window['networkTimeout'].update(disabled=True) + window['mac'].update(disabled=True) + window['staticBut'].update(disabled=True) + window['dynamicBut'].update(disabled=True) + window['usbTimeout'].update(disabled=True) + window['usbSpeed'].update(disabled=True) + window['Flash newest version'].update(disabled=True) + window['Flash configuration'].update(disabled=True) + window['Factory reset'].update(disabled=True) + window['Reset configuration'].update(disabled=True) + + window.Element('staticIp').update("") + window.Element('staticMask').update("") + window.Element('staticGateway').update("") + window.Element('dns').update("") + window.Element('dnsAlt').update("") + window.Element('networkTimeout').update("") + window.Element('mac').update("") + window.Element('usbTimeout').update("") + window.Element('usbSpeed').update("") + window.Element('devName').update("-name-") + window.Element('newBoot').update("-version-") + window.Element('currBoot').update("-version-") + + +def getDevices(window, devices): + listedDevices = [] + devices.clear() + deviceInfos = dai.Device.getAllAvailableDevices() + if not deviceInfos: + window.Element('devices').update("No devices") + sg.Popup("No devices found.") + else: + for deviceInfo in deviceInfos: + listedDevices.append(deviceInfo.desc.name) + devices[deviceInfo.desc.name] = deviceInfo + sg.Popup("Found devices.") + window.Element('devices').update("Select device", values=listedDevices) + + +def getConfigs(window, device, devType): + bl = dai.DeviceBootloader(device) + conf = bl.readConfigData() + if devType == "Poe": + window.Element('staticIp').update(conf['network']['ipv4'] if conf['network']['ipv4'] != 0 else "0.0.0.0") + window.Element('staticMask').update(conf['network']['ipv4Mask'] if conf['network']['ipv4Mask'] != 0 + else "0.0.0.0") + window.Element('staticGateway').update(conf['network']['ipv4Gateway'] if conf['network']['ipv4Gateway'] != 0 + else "0.0.0.0") + window.Element('dns').update(conf['network']['ipv4Dns'] if conf['network']['ipv4Dns'] != 0 else "0.0.0.0") + window.Element('dnsAlt').update(conf['network']['ipv4DnsAlt'] if conf['network']['ipv4DnsAlt'] != 0 + else "0.0.0.0") + window.Element('networkTimeout').update(conf['network']['timeoutMs']) + window.Element('mac').update(macCorrectFormat(conf['network']['mac'])) + window.Element('usbTimeout').update("") + window.Element('usbSpeed').update("") + else: + window.Element('staticIp').update("") + window.Element('staticMask').update("") + window.Element('staticGateway').update("") + window.Element('dns').update("") + window.Element('dnsAlt').update("") + window.Element('networkTimeout').update("") + window.Element('mac').update("") + window.Element('usbTimeout').update(conf['usb']['timeoutMs']) + window.Element('usbSpeed').update(usbSpeedCorrection(conf['usb']['maxUsbSpeed'])) + window.Element('devName').update(device.desc.name) + window.Element('newBoot').update(dai.DeviceBootloader.getEmbeddedBootloaderVersion()) + window.Element('currBoot').update(bl.getVersion()) + + +def flashBootloader(window, device): + bl = dai.DeviceBootloader(device, True) + progress = lambda p: p * 100 + bl.flashBootloader(progress) + window.Element('currBoot').update(bl.getVersion()) + sg.Popup("Flashed newest bootloader version.") + + +def flashConfig(values, window, device, devType, ipType): + bl = dai.DeviceBootloader(device, True) + conf = dai.DeviceBootloader.Config() + if devType == "Poe": + if not(check_ip(values['staticIp']) and check_ip(values['staticMask']) + and check_ip(values['staticGateway'])): + return + if not check_mac(values['mac']): + return + if int(values['networkTimeout']) <= 0: + sg.Popup("Values can not be negative!") + return + if ipType: + conf.setStaticIPv4(values['staticIp'], values['staticIp'], values['staticIp']) + else: + conf.setDynamicIPv4(values['staticIp'], values['staticIp'], values['staticIp']) + conf.setDnsIPv4(values['dns'], "") + conf.setNetworkTimeout(timedelta(seconds=int(values['networkTimeout']) / 1000)) + conf.setMacAddress(values['mac']) + else: + if int(values['usbTimeout']) <= 0: + sg.Popup("Values can not be negative!") + return + conf.setUsbTimeout(timedelta(seconds=int(values['usbTimeout']) / 1000)) + conf.setUsbMaxSpeed(usbSpeed(values['usbSpeed'])) + success, error = bl.flashConfig(conf) + if not success: + sg.Popup(f"Flashing failed: {error}") + else: + sg.Popup("Flashing successful.") + + +def factoryReset(device): + blBinary = dai.DeviceBootloader.getEmbeddedBootloaderBinary(dai.DeviceBootloader.Type.NETWORK) + blBinary = blBinary + ([0xFF] * ((8 * 1024 * 1024 + 512) - len(blBinary))) + bl = dai.DeviceBootloader(device, True) + tmpBlFw = tempfile.NamedTemporaryFile(delete=False) + tmpBlFw.write(bytes(blBinary)) + progress = lambda p: p * 100 + success, msg = bl.flashBootloader(progress, tmpBlFw.name) + if success: + sg.Popup("Factory reset was successful.") + else: + sg.Popup(f"Factory reset failed. Error: {msg}") + tmpBlFw.close() + + +def getDeviceType(device): + bl = dai.DeviceBootloader(device) + conf = bl.readConfigData() + if conf is None: + success, error = bl.flashConfig(dai.DeviceBootloader.Config()) + if str(bl.getType()) == "Type.NETWORK": + return "Poe" + else: + return "NonPoe" From 9eb95cb9324008cc58b369186ded352679e86024 Mon Sep 17 00:00:00 2001 From: zigakerzan Date: Thu, 24 Feb 2022 12:22:56 +0100 Subject: [PATCH 02/26] Fixed needed changes - renamed file and moved to ```ustilities/device_manager.py``` (everything is now in 1 file) - GUI now has 2 pages (```About``` and ```Configuration settings```) - GUI also informs dai.__version__ and dai.__commmit__ - Added button ```Flash DAP``` that runs ```flashDepthaiApplicationPackage``` function - Changed device finding from ```dai.Device.getAllAvailableDevices()``` to ```dai.XLinkConnection.getAllConnectedDevices()``` --- examples/bootloader/gui.py | 123 ------------ .../device_manager.py | 180 +++++++++++++++++- 2 files changed, 178 insertions(+), 125 deletions(-) delete mode 100644 examples/bootloader/gui.py rename examples/bootloader/guiFunctions.py => utilities/device_manager.py (52%) diff --git a/examples/bootloader/gui.py b/examples/bootloader/gui.py deleted file mode 100644 index 9248a6443..000000000 --- a/examples/bootloader/gui.py +++ /dev/null @@ -1,123 +0,0 @@ -#!/usr/bin/env python3 - -from guiFunctions import * - - -usbSpeeds = ["UNKNOWN", "LOW", "FULL", "HIGH", "SUPER", "SUPER_PLUS"] - -sg.theme('DarkGrey4') - -aboutDeviceLayout = [ - [sg.Text("About device", size=(20, 1), font=('Arial', 30, 'bold'), text_color="black")], - [sg.HSeparator()], - [ - sg.Text("Select device: ", size=(15, 1), font=('Arial', 10, 'bold'), text_color="black"), - sg.Combo([], "Search for devices", size=(30, 5), key="devices", enable_events=True), - sg.Button("Select"), sg.Button("Search") - ], - [sg.Text("Name of connected device:", size=(30, 1), font=('Arial', 10, 'bold'), text_color="black")], - [sg.Text("-name-", key="devName", size=(30, 1))], - [sg.Text("Version of newest bootloader:", size=(30, 1), font=('Arial', 10, 'bold'), text_color="black")], - [sg.Text("-version-", key="newBoot", size=(20, 1))], - [sg.Text("Current bootloader version:", size=(30, 1), font=('Arial', 10, 'bold'), text_color="black")], - [sg.Text("-version-", key="currBoot", size=(20, 1))], - [sg.HSeparator()], - [ - sg.Text("", size=(5, 2)), - sg.Button("Flash newest version", size=(20, 2), font=('Arial', 10, 'bold'), disabled=True, - button_color='#FFEA00'), - sg.Button("Factory reset", size=(20, 2), font=('Arial', 10, 'bold'), disabled=True, button_color='#FFEA00')] -] - -deviceConfigLayout = [ - [sg.Text("Configuration settings", size=(20, 1), font=('Arial', 30, 'bold'), text_color="black")], - [sg.HSeparator()], - [ - sg.Text("IPv4 type:", size=(30, 1), font=('Arial', 10, 'bold'), text_color="black"), - sg.Radio('Static', "ipType", default=True, font=('Arial', 10, 'bold'), text_color="black", - key="staticBut", enable_events=True, disabled=True), - sg.Radio('Dynamic', "ipType", default=False, font=('Arial', 10, 'bold'), text_color="black", - key="dynamicBut", enable_events=True, disabled=True) - ], - [ - sg.Text("IPv4:", size=(12, 1), font=('Arial', 10, 'bold'), text_color="black"), - sg.InputText(key="staticIp", size=(16, 2), disabled=True), - sg.Text("Mask:", size=(5, 1), font=('Arial', 10, 'bold'), text_color="black"), - sg.InputText(key="staticMask", size=(16, 2), disabled=True), - sg.Text("Gateway:", size=(8, 1), font=('Arial', 10, 'bold'), text_color="black"), - sg.InputText(key="staticGateway", size=(16, 2), disabled=True) - ], - [ - sg.Text("DNS name:", size=(30, 1), font=('Arial', 10, 'bold'), text_color="black"), - sg.InputText(key="dns", size=(30, 2), disabled=True) - ], - [ - sg.Text("Alt DNS name:", size=(30, 1), font=('Arial', 10, 'bold'), text_color="black"), - sg.InputText(key="dnsAlt", size=(30, 2), disabled=True) - ], - [ - sg.Text("USB timeout:", size=(30, 1), font=('Arial', 10, 'bold'), text_color="black"), - sg.InputText(key="usbTimeout", size=(30, 2), disabled=True) - ], - [ - sg.Text("Network timeout:", size=(30, 1), font=('Arial', 10, 'bold'), text_color="black"), - sg.InputText(key="networkTimeout", size=(30, 2), disabled=True) - ], - [ - sg.Text("MAC address:", size=(30, 1), font=('Arial', 10, 'bold'), text_color="black"), - sg.InputText(key="mac", size=(30, 2), disabled=True) - ], - [ - sg.Text("USB max speed:", size=(30, 1), font=('Arial', 10, 'bold'), text_color="black"), - sg.Combo(usbSpeeds, "Select speed", key="usbSpeed", size=(30, 6), disabled=True) - ], - [sg.HSeparator()], - [ - sg.Text("", size=(20, 2)), - sg.Button("Flash configuration", size=(20, 2), font=('Arial', 10, 'bold'), disabled=True, - button_color='#FFEA00'), - sg.Button("Clear flash", size=(20, 2), font=('Arial', 10, 'bold'), disabled=True, - button_color='#FFEA00') - ] -] - -layout = [ - [sg.VSeparator(), - sg.Column(aboutDeviceLayout), - sg.VSeparator(), - sg.Column(deviceConfigLayout), - sg.VSeparator()] -] - -devices = dict() -devType = "" -window = sg.Window(title="Bootloader GUI", layout=layout) - -while True: - event, values = window.read() - if event == sg.WIN_CLOSED: - break - dev = values['devices'] - if event == "Select": - if dev != "Select device": - devType = getDeviceType(devices[values['devices']]) - getConfigs(window, devices[values['devices']], devType) - unlockConfig(window, devType) - else: - window.Element('progress').update("No device selected.") - if event == "Search": - getDevices(window, devices) - if event == "Flash newest version": - flashBootloader(window, devices[values['devices']]) - if event == "Flash configuration": - flashConfig(values, window, devices[values['devices']], devType, values['StaticBut']) - getConfigs(window, devices[values['devices']], devType) - lockConfig(window) - if devType != "Poe": - unlockConfig(window, devType) - else: - devices.clear() - window.Element('devices').update("Search for devices", values=[]) - if event == "Factory reset": - factoryReset(devices[values['devices']]) -window.close() diff --git a/examples/bootloader/guiFunctions.py b/utilities/device_manager.py similarity index 52% rename from examples/bootloader/guiFunctions.py rename to utilities/device_manager.py index 046f02054..fca5725f0 100644 --- a/examples/bootloader/guiFunctions.py +++ b/utilities/device_manager.py @@ -1,3 +1,5 @@ +#!/usr/bin/env python3 + from datetime import timedelta import depthai as dai import tempfile @@ -83,6 +85,7 @@ def unlockConfig(window, devType): window['Flash configuration'].update(disabled=False) window['Factory reset'].update(disabled=False) # window['Reset configuration'].update(disabled=False) + window['Flash DAP'].update(disabled=False) def lockConfig(window): @@ -101,6 +104,7 @@ def lockConfig(window): window['Flash configuration'].update(disabled=True) window['Factory reset'].update(disabled=True) window['Reset configuration'].update(disabled=True) + window['Flash DAP'].update(disabled=True) window.Element('staticIp').update("") window.Element('staticMask').update("") @@ -114,17 +118,21 @@ def lockConfig(window): window.Element('devName').update("-name-") window.Element('newBoot').update("-version-") window.Element('currBoot').update("-version-") + window.Element('version').update("-version-") + window.Element('commit').update("-commit-") + window.Element('devState').update("-state-") def getDevices(window, devices): listedDevices = [] devices.clear() - deviceInfos = dai.Device.getAllAvailableDevices() + deviceInfos = dai.XLinkConnection.getAllConnectedDevices() if not deviceInfos: window.Element('devices').update("No devices") sg.Popup("No devices found.") else: for deviceInfo in deviceInfos: + # print(deviceInfo.state) listedDevices.append(deviceInfo.desc.name) devices[deviceInfo.desc.name] = deviceInfo sg.Popup("Found devices.") @@ -160,6 +168,9 @@ def getConfigs(window, device, devType): window.Element('devName').update(device.desc.name) window.Element('newBoot').update(dai.DeviceBootloader.getEmbeddedBootloaderVersion()) window.Element('currBoot').update(bl.getVersion()) + window.Element('version').update(dai.__version__) + window.Element('commit').update(dai.__commit__) + window.Element('devState').update(str(devices[device.desc.name].state).split(".")[1]) def flashBootloader(window, device): @@ -170,7 +181,7 @@ def flashBootloader(window, device): sg.Popup("Flashed newest bootloader version.") -def flashConfig(values, window, device, devType, ipType): +def flashConfig(values, device, devType, ipType): bl = dai.DeviceBootloader(device, True) conf = dai.DeviceBootloader.Config() if devType == "Poe": @@ -226,3 +237,168 @@ def getDeviceType(device): return "Poe" else: return "NonPoe" + + +def flashFromFile(file, device): + bl = dai.DeviceBootloader(device) + if str(file)[-3:] == "dap": + bl.flashDepthaiApplicationPackage(file) + else: + sg.Popup("Selected file is not .dap!") + + +usbSpeeds = ["UNKNOWN", "LOW", "FULL", "HIGH", "SUPER", "SUPER_PLUS"] + +sg.theme('DarkGrey4') + +aboutDeviceLayout = [ + [sg.Text("About device", size=(30, 1), font=('Arial', 30, 'bold'), text_color="black")], + [sg.HSeparator()], + [ + sg.Text("Select device: ", size=(15, 1), font=('Arial', 10, 'bold'), text_color="black"), + sg.Combo([], "Search for devices", size=(30, 5), key="devices", enable_events=True), + sg.Button("Select"), sg.Button("Search") + ], + [ + sg.Text("Name of connected device:", size=(30, 1), font=('Arial', 10, 'bold'), text_color="black"), + sg.VSeparator(), + sg.Text("Device state:", size=(30, 1), font=('Arial', 10, 'bold'), text_color="black") + ], + [sg.Text("-name-", key="devName", size=(30, 1)), sg.VSeparator(), sg.Text("-state-", key="devState", size=(30, 1))], + [ + sg.Text("Version of newest bootloader:", size=(30, 1), font=('Arial', 10, 'bold'), text_color="black"), + sg.VSeparator(), + sg.Text("Current bootloader version:", size=(30, 1), font=('Arial', 10, 'bold'), text_color="black") + ], + [ + sg.Text("-version-", key="newBoot", size=(30, 1)), + sg.VSeparator(), + sg.Text("-version-", key="currBoot", size=(30, 1)) + ], + [ + sg.Text("Current __version__:", size=(30, 1), font=('Arial', 10, 'bold'), text_color="black"), + sg.VSeparator(), + sg.Text("Current commit:", size=(30, 1), font=('Arial', 10, 'bold'), text_color="black"), + ], + [ + sg.Text("-version-", key="version", size=(30, 1)), + sg.VSeparator(), + sg.Text("-version-", key="commit", size=(31, 1)) + ], + [sg.HSeparator()], + [ + sg.Text("", size=(5, 2)), + sg.Button("Flash newest version", size=(17, 2), font=('Arial', 10, 'bold'), disabled=True, + button_color='#FFEA00'), + sg.Button("Factory reset", size=(17, 2), font=('Arial', 10, 'bold'), disabled=True, button_color='#FFEA00'), + sg.Button("Config", size=(17, 2), font=('Arial', 10, 'bold'), disabled=False, button_color='#FFEA00') + ] +] + +deviceConfigLayout = [ + [sg.Text("Configuration settings", size=(20, 1), font=('Arial', 30, 'bold'), text_color="black")], + [sg.HSeparator()], + [ + sg.Text("IPv4 type:", size=(30, 1), font=('Arial', 10, 'bold'), text_color="black"), + sg.Radio('Static', "ipType", default=True, font=('Arial', 10, 'bold'), text_color="black", + key="staticBut", enable_events=True, disabled=True), + sg.Radio('Dynamic', "ipType", default=False, font=('Arial', 10, 'bold'), text_color="black", + key="dynamicBut", enable_events=True, disabled=True) + ], + [ + sg.Text("IPv4:", size=(12, 1), font=('Arial', 10, 'bold'), text_color="black"), + sg.InputText(key="staticIp", size=(16, 2), disabled=True), + sg.Text("Mask:", size=(5, 1), font=('Arial', 10, 'bold'), text_color="black"), + sg.InputText(key="staticMask", size=(16, 2), disabled=True), + sg.Text("Gateway:", size=(8, 1), font=('Arial', 10, 'bold'), text_color="black"), + sg.InputText(key="staticGateway", size=(16, 2), disabled=True) + ], + [ + sg.Text("DNS name:", size=(30, 1), font=('Arial', 10, 'bold'), text_color="black"), + sg.InputText(key="dns", size=(30, 2), disabled=True) + ], + [ + sg.Text("Alt DNS name:", size=(30, 1), font=('Arial', 10, 'bold'), text_color="black"), + sg.InputText(key="dnsAlt", size=(30, 2), disabled=True) + ], + [ + sg.Text("USB timeout:", size=(30, 1), font=('Arial', 10, 'bold'), text_color="black"), + sg.InputText(key="usbTimeout", size=(30, 2), disabled=True) + ], + [ + sg.Text("Network timeout:", size=(30, 1), font=('Arial', 10, 'bold'), text_color="black"), + sg.InputText(key="networkTimeout", size=(30, 2), disabled=True) + ], + [ + sg.Text("MAC address:", size=(30, 1), font=('Arial', 10, 'bold'), text_color="black"), + sg.InputText(key="mac", size=(30, 2), disabled=True) + ], + [ + sg.Text("USB max speed:", size=(30, 1), font=('Arial', 10, 'bold'), text_color="black"), + sg.Combo(usbSpeeds, "Select speed", key="usbSpeed", size=(30, 6), disabled=True) + ], + [sg.HSeparator()], + [ + sg.Text("", size=(1, 2)), + sg.Button("Flash configuration", size=(15, 2), font=('Arial', 10, 'bold'), disabled=True, + button_color='#FFEA00'), + sg.Button("Clear flash", size=(15, 2), font=('Arial', 10, 'bold'), disabled=True, + button_color='#FFEA00'), + sg.Button("Flash DAP", size=(15, 2), font=('Arial', 10, 'bold'), disabled=True, + button_color='#FFEA00'), + sg.Button("Back", size=(15, 2), font=('Arial', 10, 'bold'), disabled=False, + button_color='#FFEA00') + ], +] + +layout = [ + [ + # sg.VSeparator(), + sg.Column(aboutDeviceLayout, key='-COL1-'), + # sg.VSeparator(), + sg.Column(deviceConfigLayout, visible=False, key='-COL2-'), + # sg.VSeparator() + ] +] + +devices = dict() +devType = "" +window = sg.Window(title="Bootloader GUI", layout=layout, size=(620, 350)) + +while True: + event, values = window.read() + if event == sg.WIN_CLOSED: + break + dev = values['devices'] + if event == "Select": + if dev != "Select device": + devType = getDeviceType(devices[values['devices']]) + getConfigs(window, devices[values['devices']], devType) + unlockConfig(window, devType) + else: + window.Element('progress').update("No device selected.") + if event == "Search": + getDevices(window, devices) + if event == "Flash newest version": + flashBootloader(window, devices[values['devices']]) + if event == "Flash configuration": + flashConfig(values, devices[values['devices']], devType, values['StaticBut']) + getConfigs(window, devices[values['devices']], devType) + lockConfig(window) + if devType != "Poe": + unlockConfig(window, devType) + else: + devices.clear() + window.Element('devices').update("Search for devices", values=[]) + if event == "Factory reset": + factoryReset(devices[values['devices']]) + if event == "Flash DAP": + file = sg.popup_get_file("Select .dap file") + flashFromFile(file, devices[values['devices']]) + if event == "Config": + window['-COL1-'].update(visible=False) + window['-COL2-'].update(visible=True) + if event == "Back": + window['-COL2-'].update(visible=False) + window['-COL1-'].update(visible=True) +window.close() From 93f52f848b4283507c719e0e288ab2c1b9ce74a9 Mon Sep 17 00:00:00 2001 From: zigakerzan Date: Thu, 24 Feb 2022 12:49:39 +0100 Subject: [PATCH 03/26] Added menu buttons Moved buttons for moving between pages to menu bar. --- utilities/device_manager.py | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/utilities/device_manager.py b/utilities/device_manager.py index fca5725f0..314384a10 100644 --- a/utilities/device_manager.py +++ b/utilities/device_manager.py @@ -248,12 +248,18 @@ def flashFromFile(file, device): usbSpeeds = ["UNKNOWN", "LOW", "FULL", "HIGH", "SUPER", "SUPER_PLUS"] +menu = [["About"], ["Config"]] sg.theme('DarkGrey4') aboutDeviceLayout = [ [sg.Text("About device", size=(30, 1), font=('Arial', 30, 'bold'), text_color="black")], [sg.HSeparator()], + [ + sg.Button("About", size=(15, 1), font=('Arial', 10, 'bold'), disabled=False, key="aboutFake"), + sg.Button("Config", size=(15, 1), font=('Arial', 10, 'bold'), disabled=False, key="configReal") + ], + [sg.HSeparator()], [ sg.Text("Select device: ", size=(15, 1), font=('Arial', 10, 'bold'), text_color="black"), sg.Combo([], "Search for devices", size=(30, 5), key="devices", enable_events=True), @@ -287,17 +293,21 @@ def flashFromFile(file, device): ], [sg.HSeparator()], [ - sg.Text("", size=(5, 2)), + sg.Text("", size=(13, 2)), sg.Button("Flash newest version", size=(17, 2), font=('Arial', 10, 'bold'), disabled=True, button_color='#FFEA00'), sg.Button("Factory reset", size=(17, 2), font=('Arial', 10, 'bold'), disabled=True, button_color='#FFEA00'), - sg.Button("Config", size=(17, 2), font=('Arial', 10, 'bold'), disabled=False, button_color='#FFEA00') ] ] deviceConfigLayout = [ [sg.Text("Configuration settings", size=(20, 1), font=('Arial', 30, 'bold'), text_color="black")], [sg.HSeparator()], + [ + sg.Button("About", size=(15, 1), font=('Arial', 10, 'bold'), disabled=False, key="aboutReal"), + sg.Button("Config", size=(15, 1), font=('Arial', 10, 'bold'), disabled=False, key="configFake") + ], + [sg.HSeparator()], [ sg.Text("IPv4 type:", size=(30, 1), font=('Arial', 10, 'bold'), text_color="black"), sg.Radio('Static', "ipType", default=True, font=('Arial', 10, 'bold'), text_color="black", @@ -339,14 +349,12 @@ def flashFromFile(file, device): ], [sg.HSeparator()], [ - sg.Text("", size=(1, 2)), + sg.Text("", size=(10, 2)), sg.Button("Flash configuration", size=(15, 2), font=('Arial', 10, 'bold'), disabled=True, button_color='#FFEA00'), sg.Button("Clear flash", size=(15, 2), font=('Arial', 10, 'bold'), disabled=True, button_color='#FFEA00'), sg.Button("Flash DAP", size=(15, 2), font=('Arial', 10, 'bold'), disabled=True, - button_color='#FFEA00'), - sg.Button("Back", size=(15, 2), font=('Arial', 10, 'bold'), disabled=False, button_color='#FFEA00') ], ] @@ -363,7 +371,7 @@ def flashFromFile(file, device): devices = dict() devType = "" -window = sg.Window(title="Bootloader GUI", layout=layout, size=(620, 350)) +window = sg.Window(title="Bootloader GUI", layout=layout, size=(620, 370)) while True: event, values = window.read() @@ -395,10 +403,10 @@ def flashFromFile(file, device): if event == "Flash DAP": file = sg.popup_get_file("Select .dap file") flashFromFile(file, devices[values['devices']]) - if event == "Config": + if event == "configReal": window['-COL1-'].update(visible=False) window['-COL2-'].update(visible=True) - if event == "Back": + if event == "aboutReal": window['-COL2-'].update(visible=False) window['-COL1-'].update(visible=True) window.close() From 9a71b495f2ee38ee89d5751d2900f37a2c004830 Mon Sep 17 00:00:00 2001 From: zigakerzan Date: Thu, 24 Feb 2022 13:02:28 +0100 Subject: [PATCH 04/26] Menu buttons animations Added pressed animation to menu buttons. --- utilities/device_manager.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/utilities/device_manager.py b/utilities/device_manager.py index 314384a10..a1a7ac9c5 100644 --- a/utilities/device_manager.py +++ b/utilities/device_manager.py @@ -248,7 +248,6 @@ def flashFromFile(file, device): usbSpeeds = ["UNKNOWN", "LOW", "FULL", "HIGH", "SUPER", "SUPER_PLUS"] -menu = [["About"], ["Config"]] sg.theme('DarkGrey4') @@ -256,7 +255,7 @@ def flashFromFile(file, device): [sg.Text("About device", size=(30, 1), font=('Arial', 30, 'bold'), text_color="black")], [sg.HSeparator()], [ - sg.Button("About", size=(15, 1), font=('Arial', 10, 'bold'), disabled=False, key="aboutFake"), + sg.Button("About", size=(15, 1), font=('Arial', 10, 'bold'), disabled=True, key="aboutFake"), sg.Button("Config", size=(15, 1), font=('Arial', 10, 'bold'), disabled=False, key="configReal") ], [sg.HSeparator()], @@ -305,7 +304,7 @@ def flashFromFile(file, device): [sg.HSeparator()], [ sg.Button("About", size=(15, 1), font=('Arial', 10, 'bold'), disabled=False, key="aboutReal"), - sg.Button("Config", size=(15, 1), font=('Arial', 10, 'bold'), disabled=False, key="configFake") + sg.Button("Config", size=(15, 1), font=('Arial', 10, 'bold'), disabled=True, key="configFake") ], [sg.HSeparator()], [ From eaa91a104b724889c4aed7a4399c61d217c4e757 Mon Sep 17 00:00:00 2001 From: zigakerzan Date: Thu, 24 Feb 2022 14:11:58 +0100 Subject: [PATCH 05/26] Changed button About Changed button "About" to "Device select" to not confuse users --- utilities/device_manager.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/utilities/device_manager.py b/utilities/device_manager.py index a1a7ac9c5..dec6372f9 100644 --- a/utilities/device_manager.py +++ b/utilities/device_manager.py @@ -255,7 +255,7 @@ def flashFromFile(file, device): [sg.Text("About device", size=(30, 1), font=('Arial', 30, 'bold'), text_color="black")], [sg.HSeparator()], [ - sg.Button("About", size=(15, 1), font=('Arial', 10, 'bold'), disabled=True, key="aboutFake"), + sg.Button("Device select", size=(15, 1), font=('Arial', 10, 'bold'), disabled=True, key="aboutFake"), sg.Button("Config", size=(15, 1), font=('Arial', 10, 'bold'), disabled=False, key="configReal") ], [sg.HSeparator()], @@ -303,7 +303,7 @@ def flashFromFile(file, device): [sg.Text("Configuration settings", size=(20, 1), font=('Arial', 30, 'bold'), text_color="black")], [sg.HSeparator()], [ - sg.Button("About", size=(15, 1), font=('Arial', 10, 'bold'), disabled=False, key="aboutReal"), + sg.Button("Device select", size=(15, 1), font=('Arial', 10, 'bold'), disabled=False, key="aboutReal"), sg.Button("Config", size=(15, 1), font=('Arial', 10, 'bold'), disabled=True, key="configFake") ], [sg.HSeparator()], @@ -360,11 +360,8 @@ def flashFromFile(file, device): layout = [ [ - # sg.VSeparator(), sg.Column(aboutDeviceLayout, key='-COL1-'), - # sg.VSeparator(), sg.Column(deviceConfigLayout, visible=False, key='-COL2-'), - # sg.VSeparator() ] ] From 7caca8925dbd50ec2f4b7cd16717792331eb5cdf Mon Sep 17 00:00:00 2001 From: Martin Peterlin Date: Tue, 1 Mar 2022 01:22:47 +0100 Subject: [PATCH 06/26] Some tweaks and support for devices lacking flash --- utilities/device_manager.py | 63 ++++++++++++++++++++----------------- 1 file changed, 35 insertions(+), 28 deletions(-) diff --git a/utilities/device_manager.py b/utilities/device_manager.py index dec6372f9..7385e2b1e 100644 --- a/utilities/device_manager.py +++ b/utilities/device_manager.py @@ -135,39 +135,45 @@ def getDevices(window, devices): # print(deviceInfo.state) listedDevices.append(deviceInfo.desc.name) devices[deviceInfo.desc.name] = deviceInfo - sg.Popup("Found devices.") + # TODO - this action drags down user on correct path, no need for it + # sg.Popup("Found devices.") window.Element('devices').update("Select device", values=listedDevices) def getConfigs(window, device, devType): bl = dai.DeviceBootloader(device) + # TODO - might be better to readConfig instead of readConfigData conf = bl.readConfigData() - if devType == "Poe": - window.Element('staticIp').update(conf['network']['ipv4'] if conf['network']['ipv4'] != 0 else "0.0.0.0") - window.Element('staticMask').update(conf['network']['ipv4Mask'] if conf['network']['ipv4Mask'] != 0 + if conf is not None: + if devType == "Poe": + window.Element('staticIp').update(conf['network']['ipv4'] if conf['network']['ipv4'] != 0 else "0.0.0.0") + window.Element('staticMask').update(conf['network']['ipv4Mask'] if conf['network']['ipv4Mask'] != 0 + else "0.0.0.0") + window.Element('staticGateway').update(conf['network']['ipv4Gateway'] if conf['network']['ipv4Gateway'] != 0 + else "0.0.0.0") + window.Element('dns').update(conf['network']['ipv4Dns'] if conf['network']['ipv4Dns'] != 0 else "0.0.0.0") + window.Element('dnsAlt').update(conf['network']['ipv4DnsAlt'] if conf['network']['ipv4DnsAlt'] != 0 else "0.0.0.0") - window.Element('staticGateway').update(conf['network']['ipv4Gateway'] if conf['network']['ipv4Gateway'] != 0 - else "0.0.0.0") - window.Element('dns').update(conf['network']['ipv4Dns'] if conf['network']['ipv4Dns'] != 0 else "0.0.0.0") - window.Element('dnsAlt').update(conf['network']['ipv4DnsAlt'] if conf['network']['ipv4DnsAlt'] != 0 - else "0.0.0.0") - window.Element('networkTimeout').update(conf['network']['timeoutMs']) - window.Element('mac').update(macCorrectFormat(conf['network']['mac'])) - window.Element('usbTimeout').update("") - window.Element('usbSpeed').update("") - else: - window.Element('staticIp').update("") - window.Element('staticMask').update("") - window.Element('staticGateway').update("") - window.Element('dns').update("") - window.Element('dnsAlt').update("") - window.Element('networkTimeout').update("") - window.Element('mac').update("") - window.Element('usbTimeout').update(conf['usb']['timeoutMs']) - window.Element('usbSpeed').update(usbSpeedCorrection(conf['usb']['maxUsbSpeed'])) + window.Element('networkTimeout').update(conf['network']['timeoutMs']) + window.Element('mac').update(macCorrectFormat(conf['network']['mac'])) + window.Element('usbTimeout').update("") + window.Element('usbSpeed').update("") + else: + window.Element('staticIp').update("") + window.Element('staticMask').update("") + window.Element('staticGateway').update("") + window.Element('dns').update("") + window.Element('dnsAlt').update("") + window.Element('networkTimeout').update("") + window.Element('mac').update("") + window.Element('usbTimeout').update(conf['usb']['timeoutMs']) + window.Element('usbSpeed').update(usbSpeedCorrection(conf['usb']['maxUsbSpeed'])) window.Element('devName').update(device.desc.name) window.Element('newBoot').update(dai.DeviceBootloader.getEmbeddedBootloaderVersion()) - window.Element('currBoot').update(bl.getVersion()) + if bl.isEmbeddedVersion() is True: + window.Element('currBoot').update('None') + else: + window.Element('currBoot').update(bl.getVersion()) window.Element('version').update(dai.__version__) window.Element('commit').update(dai.__commit__) window.Element('devState').update(str(devices[device.desc.name].state).split(".")[1]) @@ -231,8 +237,9 @@ def factoryReset(device): def getDeviceType(device): bl = dai.DeviceBootloader(device) conf = bl.readConfigData() - if conf is None: - success, error = bl.flashConfig(dai.DeviceBootloader.Config()) + # TODO - Don't modify the device without explicit action + # if conf is None: + # success, error = bl.flashConfig(dai.DeviceBootloader.Config()) if str(bl.getType()) == "Type.NETWORK": return "Poe" else: @@ -293,7 +300,7 @@ def flashFromFile(file, device): [sg.HSeparator()], [ sg.Text("", size=(13, 2)), - sg.Button("Flash newest version", size=(17, 2), font=('Arial', 10, 'bold'), disabled=True, + sg.Button("Flash newest Bootloader", size=(17, 2), font=('Arial', 10, 'bold'), disabled=True, button_color='#FFEA00'), sg.Button("Factory reset", size=(17, 2), font=('Arial', 10, 'bold'), disabled=True, button_color='#FFEA00'), ] @@ -367,7 +374,7 @@ def flashFromFile(file, device): devices = dict() devType = "" -window = sg.Window(title="Bootloader GUI", layout=layout, size=(620, 370)) +window = sg.Window(title="Device Manager", layout=layout, size=(620, 370)) while True: event, values = window.read() From 3b80b81b4a808c239263fa5e9ee3043d349fc0cb Mon Sep 17 00:00:00 2001 From: zigakerzan Date: Wed, 2 Mar 2022 16:51:00 +0100 Subject: [PATCH 07/26] Update device_manager.py Added new button "Flash from usb" --- utilities/device_manager.py | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/utilities/device_manager.py b/utilities/device_manager.py index 7385e2b1e..a20aa55b1 100644 --- a/utilities/device_manager.py +++ b/utilities/device_manager.py @@ -81,11 +81,12 @@ def unlockConfig(window, devType): else: window['usbTimeout'].update(disabled=False) window['usbSpeed'].update(disabled=False) - window['Flash newest version'].update(disabled=False) + window['Flash newest Bootloader'].update(disabled=False) window['Flash configuration'].update(disabled=False) window['Factory reset'].update(disabled=False) # window['Reset configuration'].update(disabled=False) window['Flash DAP'].update(disabled=False) + window['Boot from USB'].update(disabled=False) def lockConfig(window): @@ -100,11 +101,12 @@ def lockConfig(window): window['dynamicBut'].update(disabled=True) window['usbTimeout'].update(disabled=True) window['usbSpeed'].update(disabled=True) - window['Flash newest version'].update(disabled=True) + window['Flash newest Bootloader'].update(disabled=True) window['Flash configuration'].update(disabled=True) window['Factory reset'].update(disabled=True) window['Reset configuration'].update(disabled=True) window['Flash DAP'].update(disabled=True) + window['Boot from USB'].update(disabled=True) window.Element('staticIp').update("") window.Element('staticMask').update("") @@ -254,10 +256,16 @@ def flashFromFile(file, device): sg.Popup("Selected file is not .dap!") +def flashFromUsb(device): + bl = dai.DeviceBootloader(device) + bl.bootUsbRomBootloader() + + usbSpeeds = ["UNKNOWN", "LOW", "FULL", "HIGH", "SUPER", "SUPER_PLUS"] sg.theme('DarkGrey4') +# layout for device tab aboutDeviceLayout = [ [sg.Text("About device", size=(30, 1), font=('Arial', 30, 'bold'), text_color="black")], [sg.HSeparator()], @@ -299,13 +307,15 @@ def flashFromFile(file, device): ], [sg.HSeparator()], [ - sg.Text("", size=(13, 2)), + sg.Text("", size=(7, 2)), sg.Button("Flash newest Bootloader", size=(17, 2), font=('Arial', 10, 'bold'), disabled=True, button_color='#FFEA00'), sg.Button("Factory reset", size=(17, 2), font=('Arial', 10, 'bold'), disabled=True, button_color='#FFEA00'), + sg.Button("Boot from USB", size=(17, 2), font=('Arial', 10, 'bold'), disabled=True, button_color='#FFEA00') ] ] +# layout for config tab deviceConfigLayout = [ [sg.Text("Configuration settings", size=(20, 1), font=('Arial', 30, 'bold'), text_color="black")], [sg.HSeparator()], @@ -365,6 +375,7 @@ def flashFromFile(file, device): ], ] +# layout of whole GUI with closed tabs set to false layout = [ [ sg.Column(aboutDeviceLayout, key='-COL1-'), @@ -390,7 +401,7 @@ def flashFromFile(file, device): window.Element('progress').update("No device selected.") if event == "Search": getDevices(window, devices) - if event == "Flash newest version": + if event == "Flash newest Bootloader": flashBootloader(window, devices[values['devices']]) if event == "Flash configuration": flashConfig(values, devices[values['devices']], devType, values['StaticBut']) @@ -412,4 +423,6 @@ def flashFromFile(file, device): if event == "aboutReal": window['-COL2-'].update(visible=False) window['-COL1-'].update(visible=True) + if event == "Boot from USB": + flashFromUsb(devices[values['devices']]) window.close() From 8eb2d9b726857a1f3196db4c8c32f74773e40299 Mon Sep 17 00:00:00 2001 From: zigakerzan Date: Thu, 3 Mar 2022 08:58:33 +0100 Subject: [PATCH 08/26] Update device_manager.py Changed program so that you connect to the device only once --- utilities/device_manager.py | 62 ++++++++++++++++++------------------- 1 file changed, 31 insertions(+), 31 deletions(-) diff --git a/utilities/device_manager.py b/utilities/device_manager.py index a20aa55b1..63a948968 100644 --- a/utilities/device_manager.py +++ b/utilities/device_manager.py @@ -84,9 +84,9 @@ def unlockConfig(window, devType): window['Flash newest Bootloader'].update(disabled=False) window['Flash configuration'].update(disabled=False) window['Factory reset'].update(disabled=False) - # window['Reset configuration'].update(disabled=False) + # window['Clear flash'].update(disabled=False) window['Flash DAP'].update(disabled=False) - window['Boot from USB'].update(disabled=False) + window['Boot into USB Recovery mode'].update(disabled=False) def lockConfig(window): @@ -104,9 +104,9 @@ def lockConfig(window): window['Flash newest Bootloader'].update(disabled=True) window['Flash configuration'].update(disabled=True) window['Factory reset'].update(disabled=True) - window['Reset configuration'].update(disabled=True) + window['Clear flash'].update(disabled=True) window['Flash DAP'].update(disabled=True) - window['Boot from USB'].update(disabled=True) + window['Boot into USB Recovery mode'].update(disabled=True) window.Element('staticIp').update("") window.Element('staticMask').update("") @@ -137,13 +137,11 @@ def getDevices(window, devices): # print(deviceInfo.state) listedDevices.append(deviceInfo.desc.name) devices[deviceInfo.desc.name] = deviceInfo - # TODO - this action drags down user on correct path, no need for it - # sg.Popup("Found devices.") window.Element('devices').update("Select device", values=listedDevices) -def getConfigs(window, device, devType): - bl = dai.DeviceBootloader(device) +def getConfigs(window, bl, devType, device): + # bl = dai.DeviceBootloader(device) # TODO - might be better to readConfig instead of readConfigData conf = bl.readConfigData() if conf is not None: @@ -181,16 +179,16 @@ def getConfigs(window, device, devType): window.Element('devState').update(str(devices[device.desc.name].state).split(".")[1]) -def flashBootloader(window, device): - bl = dai.DeviceBootloader(device, True) +def flashBootloader(window, bl): + # bl = dai.DeviceBootloader(device, True) progress = lambda p: p * 100 bl.flashBootloader(progress) window.Element('currBoot').update(bl.getVersion()) sg.Popup("Flashed newest bootloader version.") -def flashConfig(values, device, devType, ipType): - bl = dai.DeviceBootloader(device, True) +def flashConfig(values, bl, devType, ipType): + # bl = dai.DeviceBootloader(device, True) conf = dai.DeviceBootloader.Config() if devType == "Poe": if not(check_ip(values['staticIp']) and check_ip(values['staticMask']) @@ -205,7 +203,7 @@ def flashConfig(values, device, devType, ipType): conf.setStaticIPv4(values['staticIp'], values['staticIp'], values['staticIp']) else: conf.setDynamicIPv4(values['staticIp'], values['staticIp'], values['staticIp']) - conf.setDnsIPv4(values['dns'], "") + conf.setDnsIPv4(values['dns'], values['dnsAlt']) conf.setNetworkTimeout(timedelta(seconds=int(values['networkTimeout']) / 1000)) conf.setMacAddress(values['mac']) else: @@ -221,10 +219,10 @@ def flashConfig(values, device, devType, ipType): sg.Popup("Flashing successful.") -def factoryReset(device): +def factoryReset(bl): blBinary = dai.DeviceBootloader.getEmbeddedBootloaderBinary(dai.DeviceBootloader.Type.NETWORK) blBinary = blBinary + ([0xFF] * ((8 * 1024 * 1024 + 512) - len(blBinary))) - bl = dai.DeviceBootloader(device, True) + # bl = dai.DeviceBootloader(device, True) tmpBlFw = tempfile.NamedTemporaryFile(delete=False) tmpBlFw.write(bytes(blBinary)) progress = lambda p: p * 100 @@ -236,8 +234,8 @@ def factoryReset(device): tmpBlFw.close() -def getDeviceType(device): - bl = dai.DeviceBootloader(device) +def getDeviceType(bl): + # bl = dai.DeviceBootloader(device) conf = bl.readConfigData() # TODO - Don't modify the device without explicit action # if conf is None: @@ -248,16 +246,16 @@ def getDeviceType(device): return "NonPoe" -def flashFromFile(file, device): - bl = dai.DeviceBootloader(device) +def flashFromFile(file, bl): + # bl = dai.DeviceBootloader(device) if str(file)[-3:] == "dap": bl.flashDepthaiApplicationPackage(file) else: sg.Popup("Selected file is not .dap!") -def flashFromUsb(device): - bl = dai.DeviceBootloader(device) +def flashFromUsb(bl): + # bl = dai.DeviceBootloader(device) bl.bootUsbRomBootloader() @@ -311,7 +309,7 @@ def flashFromUsb(device): sg.Button("Flash newest Bootloader", size=(17, 2), font=('Arial', 10, 'bold'), disabled=True, button_color='#FFEA00'), sg.Button("Factory reset", size=(17, 2), font=('Arial', 10, 'bold'), disabled=True, button_color='#FFEA00'), - sg.Button("Boot from USB", size=(17, 2), font=('Arial', 10, 'bold'), disabled=True, button_color='#FFEA00') + sg.Button("Boot into USB Recovery mode", size=(17, 2), font=('Arial', 10, 'bold'), disabled=True, button_color='#FFEA00') ] ] @@ -385,6 +383,7 @@ def flashFromUsb(device): devices = dict() devType = "" +bl = None window = sg.Window(title="Device Manager", layout=layout, size=(620, 370)) while True: @@ -394,18 +393,19 @@ def flashFromUsb(device): dev = values['devices'] if event == "Select": if dev != "Select device": - devType = getDeviceType(devices[values['devices']]) - getConfigs(window, devices[values['devices']], devType) + bl = dai.DeviceBootloader(devices[values['devices']]) + devType = getDeviceType(bl) + getConfigs(window, bl, devType, devices[values['devices']]) unlockConfig(window, devType) else: window.Element('progress').update("No device selected.") if event == "Search": getDevices(window, devices) if event == "Flash newest Bootloader": - flashBootloader(window, devices[values['devices']]) + flashBootloader(window, bl) if event == "Flash configuration": - flashConfig(values, devices[values['devices']], devType, values['StaticBut']) - getConfigs(window, devices[values['devices']], devType) + flashConfig(values, bl, devType, values['staticBut']) + getConfigs(window, bl, devType, devices[values['devices']]) lockConfig(window) if devType != "Poe": unlockConfig(window, devType) @@ -413,16 +413,16 @@ def flashFromUsb(device): devices.clear() window.Element('devices').update("Search for devices", values=[]) if event == "Factory reset": - factoryReset(devices[values['devices']]) + factoryReset(bl) if event == "Flash DAP": file = sg.popup_get_file("Select .dap file") - flashFromFile(file, devices[values['devices']]) + flashFromFile(file, bl) if event == "configReal": window['-COL1-'].update(visible=False) window['-COL2-'].update(visible=True) if event == "aboutReal": window['-COL2-'].update(visible=False) window['-COL1-'].update(visible=True) - if event == "Boot from USB": - flashFromUsb(devices[values['devices']]) + if event == "Boot into USB Recovery mode": + flashFromUsb(bl) window.close() From b9deede9b44b5852eb72289e208024b9d40356bb Mon Sep 17 00:00:00 2001 From: zigakerzan Date: Fri, 4 Mar 2022 09:38:37 +0100 Subject: [PATCH 09/26] Added requirements Added install_requirements.py and requirements.txt --- utilities/install_requirements.py | 20 ++++++++++++++++++++ utilities/requirements.txt | 1 + 2 files changed, 21 insertions(+) create mode 100644 utilities/install_requirements.py create mode 100644 utilities/requirements.txt diff --git a/utilities/install_requirements.py b/utilities/install_requirements.py new file mode 100644 index 000000000..ac402b47e --- /dev/null +++ b/utilities/install_requirements.py @@ -0,0 +1,20 @@ +import subprocess +import sys + + +in_venv = getattr(sys, "real_prefix", getattr(sys, "base_prefix", sys.prefix)) != sys.prefix +pip_call = [sys.executable, "-m", "pip"] +pip_install = pip_call + ["install"] + +if not in_venv: + pip_install.append("--user") + +subprocess.check_call([*pip_install, "pip", "-U"]) +subprocess.check_call([*pip_call, "uninstall", "depthai", "--yes"]) +subprocess.check_call([*pip_install, "-r", "requirements.txt"]) +try: + subprocess.check_call([*pip_install, "-r", "requirements-optional.txt"]) + if sys.platform == "linux": + print(f'$ sudo apt install python3-tk') +except subprocess.CalledProcessError as ex: + print(f"Optional dependencies were not installed (exit code {ex.returncode})") \ No newline at end of file diff --git a/utilities/requirements.txt b/utilities/requirements.txt new file mode 100644 index 000000000..ef2dc7bf6 --- /dev/null +++ b/utilities/requirements.txt @@ -0,0 +1 @@ +PySimpleGUI==4.57.0 \ No newline at end of file From 780fc442f29ee44eb9a3c4359cabea47336fa52f Mon Sep 17 00:00:00 2001 From: zigakerzan Date: Fri, 11 Mar 2022 21:50:06 +0100 Subject: [PATCH 10/26] Fixed requested changes Fixed requested changes and also made GUI flashing to work so that you may flash only wanted configs and not all. --- utilities/device_manager.py | 55 +++++++++++++++++++++++-------------- 1 file changed, 34 insertions(+), 21 deletions(-) diff --git a/utilities/device_manager.py b/utilities/device_manager.py index 63a948968..1de5361f9 100644 --- a/utilities/device_manager.py +++ b/utilities/device_manager.py @@ -7,6 +7,8 @@ def check_ip(s: str): + if str == "": + return True spl = s.split(".") if len(spl) != 4: sg.Popup("Wrong IP format.\nValue should be similar to 255.255.255.255") @@ -19,6 +21,8 @@ def check_ip(s: str): def check_mac(s): + if s == "": + return True if s.count(":") != 5: sg.Popup("Wrong MAC format.\nValue should be similar to FF:FF:FF:FF:FF:FF") return False @@ -170,10 +174,11 @@ def getConfigs(window, bl, devType, device): window.Element('usbSpeed').update(usbSpeedCorrection(conf['usb']['maxUsbSpeed'])) window.Element('devName').update(device.desc.name) window.Element('newBoot').update(dai.DeviceBootloader.getEmbeddedBootloaderVersion()) - if bl.isEmbeddedVersion() is True: - window.Element('currBoot').update('None') - else: - window.Element('currBoot').update(bl.getVersion()) + # if bl.isEmbeddedVersion() is True: + # window.Element('currBoot').update('None') + # else: + # window.Element('currBoot').update(bl.getVersion()) + window.Element('currBoot').update(bl.getVersion()) window.Element('version').update(dai.__version__) window.Element('commit').update(dai.__commit__) window.Element('devState').update(str(devices[device.desc.name].state).split(".")[1]) @@ -200,18 +205,25 @@ def flashConfig(values, bl, devType, ipType): sg.Popup("Values can not be negative!") return if ipType: - conf.setStaticIPv4(values['staticIp'], values['staticIp'], values['staticIp']) + if values['staticIp'] != "" and values['staticMask'] != "" and values['staticGateway'] != "": + conf.setStaticIPv4(values['staticIp'], values['staticMask'], values['staticGateway']) else: - conf.setDynamicIPv4(values['staticIp'], values['staticIp'], values['staticIp']) - conf.setDnsIPv4(values['dns'], values['dnsAlt']) - conf.setNetworkTimeout(timedelta(seconds=int(values['networkTimeout']) / 1000)) - conf.setMacAddress(values['mac']) + if values['staticIp'] != "" and values['staticMask'] != "" and values['staticGateway'] != "": + conf.setDynamicIPv4(values['staticIp'], values['staticMask'], values['staticGateway']) + if values['dns'] != "" and values['dnsAlt'] != "": + conf.setDnsIPv4(values['dns'], values['dnsAlt']) + if values['networkTimeout'] != "": + conf.setNetworkTimeout(timedelta(seconds=int(values['networkTimeout']) / 1000)) + if values['mac'] != "": + conf.setMacAddress(values['mac']) else: if int(values['usbTimeout']) <= 0: sg.Popup("Values can not be negative!") return - conf.setUsbTimeout(timedelta(seconds=int(values['usbTimeout']) / 1000)) - conf.setUsbMaxSpeed(usbSpeed(values['usbSpeed'])) + if values['usbTimeout'] != "": + conf.setUsbTimeout(timedelta(seconds=int(values['usbTimeout']) / 1000)) + if values['usbSpeed'] != "": + conf.setUsbMaxSpeed(usbSpeed(values['usbSpeed'])) success, error = bl.flashConfig(conf) if not success: sg.Popup(f"Flashing failed: {error}") @@ -236,7 +248,7 @@ def factoryReset(bl): def getDeviceType(bl): # bl = dai.DeviceBootloader(device) - conf = bl.readConfigData() + # conf = bl.readConfigData() # TODO - Don't modify the device without explicit action # if conf is None: # success, error = bl.flashConfig(dai.DeviceBootloader.Config()) @@ -261,7 +273,7 @@ def flashFromUsb(bl): usbSpeeds = ["UNKNOWN", "LOW", "FULL", "HIGH", "SUPER", "SUPER_PLUS"] -sg.theme('DarkGrey4') +sg.theme('LightGrey2') # layout for device tab aboutDeviceLayout = [ @@ -294,7 +306,7 @@ def flashFromUsb(bl): sg.Text("-version-", key="currBoot", size=(30, 1)) ], [ - sg.Text("Current __version__:", size=(30, 1), font=('Arial', 10, 'bold'), text_color="black"), + sg.Text("Current Depthai version:", size=(30, 1), font=('Arial', 10, 'bold'), text_color="black"), sg.VSeparator(), sg.Text("Current commit:", size=(30, 1), font=('Arial', 10, 'bold'), text_color="black"), ], @@ -307,9 +319,10 @@ def flashFromUsb(bl): [ sg.Text("", size=(7, 2)), sg.Button("Flash newest Bootloader", size=(17, 2), font=('Arial', 10, 'bold'), disabled=True, - button_color='#FFEA00'), - sg.Button("Factory reset", size=(17, 2), font=('Arial', 10, 'bold'), disabled=True, button_color='#FFEA00'), - sg.Button("Boot into USB Recovery mode", size=(17, 2), font=('Arial', 10, 'bold'), disabled=True, button_color='#FFEA00') + button_color='#FFA500'), + sg.Button("Factory reset", size=(17, 2), font=('Arial', 10, 'bold'), disabled=True, button_color='#FFA500'), + sg.Button("Boot into USB Recovery mode", size=(17, 2), font=('Arial', 10, 'bold'), disabled=True, + button_color='#FFA500') ] ] @@ -365,11 +378,11 @@ def flashFromUsb(bl): [ sg.Text("", size=(10, 2)), sg.Button("Flash configuration", size=(15, 2), font=('Arial', 10, 'bold'), disabled=True, - button_color='#FFEA00'), + button_color='#FFA500'), sg.Button("Clear flash", size=(15, 2), font=('Arial', 10, 'bold'), disabled=True, - button_color='#FFEA00'), + button_color='#FFA500'), sg.Button("Flash DAP", size=(15, 2), font=('Arial', 10, 'bold'), disabled=True, - button_color='#FFEA00') + button_color='#FFA500') ], ] @@ -393,7 +406,7 @@ def flashFromUsb(bl): dev = values['devices'] if event == "Select": if dev != "Select device": - bl = dai.DeviceBootloader(devices[values['devices']]) + bl = dai.DeviceBootloader(devices[values['devices']], True) devType = getDeviceType(bl) getConfigs(window, bl, devType, devices[values['devices']]) unlockConfig(window, devType) From 350c4f2031e88ca7cdcc78df035e3cb7a8b582e0 Mon Sep 17 00:00:00 2001 From: zigakerzan Date: Sat, 12 Mar 2022 09:57:09 +0100 Subject: [PATCH 11/26] Removed select button Buttons select removed, GUI works with combo' events now( clicking in the combo box triggers same event as select did). --- utilities/device_manager.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/utilities/device_manager.py b/utilities/device_manager.py index 1de5361f9..1b076ee44 100644 --- a/utilities/device_manager.py +++ b/utilities/device_manager.py @@ -287,7 +287,7 @@ def flashFromUsb(bl): [ sg.Text("Select device: ", size=(15, 1), font=('Arial', 10, 'bold'), text_color="black"), sg.Combo([], "Search for devices", size=(30, 5), key="devices", enable_events=True), - sg.Button("Select"), sg.Button("Search") + sg.Button("Search") ], [ sg.Text("Name of connected device:", size=(30, 1), font=('Arial', 10, 'bold'), text_color="black"), @@ -404,7 +404,7 @@ def flashFromUsb(bl): if event == sg.WIN_CLOSED: break dev = values['devices'] - if event == "Select": + if event == "devices": if dev != "Select device": bl = dai.DeviceBootloader(devices[values['devices']], True) devType = getDeviceType(bl) From a3bb08ac09c42676e813a027dc38b64a9cc58cc0 Mon Sep 17 00:00:00 2001 From: zigakerzan Date: Sat, 12 Mar 2022 12:03:40 +0100 Subject: [PATCH 12/26] Changed widow size Changed window size and added device name to config tab. --- utilities/device_manager.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/utilities/device_manager.py b/utilities/device_manager.py index 1b076ee44..091eb2a59 100644 --- a/utilities/device_manager.py +++ b/utilities/device_manager.py @@ -122,6 +122,7 @@ def lockConfig(window): window.Element('usbTimeout').update("") window.Element('usbSpeed').update("") window.Element('devName').update("-name-") + window.Element('devNameConf').update("") window.Element('newBoot').update("-version-") window.Element('currBoot').update("-version-") window.Element('version').update("-version-") @@ -173,6 +174,7 @@ def getConfigs(window, bl, devType, device): window.Element('usbTimeout').update(conf['usb']['timeoutMs']) window.Element('usbSpeed').update(usbSpeedCorrection(conf['usb']['maxUsbSpeed'])) window.Element('devName').update(device.desc.name) + window.Element('devNameConf').update(device.desc.name) window.Element('newBoot').update(dai.DeviceBootloader.getEmbeddedBootloaderVersion()) # if bl.isEmbeddedVersion() is True: # window.Element('currBoot').update('None') @@ -332,7 +334,11 @@ def flashFromUsb(bl): [sg.HSeparator()], [ sg.Button("Device select", size=(15, 1), font=('Arial', 10, 'bold'), disabled=False, key="aboutReal"), - sg.Button("Config", size=(15, 1), font=('Arial', 10, 'bold'), disabled=True, key="configFake") + sg.Button("Config", size=(15, 1), font=('Arial', 10, 'bold'), disabled=True, key="configFake"), + # TODO create library tab + # sg.Button("Library", size=(15, 1), font=('Arial', 10, 'bold'), disabled=True, key="configLib"), + sg.Text("", key="devNameConf", size=(30, 1)) + ], [sg.HSeparator()], [ @@ -397,7 +403,7 @@ def flashFromUsb(bl): devices = dict() devType = "" bl = None -window = sg.Window(title="Device Manager", layout=layout, size=(620, 370)) +window = sg.Window(title="Device Manager", layout=layout, size=(645, 370)) while True: event, values = window.read() From 1adcd96613cda97b7cd60d4f85ecb2bbf960041b Mon Sep 17 00:00:00 2001 From: zigakerzan Date: Mon, 14 Mar 2022 17:20:18 +0100 Subject: [PATCH 13/26] Update device_manager.py Fixed values error --- utilities/device_manager.py | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/utilities/device_manager.py b/utilities/device_manager.py index 091eb2a59..d06459b77 100644 --- a/utilities/device_manager.py +++ b/utilities/device_manager.py @@ -198,32 +198,32 @@ def flashConfig(values, bl, devType, ipType): # bl = dai.DeviceBootloader(device, True) conf = dai.DeviceBootloader.Config() if devType == "Poe": - if not(check_ip(values['staticIp']) and check_ip(values['staticMask']) - and check_ip(values['staticGateway'])): - return - if not check_mac(values['mac']): - return - if int(values['networkTimeout']) <= 0: - sg.Popup("Values can not be negative!") - return if ipType: if values['staticIp'] != "" and values['staticMask'] != "" and values['staticGateway'] != "": - conf.setStaticIPv4(values['staticIp'], values['staticMask'], values['staticGateway']) + if check_ip(values['staticIp']) and check_ip(values['staticMask']) and check_ip( + values['staticGateway']): + conf.setStaticIPv4(values['staticIp'], values['staticMask'], values['staticGateway']) else: if values['staticIp'] != "" and values['staticMask'] != "" and values['staticGateway'] != "": - conf.setDynamicIPv4(values['staticIp'], values['staticMask'], values['staticGateway']) + if check_ip(values['staticIp']) and check_ip(values['staticMask']) and check_ip( + values['staticGateway']): + conf.setDynamicIPv4(values['staticIp'], values['staticMask'], values['staticGateway']) if values['dns'] != "" and values['dnsAlt'] != "": conf.setDnsIPv4(values['dns'], values['dnsAlt']) if values['networkTimeout'] != "": - conf.setNetworkTimeout(timedelta(seconds=int(values['networkTimeout']) / 1000)) + if int(values['networkTimeout']) >= 0: + conf.setNetworkTimeout(timedelta(seconds=int(values['networkTimeout']) / 1000)) + else: + sg.Popup("Values can not be negative!") if values['mac'] != "": - conf.setMacAddress(values['mac']) + if check_mac(values['mac']): + conf.setMacAddress(values['mac']) else: - if int(values['usbTimeout']) <= 0: - sg.Popup("Values can not be negative!") - return if values['usbTimeout'] != "": - conf.setUsbTimeout(timedelta(seconds=int(values['usbTimeout']) / 1000)) + if int(values['usbTimeout']) >= 0: + conf.setUsbTimeout(timedelta(seconds=int(values['usbTimeout']) / 1000)) + else: + sg.Popup("Values can not be negative!") if values['usbSpeed'] != "": conf.setUsbMaxSpeed(usbSpeed(values['usbSpeed'])) success, error = bl.flashConfig(conf) From 0336a70f8b86010ba6ba58a342c0c075cc532be0 Mon Sep 17 00:00:00 2001 From: TheMarpe Date: Tue, 15 Mar 2022 18:46:05 +0100 Subject: [PATCH 14/26] Some fixes and review comments --- utilities/device_manager.py | 63 ++++++++++++++++++++++--------------- 1 file changed, 38 insertions(+), 25 deletions(-) diff --git a/utilities/device_manager.py b/utilities/device_manager.py index d06459b77..4c495c801 100644 --- a/utilities/device_manager.py +++ b/utilities/device_manager.py @@ -148,31 +148,39 @@ def getDevices(window, devices): def getConfigs(window, bl, devType, device): # bl = dai.DeviceBootloader(device) # TODO - might be better to readConfig instead of readConfigData - conf = bl.readConfigData() - if conf is not None: - if devType == "Poe": - window.Element('staticIp').update(conf['network']['ipv4'] if conf['network']['ipv4'] != 0 else "0.0.0.0") - window.Element('staticMask').update(conf['network']['ipv4Mask'] if conf['network']['ipv4Mask'] != 0 - else "0.0.0.0") - window.Element('staticGateway').update(conf['network']['ipv4Gateway'] if conf['network']['ipv4Gateway'] != 0 + + # TODO - apply to other functions as well, most of them may throw, catch such cases and present the user with an appropriate message + # Most of time the version difference is the culprit + try: + conf = bl.readConfigData() + if conf is not None: + if devType == "Poe": + window.Element('staticIp').update(conf['network']['ipv4'] if conf['network']['ipv4'] != 0 else "0.0.0.0") + window.Element('staticMask').update(conf['network']['ipv4Mask'] if conf['network']['ipv4Mask'] != 0 + else "0.0.0.0") + window.Element('staticGateway').update(conf['network']['ipv4Gateway'] if conf['network']['ipv4Gateway'] != 0 + else "0.0.0.0") + window.Element('dns').update(conf['network']['ipv4Dns'] if conf['network']['ipv4Dns'] != 0 else "0.0.0.0") + window.Element('dnsAlt').update(conf['network']['ipv4DnsAlt'] if conf['network']['ipv4DnsAlt'] != 0 else "0.0.0.0") - window.Element('dns').update(conf['network']['ipv4Dns'] if conf['network']['ipv4Dns'] != 0 else "0.0.0.0") - window.Element('dnsAlt').update(conf['network']['ipv4DnsAlt'] if conf['network']['ipv4DnsAlt'] != 0 - else "0.0.0.0") - window.Element('networkTimeout').update(conf['network']['timeoutMs']) - window.Element('mac').update(macCorrectFormat(conf['network']['mac'])) - window.Element('usbTimeout').update("") - window.Element('usbSpeed').update("") - else: - window.Element('staticIp').update("") - window.Element('staticMask').update("") - window.Element('staticGateway').update("") - window.Element('dns').update("") - window.Element('dnsAlt').update("") - window.Element('networkTimeout').update("") - window.Element('mac').update("") - window.Element('usbTimeout').update(conf['usb']['timeoutMs']) - window.Element('usbSpeed').update(usbSpeedCorrection(conf['usb']['maxUsbSpeed'])) + window.Element('networkTimeout').update(conf['network']['timeoutMs']) + window.Element('mac').update(macCorrectFormat(conf['network']['mac'])) + window.Element('usbTimeout').update("") + window.Element('usbSpeed').update("") + else: + window.Element('staticIp').update("") + window.Element('staticMask').update("") + window.Element('staticGateway').update("") + window.Element('dns').update("") + window.Element('dnsAlt').update("") + window.Element('networkTimeout').update("") + window.Element('mac').update("") + window.Element('usbTimeout').update(conf['usb']['timeoutMs']) + window.Element('usbSpeed').update(usbSpeedCorrection(conf['usb']['maxUsbSpeed'])) + except Exception as ex: + print(f'Exception: {ex}') + sg.Popup(f'{ex}') + window.Element('devName').update(device.desc.name) window.Element('devNameConf').update(device.desc.name) window.Element('newBoot').update(dai.DeviceBootloader.getEmbeddedBootloaderVersion()) @@ -187,6 +195,8 @@ def getConfigs(window, bl, devType, device): def flashBootloader(window, bl): + # FIXME - to flash bootloader, boot the same device again (from saved device info) but with allowFlashingBootloader = True + # FIXME 2 - Allow selection of bootloader type explicitly (eg network type when flashing a fresh PoE device which doesn't have an Ethernet bootloader on it yet) # bl = dai.DeviceBootloader(device, True) progress = lambda p: p * 100 bl.flashBootloader(progress) @@ -412,7 +422,10 @@ def flashFromUsb(bl): dev = values['devices'] if event == "devices": if dev != "Select device": - bl = dai.DeviceBootloader(devices[values['devices']], True) + # "allow flashing bootloader" boots latest bootloader first + # which makes the information of current bootloader, etc.. not correct (can be checked by "isEmbeddedVersion") + # So leave it to false, uncomment the isEmbeddedVersion below and only boot into latest bootlaoder upon the request to flash new bootloader + bl = dai.DeviceBootloader(devices[values['devices']], False) devType = getDeviceType(bl) getConfigs(window, bl, devType, devices[values['devices']]) unlockConfig(window, devType) From bab621636a50b5bbffb34911c341fdaf9bd881e0 Mon Sep 17 00:00:00 2001 From: zigakerzan Date: Tue, 22 Mar 2022 10:02:11 +0100 Subject: [PATCH 15/26] Addressed requested changes Fixed addressed changes. --- utilities/device_manager.py | 280 ++++++++++++++++++++++-------------- 1 file changed, 174 insertions(+), 106 deletions(-) diff --git a/utilities/device_manager.py b/utilities/device_manager.py index 4c495c801..572d9cdbd 100644 --- a/utilities/device_manager.py +++ b/utilities/device_manager.py @@ -131,18 +131,22 @@ def lockConfig(window): def getDevices(window, devices): - listedDevices = [] - devices.clear() - deviceInfos = dai.XLinkConnection.getAllConnectedDevices() - if not deviceInfos: - window.Element('devices').update("No devices") - sg.Popup("No devices found.") - else: - for deviceInfo in deviceInfos: - # print(deviceInfo.state) - listedDevices.append(deviceInfo.desc.name) - devices[deviceInfo.desc.name] = deviceInfo - window.Element('devices').update("Select device", values=listedDevices) + try: + listedDevices = [] + devices.clear() + deviceInfos = dai.XLinkConnection.getAllConnectedDevices() + if not deviceInfos: + window.Element('devices').update("No devices") + sg.Popup("No devices found.") + else: + for deviceInfo in deviceInfos: + # print(deviceInfo.state) + listedDevices.append(deviceInfo.desc.name) + devices[deviceInfo.desc.name] = deviceInfo + window.Element('devices').update("Select device", values=listedDevices) + except Exception as ex: + print(f'Exception: {ex}') + sg.Popup(f'{ex}') def getConfigs(window, bl, devType, device): @@ -177,85 +181,100 @@ def getConfigs(window, bl, devType, device): window.Element('mac').update("") window.Element('usbTimeout').update(conf['usb']['timeoutMs']) window.Element('usbSpeed').update(usbSpeedCorrection(conf['usb']['maxUsbSpeed'])) + + window.Element('devName').update(device.desc.name) + window.Element('devNameConf').update(device.desc.name) + window.Element('newBoot').update(dai.DeviceBootloader.getEmbeddedBootloaderVersion()) + # if bl.isEmbeddedVersion() is True: + # window.Element('currBoot').update('None') + # else: + # window.Element('currBoot').update(bl.getVersion()) + window.Element('currBoot').update(bl.getVersion()) + window.Element('version').update(dai.__version__) + window.Element('commit').update(dai.__commit__) + window.Element('devState').update(str(devices[device.desc.name].state).split(".")[1]) except Exception as ex: print(f'Exception: {ex}') sg.Popup(f'{ex}') - window.Element('devName').update(device.desc.name) - window.Element('devNameConf').update(device.desc.name) - window.Element('newBoot').update(dai.DeviceBootloader.getEmbeddedBootloaderVersion()) - # if bl.isEmbeddedVersion() is True: - # window.Element('currBoot').update('None') - # else: - # window.Element('currBoot').update(bl.getVersion()) - window.Element('currBoot').update(bl.getVersion()) - window.Element('version').update(dai.__version__) - window.Element('commit').update(dai.__commit__) - window.Element('devState').update(str(devices[device.desc.name].state).split(".")[1]) - -def flashBootloader(window, bl): +def flashBootloader(window, device, values): # FIXME - to flash bootloader, boot the same device again (from saved device info) but with allowFlashingBootloader = True # FIXME 2 - Allow selection of bootloader type explicitly (eg network type when flashing a fresh PoE device which doesn't have an Ethernet bootloader on it yet) - # bl = dai.DeviceBootloader(device, True) - progress = lambda p: p * 100 - bl.flashBootloader(progress) - window.Element('currBoot').update(bl.getVersion()) - sg.Popup("Flashed newest bootloader version.") + try: + type = dai.DeviceBootloader.Type.USB + if values['bootType'] == "NETWORK": + type = dai.DeviceBootloader.Type.NETWORK + bl = dai.DeviceBootloader(device, True) + progress = lambda p: p * 100 + bl.flashBootloader(memory=dai.DeviceBootloader.Memory.FLASH, type=type, progressCallback=progress) + window.Element('currBoot').update(bl.getVersion()) + sg.Popup("Flashed newest bootloader version.") + except Exception as ex: + print(f'Exception: {ex}') + sg.Popup(f'{ex}') -def flashConfig(values, bl, devType, ipType): - # bl = dai.DeviceBootloader(device, True) - conf = dai.DeviceBootloader.Config() - if devType == "Poe": - if ipType: - if values['staticIp'] != "" and values['staticMask'] != "" and values['staticGateway'] != "": - if check_ip(values['staticIp']) and check_ip(values['staticMask']) and check_ip( - values['staticGateway']): - conf.setStaticIPv4(values['staticIp'], values['staticMask'], values['staticGateway']) - else: - if values['staticIp'] != "" and values['staticMask'] != "" and values['staticGateway'] != "": - if check_ip(values['staticIp']) and check_ip(values['staticMask']) and check_ip( - values['staticGateway']): - conf.setDynamicIPv4(values['staticIp'], values['staticMask'], values['staticGateway']) - if values['dns'] != "" and values['dnsAlt'] != "": - conf.setDnsIPv4(values['dns'], values['dnsAlt']) - if values['networkTimeout'] != "": - if int(values['networkTimeout']) >= 0: - conf.setNetworkTimeout(timedelta(seconds=int(values['networkTimeout']) / 1000)) - else: - sg.Popup("Values can not be negative!") - if values['mac'] != "": - if check_mac(values['mac']): - conf.setMacAddress(values['mac']) - else: - if values['usbTimeout'] != "": - if int(values['usbTimeout']) >= 0: - conf.setUsbTimeout(timedelta(seconds=int(values['usbTimeout']) / 1000)) +def flashConfig(values, device, devType, ipType): + try: + bl = dai.DeviceBootloader(device, True) + conf = dai.DeviceBootloader.Config() + if devType == "Poe": + if ipType: + if values['staticIp'] != "" and values['staticMask'] != "" and values['staticGateway'] != "": + if check_ip(values['staticIp']) and check_ip(values['staticMask']) and check_ip( + values['staticGateway']): + conf.setStaticIPv4(values['staticIp'], values['staticMask'], values['staticGateway']) else: - sg.Popup("Values can not be negative!") - if values['usbSpeed'] != "": - conf.setUsbMaxSpeed(usbSpeed(values['usbSpeed'])) - success, error = bl.flashConfig(conf) - if not success: - sg.Popup(f"Flashing failed: {error}") - else: - sg.Popup("Flashing successful.") - - -def factoryReset(bl): - blBinary = dai.DeviceBootloader.getEmbeddedBootloaderBinary(dai.DeviceBootloader.Type.NETWORK) - blBinary = blBinary + ([0xFF] * ((8 * 1024 * 1024 + 512) - len(blBinary))) - # bl = dai.DeviceBootloader(device, True) - tmpBlFw = tempfile.NamedTemporaryFile(delete=False) - tmpBlFw.write(bytes(blBinary)) - progress = lambda p: p * 100 - success, msg = bl.flashBootloader(progress, tmpBlFw.name) - if success: - sg.Popup("Factory reset was successful.") - else: - sg.Popup(f"Factory reset failed. Error: {msg}") - tmpBlFw.close() + if values['staticIp'] != "" and values['staticMask'] != "" and values['staticGateway'] != "": + if check_ip(values['staticIp']) and check_ip(values['staticMask']) and check_ip( + values['staticGateway']): + conf.setDynamicIPv4(values['staticIp'], values['staticMask'], values['staticGateway']) + if values['dns'] != "" and values['dnsAlt'] != "": + conf.setDnsIPv4(values['dns'], values['dnsAlt']) + if values['networkTimeout'] != "": + if int(values['networkTimeout']) >= 0: + conf.setNetworkTimeout(timedelta(seconds=int(values['networkTimeout']) / 1000)) + else: + sg.Popup("Values can not be negative!") + if values['mac'] != "": + if check_mac(values['mac']): + conf.setMacAddress(values['mac']) + else: + if values['usbTimeout'] != "": + if int(values['usbTimeout']) >= 0: + conf.setUsbTimeout(timedelta(seconds=int(values['usbTimeout']) / 1000)) + else: + sg.Popup("Values can not be negative!") + if values['usbSpeed'] != "": + conf.setUsbMaxSpeed(usbSpeed(values['usbSpeed'])) + success, error = bl.flashConfig(conf) + if not success: + sg.Popup(f"Flashing failed: {error}") + else: + sg.Popup("Flashing successful.") + except Exception as ex: + print(f'Exception: {ex}') + sg.Popup(f'{ex}') + + +def factoryReset(device): + try: + blBinary = dai.DeviceBootloader.getEmbeddedBootloaderBinary(dai.DeviceBootloader.Type.NETWORK) + blBinary = blBinary + ([0xFF] * ((8 * 1024 * 1024 + 512) - len(blBinary))) + bl = dai.DeviceBootloader(device, True) + tmpBlFw = tempfile.NamedTemporaryFile(delete=False) + tmpBlFw.write(bytes(blBinary)) + progress = lambda p: p * 100 + success, msg = bl.flashBootloader(progress, tmpBlFw.name) + if success: + sg.Popup("Factory reset was successful.") + else: + sg.Popup(f"Factory reset failed. Error: {msg}") + tmpBlFw.close() + except Exception as ex: + print(f'Exception: {ex}') + sg.Popup(f'{ex}') def getDeviceType(bl): @@ -264,29 +283,62 @@ def getDeviceType(bl): # TODO - Don't modify the device without explicit action # if conf is None: # success, error = bl.flashConfig(dai.DeviceBootloader.Config()) - if str(bl.getType()) == "Type.NETWORK": - return "Poe" - else: - return "NonPoe" + try: + if str(bl.getType()) == "Type.NETWORK": + return "Poe" + else: + return "NonPoe" + except Exception as ex: + print(f'Exception: {ex}') + sg.Popup(f'{ex}') -def flashFromFile(file, bl): - # bl = dai.DeviceBootloader(device) - if str(file)[-3:] == "dap": - bl.flashDepthaiApplicationPackage(file) - else: - sg.Popup("Selected file is not .dap!") +def flashFromFile(file, device): + try: + bl = dai.DeviceBootloader(device, True) + if str(file)[-3:] == "dap": + bl.flashDepthaiApplicationPackage(file) + else: + sg.Popup("Selected file is not .dap!") + except Exception as ex: + print(f'Exception: {ex}') + sg.Popup(f'{ex}') -def flashFromUsb(bl): - # bl = dai.DeviceBootloader(device) - bl.bootUsbRomBootloader() +def flashFromUsb(device): + try: + bl = dai.DeviceBootloader(device, True) + bl.bootUsbRomBootloader() + except Exception as ex: + print(f'Exception: {ex}') + sg.Popup(f'{ex}') + + +def connectToDevice(device): + try: + bl = dai.DeviceBootloader(device, False) + return bl + except Exception as ex: + print(f'Exception: {ex}') + sg.Popup(f'{ex}') usbSpeeds = ["UNKNOWN", "LOW", "FULL", "HIGH", "SUPER", "SUPER_PLUS"] sg.theme('LightGrey2') +# first device search +allDevices = [] +devices = dict() +tmp = "Search for devices" +deviceInfos = dai.XLinkConnection.getAllConnectedDevices() + +if deviceInfos: + tmp = "Select device" + for deviceInfo in deviceInfos: + allDevices.append(deviceInfo.desc.name) + devices[deviceInfo.desc.name] = deviceInfo + # layout for device tab aboutDeviceLayout = [ [sg.Text("About device", size=(30, 1), font=('Arial', 30, 'bold'), text_color="black")], @@ -298,7 +350,7 @@ def flashFromUsb(bl): [sg.HSeparator()], [ sg.Text("Select device: ", size=(15, 1), font=('Arial', 10, 'bold'), text_color="black"), - sg.Combo([], "Search for devices", size=(30, 5), key="devices", enable_events=True), + sg.Combo(allDevices, tmp, size=(30, 5), key="devices", enable_events=True), sg.Button("Search") ], [ @@ -328,6 +380,12 @@ def flashFromUsb(bl): sg.Text("-version-", key="commit", size=(31, 1)) ], [sg.HSeparator()], + [ + sg.Text("Bootloader type:", size=(30, 1), font=('Arial', 10, 'bold'), text_color="black"), + sg.VSeparator(), + sg.Combo(["USB", "NETWORK"], "Select bootloader type", size=(30, 2), key="bootType"), + ], + [sg.HSeparator()], [ sg.Text("", size=(7, 2)), sg.Button("Flash newest Bootloader", size=(17, 2), font=('Arial', 10, 'bold'), disabled=True, @@ -410,10 +468,9 @@ def flashFromUsb(bl): ] ] -devices = dict() devType = "" bl = None -window = sg.Window(title="Device Manager", layout=layout, size=(645, 370)) +window = sg.Window(title="Device Manager", layout=layout, size=(645, 410)) while True: event, values = window.read() @@ -425,18 +482,26 @@ def flashFromUsb(bl): # "allow flashing bootloader" boots latest bootloader first # which makes the information of current bootloader, etc.. not correct (can be checked by "isEmbeddedVersion") # So leave it to false, uncomment the isEmbeddedVersion below and only boot into latest bootlaoder upon the request to flash new bootloader - bl = dai.DeviceBootloader(devices[values['devices']], False) - devType = getDeviceType(bl) - getConfigs(window, bl, devType, devices[values['devices']]) - unlockConfig(window, devType) + # bl = dai.DeviceBootloader(devices[values['devices']], False) + if str(devices[values['devices']].state).replace("XLinkDeviceState.X_LINK_", "") == "BOOTED": + # device is already booted somewhere else + sg.Popup("Device is already booted somewhere else!") + else: + bl = connectToDevice(devices[values['devices']]) + devType = getDeviceType(bl) + getConfigs(window, bl, devType, devices[values['devices']]) + unlockConfig(window, devType) else: window.Element('progress').update("No device selected.") if event == "Search": getDevices(window, devices) if event == "Flash newest Bootloader": - flashBootloader(window, bl) + bl.close() + flashBootloader(window, devices[values['devices']], values) if event == "Flash configuration": - flashConfig(values, bl, devType, values['staticBut']) + bl.close() + flashConfig(values, devices[values['devices']], devType, values['staticBut']) + bl = connectToDevice(devices[values['devices']]) getConfigs(window, bl, devType, devices[values['devices']]) lockConfig(window) if devType != "Poe": @@ -445,10 +510,12 @@ def flashFromUsb(bl): devices.clear() window.Element('devices').update("Search for devices", values=[]) if event == "Factory reset": - factoryReset(bl) + bl.close() + factoryReset(devices[values['devices']]) if event == "Flash DAP": file = sg.popup_get_file("Select .dap file") - flashFromFile(file, bl) + bl = None + flashFromFile(file, devices[values['devices']]) if event == "configReal": window['-COL1-'].update(visible=False) window['-COL2-'].update(visible=True) @@ -456,5 +523,6 @@ def flashFromUsb(bl): window['-COL2-'].update(visible=False) window['-COL1-'].update(visible=True) if event == "Boot into USB Recovery mode": - flashFromUsb(bl) + bl = None + flashFromUsb(devices[values['devices']]) window.close() From a61a609ca6901c8c05294c8739cf11cd235b5a09 Mon Sep 17 00:00:00 2001 From: zigakerzan Date: Tue, 22 Mar 2022 16:57:17 +0100 Subject: [PATCH 16/26] Added default to bootloader type Added a default option which will auto select bootloader type based on device type --- utilities/device_manager.py | 28 +++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/utilities/device_manager.py b/utilities/device_manager.py index 572d9cdbd..836e10ceb 100644 --- a/utilities/device_manager.py +++ b/utilities/device_manager.py @@ -126,7 +126,7 @@ def lockConfig(window): window.Element('newBoot').update("-version-") window.Element('currBoot').update("-version-") window.Element('version').update("-version-") - window.Element('commit').update("-commit-") + window.Element('commit').update("-version-") window.Element('devState').update("-state-") @@ -185,11 +185,14 @@ def getConfigs(window, bl, devType, device): window.Element('devName').update(device.desc.name) window.Element('devNameConf').update(device.desc.name) window.Element('newBoot').update(dai.DeviceBootloader.getEmbeddedBootloaderVersion()) - # if bl.isEmbeddedVersion() is True: - # window.Element('currBoot').update('None') - # else: - # window.Element('currBoot').update(bl.getVersion()) - window.Element('currBoot').update(bl.getVersion()) + + # The "isEmbeddedVersion" tells you whether BL had to be booted, + # or we connected to an already flashed Bootloader. + if bl.isEmbeddedVersion() is True: + window.Element('currBoot').update('None') + else: + window.Element('currBoot').update(bl.getVersion()) + # window.Element('currBoot').update(bl.getVersion()) window.Element('version').update(dai.__version__) window.Element('commit').update(dai.__commit__) window.Element('devState').update(str(devices[device.desc.name].state).split(".")[1]) @@ -202,10 +205,16 @@ def flashBootloader(window, device, values): # FIXME - to flash bootloader, boot the same device again (from saved device info) but with allowFlashingBootloader = True # FIXME 2 - Allow selection of bootloader type explicitly (eg network type when flashing a fresh PoE device which doesn't have an Ethernet bootloader on it yet) try: - type = dai.DeviceBootloader.Type.USB + bl = dai.DeviceBootloader(device, True) if values['bootType'] == "NETWORK": type = dai.DeviceBootloader.Type.NETWORK - bl = dai.DeviceBootloader(device, True) + elif values['bootType'] == "USB": + type = dai.DeviceBootloader.Type.USB + else: + if str(bl.getType()) == "Type.NETWORK": + type = dai.DeviceBootloader.Type.NETWORK + else: + type = dai.DeviceBootloader.Type.USB progress = lambda p: p * 100 bl.flashBootloader(memory=dai.DeviceBootloader.Memory.FLASH, type=type, progressCallback=progress) window.Element('currBoot').update(bl.getVersion()) @@ -383,7 +392,7 @@ def connectToDevice(device): [ sg.Text("Bootloader type:", size=(30, 1), font=('Arial', 10, 'bold'), text_color="black"), sg.VSeparator(), - sg.Combo(["USB", "NETWORK"], "Select bootloader type", size=(30, 2), key="bootType"), + sg.Combo(["DEFAULT", "USB", "NETWORK"], "DEFAULT", size=(30, 2), key="bootType"), ], [sg.HSeparator()], [ @@ -495,6 +504,7 @@ def connectToDevice(device): window.Element('progress').update("No device selected.") if event == "Search": getDevices(window, devices) + lockConfig(window) if event == "Flash newest Bootloader": bl.close() flashBootloader(window, devices[values['devices']], values) From 0b95dcbed51ad527ff2e771dc7785de884e2b9ce Mon Sep 17 00:00:00 2001 From: zigakerzan Date: Tue, 22 Mar 2022 17:01:59 +0100 Subject: [PATCH 17/26] Addressed requested changes Addressed requested changes. --- utilities/device_manager.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/utilities/device_manager.py b/utilities/device_manager.py index 836e10ceb..8cbd5fcf4 100644 --- a/utilities/device_manager.py +++ b/utilities/device_manager.py @@ -128,6 +128,7 @@ def lockConfig(window): window.Element('version').update("-version-") window.Element('commit').update("-version-") window.Element('devState').update("-state-") + window.Element('bootType').update("DEFAULT") def getDevices(window, devices): @@ -392,7 +393,7 @@ def connectToDevice(device): [ sg.Text("Bootloader type:", size=(30, 1), font=('Arial', 10, 'bold'), text_color="black"), sg.VSeparator(), - sg.Combo(["DEFAULT", "USB", "NETWORK"], "DEFAULT", size=(30, 2), key="bootType"), + sg.Combo(["DEFAULT", "USB", "NETWORK"], "DEFAULT", size=(30, 3), key="bootType"), ], [sg.HSeparator()], [ From c5e4d1898dae9a2aec102b6912b6f8ea088906f0 Mon Sep 17 00:00:00 2001 From: Erol444 Date: Tue, 10 May 2022 17:06:05 +0200 Subject: [PATCH 18/26] Added app icon, changed boot type to AUTO, fixed get bootloader version # Conflicts: # utilities/device_manager.py --- utilities/device_manager.py | 25 ++++++++++--------------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/utilities/device_manager.py b/utilities/device_manager.py index 8cbd5fcf4..ed29c827d 100644 --- a/utilities/device_manager.py +++ b/utilities/device_manager.py @@ -5,7 +5,6 @@ import tempfile import PySimpleGUI as sg - def check_ip(s: str): if str == "": return True @@ -19,7 +18,6 @@ def check_ip(s: str): return False return True - def check_mac(s): if s == "": return True @@ -128,7 +126,7 @@ def lockConfig(window): window.Element('version').update("-version-") window.Element('commit').update("-version-") window.Element('devState').update("-state-") - window.Element('bootType').update("DEFAULT") + window.Element('bootType').update("AUTO") def getDevices(window, devices): @@ -190,10 +188,10 @@ def getConfigs(window, bl, devType, device): # The "isEmbeddedVersion" tells you whether BL had to be booted, # or we connected to an already flashed Bootloader. if bl.isEmbeddedVersion() is True: - window.Element('currBoot').update('None') - else: window.Element('currBoot').update(bl.getVersion()) - # window.Element('currBoot').update(bl.getVersion()) + else: + window.Element('currBoot').update('None') + window.Element('version').update(dai.__version__) window.Element('commit').update(dai.__commit__) window.Element('devState').update(str(devices[device.desc.name].state).split(".")[1]) @@ -204,18 +202,15 @@ def getConfigs(window, bl, devType, device): def flashBootloader(window, device, values): # FIXME - to flash bootloader, boot the same device again (from saved device info) but with allowFlashingBootloader = True - # FIXME 2 - Allow selection of bootloader type explicitly (eg network type when flashing a fresh PoE device which doesn't have an Ethernet bootloader on it yet) try: bl = dai.DeviceBootloader(device, True) if values['bootType'] == "NETWORK": type = dai.DeviceBootloader.Type.NETWORK elif values['bootType'] == "USB": type = dai.DeviceBootloader.Type.USB - else: - if str(bl.getType()) == "Type.NETWORK": - type = dai.DeviceBootloader.Type.NETWORK - else: - type = dai.DeviceBootloader.Type.USB + else: # AUTO + type = dai.DeviceBootloader.Type.AUTO + progress = lambda p: p * 100 bl.flashBootloader(memory=dai.DeviceBootloader.Memory.FLASH, type=type, progressCallback=progress) window.Element('currBoot').update(bl.getVersion()) @@ -393,7 +388,7 @@ def connectToDevice(device): [ sg.Text("Bootloader type:", size=(30, 1), font=('Arial', 10, 'bold'), text_color="black"), sg.VSeparator(), - sg.Combo(["DEFAULT", "USB", "NETWORK"], "DEFAULT", size=(30, 3), key="bootType"), + sg.Combo(["AUTO", "USB", "NETWORK"], "AUTO", size=(30, 3), key="bootType"), ], [sg.HSeparator()], [ @@ -480,7 +475,7 @@ def connectToDevice(device): devType = "" bl = None -window = sg.Window(title="Device Manager", layout=layout, size=(645, 410)) +window = sg.Window(title="Device Manager", icon="assets/icon.png", layout=layout, size=(645, 410)) while True: event, values = window.read() @@ -524,7 +519,7 @@ def connectToDevice(device): bl.close() factoryReset(devices[values['devices']]) if event == "Flash DAP": - file = sg.popup_get_file("Select .dap file") + file = sg.popup_get_file("Select .dap file", file_types=(('DepthAI Application Package', '*.dap'), ('All Files', '*.* *'))) bl = None flashFromFile(file, devices[values['devices']]) if event == "configReal": From a8ee5e6f12cc91cfd1379628b749aa2f23184de5 Mon Sep 17 00:00:00 2001 From: Erol444 Date: Mon, 9 May 2022 17:58:30 +0200 Subject: [PATCH 19/26] Updated reading/setting USB/POE configs --- utilities/device_manager.py | 67 +++++++------------------------------ 1 file changed, 13 insertions(+), 54 deletions(-) diff --git a/utilities/device_manager.py b/utilities/device_manager.py index ed29c827d..4ca43c020 100644 --- a/utilities/device_manager.py +++ b/utilities/device_manager.py @@ -31,44 +31,6 @@ def check_mac(s): return False return True - -def usbSpeed(s): - if s == "UNKNOWN": - return dai.UsbSpeed.UNKNOWN - elif s == "LOW": - return dai.UsbSpeed.LOW - elif s == "FULL": - return dai.UsbSpeed.FULL - elif s == "HIGH": - return dai.UsbSpeed.HIGH - elif s == "SUPER": - return dai.UsbSpeed.SUPER - else: - return dai.UsbSpeed.SUPER_PLUS - - -def usbSpeedCorrection(s): - if s == 0: - return "UNKNOWN" - elif s == 1: - return "LOW" - elif s == 2: - return "FULL" - elif s == 3: - return "HIGH" - elif s == 4: - return "SUPER" - else: - return "SUPER_PLUS" - - -def macCorrectFormat(s): - ret = "" - for x in s: - ret += str(hex(x)).strip("0x").upper() + ":" - return ret[:-1] - - def unlockConfig(window, devType): if devType == "Poe": window['staticIp'].update(disabled=False) @@ -155,19 +117,16 @@ def getConfigs(window, bl, devType, device): # TODO - apply to other functions as well, most of them may throw, catch such cases and present the user with an appropriate message # Most of time the version difference is the culprit try: - conf = bl.readConfigData() + conf = bl.readConfig() if conf is not None: if devType == "Poe": - window.Element('staticIp').update(conf['network']['ipv4'] if conf['network']['ipv4'] != 0 else "0.0.0.0") - window.Element('staticMask').update(conf['network']['ipv4Mask'] if conf['network']['ipv4Mask'] != 0 - else "0.0.0.0") - window.Element('staticGateway').update(conf['network']['ipv4Gateway'] if conf['network']['ipv4Gateway'] != 0 - else "0.0.0.0") - window.Element('dns').update(conf['network']['ipv4Dns'] if conf['network']['ipv4Dns'] != 0 else "0.0.0.0") - window.Element('dnsAlt').update(conf['network']['ipv4DnsAlt'] if conf['network']['ipv4DnsAlt'] != 0 - else "0.0.0.0") - window.Element('networkTimeout').update(conf['network']['timeoutMs']) - window.Element('mac').update(macCorrectFormat(conf['network']['mac'])) + window.Element('staticIp').update(conf.getIPv4() if conf.getIPv4() is not None else "0.0.0.0") + window.Element('staticMask').update(conf.getIPv4Mask() if conf.getIPv4Mask() is not None else "0.0.0.0") + window.Element('staticGateway').update(conf.getIPv4Gateway() if conf.getIPv4Gateway() is not None else "0.0.0.0") + window.Element('dns').update(conf.getDnsIPv4() if conf.getDnsIPv4() is not None else "0.0.0.0") + window.Element('dnsAlt').update(conf.getDnsAltIPv4() if conf.getDnsAltIPv4() is not None else "0.0.0.0") + window.Element('networkTimeout').update(int(conf.getNetworkTimeout().total_seconds() * 1000)) + window.Element('mac').update(conf.getMacAddress()) window.Element('usbTimeout').update("") window.Element('usbSpeed').update("") else: @@ -178,8 +137,8 @@ def getConfigs(window, bl, devType, device): window.Element('dnsAlt').update("") window.Element('networkTimeout').update("") window.Element('mac').update("") - window.Element('usbTimeout').update(conf['usb']['timeoutMs']) - window.Element('usbSpeed').update(usbSpeedCorrection(conf['usb']['maxUsbSpeed'])) + window.Element('usbTimeout').update(int(conf.getUsbTimeout().total_seconds() * 1000)) + window.Element('usbSpeed').update(str(conf.getUsbMaxSpeed()).split('.')[1]) window.Element('devName').update(device.desc.name) window.Element('devNameConf').update(device.desc.name) @@ -252,7 +211,7 @@ def flashConfig(values, device, devType, ipType): else: sg.Popup("Values can not be negative!") if values['usbSpeed'] != "": - conf.setUsbMaxSpeed(usbSpeed(values['usbSpeed'])) + conf.setUsbMaxSpeed(getattr(dai.UsbSpeed, values['usbSpeed'])) success, error = bl.flashConfig(conf) if not success: sg.Popup(f"Flashing failed: {error}") @@ -438,11 +397,11 @@ def connectToDevice(device): sg.InputText(key="dnsAlt", size=(30, 2), disabled=True) ], [ - sg.Text("USB timeout:", size=(30, 1), font=('Arial', 10, 'bold'), text_color="black"), + sg.Text("USB timeout [ms]:", size=(30, 1), font=('Arial', 10, 'bold'), text_color="black"), sg.InputText(key="usbTimeout", size=(30, 2), disabled=True) ], [ - sg.Text("Network timeout:", size=(30, 1), font=('Arial', 10, 'bold'), text_color="black"), + sg.Text("Network timeout [ms]:", size=(30, 1), font=('Arial', 10, 'bold'), text_color="black"), sg.InputText(key="networkTimeout", size=(30, 2), disabled=True) ], [ From 97aeb2b27d64ee432aa15e6b928113b2525a1e98 Mon Sep 17 00:00:00 2001 From: Erol444 Date: Mon, 9 May 2022 18:15:44 +0200 Subject: [PATCH 20/26] Added some type safety, logging progress --- utilities/device_manager.py | 24 +++++++++--------------- 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/utilities/device_manager.py b/utilities/device_manager.py index 4ca43c020..68c9ccced 100644 --- a/utilities/device_manager.py +++ b/utilities/device_manager.py @@ -19,8 +19,6 @@ def check_ip(s: str): return True def check_mac(s): - if s == "": - return True if s.count(":") != 5: sg.Popup("Wrong MAC format.\nValue should be similar to FF:FF:FF:FF:FF:FF") return False @@ -32,7 +30,7 @@ def check_mac(s): return True def unlockConfig(window, devType): - if devType == "Poe": + if devType == "POE": window['staticIp'].update(disabled=False) window['staticMask'].update(disabled=False) window['staticGateway'].update(disabled=False) @@ -119,7 +117,7 @@ def getConfigs(window, bl, devType, device): try: conf = bl.readConfig() if conf is not None: - if devType == "Poe": + if devType == "POE": window.Element('staticIp').update(conf.getIPv4() if conf.getIPv4() is not None else "0.0.0.0") window.Element('staticMask').update(conf.getIPv4Mask() if conf.getIPv4Mask() is not None else "0.0.0.0") window.Element('staticGateway').update(conf.getIPv4Gateway() if conf.getIPv4Gateway() is not None else "0.0.0.0") @@ -170,7 +168,7 @@ def flashBootloader(window, device, values): else: # AUTO type = dai.DeviceBootloader.Type.AUTO - progress = lambda p: p * 100 + progress = lambda p : print(f'Flashing progress: {p*100:.1f}%') bl.flashBootloader(memory=dai.DeviceBootloader.Memory.FLASH, type=type, progressCallback=progress) window.Element('currBoot').update(bl.getVersion()) sg.Popup("Flashed newest bootloader version.") @@ -183,7 +181,7 @@ def flashConfig(values, device, devType, ipType): try: bl = dai.DeviceBootloader(device, True) conf = dai.DeviceBootloader.Config() - if devType == "Poe": + if devType == "POE": if ipType: if values['staticIp'] != "" and values['staticMask'] != "" and values['staticGateway'] != "": if check_ip(values['staticIp']) and check_ip(values['staticMask']) and check_ip( @@ -212,6 +210,7 @@ def flashConfig(values, device, devType, ipType): sg.Popup("Values can not be negative!") if values['usbSpeed'] != "": conf.setUsbMaxSpeed(getattr(dai.UsbSpeed, values['usbSpeed'])) + success, error = bl.flashConfig(conf) if not success: sg.Popup(f"Flashing failed: {error}") @@ -242,16 +241,11 @@ def factoryReset(device): def getDeviceType(bl): - # bl = dai.DeviceBootloader(device) - # conf = bl.readConfigData() - # TODO - Don't modify the device without explicit action - # if conf is None: - # success, error = bl.flashConfig(dai.DeviceBootloader.Config()) try: - if str(bl.getType()) == "Type.NETWORK": - return "Poe" + if bl.getType() == dai.DeviceBootloader.Type.NETWORK: + return "POE" else: - return "NonPoe" + return "USB" except Exception as ex: print(f'Exception: {ex}') sg.Popup(f'{ex}') @@ -469,7 +463,7 @@ def connectToDevice(device): bl = connectToDevice(devices[values['devices']]) getConfigs(window, bl, devType, devices[values['devices']]) lockConfig(window) - if devType != "Poe": + if devType != "POE": unlockConfig(window, devType) else: devices.clear() From 6c5c41cdbe7245aff8dc69ed7058598a82a81f2b Mon Sep 17 00:00:00 2001 From: Erol444 Date: Mon, 9 May 2022 18:37:37 +0200 Subject: [PATCH 21/26] Added progress window when flashing --- utilities/assets/icon.png | Bin 0 -> 36119 bytes utilities/device_manager.py | 29 ++++++++++++++++++++++------- 2 files changed, 22 insertions(+), 7 deletions(-) create mode 100644 utilities/assets/icon.png diff --git a/utilities/assets/icon.png b/utilities/assets/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..d05bd35f8fb354a99338cae015bf671a5b5dc173 GIT binary patch literal 36119 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 Date: Tue, 10 May 2022 18:20:44 +0200 Subject: [PATCH 22/26] Fixed install requirement. Added bootloader selection window --- utilities/device_manager.py | 38 +++++++++++++++++++------------ utilities/install_requirements.py | 11 ++++----- 2 files changed, 29 insertions(+), 20 deletions(-) diff --git a/utilities/device_manager.py b/utilities/device_manager.py index 50bcd8017..9addcee3e 100644 --- a/utilities/device_manager.py +++ b/utilities/device_manager.py @@ -43,6 +43,25 @@ def finish(self, msg): self.window.close() sg.Popup(msg) +class SelectBootloader: + def __init__(self, options, text): + self.ok = False + layout = [ + [sg.Text(text)], + [ + sg.Text("Bootloader type:", font=('Arial', 10, 'bold'), text_color="black"), + sg.Combo(options, options[0], size=(30, 3), key="bootType"), + ], + [sg.Text("Warning! This may soft-brick your device,\nproceed if you are sure what you are doing!")], + [sg.Submit(), sg.Cancel()], + ] + self.window = sg.Window("Select bootloader", layout, size=(300,150), modal=True, finalize=True) + def wait(self): + event, values = self.window.Read() + self.window.close() + type = getattr(dai.DeviceBootloader.Type, values['bootType']) + return (str(event) == "Submit", type) + def unlockConfig(window, devType): if devType == "POE": window['staticIp'].update(disabled=False) @@ -100,7 +119,6 @@ def lockConfig(window): window.Element('version').update("-version-") window.Element('commit').update("-version-") window.Element('devState').update("-state-") - window.Element('bootType').update("AUTO") def getDevices(window, devices): @@ -174,13 +192,11 @@ def getConfigs(window, bl, devType, device): def flashBootloader(window, device, values): # FIXME - to flash bootloader, boot the same device again (from saved device info) but with allowFlashingBootloader = True try: + sel = SelectBootloader(['AUTO', 'USB', 'NETWORK'], "Select bootloader type to flash.") + ok, type = sel.wait() + print("sel finished. ok:", ok, "selected:", type) + return bl = dai.DeviceBootloader(device, True) - if values['bootType'] == "NETWORK": - type = dai.DeviceBootloader.Type.NETWORK - elif values['bootType'] == "USB": - type = dai.DeviceBootloader.Type.USB - else: # AUTO - type = dai.DeviceBootloader.Type.AUTO pr = Progress() progress = lambda p : pr.update(p) @@ -353,12 +369,6 @@ def connectToDevice(device): sg.Text("-version-", key="commit", size=(31, 1)) ], [sg.HSeparator()], - [ - sg.Text("Bootloader type:", size=(30, 1), font=('Arial', 10, 'bold'), text_color="black"), - sg.VSeparator(), - sg.Combo(["AUTO", "USB", "NETWORK"], "AUTO", size=(30, 3), key="bootType"), - ], - [sg.HSeparator()], [ sg.Text("", size=(7, 2)), sg.Button("Flash newest Bootloader", size=(17, 2), font=('Arial', 10, 'bold'), disabled=True, @@ -443,7 +453,7 @@ def connectToDevice(device): devType = "" bl = None -window = sg.Window(title="Device Manager", icon="assets/icon.png", layout=layout, size=(645, 410)) +window = sg.Window(title="Device Manager", icon="assets/icon.png", layout=layout, size=(645, 380)) while True: event, values = window.read() diff --git a/utilities/install_requirements.py b/utilities/install_requirements.py index ac402b47e..0d7dcad4f 100644 --- a/utilities/install_requirements.py +++ b/utilities/install_requirements.py @@ -1,7 +1,6 @@ import subprocess import sys - in_venv = getattr(sys, "real_prefix", getattr(sys, "base_prefix", sys.prefix)) != sys.prefix pip_call = [sys.executable, "-m", "pip"] pip_install = pip_call + ["install"] @@ -10,11 +9,11 @@ pip_install.append("--user") subprocess.check_call([*pip_install, "pip", "-U"]) -subprocess.check_call([*pip_call, "uninstall", "depthai", "--yes"]) subprocess.check_call([*pip_install, "-r", "requirements.txt"]) try: - subprocess.check_call([*pip_install, "-r", "requirements-optional.txt"]) - if sys.platform == "linux": - print(f'$ sudo apt install python3-tk') + subprocess.check_call([sys.executable, "-c", "import tkinter"]) except subprocess.CalledProcessError as ex: - print(f"Optional dependencies were not installed (exit code {ex.returncode})") \ No newline at end of file + if sys.platform == "linux": + print('Missing `python3-tk` on the system. Install with `sudo apt install python3-tk`') + else: + print('Missing python3-tkinter on the system.') \ No newline at end of file From c6ffbae5b6f30c7b57b30a5c551cd7d1ce8ebc04 Mon Sep 17 00:00:00 2001 From: Erol444 Date: Tue, 10 May 2022 18:33:23 +0200 Subject: [PATCH 23/26] Added bootloader select popup to flashing bootloader and factory reseting. --- utilities/device_manager.py | 33 +++++++++++++++++---------------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/utilities/device_manager.py b/utilities/device_manager.py index 9addcee3e..e2e978f63 100644 --- a/utilities/device_manager.py +++ b/utilities/device_manager.py @@ -141,11 +141,6 @@ def getDevices(window, devices): def getConfigs(window, bl, devType, device): - # bl = dai.DeviceBootloader(device) - # TODO - might be better to readConfig instead of readConfigData - - # TODO - apply to other functions as well, most of them may throw, catch such cases and present the user with an appropriate message - # Most of time the version difference is the culprit try: conf = bl.readConfig() if conf is not None: @@ -176,10 +171,10 @@ def getConfigs(window, bl, devType, device): # The "isEmbeddedVersion" tells you whether BL had to be booted, # or we connected to an already flashed Bootloader. - if bl.isEmbeddedVersion() is True: - window.Element('currBoot').update(bl.getVersion()) + if bl.isEmbeddedVersion(): + window.Element('currBoot').update('Not Flashed') else: - window.Element('currBoot').update('None') + window.Element('currBoot').update(bl.getVersion()) window.Element('version').update(dai.__version__) window.Element('commit').update(dai.__commit__) @@ -189,13 +184,15 @@ def getConfigs(window, bl, devType, device): sg.Popup(f'{ex}') -def flashBootloader(window, device, values): +def flashBootloader(window, device): # FIXME - to flash bootloader, boot the same device again (from saved device info) but with allowFlashingBootloader = True try: sel = SelectBootloader(['AUTO', 'USB', 'NETWORK'], "Select bootloader type to flash.") ok, type = sel.wait() - print("sel finished. ok:", ok, "selected:", type) - return + if not ok: + print("Flashing bootloader canceled.") + return + bl = dai.DeviceBootloader(device, True) pr = Progress() @@ -253,8 +250,14 @@ def flashConfig(values, device, devType, ipType): def factoryReset(device): + sel = SelectBootloader(['USB', 'NETWORK'], "Select bootloader type used for factory reset.") + ok, type = sel.wait() + if not ok: + print("Factory reset canceled.") + return + try: - blBinary = dai.DeviceBootloader.getEmbeddedBootloaderBinary(dai.DeviceBootloader.Type.NETWORK) + blBinary = dai.DeviceBootloader.getEmbeddedBootloaderBinary(type) blBinary = blBinary + ([0xFF] * ((8 * 1024 * 1024 + 512) - len(blBinary))) bl = dai.DeviceBootloader(device, True) tmpBlFw = tempfile.NamedTemporaryFile(delete=False) @@ -270,7 +273,6 @@ def factoryReset(device): print(f'Exception: {ex}') sg.Popup(f'{ex}') - def getDeviceType(bl): try: if bl.getType() == dai.DeviceBootloader.Type.NETWORK: @@ -281,7 +283,6 @@ def getDeviceType(bl): print(f'Exception: {ex}') sg.Popup(f'{ex}') - def flashFromFile(file, device): try: bl = dai.DeviceBootloader(device, True) @@ -351,7 +352,7 @@ def connectToDevice(device): [ sg.Text("Version of newest bootloader:", size=(30, 1), font=('Arial', 10, 'bold'), text_color="black"), sg.VSeparator(), - sg.Text("Current bootloader version:", size=(30, 1), font=('Arial', 10, 'bold'), text_color="black") + sg.Text("Flashed bootloader version:", size=(30, 1), font=('Arial', 10, 'bold'), text_color="black") ], [ sg.Text("-version-", key="newBoot", size=(30, 1)), @@ -481,7 +482,7 @@ def connectToDevice(device): lockConfig(window) if event == "Flash newest Bootloader": bl.close() - flashBootloader(window, devices[values['devices']], values) + flashBootloader(window, devices[values['devices']]) if event == "Flash configuration": bl.close() flashConfig(values, devices[values['devices']], devType, values['staticBut']) From 08dc8db717a24fd52f9d939be6c03f44c46566fa Mon Sep 17 00:00:00 2001 From: Erol444 Date: Tue, 10 May 2022 19:00:00 +0200 Subject: [PATCH 24/26] Fixed flashing empty static/dynamic IP, simplifying logic --- utilities/device_manager.py | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/utilities/device_manager.py b/utilities/device_manager.py index e2e978f63..10833e4dc 100644 --- a/utilities/device_manager.py +++ b/utilities/device_manager.py @@ -6,8 +6,8 @@ import PySimpleGUI as sg def check_ip(s: str): - if str == "": - return True + if s == "": + return False spl = s.split(".") if len(spl) != 4: sg.Popup("Wrong IP format.\nValue should be similar to 255.255.255.255") @@ -205,21 +205,17 @@ def flashBootloader(window, device): sg.Popup(f'{ex}') -def flashConfig(values, device, devType, ipType): +def flashConfig(values, device, devType, staticIp): try: bl = dai.DeviceBootloader(device, True) conf = dai.DeviceBootloader.Config() if devType == "POE": - if ipType: - if values['staticIp'] != "" and values['staticMask'] != "" and values['staticGateway'] != "": - if check_ip(values['staticIp']) and check_ip(values['staticMask']) and check_ip( - values['staticGateway']): - conf.setStaticIPv4(values['staticIp'], values['staticMask'], values['staticGateway']) + if staticIp: + if check_ip(values['staticIp']) and check_ip(values['staticMask']) and check_ip(values['staticGateway']): + conf.setStaticIPv4(values['staticIp'], values['staticMask'], values['staticGateway']) else: - if values['staticIp'] != "" and values['staticMask'] != "" and values['staticGateway'] != "": - if check_ip(values['staticIp']) and check_ip(values['staticMask']) and check_ip( - values['staticGateway']): - conf.setDynamicIPv4(values['staticIp'], values['staticMask'], values['staticGateway']) + if check_ip(values['staticIp']) and check_ip(values['staticMask']) and check_ip(values['staticGateway']): + conf.setDynamicIPv4(values['staticIp'], values['staticMask'], values['staticGateway']) if values['dns'] != "" and values['dnsAlt'] != "": conf.setDnsIPv4(values['dns'], values['dnsAlt']) if values['networkTimeout'] != "": From 88055f53e1e1f8ced3a06a17ec2792ec1675c5e1 Mon Sep 17 00:00:00 2001 From: Erol444 Date: Wed, 11 May 2022 12:41:01 +0200 Subject: [PATCH 25/26] Grayed out disabled configs, moved USB configs together with seperator --- utilities/device_manager.py | 120 ++++++++++++++++-------------------- 1 file changed, 53 insertions(+), 67 deletions(-) diff --git a/utilities/device_manager.py b/utilities/device_manager.py index 10833e4dc..6a30396b6 100644 --- a/utilities/device_manager.py +++ b/utilities/device_manager.py @@ -5,6 +5,13 @@ import tempfile import PySimpleGUI as sg + +CONF_TEXT_POE = ['ipTypeText', 'ipText', 'maskText', 'gatewayText', 'dnsText', 'dnsAltText', 'networkTimeoutText', 'macText'] +CONF_INPUT_POE = ['staticBut', 'dynamicBut', 'ip', 'mask', 'gateway', 'dns', 'dnsAlt', 'networkTimeout', 'mac'] +CONF_TEXT_USB = ['usbTimeoutText', 'usbSpeedText'] +CONF_INPUT_USB = ['usbTimeout', 'usbSpeed'] +USB_SPEEDS = ["UNKNOWN", "LOW", "FULL", "HIGH", "SUPER", "SUPER_PLUS"] + def check_ip(s: str): if s == "": return False @@ -64,18 +71,16 @@ def wait(self): def unlockConfig(window, devType): if devType == "POE": - window['staticIp'].update(disabled=False) - window['staticMask'].update(disabled=False) - window['staticGateway'].update(disabled=False) - window['dns'].update(disabled=False) - window['dnsAlt'].update(disabled=False) - window['networkTimeout'].update(disabled=False) - window['mac'].update(disabled=False) - window['staticBut'].update(disabled=False) - window['dynamicBut'].update(disabled=False) + for el in CONF_INPUT_POE: + window[el].update(disabled=False) + for el in CONF_TEXT_POE: + window[el].update(text_color="black") else: - window['usbTimeout'].update(disabled=False) - window['usbSpeed'].update(disabled=False) + for el in CONF_INPUT_USB: + window[el].update(disabled=False) + for el in CONF_TEXT_USB: + window[el].update(text_color="black") + window['Flash newest Bootloader'].update(disabled=False) window['Flash configuration'].update(disabled=False) window['Factory reset'].update(disabled=False) @@ -85,17 +90,14 @@ def unlockConfig(window, devType): def lockConfig(window): - window['staticIp'].update(disabled=True) - window['staticMask'].update(disabled=True) - window['staticGateway'].update(disabled=True) - window['dns'].update(disabled=True) - window['dnsAlt'].update(disabled=True) - window['networkTimeout'].update(disabled=True) - window['mac'].update(disabled=True) - window['staticBut'].update(disabled=True) - window['dynamicBut'].update(disabled=True) - window['usbTimeout'].update(disabled=True) - window['usbSpeed'].update(disabled=True) + for conf in [CONF_INPUT_POE, CONF_INPUT_USB]: + for el in conf: + window[el].update(disabled=True) + window[el].update("") + for conf in [CONF_TEXT_POE, CONF_TEXT_USB]: + for el in conf: + window[el].update(text_color="gray") + window['Flash newest Bootloader'].update(disabled=True) window['Flash configuration'].update(disabled=True) window['Factory reset'].update(disabled=True) @@ -103,15 +105,6 @@ def lockConfig(window): window['Flash DAP'].update(disabled=True) window['Boot into USB Recovery mode'].update(disabled=True) - window.Element('staticIp').update("") - window.Element('staticMask').update("") - window.Element('staticGateway').update("") - window.Element('dns').update("") - window.Element('dnsAlt').update("") - window.Element('networkTimeout').update("") - window.Element('mac').update("") - window.Element('usbTimeout').update("") - window.Element('usbSpeed').update("") window.Element('devName').update("-name-") window.Element('devNameConf').update("") window.Element('newBoot').update("-version-") @@ -145,23 +138,18 @@ def getConfigs(window, bl, devType, device): conf = bl.readConfig() if conf is not None: if devType == "POE": - window.Element('staticIp').update(conf.getIPv4() if conf.getIPv4() is not None else "0.0.0.0") - window.Element('staticMask').update(conf.getIPv4Mask() if conf.getIPv4Mask() is not None else "0.0.0.0") - window.Element('staticGateway').update(conf.getIPv4Gateway() if conf.getIPv4Gateway() is not None else "0.0.0.0") + window.Element('ip').update(conf.getIPv4() if conf.getIPv4() is not None else "0.0.0.0") + window.Element('mask').update(conf.getIPv4Mask() if conf.getIPv4Mask() is not None else "0.0.0.0") + window.Element('gateway').update(conf.getIPv4Gateway() if conf.getIPv4Gateway() is not None else "0.0.0.0") window.Element('dns').update(conf.getDnsIPv4() if conf.getDnsIPv4() is not None else "0.0.0.0") window.Element('dnsAlt').update(conf.getDnsAltIPv4() if conf.getDnsAltIPv4() is not None else "0.0.0.0") window.Element('networkTimeout').update(int(conf.getNetworkTimeout().total_seconds() * 1000)) window.Element('mac').update(conf.getMacAddress()) - window.Element('usbTimeout').update("") - window.Element('usbSpeed').update("") + for el in CONF_INPUT_USB: + window[el].update("") else: - window.Element('staticIp').update("") - window.Element('staticMask').update("") - window.Element('staticGateway').update("") - window.Element('dns').update("") - window.Element('dnsAlt').update("") - window.Element('networkTimeout').update("") - window.Element('mac').update("") + for el in CONF_INPUT_POE: + window[el].update("") window.Element('usbTimeout').update(int(conf.getUsbTimeout().total_seconds() * 1000)) window.Element('usbSpeed').update(str(conf.getUsbMaxSpeed()).split('.')[1]) @@ -211,11 +199,11 @@ def flashConfig(values, device, devType, staticIp): conf = dai.DeviceBootloader.Config() if devType == "POE": if staticIp: - if check_ip(values['staticIp']) and check_ip(values['staticMask']) and check_ip(values['staticGateway']): - conf.setStaticIPv4(values['staticIp'], values['staticMask'], values['staticGateway']) + if check_ip(values['ip']) and check_ip(values['mask']) and check_ip(values['gateway']): + conf.setStaticIPv4(values['ip'], values['mask'], values['gateway']) else: - if check_ip(values['staticIp']) and check_ip(values['staticMask']) and check_ip(values['staticGateway']): - conf.setDynamicIPv4(values['staticIp'], values['staticMask'], values['staticGateway']) + if check_ip(values['ip']) and check_ip(values['mask']) and check_ip(values['gateway']): + conf.setDynamicIPv4(values['ip'], values['mask'], values['gateway']) if values['dns'] != "" and values['dnsAlt'] != "": conf.setDnsIPv4(values['dns'], values['dnsAlt']) if values['networkTimeout'] != "": @@ -308,9 +296,6 @@ def connectToDevice(device): print(f'Exception: {ex}') sg.Popup(f'{ex}') - -usbSpeeds = ["UNKNOWN", "LOW", "FULL", "HIGH", "SUPER", "SUPER_PLUS"] - sg.theme('LightGrey2') # first device search @@ -390,43 +375,44 @@ def connectToDevice(device): ], [sg.HSeparator()], [ - sg.Text("IPv4 type:", size=(30, 1), font=('Arial', 10, 'bold'), text_color="black"), + sg.Text("IPv4 type:", size=(30, 1), font=('Arial', 10, 'bold'), text_color="gray", key="ipTypeText"), sg.Radio('Static', "ipType", default=True, font=('Arial', 10, 'bold'), text_color="black", key="staticBut", enable_events=True, disabled=True), sg.Radio('Dynamic', "ipType", default=False, font=('Arial', 10, 'bold'), text_color="black", key="dynamicBut", enable_events=True, disabled=True) ], [ - sg.Text("IPv4:", size=(12, 1), font=('Arial', 10, 'bold'), text_color="black"), - sg.InputText(key="staticIp", size=(16, 2), disabled=True), - sg.Text("Mask:", size=(5, 1), font=('Arial', 10, 'bold'), text_color="black"), - sg.InputText(key="staticMask", size=(16, 2), disabled=True), - sg.Text("Gateway:", size=(8, 1), font=('Arial', 10, 'bold'), text_color="black"), - sg.InputText(key="staticGateway", size=(16, 2), disabled=True) + sg.Text("IPv4:", size=(12, 1), font=('Arial', 10, 'bold'), text_color="gray", key="ipText"), + sg.InputText(key="ip", size=(16, 2), disabled=True), + sg.Text("Mask:", size=(5, 1), font=('Arial', 10, 'bold'), text_color="gray", key="maskText"), + sg.InputText(key="mask", size=(16, 2), disabled=True), + sg.Text("Gateway:", size=(8, 1), font=('Arial', 10, 'bold'), text_color="gray", key="gatewayText"), + sg.InputText(key="gateway", size=(16, 2), disabled=True) ], [ - sg.Text("DNS name:", size=(30, 1), font=('Arial', 10, 'bold'), text_color="black"), + sg.Text("DNS name:", size=(30, 1), font=('Arial', 10, 'bold'), text_color="gray", key="dnsText"), sg.InputText(key="dns", size=(30, 2), disabled=True) ], [ - sg.Text("Alt DNS name:", size=(30, 1), font=('Arial', 10, 'bold'), text_color="black"), + sg.Text("Alt DNS name:", size=(30, 1), font=('Arial', 10, 'bold'), text_color="gray", key="dnsAltText"), sg.InputText(key="dnsAlt", size=(30, 2), disabled=True) ], [ - sg.Text("USB timeout [ms]:", size=(30, 1), font=('Arial', 10, 'bold'), text_color="black"), - sg.InputText(key="usbTimeout", size=(30, 2), disabled=True) - ], - [ - sg.Text("Network timeout [ms]:", size=(30, 1), font=('Arial', 10, 'bold'), text_color="black"), + sg.Text("Network timeout [ms]:", size=(30, 1), font=('Arial', 10, 'bold'), text_color="gray", key="networkTimeoutText"), sg.InputText(key="networkTimeout", size=(30, 2), disabled=True) ], [ - sg.Text("MAC address:", size=(30, 1), font=('Arial', 10, 'bold'), text_color="black"), + sg.Text("MAC address:", size=(30, 1), font=('Arial', 10, 'bold'), text_color="gray", key="macText"), sg.InputText(key="mac", size=(30, 2), disabled=True) ], + [sg.HSeparator()], + [ + sg.Text("USB timeout [ms]:", size=(30, 1), font=('Arial', 10, 'bold'), text_color="gray", key="usbTimeoutText"), + sg.InputText(key="usbTimeout", size=(30, 2), disabled=True) + ], [ - sg.Text("USB max speed:", size=(30, 1), font=('Arial', 10, 'bold'), text_color="black"), - sg.Combo(usbSpeeds, "Select speed", key="usbSpeed", size=(30, 6), disabled=True) + sg.Text("USB max speed:", size=(30, 1), font=('Arial', 10, 'bold'), text_color="gray", key="usbSpeedText"), + sg.Combo(USB_SPEEDS, "Select speed", key="usbSpeed", size=(30, 6), disabled=True) ], [sg.HSeparator()], [ From 50d3bce28541688119a23ea52bb9dcbfbe5873eb Mon Sep 17 00:00:00 2001 From: Erol444 Date: Wed, 11 May 2022 13:03:35 +0200 Subject: [PATCH 26/26] Fixed button text overflowing --- utilities/device_manager.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/utilities/device_manager.py b/utilities/device_manager.py index 6a30396b6..73ad57a37 100644 --- a/utilities/device_manager.py +++ b/utilities/device_manager.py @@ -86,7 +86,7 @@ def unlockConfig(window, devType): window['Factory reset'].update(disabled=False) # window['Clear flash'].update(disabled=False) window['Flash DAP'].update(disabled=False) - window['Boot into USB Recovery mode'].update(disabled=False) + window['recoveryMode'].update(disabled=False) def lockConfig(window): @@ -103,7 +103,7 @@ def lockConfig(window): window['Factory reset'].update(disabled=True) window['Clear flash'].update(disabled=True) window['Flash DAP'].update(disabled=True) - window['Boot into USB Recovery mode'].update(disabled=True) + window['recoveryMode'].update(disabled=True) window.Element('devName').update("-name-") window.Element('devNameConf').update("") @@ -353,11 +353,11 @@ def connectToDevice(device): [sg.HSeparator()], [ sg.Text("", size=(7, 2)), - sg.Button("Flash newest Bootloader", size=(17, 2), font=('Arial', 10, 'bold'), disabled=True, + sg.Button("Flash newest Bootloader", size=(20, 2), font=('Arial', 10, 'bold'), disabled=True, button_color='#FFA500'), sg.Button("Factory reset", size=(17, 2), font=('Arial', 10, 'bold'), disabled=True, button_color='#FFA500'), - sg.Button("Boot into USB Recovery mode", size=(17, 2), font=('Arial', 10, 'bold'), disabled=True, - button_color='#FFA500') + sg.Button("Boot into USB\nRecovery mode", size=(20, 2), font=('Arial', 10, 'bold'), disabled=True, + key='recoveryMode', button_color='#FFA500') ] ] @@ -489,7 +489,7 @@ def connectToDevice(device): if event == "aboutReal": window['-COL2-'].update(visible=False) window['-COL1-'].update(visible=True) - if event == "Boot into USB Recovery mode": + if event == "recoveryMode": bl = None flashFromUsb(devices[values['devices']]) window.close()