|
1 # -*- coding: utf-8 -*- |
|
2 |
|
3 # Copyright (c) 2021 - 2023 Detlev Offenbach <detlev@die-offenbachs.de> |
|
4 # |
|
5 |
|
6 """ |
|
7 Module implementing the device interface class for RP2040 based boards |
|
8 (e.g. Raspberry Pi Pico). |
|
9 """ |
|
10 |
|
11 from PyQt6.QtCore import QUrl, pyqtSlot |
|
12 from PyQt6.QtNetwork import QNetworkRequest |
|
13 from PyQt6.QtWidgets import QMenu |
|
14 |
|
15 from eric7 import Globals, Preferences |
|
16 from eric7.EricWidgets import EricMessageBox |
|
17 from eric7.EricWidgets.EricApplication import ericApp |
|
18 |
|
19 from . import FirmwareGithubUrls |
|
20 from .DeviceBase import BaseDevice |
|
21 from ..MicroPythonWidget import HAS_QTCHART |
|
22 |
|
23 |
|
24 class RP2040Device(BaseDevice): |
|
25 """ |
|
26 Class implementing the device for RP2040 based boards. |
|
27 """ |
|
28 |
|
29 def __init__(self, microPythonWidget, deviceType, parent=None): |
|
30 """ |
|
31 Constructor |
|
32 |
|
33 @param microPythonWidget reference to the main MicroPython widget |
|
34 @type MicroPythonWidget |
|
35 @param deviceType device type assigned to this device interface |
|
36 @type str |
|
37 @param parent reference to the parent object |
|
38 @type QObject |
|
39 """ |
|
40 super().__init__(microPythonWidget, deviceType, parent) |
|
41 |
|
42 self.__createRP2040Menu() |
|
43 |
|
44 def setButtons(self): |
|
45 """ |
|
46 Public method to enable the supported action buttons. |
|
47 """ |
|
48 super().setButtons() |
|
49 self.microPython.setActionButtons( |
|
50 run=True, repl=True, files=True, chart=HAS_QTCHART |
|
51 ) |
|
52 |
|
53 def forceInterrupt(self): |
|
54 """ |
|
55 Public method to determine the need for an interrupt when opening the |
|
56 serial connection. |
|
57 |
|
58 @return flag indicating an interrupt is needed |
|
59 @rtype bool |
|
60 """ |
|
61 return False |
|
62 |
|
63 def deviceName(self): |
|
64 """ |
|
65 Public method to get the name of the device. |
|
66 |
|
67 @return name of the device |
|
68 @rtype str |
|
69 """ |
|
70 return self.tr("RP2040") |
|
71 |
|
72 def canStartRepl(self): |
|
73 """ |
|
74 Public method to determine, if a REPL can be started. |
|
75 |
|
76 @return tuple containing a flag indicating it is safe to start a REPL |
|
77 and a reason why it cannot. |
|
78 @rtype tuple of (bool, str) |
|
79 """ |
|
80 return True, "" |
|
81 |
|
82 def canStartPlotter(self): |
|
83 """ |
|
84 Public method to determine, if a Plotter can be started. |
|
85 |
|
86 @return tuple containing a flag indicating it is safe to start a |
|
87 Plotter and a reason why it cannot. |
|
88 @rtype tuple of (bool, str) |
|
89 """ |
|
90 return True, "" |
|
91 |
|
92 def canRunScript(self): |
|
93 """ |
|
94 Public method to determine, if a script can be executed. |
|
95 |
|
96 @return tuple containing a flag indicating it is safe to start a |
|
97 Plotter and a reason why it cannot. |
|
98 @rtype tuple of (bool, str) |
|
99 """ |
|
100 return True, "" |
|
101 |
|
102 def runScript(self, script): |
|
103 """ |
|
104 Public method to run the given Python script. |
|
105 |
|
106 @param script script to be executed |
|
107 @type str |
|
108 """ |
|
109 pythonScript = script.split("\n") |
|
110 self.sendCommands(pythonScript) |
|
111 |
|
112 def canStartFileManager(self): |
|
113 """ |
|
114 Public method to determine, if a File Manager can be started. |
|
115 |
|
116 @return tuple containing a flag indicating it is safe to start a |
|
117 File Manager and a reason why it cannot. |
|
118 @rtype tuple of (bool, str) |
|
119 """ |
|
120 return True, "" |
|
121 |
|
122 def __createRP2040Menu(self): |
|
123 """ |
|
124 Private method to create the RO2040 submenu. |
|
125 """ |
|
126 self.__rp2040Menu = QMenu(self.tr("RP2040 Functions")) |
|
127 |
|
128 self.__showMpyAct = self.__rp2040Menu.addAction( |
|
129 self.tr("Show MicroPython Versions"), self.__showFirmwareVersions |
|
130 ) |
|
131 self.__rp2040Menu.addSeparator() |
|
132 self.__bootloaderAct = self.__rp2040Menu.addAction( |
|
133 self.tr("Activate Bootloader"), self.__activateBootloader |
|
134 ) |
|
135 self.__flashMpyAct = self.__rp2040Menu.addAction( |
|
136 self.tr("Flash MicroPython Firmware"), self.__flashPython |
|
137 ) |
|
138 |
|
139 def addDeviceMenuEntries(self, menu): |
|
140 """ |
|
141 Public method to add device specific entries to the given menu. |
|
142 |
|
143 @param menu reference to the context menu |
|
144 @type QMenu |
|
145 """ |
|
146 connected = self.microPython.isConnected() |
|
147 linkConnected = self.microPython.isLinkConnected() |
|
148 |
|
149 self.__showMpyAct.setEnabled(connected) |
|
150 self.__bootloaderAct.setEnabled(connected) |
|
151 self.__flashMpyAct.setEnabled(not linkConnected) |
|
152 |
|
153 menu.addMenu(self.__rp2040Menu) |
|
154 |
|
155 def hasFlashMenuEntry(self): |
|
156 """ |
|
157 Public method to check, if the device has its own flash menu entry. |
|
158 |
|
159 @return flag indicating a specific flash menu entry |
|
160 @rtype bool |
|
161 """ |
|
162 return True |
|
163 |
|
164 @pyqtSlot() |
|
165 def __flashPython(self): |
|
166 """ |
|
167 Private slot to flash a MicroPython firmware to the device. |
|
168 """ |
|
169 from ..UF2FlashDialog import UF2FlashDialog |
|
170 |
|
171 dlg = UF2FlashDialog(boardType="rp2040") |
|
172 dlg.exec() |
|
173 |
|
174 def __activateBootloader(self): |
|
175 """ |
|
176 Private method to switch the board into 'bootloader' mode. |
|
177 """ |
|
178 if self.microPython.isConnected(): |
|
179 self.microPython.commandsInterface().execute( |
|
180 [ |
|
181 "import machine", |
|
182 "machine.bootloader()", |
|
183 ] |
|
184 ) |
|
185 # simulate pressing the disconnect button |
|
186 self.microPython.on_connectButton_clicked() |
|
187 |
|
188 @pyqtSlot() |
|
189 def __showFirmwareVersions(self): |
|
190 """ |
|
191 Private slot to show the firmware version of the connected device and the |
|
192 available firmware version. |
|
193 """ |
|
194 if self.microPython.isConnected(): |
|
195 if self._deviceData["mpy_name"] != "micropython": |
|
196 EricMessageBox.critical( |
|
197 None, |
|
198 self.tr("Show MicroPython Versions"), |
|
199 self.tr( |
|
200 """The firmware of the connected device cannot be""" |
|
201 """ determined or the board does not run MicroPython.""" |
|
202 """ Aborting...""" |
|
203 ), |
|
204 ) |
|
205 else: |
|
206 if self._deviceData["mpy_variant"] == "Pimoroni": |
|
207 # MicroPython with Pimoroni add-on libraries |
|
208 url = QUrl(FirmwareGithubUrls["pimoroni_pico"]) |
|
209 else: |
|
210 url = QUrl(FirmwareGithubUrls["micropython"]) |
|
211 ui = ericApp().getObject("UserInterface") |
|
212 request = QNetworkRequest(url) |
|
213 reply = ui.networkAccessManager().head(request) |
|
214 reply.finished.connect(lambda: self.__firmwareVersionResponse(reply)) |
|
215 |
|
216 def __firmwareVersionResponse(self, reply): |
|
217 """ |
|
218 Private method handling the response of the latest version request. |
|
219 |
|
220 @param reply reference to the reply object |
|
221 @type QNetworkReply |
|
222 """ |
|
223 latestUrl = reply.url().toString() |
|
224 tag = latestUrl.rsplit("/", 1)[-1] |
|
225 while tag and not tag[0].isdecimal(): |
|
226 # get rid of leading non-decimal characters |
|
227 tag = tag[1:] |
|
228 latestVersion = Globals.versionToTuple(tag) |
|
229 |
|
230 if self._deviceData["mpy_version"] == "unknown": |
|
231 currentVersionStr = self.tr("unknown") |
|
232 currentVersion = (0, 0, 0) |
|
233 else: |
|
234 currentVersionStr = self._deviceData["mpy_version"] |
|
235 currentVersion = Globals.versionToTuple(currentVersionStr) |
|
236 |
|
237 msg = self.tr( |
|
238 "<h4>MicroPython Version Information</h4>" |
|
239 "<table>" |
|
240 "<tr><td>Installed:</td><td>{0}</td><td></td></tr>" |
|
241 "<tr><td>Available:</td><td>{1}</td><td>{2}</td></tr>" |
|
242 "</table>" |
|
243 ).format( |
|
244 currentVersionStr, |
|
245 tag, |
|
246 self.tr("({0})").format(self._deviceData["mpy_variant"]) |
|
247 if self._deviceData["mpy_variant"] |
|
248 else "", |
|
249 ) |
|
250 if ( |
|
251 self._deviceData["mpy_variant"] not in ["Pimoroni"] |
|
252 and currentVersion < latestVersion |
|
253 ): |
|
254 # cannot derive that info for 'Pimoroni' variant |
|
255 msg += self.tr("<p><b>Update available!</b></p>") |
|
256 |
|
257 EricMessageBox.information( |
|
258 None, |
|
259 self.tr("MicroPython Version"), |
|
260 msg, |
|
261 ) |
|
262 |
|
263 def getDocumentationUrl(self): |
|
264 """ |
|
265 Public method to get the device documentation URL. |
|
266 |
|
267 @return documentation URL of the device |
|
268 @rtype str |
|
269 """ |
|
270 return Preferences.getMicroPython("MicroPythonDocuUrl") |
|
271 |
|
272 def getDownloadMenuEntries(self): |
|
273 """ |
|
274 Public method to retrieve the entries for the downloads menu. |
|
275 |
|
276 @return list of tuples with menu text and URL to be opened for each |
|
277 entry |
|
278 @rtype list of tuple of (str, str) |
|
279 """ |
|
280 return [ |
|
281 ( |
|
282 self.tr("MicroPython Firmware"), |
|
283 Preferences.getMicroPython("MicroPythonFirmwareUrl"), |
|
284 ), |
|
285 ("<separator>", ""), |
|
286 (self.tr("Pimoroni Pico Firmware"), FirmwareGithubUrls["pimoroni_pico"]), |
|
287 ("<separator>", ""), |
|
288 ( |
|
289 self.tr("CircuitPython Firmware"), |
|
290 Preferences.getMicroPython("CircuitPythonFirmwareUrl"), |
|
291 ), |
|
292 ( |
|
293 self.tr("CircuitPython Libraries"), |
|
294 Preferences.getMicroPython("CircuitPythonLibrariesUrl"), |
|
295 ), |
|
296 ] |
|
297 |
|
298 |
|
299 def createDevice(microPythonWidget, deviceType, vid, pid, boardName, serialNumber): |
|
300 """ |
|
301 Function to instantiate a MicroPython device object. |
|
302 |
|
303 @param microPythonWidget reference to the main MicroPython widget |
|
304 @type MicroPythonWidget |
|
305 @param deviceType device type assigned to this device interface |
|
306 @type str |
|
307 @param vid vendor ID |
|
308 @type int |
|
309 @param pid product ID |
|
310 @type int |
|
311 @param boardName name of the board |
|
312 @type str |
|
313 @param serialNumber serial number of the board |
|
314 @type str |
|
315 @return reference to the instantiated device object |
|
316 @rtype RP2040Device |
|
317 """ |
|
318 return RP2040Device(microPythonWidget, deviceType) |