3535
3636from typing import Iterator , Tuple
3737
38- from PyQt5 .QtCore import QSettings , QProcess , QTimer , Qt , QIODevice , pyqtSlot
38+ from PyQt5 .QtCore import QSettings , QProcess , QTimer , Qt , QIODevice , pyqtSlot , \
39+ QObject , QThread , pyqtSignal
3940from PyQt5 .QtWidgets import QWidget , QLabel , QComboBox , QGridLayout , \
4041 QPushButton , QApplication , QLineEdit , QFileDialog , QPlainTextEdit , \
4142 QAction , QActionGroup , QMenu , QMenuBar , QMainWindow , QMessageBox
4243from PyQt5 .QtGui import QCloseEvent , QTextCursor , QIcon , QFont
43- from PyQt5 .QtSerialPort import QSerialPortInfo , QSerialPortInfo
44+ from PyQt5 .QtSerialPort import QSerialPortInfo
4445
4546import sys
4647import os
48+ from time import sleep
4749
4850import esptool
4951from esptool import ESPLoader
5557# Setting constants
5658SETTING_PORT_NAME = 'port_name'
5759SETTING_FILE_LOCATION = 'file_location'
60+ SETTING_PARTITION_LOCATION = 'partition_location'
5861SETTING_BAUD_RATE = 'baud'
5962
60- guiVersion = 'v1.2 '
63+ guiVersion = 'v1.3 '
6164
6265def gen_serial_ports () -> Iterator [Tuple [str , str , str ]]:
6366 """Return all available serial ports."""
@@ -95,6 +98,19 @@ def flush(self) -> None:
9598 def isatty (self ):
9699 return True
97100
101+ class ESPTool (QObject ):
102+ finished = pyqtSignal ()
103+
104+ def run (self , command ):
105+ """Run esptool in a separate thread to stop the GUI from freezing"""
106+ try :
107+ esptool .main (command )
108+ except (ValueError , IOError , FatalError , ImportError , NotImplementedInROMError , UnsupportedCommandError , NotSupportedError , RuntimeError ) as err :
109+ print (str (err ))
110+ except :
111+ pass
112+ self .finished .emit ()
113+
98114# noinspection PyArgumentList
99115
100116class MainWidget (QWidget ):
@@ -103,13 +119,10 @@ class MainWidget(QWidget):
103119 def __init__ (self , parent : QWidget = None ) -> None :
104120 super ().__init__ (parent )
105121
106- self .timer = QTimer ()
107- self .timer .timeout .connect (self .repaintMessageBox )
108-
109122 # File location line edit
110- self .msg_label = QLabel (self .tr ('Firmware File:' ))
123+ self .file_label = QLabel (self .tr ('Firmware File:' ))
111124 self .fileLocation_lineedit = QLineEdit ()
112- self .msg_label .setBuddy (self .fileLocation_lineedit )
125+ self .file_label .setBuddy (self .fileLocation_lineedit )
113126 self .fileLocation_lineedit .setEnabled (False )
114127 self .fileLocation_lineedit .returnPressed .connect (self .on_browse_btn_pressed )
115128
@@ -118,6 +131,18 @@ def __init__(self, parent: QWidget = None) -> None:
118131 self .browse_btn .setEnabled (True )
119132 self .browse_btn .pressed .connect (self .on_browse_btn_pressed )
120133
134+ # Partition file location line edit
135+ self .partition_label = QLabel (self .tr ('Partition File:' ))
136+ self .partitionFileLocation_lineedit = QLineEdit ()
137+ self .partition_label .setBuddy (self .partitionFileLocation_lineedit )
138+ self .partitionFileLocation_lineedit .setEnabled (False )
139+ self .partitionFileLocation_lineedit .returnPressed .connect (self .on_partition_browse_btn_pressed )
140+
141+ # Browse for new file button
142+ self .partition_browse_btn = QPushButton (self .tr ('Browse' ))
143+ self .partition_browse_btn .setEnabled (True )
144+ self .partition_browse_btn .pressed .connect (self .on_partition_browse_btn_pressed )
145+
121146 # Port Combobox
122147 self .port_label = QLabel (self .tr ('COM Port:' ))
123148 self .port_combobox = QComboBox ()
@@ -152,20 +177,24 @@ def __init__(self, parent: QWidget = None) -> None:
152177 # Arrange Layout
153178 layout = QGridLayout ()
154179
155- layout .addWidget (self .msg_label , 1 , 0 )
180+ layout .addWidget (self .file_label , 1 , 0 )
156181 layout .addWidget (self .fileLocation_lineedit , 1 , 1 )
157182 layout .addWidget (self .browse_btn , 1 , 2 )
158183
159- layout .addWidget (self .port_label , 2 , 0 )
160- layout .addWidget (self .port_combobox , 2 , 1 )
161- layout .addWidget (self .refresh_btn , 2 , 2 )
184+ layout .addWidget (self .partition_label , 2 , 0 )
185+ layout .addWidget (self .partitionFileLocation_lineedit , 2 , 1 )
186+ layout .addWidget (self .partition_browse_btn , 2 , 2 )
187+
188+ layout .addWidget (self .port_label , 3 , 0 )
189+ layout .addWidget (self .port_combobox , 3 , 1 )
190+ layout .addWidget (self .refresh_btn , 3 , 2 )
162191
163- layout .addWidget (self .baud_label , 3 , 0 )
164- layout .addWidget (self .baud_combobox , 3 , 1 )
165- layout .addWidget (self .upload_btn , 3 , 2 )
192+ layout .addWidget (self .baud_label , 4 , 0 )
193+ layout .addWidget (self .baud_combobox , 4 , 1 )
194+ layout .addWidget (self .upload_btn , 4 , 2 )
166195
167- layout .addWidget (self .messages_label , 4 , 0 )
168- layout .addWidget (self .messageBox , 5 , 0 , 5 , 3 )
196+ layout .addWidget (self .messages_label , 5 , 0 )
197+ layout .addWidget (self .messageBox , 6 , 0 , 5 , 3 )
169198
170199 self .setLayout (layout )
171200
@@ -180,16 +209,6 @@ def writeMessage(self, msg) -> None:
180209 self .messageBox .ensureCursorVisible ()
181210 self .messageBox .repaint ()
182211
183- def startTimer (self ) -> None :
184- self .timer .start (1000 )
185-
186- def endTimer (self ) -> None :
187- self .timer .stop ()
188-
189- def repaintMessageBox (self ) -> None :
190- self .messageBox .ensureCursorVisible ()
191- self .messageBox .repaint ()
192-
193212 def _load_settings (self ) -> None :
194213 """Load settings on startup."""
195214 port_name = self .settings .value (SETTING_PORT_NAME )
@@ -202,6 +221,12 @@ def _load_settings(self) -> None:
202221 if lastFile is not None :
203222 self .fileLocation_lineedit .setText (lastFile )
204223
224+ lastFile = self .settings .value (SETTING_PARTITION_LOCATION )
225+ if lastFile is not None :
226+ self .partitionFileLocation_lineedit .setText (lastFile )
227+ else :
228+ self .partitionFileLocation_lineedit .setText (resource_path ("RTK_Surveyor.ino.partitions.bin" ))
229+
205230 baud = self .settings .value (SETTING_BAUD_RATE )
206231 if baud is not None :
207232 index = self .baud_combobox .findData (baud )
@@ -212,6 +237,7 @@ def _save_settings(self) -> None:
212237 """Save settings on shutdown."""
213238 self .settings .setValue (SETTING_PORT_NAME , self .port )
214239 self .settings .setValue (SETTING_FILE_LOCATION , self .theFileName )
240+ self .settings .setValue (SETTING_PARTITION_LOCATION , self .thePartitionFileName )
215241 self .settings .setValue (SETTING_BAUD_RATE , self .baudRate )
216242
217243 def _clean_settings (self ) -> None :
@@ -264,15 +290,18 @@ def theFileName(self) -> str:
264290 """Return the file name."""
265291 return self .fileLocation_lineedit .text ()
266292
293+ @property
294+ def thePartitionFileName (self ) -> str :
295+ """Return the partition file name."""
296+ return self .partitionFileLocation_lineedit .text ()
297+
267298 def closeEvent (self , event : QCloseEvent ) -> None :
268299 """Handle Close event of the Widget."""
269300 try :
270301 self ._save_settings ()
271302 except :
272303 pass
273304
274- self .endTimer ()
275-
276305 event .accept ()
277306
278307 def on_refresh_btn_pressed (self ) -> None :
@@ -291,6 +320,18 @@ def on_browse_btn_pressed(self) -> None:
291320 if fileName :
292321 self .fileLocation_lineedit .setText (fileName )
293322
323+ def on_partition_browse_btn_pressed (self ) -> None :
324+ """Open dialog to select partition bin file."""
325+ options = QFileDialog .Options ()
326+ fileName , _ = QFileDialog .getOpenFileName (
327+ None ,
328+ "Select Partition File" ,
329+ "" ,
330+ "Parition Files (*.bin);;All Files (*)" ,
331+ options = options )
332+ if fileName :
333+ self .partitionFileLocation_lineedit .setText (fileName )
334+
294335 def on_upload_btn_pressed (self ) -> None :
295336 """Upload the firmware"""
296337 portAvailable = False
@@ -327,22 +368,32 @@ def on_upload_btn_pressed(self) -> None:
327368 command .extend (["--baud" ,self .baudRate ])
328369 command .extend (["--before" ,"default_reset" ,"--after" ,"hard_reset" ,"write_flash" ,"-z" ,"--flash_mode" ,"dio" ,"--flash_freq" ,"80m" ,"--flash_size" ,"detect" ])
329370 command .extend (["0x1000" ,resource_path ("RTK_Surveyor.ino.bootloader.bin" )])
330- command .extend (["0x8000" ,resource_path ( "RTK_Surveyor.ino.partitions.bin" ) ])
371+ command .extend (["0x8000" ,self . thePartitionFileName ])
331372 command .extend (["0xe000" ,resource_path ("boot_app0.bin" )])
332373 command .extend (["0x10000" ,self .theFileName ])
333374
334375 self .writeMessage ("Command: esptool.main(%s)\n \n " % " " .join (command ))
335376
336377 #print("python esptool.py %s\n\n" % " ".join(command)) # Useful for debugging - cut and paste into a command prompt
337378
338- self .startTimer ()
379+ self .thread = QThread ()
380+ self .espTool = ESPTool ()
381+ self .espTool .moveToThread (self .thread )
382+ self .thread .started .connect (self .espTool .run (command ))
383+ self .thread .finished .connect (lambda : self .upload_btn .setEnabled (True ))
384+ self .espTool .finished .connect (self .thread .quit )
385+ self .espTool .finished .connect (self .espTool .deleteLater )
386+ self .thread .finished .connect (self .thread .deleteLater )
387+ self .upload_btn .setEnabled (False )
388+ #self.thread.start()
339389
340390 try :
341391 esptool .main (command )
342392 except (ValueError , IOError , FatalError , ImportError , NotImplementedInROMError , UnsupportedCommandError , NotSupportedError , RuntimeError ) as err :
343393 self .writeMessage (str (err ))
394+ except :
395+ pass
344396
345- self .endTimer ()
346397
347398if __name__ == '__main__' :
348399 from sys import exit as sysExit
0 commit comments