24 |
24 |
25 class PyBoardDevice(MicroPythonDevice): |
25 class PyBoardDevice(MicroPythonDevice): |
26 """ |
26 """ |
27 Class implementing the device for PyBoard boards. |
27 Class implementing the device for PyBoard boards. |
28 """ |
28 """ |
|
29 |
29 DeviceVolumeName = "PYBFLASH" |
30 DeviceVolumeName = "PYBFLASH" |
30 |
31 |
31 FlashInstructionsURL = ( |
32 FlashInstructionsURL = ( |
32 "https://github.com/micropython/micropython/wiki/" |
33 "https://github.com/micropython/micropython/wiki/" "Pyboard-Firmware-Update" |
33 "Pyboard-Firmware-Update" |
|
34 ) |
34 ) |
35 |
35 |
36 def __init__(self, microPythonWidget, deviceType, parent=None): |
36 def __init__(self, microPythonWidget, deviceType, parent=None): |
37 """ |
37 """ |
38 Constructor |
38 Constructor |
39 |
39 |
40 @param microPythonWidget reference to the main MicroPython widget |
40 @param microPythonWidget reference to the main MicroPython widget |
41 @type MicroPythonWidget |
41 @type MicroPythonWidget |
42 @param deviceType device type assigned to this device interface |
42 @param deviceType device type assigned to this device interface |
43 @type str |
43 @type str |
44 @param parent reference to the parent object |
44 @param parent reference to the parent object |
45 @type QObject |
45 @type QObject |
46 """ |
46 """ |
47 super().__init__(microPythonWidget, deviceType, parent) |
47 super().__init__(microPythonWidget, deviceType, parent) |
48 |
48 |
49 self.__workspace = self.__findWorkspace() |
49 self.__workspace = self.__findWorkspace() |
50 |
50 |
51 def setButtons(self): |
51 def setButtons(self): |
52 """ |
52 """ |
53 Public method to enable the supported action buttons. |
53 Public method to enable the supported action buttons. |
54 """ |
54 """ |
55 super().setButtons() |
55 super().setButtons() |
56 self.microPython.setActionButtons( |
56 self.microPython.setActionButtons( |
57 run=True, repl=True, files=True, chart=HAS_QTCHART) |
57 run=True, repl=True, files=True, chart=HAS_QTCHART |
58 |
58 ) |
|
59 |
59 if self.__deviceVolumeMounted(): |
60 if self.__deviceVolumeMounted(): |
60 self.microPython.setActionButtons(open=True, save=True) |
61 self.microPython.setActionButtons(open=True, save=True) |
61 |
62 |
62 def forceInterrupt(self): |
63 def forceInterrupt(self): |
63 """ |
64 """ |
64 Public method to determine the need for an interrupt when opening the |
65 Public method to determine the need for an interrupt when opening the |
65 serial connection. |
66 serial connection. |
66 |
67 |
67 @return flag indicating an interrupt is needed |
68 @return flag indicating an interrupt is needed |
68 @rtype bool |
69 @rtype bool |
69 """ |
70 """ |
70 return False |
71 return False |
71 |
72 |
72 def deviceName(self): |
73 def deviceName(self): |
73 """ |
74 """ |
74 Public method to get the name of the device. |
75 Public method to get the name of the device. |
75 |
76 |
76 @return name of the device |
77 @return name of the device |
77 @rtype str |
78 @rtype str |
78 """ |
79 """ |
79 return self.tr("PyBoard") |
80 return self.tr("PyBoard") |
80 |
81 |
81 def canStartRepl(self): |
82 def canStartRepl(self): |
82 """ |
83 """ |
83 Public method to determine, if a REPL can be started. |
84 Public method to determine, if a REPL can be started. |
84 |
85 |
85 @return tuple containing a flag indicating it is safe to start a REPL |
86 @return tuple containing a flag indicating it is safe to start a REPL |
86 and a reason why it cannot. |
87 and a reason why it cannot. |
87 @rtype tuple of (bool, str) |
88 @rtype tuple of (bool, str) |
88 """ |
89 """ |
89 return True, "" |
90 return True, "" |
90 |
91 |
91 def canStartPlotter(self): |
92 def canStartPlotter(self): |
92 """ |
93 """ |
93 Public method to determine, if a Plotter can be started. |
94 Public method to determine, if a Plotter can be started. |
94 |
95 |
95 @return tuple containing a flag indicating it is safe to start a |
96 @return tuple containing a flag indicating it is safe to start a |
96 Plotter and a reason why it cannot. |
97 Plotter and a reason why it cannot. |
97 @rtype tuple of (bool, str) |
98 @rtype tuple of (bool, str) |
98 """ |
99 """ |
99 return True, "" |
100 return True, "" |
100 |
101 |
101 def canRunScript(self): |
102 def canRunScript(self): |
102 """ |
103 """ |
103 Public method to determine, if a script can be executed. |
104 Public method to determine, if a script can be executed. |
104 |
105 |
105 @return tuple containing a flag indicating it is safe to start a |
106 @return tuple containing a flag indicating it is safe to start a |
106 Plotter and a reason why it cannot. |
107 Plotter and a reason why it cannot. |
107 @rtype tuple of (bool, str) |
108 @rtype tuple of (bool, str) |
108 """ |
109 """ |
109 return True, "" |
110 return True, "" |
110 |
111 |
111 def runScript(self, script): |
112 def runScript(self, script): |
112 """ |
113 """ |
113 Public method to run the given Python script. |
114 Public method to run the given Python script. |
114 |
115 |
115 @param script script to be executed |
116 @param script script to be executed |
116 @type str |
117 @type str |
117 """ |
118 """ |
118 pythonScript = script.split("\n") |
119 pythonScript = script.split("\n") |
119 self.sendCommands(pythonScript) |
120 self.sendCommands(pythonScript) |
120 |
121 |
121 def canStartFileManager(self): |
122 def canStartFileManager(self): |
122 """ |
123 """ |
123 Public method to determine, if a File Manager can be started. |
124 Public method to determine, if a File Manager can be started. |
124 |
125 |
125 @return tuple containing a flag indicating it is safe to start a |
126 @return tuple containing a flag indicating it is safe to start a |
126 File Manager and a reason why it cannot. |
127 File Manager and a reason why it cannot. |
127 @rtype tuple of (bool, str) |
128 @rtype tuple of (bool, str) |
128 """ |
129 """ |
129 return True, "" |
130 return True, "" |
130 |
131 |
131 def supportsLocalFileAccess(self): |
132 def supportsLocalFileAccess(self): |
132 """ |
133 """ |
133 Public method to indicate file access via a local directory. |
134 Public method to indicate file access via a local directory. |
134 |
135 |
135 @return flag indicating file access via local directory |
136 @return flag indicating file access via local directory |
136 @rtype bool |
137 @rtype bool |
137 """ |
138 """ |
138 return self.__deviceVolumeMounted() |
139 return self.__deviceVolumeMounted() |
139 |
140 |
140 def __deviceVolumeMounted(self): |
141 def __deviceVolumeMounted(self): |
141 """ |
142 """ |
142 Private method to check, if the device volume is mounted. |
143 Private method to check, if the device volume is mounted. |
143 |
144 |
144 @return flag indicated a mounted device |
145 @return flag indicated a mounted device |
145 @rtype bool |
146 @rtype bool |
146 """ |
147 """ |
147 if self.__workspace and not os.path.exists(self.__workspace): |
148 if self.__workspace and not os.path.exists(self.__workspace): |
148 self.__workspace = "" # reset |
149 self.__workspace = "" # reset |
149 |
150 |
150 return self.DeviceVolumeName in self.getWorkspace(silent=True) |
151 return self.DeviceVolumeName in self.getWorkspace(silent=True) |
151 |
152 |
152 def getWorkspace(self, silent=False): |
153 def getWorkspace(self, silent=False): |
153 """ |
154 """ |
154 Public method to get the workspace directory. |
155 Public method to get the workspace directory. |
155 |
156 |
156 @param silent flag indicating silent operations |
157 @param silent flag indicating silent operations |
157 @type bool |
158 @type bool |
158 @return workspace directory used for saving files |
159 @return workspace directory used for saving files |
159 @rtype str |
160 @rtype str |
160 """ |
161 """ |
162 # return cached entry |
163 # return cached entry |
163 return self.__workspace |
164 return self.__workspace |
164 else: |
165 else: |
165 self.__workspace = self.__findWorkspace(silent=silent) |
166 self.__workspace = self.__findWorkspace(silent=silent) |
166 return self.__workspace |
167 return self.__workspace |
167 |
168 |
168 def __findWorkspace(self, silent=False): |
169 def __findWorkspace(self, silent=False): |
169 """ |
170 """ |
170 Private method to find the workspace directory. |
171 Private method to find the workspace directory. |
171 |
172 |
172 @param silent flag indicating silent operations |
173 @param silent flag indicating silent operations |
173 @type bool |
174 @type bool |
174 @return workspace directory used for saving files |
175 @return workspace directory used for saving files |
175 @rtype str |
176 @rtype str |
176 """ |
177 """ |
177 # Attempts to find the path on the filesystem that represents the |
178 # Attempts to find the path on the filesystem that represents the |
178 # plugged in PyBoard board. |
179 # plugged in PyBoard board. |
179 deviceDirectories = Utilities.findVolume(self.DeviceVolumeName, |
180 deviceDirectories = Utilities.findVolume(self.DeviceVolumeName, findAll=True) |
180 findAll=True) |
181 |
181 |
|
182 if deviceDirectories: |
182 if deviceDirectories: |
183 if len(deviceDirectories) == 1: |
183 if len(deviceDirectories) == 1: |
184 return deviceDirectories[0] |
184 return deviceDirectories[0] |
185 else: |
185 else: |
186 return self.selectDeviceDirectory(deviceDirectories) |
186 return self.selectDeviceDirectory(deviceDirectories) |
189 # silent mode is selected) |
189 # silent mode is selected) |
190 if not silent: |
190 if not silent: |
191 EricMessageBox.warning( |
191 EricMessageBox.warning( |
192 self.microPython, |
192 self.microPython, |
193 self.tr("Workspace Directory"), |
193 self.tr("Workspace Directory"), |
194 self.tr("Python files for PyBoard can be edited in" |
194 self.tr( |
195 " place, if the device volume is locally" |
195 "Python files for PyBoard can be edited in" |
196 " available. Such a volume was not found. In" |
196 " place, if the device volume is locally" |
197 " place editing will not be available." |
197 " available. Such a volume was not found. In" |
198 ) |
198 " place editing will not be available." |
|
199 ), |
199 ) |
200 ) |
200 |
201 |
201 return super().getWorkspace() |
202 return super().getWorkspace() |
202 |
203 |
203 def getDocumentationUrl(self): |
204 def getDocumentationUrl(self): |
204 """ |
205 """ |
205 Public method to get the device documentation URL. |
206 Public method to get the device documentation URL. |
206 |
207 |
207 @return documentation URL of the device |
208 @return documentation URL of the device |
208 @rtype str |
209 @rtype str |
209 """ |
210 """ |
210 return Preferences.getMicroPython("MicroPythonDocuUrl") |
211 return Preferences.getMicroPython("MicroPythonDocuUrl") |
211 |
212 |
212 def getFirmwareUrl(self): |
213 def getFirmwareUrl(self): |
213 """ |
214 """ |
214 Public method to get the device firmware download URL. |
215 Public method to get the device firmware download URL. |
215 |
216 |
216 @return firmware download URL of the device |
217 @return firmware download URL of the device |
217 @rtype str |
218 @rtype str |
218 """ |
219 """ |
219 return Preferences.getMicroPython("MicroPythonFirmwareUrl") |
220 return Preferences.getMicroPython("MicroPythonFirmwareUrl") |
220 |
221 |
221 def addDeviceMenuEntries(self, menu): |
222 def addDeviceMenuEntries(self, menu): |
222 """ |
223 """ |
223 Public method to add device specific entries to the given menu. |
224 Public method to add device specific entries to the given menu. |
224 |
225 |
225 @param menu reference to the context menu |
226 @param menu reference to the context menu |
226 @type QMenu |
227 @type QMenu |
227 """ |
228 """ |
228 connected = self.microPython.isConnected() |
229 connected = self.microPython.isConnected() |
229 |
230 |
230 act = menu.addAction(self.tr("Activate Bootloader"), |
231 act = menu.addAction(self.tr("Activate Bootloader"), self.__activateBootloader) |
231 self.__activateBootloader) |
|
232 act.setEnabled(connected) |
232 act.setEnabled(connected) |
233 act = menu.addAction(self.tr("List DFU-capable Devices"), |
233 act = menu.addAction( |
234 self.__listDfuCapableDevices) |
234 self.tr("List DFU-capable Devices"), self.__listDfuCapableDevices |
|
235 ) |
235 act.setEnabled(not connected) |
236 act.setEnabled(not connected) |
236 act = menu.addAction(self.tr("Flash MicroPython Firmware"), |
237 act = menu.addAction( |
237 self.__flashMicroPython) |
238 self.tr("Flash MicroPython Firmware"), self.__flashMicroPython |
|
239 ) |
238 act.setEnabled(not connected) |
240 act.setEnabled(not connected) |
239 menu.addSeparator() |
241 menu.addSeparator() |
240 menu.addAction(self.tr("MicroPython Flash Instructions"), |
242 menu.addAction( |
241 self.__showFlashInstructions) |
243 self.tr("MicroPython Flash Instructions"), self.__showFlashInstructions |
242 |
244 ) |
|
245 |
243 def hasFlashMenuEntry(self): |
246 def hasFlashMenuEntry(self): |
244 """ |
247 """ |
245 Public method to check, if the device has its own flash menu entry. |
248 Public method to check, if the device has its own flash menu entry. |
246 |
249 |
247 @return flag indicating a specific flash menu entry |
250 @return flag indicating a specific flash menu entry |
248 @rtype bool |
251 @rtype bool |
249 """ |
252 """ |
250 return True |
253 return True |
251 |
254 |
252 @pyqtSlot() |
255 @pyqtSlot() |
253 def __showFlashInstructions(self): |
256 def __showFlashInstructions(self): |
254 """ |
257 """ |
255 Private slot to open the URL containing instructions for installing |
258 Private slot to open the URL containing instructions for installing |
256 MicroPython on the pyboard. |
259 MicroPython on the pyboard. |
257 """ |
260 """ |
258 ericApp().getObject("UserInterface").launchHelpViewer( |
261 ericApp().getObject("UserInterface").launchHelpViewer( |
259 PyBoardDevice.FlashInstructionsURL) |
262 PyBoardDevice.FlashInstructionsURL |
260 |
263 ) |
|
264 |
261 def __dfuUtilAvailable(self): |
265 def __dfuUtilAvailable(self): |
262 """ |
266 """ |
263 Private method to check the availability of dfu-util. |
267 Private method to check the availability of dfu-util. |
264 |
268 |
265 @return flag indicating the availability of dfu-util |
269 @return flag indicating the availability of dfu-util |
266 @rtype bool |
270 @rtype bool |
267 """ |
271 """ |
268 available = False |
272 available = False |
269 program = Preferences.getMicroPython("DfuUtilPath") |
273 program = Preferences.getMicroPython("DfuUtilPath") |
374 ok2continue = self.__showDfuEnableInstructions() |
374 ok2continue = self.__showDfuEnableInstructions() |
375 if ok2continue: |
375 if ok2continue: |
376 program = Preferences.getMicroPython("DfuUtilPath") |
376 program = Preferences.getMicroPython("DfuUtilPath") |
377 if not program: |
377 if not program: |
378 program = "dfu-util" |
378 program = "dfu-util" |
379 |
379 |
380 downloadsPath = QStandardPaths.standardLocations( |
380 downloadsPath = QStandardPaths.standardLocations( |
381 QStandardPaths.StandardLocation.DownloadLocation)[0] |
381 QStandardPaths.StandardLocation.DownloadLocation |
|
382 )[0] |
382 firmware = EricFileDialog.getOpenFileName( |
383 firmware = EricFileDialog.getOpenFileName( |
383 self.microPython, |
384 self.microPython, |
384 self.tr("Flash MicroPython Firmware"), |
385 self.tr("Flash MicroPython Firmware"), |
385 downloadsPath, |
386 downloadsPath, |
386 self.tr( |
387 self.tr("MicroPython Firmware Files (*.dfu);;All Files (*)"), |
387 "MicroPython Firmware Files (*.dfu);;All Files (*)") |
|
388 ) |
388 ) |
389 if firmware and os.path.exists(firmware): |
389 if firmware and os.path.exists(firmware): |
390 args = [ |
390 args = [ |
391 "--alt", "0", |
391 "--alt", |
392 "--download", firmware, |
392 "0", |
|
393 "--download", |
|
394 firmware, |
393 ] |
395 ] |
394 dlg = EricProcessDialog( |
396 dlg = EricProcessDialog( |
395 self.tr("'dfu-util' Output"), |
397 self.tr("'dfu-util' Output"), |
396 self.tr("Flash MicroPython Firmware") |
398 self.tr("Flash MicroPython Firmware"), |
397 ) |
399 ) |
398 res = dlg.startProcess(program, args) |
400 res = dlg.startProcess(program, args) |
399 if res: |
401 if res: |
400 dlg.exec() |
402 dlg.exec() |
401 self.__showDfuDisableInstructions() |
403 self.__showDfuDisableInstructions() |
402 |
404 |
403 @pyqtSlot() |
405 @pyqtSlot() |
404 def __activateBootloader(self): |
406 def __activateBootloader(self): |
405 """ |
407 """ |
406 Private slot to activate the bootloader and disconnect. |
408 Private slot to activate the bootloader and disconnect. |
407 """ |
409 """ |
408 if self.microPython.isConnected(): |
410 if self.microPython.isConnected(): |
409 self.microPython.commandsInterface().execute([ |
411 self.microPython.commandsInterface().execute( |
410 "import pyb", |
412 [ |
411 "pyb.bootloader()", |
413 "import pyb", |
412 ]) |
414 "pyb.bootloader()", |
|
415 ] |
|
416 ) |
413 # simulate pressing the disconnect button |
417 # simulate pressing the disconnect button |
414 self.microPython.on_connectButton_clicked() |
418 self.microPython.on_connectButton_clicked() |