pyflashgui 0.2.0__tar.gz → 0.2.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {pyflashgui-0.2.0/pyflashgui.egg-info → pyflashgui-0.2.1}/PKG-INFO +3 -2
- {pyflashgui-0.2.0 → pyflashgui-0.2.1}/PYPI.md +1 -0
- {pyflashgui-0.2.0 → pyflashgui-0.2.1}/pyflashgui/__init__.py +1 -1
- {pyflashgui-0.2.0 → pyflashgui-0.2.1}/pyflashgui/__main__.py +3 -2
- {pyflashgui-0.2.0 → pyflashgui-0.2.1}/pyflashgui/backends/base.py +10 -0
- {pyflashgui-0.2.0 → pyflashgui-0.2.1}/pyflashgui/backends/jlink_backend.py +29 -6
- {pyflashgui-0.2.0 → pyflashgui-0.2.1}/pyflashgui/backends/pyocd_backend.py +58 -20
- {pyflashgui-0.2.0 → pyflashgui-0.2.1}/pyflashgui/backends/pyocd_packs.py +20 -5
- {pyflashgui-0.2.0 → pyflashgui-0.2.1}/pyflashgui/core/excepthook.py +6 -7
- {pyflashgui-0.2.0 → pyflashgui-0.2.1}/pyflashgui/core/flasher.py +46 -3
- pyflashgui-0.2.1/pyflashgui/core/library_log.py +96 -0
- {pyflashgui-0.2.0 → pyflashgui-0.2.1}/pyflashgui/core/memory_map.py +2 -2
- {pyflashgui-0.2.0 → pyflashgui-0.2.1}/pyflashgui/core/worker.py +1 -2
- {pyflashgui-0.2.0 → pyflashgui-0.2.1}/pyflashgui/help/about.md +26 -2
- {pyflashgui-0.2.0 → pyflashgui-0.2.1}/pyflashgui/help/changelog.md +28 -5
- {pyflashgui-0.2.0 → pyflashgui-0.2.1}/pyflashgui/help/manual.md +1 -1
- {pyflashgui-0.2.0 → pyflashgui-0.2.1}/pyflashgui/help/memory.md +2 -2
- {pyflashgui-0.2.0 → pyflashgui-0.2.1}/pyflashgui/help/probe.md +2 -2
- {pyflashgui-0.2.0 → pyflashgui-0.2.1}/pyflashgui/ui/confirm.py +8 -0
- {pyflashgui-0.2.0 → pyflashgui-0.2.1}/pyflashgui/ui/crop_dialog.py +2 -2
- pyflashgui-0.2.1/pyflashgui/ui/diagnostics.py +264 -0
- {pyflashgui-0.2.0 → pyflashgui-0.2.1}/pyflashgui/ui/fonts.py +1 -1
- {pyflashgui-0.2.0 → pyflashgui-0.2.1}/pyflashgui/ui/help_menu.py +33 -13
- {pyflashgui-0.2.0 → pyflashgui-0.2.1}/pyflashgui/ui/main_window.py +13 -1
- {pyflashgui-0.2.0 → pyflashgui-0.2.1}/pyflashgui/ui/packs_dialog.py +17 -3
- {pyflashgui-0.2.0 → pyflashgui-0.2.1}/pyflashgui/ui/probe_bar.py +2 -2
- {pyflashgui-0.2.0 → pyflashgui-0.2.1}/pyflashgui/ui/theme.py +1 -1
- {pyflashgui-0.2.0 → pyflashgui-0.2.1/pyflashgui.egg-info}/PKG-INFO +3 -2
- {pyflashgui-0.2.0 → pyflashgui-0.2.1}/pyflashgui.egg-info/SOURCES.txt +3 -0
- {pyflashgui-0.2.0 → pyflashgui-0.2.1}/pyflashgui.egg-info/requires.txt +1 -1
- {pyflashgui-0.2.0 → pyflashgui-0.2.1}/pyproject.toml +5 -2
- {pyflashgui-0.2.0 → pyflashgui-0.2.1}/tests/test_backends.py +33 -2
- pyflashgui-0.2.1/tests/test_diagnostics.py +174 -0
- {pyflashgui-0.2.0 → pyflashgui-0.2.1}/tests/test_jlink_backend.py +1 -1
- {pyflashgui-0.2.0 → pyflashgui-0.2.1}/tests/test_packs.py +2 -0
- {pyflashgui-0.2.0 → pyflashgui-0.2.1}/tests/test_read_back.py +1 -1
- {pyflashgui-0.2.0 → pyflashgui-0.2.1}/LICENSE +0 -0
- {pyflashgui-0.2.0 → pyflashgui-0.2.1}/NOTICE +0 -0
- {pyflashgui-0.2.0 → pyflashgui-0.2.1}/README.md +0 -0
- {pyflashgui-0.2.0 → pyflashgui-0.2.1}/pyflashgui/backends/__init__.py +0 -0
- {pyflashgui-0.2.0 → pyflashgui-0.2.1}/pyflashgui/backends/simulated.py +0 -0
- {pyflashgui-0.2.0 → pyflashgui-0.2.1}/pyflashgui/core/__init__.py +0 -0
- {pyflashgui-0.2.0 → pyflashgui-0.2.1}/pyflashgui/core/checkout.py +0 -0
- {pyflashgui-0.2.0 → pyflashgui-0.2.1}/pyflashgui/core/compare.py +0 -0
- {pyflashgui-0.2.0 → pyflashgui-0.2.1}/pyflashgui/core/context.py +0 -0
- {pyflashgui-0.2.0 → pyflashgui-0.2.1}/pyflashgui/core/events.py +0 -0
- {pyflashgui-0.2.0 → pyflashgui-0.2.1}/pyflashgui/core/images.py +0 -0
- {pyflashgui-0.2.0 → pyflashgui-0.2.1}/pyflashgui/core/installation.py +0 -0
- {pyflashgui-0.2.0 → pyflashgui-0.2.1}/pyflashgui/core/layout.py +0 -0
- {pyflashgui-0.2.0 → pyflashgui-0.2.1}/pyflashgui/core/pack_manager.py +0 -0
- {pyflashgui-0.2.0 → pyflashgui-0.2.1}/pyflashgui/core/packages.py +0 -0
- {pyflashgui-0.2.0 → pyflashgui-0.2.1}/pyflashgui/core/paths.py +0 -0
- {pyflashgui-0.2.0 → pyflashgui-0.2.1}/pyflashgui/core/settings.py +0 -0
- {pyflashgui-0.2.0 → pyflashgui-0.2.1}/pyflashgui/core/shortcut.py +0 -0
- {pyflashgui-0.2.0 → pyflashgui-0.2.1}/pyflashgui/core/slow_start.py +0 -0
- {pyflashgui-0.2.0 → pyflashgui-0.2.1}/pyflashgui/core/timing.py +0 -0
- {pyflashgui-0.2.0 → pyflashgui-0.2.1}/pyflashgui/core/updates.py +0 -0
- {pyflashgui-0.2.0 → pyflashgui-0.2.1}/pyflashgui/core/workspaces.py +0 -0
- {pyflashgui-0.2.0 → pyflashgui-0.2.1}/pyflashgui/help/__init__.py +0 -0
- {pyflashgui-0.2.0 → pyflashgui-0.2.1}/pyflashgui/help/console.md +0 -0
- {pyflashgui-0.2.0 → pyflashgui-0.2.1}/pyflashgui/help/files.md +0 -0
- {pyflashgui-0.2.0 → pyflashgui-0.2.1}/pyflashgui/help/main-window.png +0 -0
- {pyflashgui-0.2.0 → pyflashgui-0.2.1}/pyflashgui/help/memory-map.md +0 -0
- {pyflashgui-0.2.0 → pyflashgui-0.2.1}/pyflashgui/resources/__init__.py +0 -0
- {pyflashgui-0.2.0 → pyflashgui-0.2.1}/pyflashgui/resources/pyflashgui.ico +0 -0
- {pyflashgui-0.2.0 → pyflashgui-0.2.1}/pyflashgui/resources/pyflashgui.png +0 -0
- {pyflashgui-0.2.0 → pyflashgui-0.2.1}/pyflashgui/resources/pyflashgui.svg +0 -0
- {pyflashgui-0.2.0 → pyflashgui-0.2.1}/pyflashgui/resources/pyflashgui_small.svg +0 -0
- {pyflashgui-0.2.0 → pyflashgui-0.2.1}/pyflashgui/resources/tick.svg +0 -0
- {pyflashgui-0.2.0 → pyflashgui-0.2.1}/pyflashgui/ui/__init__.py +0 -0
- {pyflashgui-0.2.0 → pyflashgui-0.2.1}/pyflashgui/ui/buttons.py +0 -0
- {pyflashgui-0.2.0 → pyflashgui-0.2.1}/pyflashgui/ui/console_view.py +0 -0
- {pyflashgui-0.2.0 → pyflashgui-0.2.1}/pyflashgui/ui/detached.py +0 -0
- {pyflashgui-0.2.0 → pyflashgui-0.2.1}/pyflashgui/ui/erase_dialog.py +0 -0
- {pyflashgui-0.2.0 → pyflashgui-0.2.1}/pyflashgui/ui/event_colours.py +0 -0
- {pyflashgui-0.2.0 → pyflashgui-0.2.1}/pyflashgui/ui/files_view.py +0 -0
- {pyflashgui-0.2.0 → pyflashgui-0.2.1}/pyflashgui/ui/folders.py +0 -0
- {pyflashgui-0.2.0 → pyflashgui-0.2.1}/pyflashgui/ui/map_view.py +0 -0
- {pyflashgui-0.2.0 → pyflashgui-0.2.1}/pyflashgui/ui/memory_view.py +0 -0
- {pyflashgui-0.2.0 → pyflashgui-0.2.1}/pyflashgui/ui/messages.py +0 -0
- {pyflashgui-0.2.0 → pyflashgui-0.2.1}/pyflashgui/ui/pane_bar.py +0 -0
- {pyflashgui-0.2.0 → pyflashgui-0.2.1}/pyflashgui/ui/panes.py +0 -0
- {pyflashgui-0.2.0 → pyflashgui-0.2.1}/pyflashgui/ui/persist.py +0 -0
- {pyflashgui-0.2.0 → pyflashgui-0.2.1}/pyflashgui/ui/read_dialog.py +0 -0
- {pyflashgui-0.2.0 → pyflashgui-0.2.1}/pyflashgui/ui/scripting.py +0 -0
- {pyflashgui-0.2.0 → pyflashgui-0.2.1}/pyflashgui/ui/scripts_menu.py +0 -0
- {pyflashgui-0.2.0 → pyflashgui-0.2.1}/pyflashgui/ui/session.py +0 -0
- {pyflashgui-0.2.0 → pyflashgui-0.2.1}/pyflashgui/ui/workspace_menu.py +0 -0
- {pyflashgui-0.2.0 → pyflashgui-0.2.1}/pyflashgui.egg-info/dependency_links.txt +0 -0
- {pyflashgui-0.2.0 → pyflashgui-0.2.1}/pyflashgui.egg-info/entry_points.txt +0 -0
- {pyflashgui-0.2.0 → pyflashgui-0.2.1}/pyflashgui.egg-info/top_level.txt +0 -0
- {pyflashgui-0.2.0 → pyflashgui-0.2.1}/setup.cfg +0 -0
- {pyflashgui-0.2.0 → pyflashgui-0.2.1}/tests/test_build_version.py +0 -0
- {pyflashgui-0.2.0 → pyflashgui-0.2.1}/tests/test_changelog.py +0 -0
- {pyflashgui-0.2.0 → pyflashgui-0.2.1}/tests/test_checkout.py +0 -0
- {pyflashgui-0.2.0 → pyflashgui-0.2.1}/tests/test_compare.py +0 -0
- {pyflashgui-0.2.0 → pyflashgui-0.2.1}/tests/test_console.py +0 -0
- {pyflashgui-0.2.0 → pyflashgui-0.2.1}/tests/test_erase.py +0 -0
- {pyflashgui-0.2.0 → pyflashgui-0.2.1}/tests/test_events.py +0 -0
- {pyflashgui-0.2.0 → pyflashgui-0.2.1}/tests/test_excepthook.py +0 -0
- {pyflashgui-0.2.0 → pyflashgui-0.2.1}/tests/test_flash_panes.py +0 -0
- {pyflashgui-0.2.0 → pyflashgui-0.2.1}/tests/test_flasher.py +0 -0
- {pyflashgui-0.2.0 → pyflashgui-0.2.1}/tests/test_folders.py +0 -0
- {pyflashgui-0.2.0 → pyflashgui-0.2.1}/tests/test_fonts.py +0 -0
- {pyflashgui-0.2.0 → pyflashgui-0.2.1}/tests/test_headers.py +0 -0
- {pyflashgui-0.2.0 → pyflashgui-0.2.1}/tests/test_help.py +0 -0
- {pyflashgui-0.2.0 → pyflashgui-0.2.1}/tests/test_icon.py +0 -0
- {pyflashgui-0.2.0 → pyflashgui-0.2.1}/tests/test_images.py +0 -0
- {pyflashgui-0.2.0 → pyflashgui-0.2.1}/tests/test_installation.py +0 -0
- {pyflashgui-0.2.0 → pyflashgui-0.2.1}/tests/test_launcher.py +0 -0
- {pyflashgui-0.2.0 → pyflashgui-0.2.1}/tests/test_memory_map.py +0 -0
- {pyflashgui-0.2.0 → pyflashgui-0.2.1}/tests/test_messages.py +0 -0
- {pyflashgui-0.2.0 → pyflashgui-0.2.1}/tests/test_notice.py +0 -0
- {pyflashgui-0.2.0 → pyflashgui-0.2.1}/tests/test_packages.py +0 -0
- {pyflashgui-0.2.0 → pyflashgui-0.2.1}/tests/test_packaging.py +0 -0
- {pyflashgui-0.2.0 → pyflashgui-0.2.1}/tests/test_persist.py +0 -0
- {pyflashgui-0.2.0 → pyflashgui-0.2.1}/tests/test_readme.py +0 -0
- {pyflashgui-0.2.0 → pyflashgui-0.2.1}/tests/test_scripts_menu.py +0 -0
- {pyflashgui-0.2.0 → pyflashgui-0.2.1}/tests/test_shortcut.py +0 -0
- {pyflashgui-0.2.0 → pyflashgui-0.2.1}/tests/test_slow_start.py +0 -0
- {pyflashgui-0.2.0 → pyflashgui-0.2.1}/tests/test_theme.py +0 -0
- {pyflashgui-0.2.0 → pyflashgui-0.2.1}/tests/test_workspaces.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pyflashgui
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.1
|
|
4
4
|
Summary: Graphical flash programming tool: Arm Cortex-M with any probe pyOCD supports, and more through J-Link Commander
|
|
5
5
|
Author: davhodg
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -29,7 +29,7 @@ Description-Content-Type: text/markdown
|
|
|
29
29
|
License-File: LICENSE
|
|
30
30
|
License-File: NOTICE
|
|
31
31
|
Requires-Dist: PySide6-Essentials>=6.8
|
|
32
|
-
Requires-Dist: pyocd>=0.
|
|
32
|
+
Requires-Dist: pyocd>=0.46
|
|
33
33
|
Requires-Dist: bincopy>=20.0
|
|
34
34
|
Provides-Extra: dev
|
|
35
35
|
Requires-Dist: pytest; extra == "dev"
|
|
@@ -97,6 +97,7 @@ A probe is usable without root on Linux once a udev rule allows it; pyOCD publis
|
|
|
97
97
|
- **Workspaces**: one for each board or project, holding its probe, target, files and layout.
|
|
98
98
|
- **A Python console** with the probe and the target as `flash`: read, write RAM, erase, program, verify and reset from a prompt or a script, each call waiting for its answer. *File > Run script...* runs one, and keeps the last five a click away.
|
|
99
99
|
- **A simulated probe and target**, to try all of it with no hardware.
|
|
100
|
+
- **Help > Diagnostics**: how the connection was made, the last operations in order and what pyOCD reported, ready to paste into a bug report.
|
|
100
101
|
|
|
101
102
|
The [manual](https://github.com/davhodg/pyflashgui/blob/master/pyflashgui/help/manual.md) covers each of these, and is also under **Help > Documentation** in the application.
|
|
102
103
|
|
|
@@ -57,6 +57,7 @@ A probe is usable without root on Linux once a udev rule allows it; pyOCD publis
|
|
|
57
57
|
- **Workspaces**: one for each board or project, holding its probe, target, files and layout.
|
|
58
58
|
- **A Python console** with the probe and the target as `flash`: read, write RAM, erase, program, verify and reset from a prompt or a script, each call waiting for its answer. *File > Run script...* runs one, and keeps the last five a click away.
|
|
59
59
|
- **A simulated probe and target**, to try all of it with no hardware.
|
|
60
|
+
- **Help > Diagnostics**: how the connection was made, the last operations in order and what pyOCD reported, ready to paste into a bug report.
|
|
60
61
|
|
|
61
62
|
The [manual](https://github.com/davhodg/pyflashgui/blob/master/pyflashgui/help/manual.md) covers each of these, and is also under **Help > Documentation** in the application.
|
|
62
63
|
|
|
@@ -89,7 +89,7 @@ def selftest() -> int:
|
|
|
89
89
|
|
|
90
90
|
# libusb is a native library carried inside a Python package, which is
|
|
91
91
|
# the kind of file a build leaves behind. Without it pyOCD lists no
|
|
92
|
-
# CMSIS-DAP v2 probe and no ST-Link,
|
|
92
|
+
# CMSIS-DAP v2 probe and no ST-Link, so the self-test checks for it.
|
|
93
93
|
try:
|
|
94
94
|
import libusb_package
|
|
95
95
|
|
|
@@ -160,7 +160,8 @@ def set_icon(app) -> None:
|
|
|
160
160
|
|
|
161
161
|
#: The libraries Qt's X11 plugin needs that Debian, Ubuntu and Linux Mint can
|
|
162
162
|
#: leave out, each with the package it comes in. Without one, Qt stops with
|
|
163
|
-
#: "could not load the Qt platform plugin xcb"
|
|
163
|
+
#: "could not load the Qt platform plugin xcb", so they are looked for first
|
|
164
|
+
#: and the package to install is named.
|
|
164
165
|
XCB_LIBRARIES = (
|
|
165
166
|
("libxcb-cursor.so.0", "libxcb-cursor0"),
|
|
166
167
|
("libxcb-icccm.so.4", "libxcb-icccm4"),
|
|
@@ -197,6 +197,16 @@ class Connection(ABC):
|
|
|
197
197
|
def close(self) -> None:
|
|
198
198
|
"""Let go of the probe. Safe to call twice."""
|
|
199
199
|
|
|
200
|
+
def details(self) -> list[str]:
|
|
201
|
+
"""What Help > Diagnostics says of this connection, a line each.
|
|
202
|
+
|
|
203
|
+
How the backend was asked to connect and what it found there, beyond
|
|
204
|
+
the probe and the target the window already knows. Read on the
|
|
205
|
+
window's thread while the worker may be using the connection, so
|
|
206
|
+
only what is already known is said: nothing here asks the probe.
|
|
207
|
+
"""
|
|
208
|
+
return []
|
|
209
|
+
|
|
200
210
|
def read_range(
|
|
201
211
|
self,
|
|
202
212
|
address: int,
|
|
@@ -72,13 +72,14 @@ NOT_FOUND = (
|
|
|
72
72
|
)
|
|
73
73
|
|
|
74
74
|
#: Said by Commander when the probe is there and the device does not answer.
|
|
75
|
-
#: Looked for as well as its exit code, which
|
|
75
|
+
#: Looked for as well as its exit code, which can be 0 with it.
|
|
76
76
|
NO_TARGET = "Cannot connect to target"
|
|
77
77
|
|
|
78
|
-
#: A J-Link script that does nothing where
|
|
78
|
+
#: A J-Link script that does nothing where J-Link's software would set the
|
|
79
79
|
#: device up after connecting to it. On some parts that set-up writes to
|
|
80
80
|
#: the device -- an S32K3's RAM is filled, for its error correction -- and
|
|
81
|
-
#: a read made to see what a running program left there finds
|
|
81
|
+
#: a read made to see what a running program left there finds the set-up's
|
|
82
|
+
#: bytes instead.
|
|
82
83
|
NO_SETUP_SCRIPT = "int SetupTarget(void) {\n return 0;\n}\n"
|
|
83
84
|
|
|
84
85
|
#: Where Commander is, when it has been said. None to go and look.
|
|
@@ -146,6 +147,20 @@ def find_commander() -> list[str] | None:
|
|
|
146
147
|
return None
|
|
147
148
|
|
|
148
149
|
|
|
150
|
+
def report() -> list[str]:
|
|
151
|
+
"""For Help > Diagnostics: the Commander used, how it was found, and every
|
|
152
|
+
J-Link software folder there is, newest first."""
|
|
153
|
+
found = find_commander()
|
|
154
|
+
if found is None:
|
|
155
|
+
lines = ["not found"]
|
|
156
|
+
else:
|
|
157
|
+
how = "chosen in Tools > Settings" if _chosen is not None else "found"
|
|
158
|
+
lines = [f"{' '.join(found)} ({how})"]
|
|
159
|
+
folders = _installed()
|
|
160
|
+
lines += [f"installed: {folder}" for folder in folders] or ["no J-Link software folder found"]
|
|
161
|
+
return lines
|
|
162
|
+
|
|
163
|
+
|
|
149
164
|
def _is_seggers(program: Path) -> bool:
|
|
150
165
|
"""Whether a program of Commander's name is Commander, and not Java's jlink.
|
|
151
166
|
|
|
@@ -407,6 +422,11 @@ class JLinkConnection(Connection):
|
|
|
407
422
|
def regions(self) -> list[Region]:
|
|
408
423
|
return list(self._regions)
|
|
409
424
|
|
|
425
|
+
def details(self) -> list[str]:
|
|
426
|
+
if self._arguments is None:
|
|
427
|
+
return ["closed"]
|
|
428
|
+
return [f"device: {self._device}", f"Commander run with: {' '.join(self._arguments)}"]
|
|
429
|
+
|
|
410
430
|
def erase_chip(self) -> None:
|
|
411
431
|
self._run(["H", "Erase"], WORK_S, f"Erasing {self._device}")
|
|
412
432
|
|
|
@@ -535,7 +555,10 @@ class JLinkConnection(Connection):
|
|
|
535
555
|
|
|
536
556
|
def reset(self, halt: bool = False, kind: str = RESET_DEFAULT) -> None:
|
|
537
557
|
if kind != RESET_DEFAULT:
|
|
538
|
-
raise BackendError(
|
|
558
|
+
raise BackendError(
|
|
559
|
+
"Through J-Link Commander, a device is reset the usual way only. The other "
|
|
560
|
+
"ways are offered through pyOCD."
|
|
561
|
+
)
|
|
539
562
|
# R restarts the device and leaves it stopped; G sets it going.
|
|
540
563
|
self._run(["R"] if halt else ["R", "G"], QUICK_S, f"Resetting {self._device}")
|
|
541
564
|
|
|
@@ -581,8 +604,8 @@ class JLinkBackend(Backend):
|
|
|
581
604
|
# it that is not, and latin-1 reads any byte there is.
|
|
582
605
|
text = path.read_text(encoding="latin-1")
|
|
583
606
|
except (BackendError, OSError):
|
|
584
|
-
#
|
|
585
|
-
#
|
|
607
|
+
# Without the list, a device whose name is typed in is still
|
|
608
|
+
# programmed.
|
|
586
609
|
text = ""
|
|
587
610
|
self._devices = {device.name.upper(): device for device in parse_devices(text)}
|
|
588
611
|
return self._devices
|
|
@@ -7,7 +7,7 @@ exception said again in pyflashgui's words. Anything worth testing without a
|
|
|
7
7
|
probe plugged in is in ``core`` or in the base class, and what is left here
|
|
8
8
|
can only be tried on hardware.
|
|
9
9
|
|
|
10
|
-
pyOCD is imported inside the methods that use it. It is the
|
|
10
|
+
pyOCD is imported inside the methods that use it. It is the largest thing
|
|
11
11
|
pyflashgui loads, and a window should not wait for it to say which probes
|
|
12
12
|
are plugged in before it can be drawn.
|
|
13
13
|
"""
|
|
@@ -17,6 +17,7 @@ from __future__ import annotations
|
|
|
17
17
|
import contextlib
|
|
18
18
|
import time
|
|
19
19
|
from collections.abc import Sequence
|
|
20
|
+
from pathlib import Path
|
|
20
21
|
|
|
21
22
|
from pyflashgui.backends import pyocd_packs
|
|
22
23
|
from pyflashgui.backends.base import (
|
|
@@ -90,9 +91,8 @@ def regions_of(memory_map, generic: bool = False) -> list[Region]:
|
|
|
90
91
|
def needs_one_packet_at_a_time(*names: str | None) -> bool:
|
|
91
92
|
"""Whether a probe called this must be sent one command and then waited for.
|
|
92
93
|
|
|
93
|
-
pyOCD's own advice for the TI XDS110
|
|
94
|
-
|
|
95
|
-
is not known which versions are free of it, and the cost is only speed.
|
|
94
|
+
pyOCD's own advice for the TI XDS110: one command at a time. Set for
|
|
95
|
+
every one of them, as pyOCD advises, and the cost is only speed.
|
|
96
96
|
"""
|
|
97
97
|
return any("xds110" in (name or "").lower() for name in names)
|
|
98
98
|
|
|
@@ -144,11 +144,11 @@ def _stages(progress: Progress) -> Progress:
|
|
|
144
144
|
def _let_go(session) -> Exception | None:
|
|
145
145
|
"""Close a pyOCD session, and its probe even where closing the session fails.
|
|
146
146
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
Returns what went wrong, if anything did.
|
|
147
|
+
Closing a session lets go of the target, then of the probe. With the
|
|
148
|
+
board's power turned off, letting go of the target raises, and the probe
|
|
149
|
+
is still open: a J-Link still open is listed by Find probes, and every
|
|
150
|
+
connect says "No emulator with serial number". So the probe is closed
|
|
151
|
+
here as well. Returns what went wrong, if anything did.
|
|
152
152
|
"""
|
|
153
153
|
try:
|
|
154
154
|
session.close()
|
|
@@ -202,6 +202,44 @@ class PyocdConnection(Connection):
|
|
|
202
202
|
#: Makes a new session on the same probe and target, attaching to it
|
|
203
203
|
#: as it is, for when a reset has taken the old one with it.
|
|
204
204
|
self._reopen = reopen
|
|
205
|
+
#: The options pyOCD was given, for Help > Diagnostics.
|
|
206
|
+
self.options: dict = {}
|
|
207
|
+
#: How many times a reset took the connection with it and it was made again.
|
|
208
|
+
self.reattached = 0
|
|
209
|
+
|
|
210
|
+
def details(self) -> list[str]:
|
|
211
|
+
packs = self.options.get("pack") or []
|
|
212
|
+
given = ", ".join(f"{k}={v}" for k, v in sorted(self.options.items()) if k != "pack")
|
|
213
|
+
lines = [f"pyOCD options: {given or 'none'}"]
|
|
214
|
+
if packs:
|
|
215
|
+
lines.append(f"packs handed to pyOCD: {', '.join(Path(p).name for p in packs)}")
|
|
216
|
+
if self.reattached:
|
|
217
|
+
lines.append(f"connected again after a reset: {self.reattached} time(s)")
|
|
218
|
+
session = self._session
|
|
219
|
+
if session is None:
|
|
220
|
+
return [*lines, "session: closed"]
|
|
221
|
+
target, probe = getattr(session, "target", None), getattr(session, "probe", None)
|
|
222
|
+
if target is not None:
|
|
223
|
+
if is_generic(target):
|
|
224
|
+
where = "no device in particular: the generic Cortex-M"
|
|
225
|
+
elif "_pack_device" in vars(type(target)):
|
|
226
|
+
where = "from a pack"
|
|
227
|
+
else:
|
|
228
|
+
where = "built in"
|
|
229
|
+
lines.append(f"target class: {type(target).__name__}, {where}")
|
|
230
|
+
for core in getattr(target, "cores", {}).values():
|
|
231
|
+
architecture = getattr(getattr(core, "architecture", None), "name", "")
|
|
232
|
+
lines.append(
|
|
233
|
+
f"core {getattr(core, 'core_number', '?')}: {getattr(core, 'name', '?')}"
|
|
234
|
+
+ (f" ({architecture})" if architecture else "")
|
|
235
|
+
)
|
|
236
|
+
if probe is not None:
|
|
237
|
+
wire = getattr(getattr(probe, "wire_protocol", None), "name", "") or "not known"
|
|
238
|
+
lines.append(
|
|
239
|
+
f"probe: {probe.description}; {type(probe).__name__}; "
|
|
240
|
+
f"{probe.vendor_name or '?'} {probe.product_name or '?'}; wire protocol {wire}"
|
|
241
|
+
)
|
|
242
|
+
return lines
|
|
205
243
|
|
|
206
244
|
def _target(self):
|
|
207
245
|
if self._session is None:
|
|
@@ -277,13 +315,12 @@ class PyocdConnection(Connection):
|
|
|
277
315
|
"""Reset the target and stop it at its first instruction, before a flash
|
|
278
316
|
routine is run on it.
|
|
279
317
|
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
download does.
|
|
318
|
+
A flash routine runs on the processor as it is when stopped.
|
|
319
|
+
Reported on an XMC4500 attached to its firmware as it ran: stopped
|
|
320
|
+
amid the firmware, with its interrupts and the rest of what it had set
|
|
321
|
+
up still on, the erase routine ended in a HardFault (IPSR=3). Reset
|
|
322
|
+
first, the routine runs on a device as it starts, which is what flash
|
|
323
|
+
routines are written for.
|
|
287
324
|
"""
|
|
288
325
|
try:
|
|
289
326
|
self._target().reset_and_halt()
|
|
@@ -328,9 +365,8 @@ class PyocdConnection(Connection):
|
|
|
328
365
|
progress((before + fraction * size) / total)
|
|
329
366
|
|
|
330
367
|
# keep_unwritten: the rest of a page written to is read and
|
|
331
|
-
# written back as it is
|
|
332
|
-
# erased flash
|
|
333
|
-
# says FF for an XMC4500, whose erased flash reads 00.
|
|
368
|
+
# written back as it is, so it holds what it held, whatever
|
|
369
|
+
# erased flash reads as on this device.
|
|
334
370
|
builder.program(progress_cb=report, smart_flash=False, keep_unwritten=True)
|
|
335
371
|
written += size
|
|
336
372
|
except BackendError:
|
|
@@ -416,6 +452,7 @@ class PyocdConnection(Connection):
|
|
|
416
452
|
time.sleep(wait)
|
|
417
453
|
try:
|
|
418
454
|
self._session = self._reopen()
|
|
455
|
+
self.reattached += 1
|
|
419
456
|
break
|
|
420
457
|
except BackendError as exc:
|
|
421
458
|
failed = exc
|
|
@@ -444,7 +481,7 @@ class PyocdConnection(Connection):
|
|
|
444
481
|
|
|
445
482
|
class PyocdBackend(Backend):
|
|
446
483
|
# pyOCD's own, whatever the probe could do: 1 MHz, chosen to work with
|
|
447
|
-
# a long or untidy cable
|
|
484
|
+
# a long or untidy cable. A faster one can be chosen in the bar.
|
|
448
485
|
default_speed = "Default (1 MHz)"
|
|
449
486
|
can_skip_erase = True
|
|
450
487
|
can_attach = True
|
|
@@ -587,6 +624,7 @@ class PyocdBackend(Backend):
|
|
|
587
624
|
connection = PyocdConnection(session, lambda: self._open(chosen, again, target))
|
|
588
625
|
connection.speed = speed_text(session.options.get("frequency"))
|
|
589
626
|
connection.notes = tuple(notes)
|
|
627
|
+
connection.options = dict(options)
|
|
590
628
|
return connection
|
|
591
629
|
|
|
592
630
|
@staticmethod
|
|
@@ -248,9 +248,8 @@ def read_pack(path: Path) -> tuple[Pack, tuple[tuple[str, str], ...]]:
|
|
|
248
248
|
from xml.etree import ElementTree
|
|
249
249
|
|
|
250
250
|
# By the way in pyOCD's own modules take, before the part of it wanted
|
|
251
|
-
# here
|
|
252
|
-
#
|
|
253
|
-
# thread at the same moment.
|
|
251
|
+
# here: its modules are written to be imported from the top, and the
|
|
252
|
+
# backend may be importing it on another thread at the same moment.
|
|
254
253
|
import pyocd.target # noqa: F401
|
|
255
254
|
from pyocd.target.pack.cmsis_pack import CmsisPack
|
|
256
255
|
|
|
@@ -263,8 +262,8 @@ def read_pack(path: Path) -> tuple[Pack, tuple[tuple[str, str], ...]]:
|
|
|
263
262
|
f"{path.name} is not a CMSIS-Pack: there is no description (.pdsc) in it."
|
|
264
263
|
)
|
|
265
264
|
root = ElementTree.fromstring(archive.read(min(described, key=lambda n: n.count("/"))))
|
|
266
|
-
# Read through this archive, which is closed after
|
|
267
|
-
#
|
|
265
|
+
# Read through this archive, which is closed after, so the file can
|
|
266
|
+
# be removed again: Windows will not delete an open file.
|
|
268
267
|
found = {
|
|
269
268
|
(d.part_number, (d.vendor or "").split(":")[0]) for d in CmsisPack(archive).devices
|
|
270
269
|
}
|
|
@@ -314,6 +313,22 @@ def file_devices() -> list[Device]:
|
|
|
314
313
|
return out
|
|
315
314
|
|
|
316
315
|
|
|
316
|
+
def report() -> list[str]:
|
|
317
|
+
"""For Help > Diagnostics: where the packs are, which there are, and how
|
|
318
|
+
old the list of devices is. Found from the files, without reading the
|
|
319
|
+
list itself, which is twenty megabytes."""
|
|
320
|
+
where = folder()
|
|
321
|
+
fetched = list_fetched()
|
|
322
|
+
lines = [
|
|
323
|
+
f"folder: {where}",
|
|
324
|
+
f"list of devices fetched {fetched:%Y-%m-%d %H:%M}" if fetched else "list never fetched",
|
|
325
|
+
]
|
|
326
|
+
downloaded = sorted(p.relative_to(where).as_posix() for p in where.rglob("*.pack"))
|
|
327
|
+
lines += [f"installed: {name}" for name in downloaded] or ["none installed"]
|
|
328
|
+
lines += [f"added from a file: {p.name}" for p in sorted(files_folder().glob("*.pack"))]
|
|
329
|
+
return lines
|
|
330
|
+
|
|
331
|
+
|
|
317
332
|
def same_name(part: str) -> str:
|
|
318
333
|
"""A device's name as pyOCD files it, whichever way it is spelt.
|
|
319
334
|
|
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
"""Put unhandled exceptions in the Event Log instead of nowhere.
|
|
4
4
|
|
|
5
5
|
pyflashgui is started with ``pythonw.exe``, which has no console. An exception
|
|
6
|
-
raised inside a Qt slot
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
raised inside a Qt slot is printed by PySide to standard error, which pythonw
|
|
7
|
+
does not have: the slot does not finish, and the window carries on looking
|
|
8
|
+
perfectly healthy.
|
|
9
9
|
|
|
10
10
|
A stale call to a widget method -- ``.text()`` on what had become a combo box
|
|
11
11
|
-- is enough: nothing is logged, no dialog appears, and whatever the slot was
|
|
@@ -27,10 +27,9 @@ from PySide6.QtCore import QObject, Qt, QtMsgType, Signal, Slot, qInstallMessage
|
|
|
27
27
|
|
|
28
28
|
from pyflashgui.core.events import ERROR, INFORMATION
|
|
29
29
|
|
|
30
|
-
#: Said of a Python traceback and of nothing else. Qt
|
|
31
|
-
#: same handler
|
|
32
|
-
#:
|
|
33
|
-
#: this application is both wrong and unhelpful.
|
|
30
|
+
#: Said of a Python traceback and of nothing else. Qt reports through the
|
|
31
|
+
#: same handler what other parts of the system say -- a platform plugin, a
|
|
32
|
+
#: window manager -- and those are not faults in this application.
|
|
34
33
|
BUG = "Unhandled error (this is a bug in pyflashgui; the action it interrupted did not finish):"
|
|
35
34
|
|
|
36
35
|
|
|
@@ -16,7 +16,10 @@ from __future__ import annotations
|
|
|
16
16
|
|
|
17
17
|
import threading
|
|
18
18
|
import time
|
|
19
|
+
from collections import deque
|
|
19
20
|
from collections.abc import Callable, Sequence
|
|
21
|
+
from dataclasses import dataclass
|
|
22
|
+
from datetime import datetime
|
|
20
23
|
from typing import Any
|
|
21
24
|
|
|
22
25
|
from PySide6.QtCore import QObject, Signal
|
|
@@ -47,7 +50,7 @@ RESET_SAID = {
|
|
|
47
50
|
RESET_CORE: "by restarting its processor only",
|
|
48
51
|
}
|
|
49
52
|
#: How long letting go of the probe is waited for when the window closes, in
|
|
50
|
-
#: seconds: a
|
|
53
|
+
#: seconds: a probe that does not answer must not keep the window waiting.
|
|
51
54
|
CLOSE_S = 5.0
|
|
52
55
|
|
|
53
56
|
|
|
@@ -59,6 +62,30 @@ def _close_quietly(connection) -> None:
|
|
|
59
62
|
pass
|
|
60
63
|
|
|
61
64
|
|
|
65
|
+
#: How many operations are remembered for Help > Diagnostics.
|
|
66
|
+
REMEMBERED = 20
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
@dataclass(frozen=True)
|
|
70
|
+
class Operation:
|
|
71
|
+
"""One thing done, or asked for, for Help > Diagnostics to list in order."""
|
|
72
|
+
|
|
73
|
+
#: When it was asked for, as a time of day.
|
|
74
|
+
when: str
|
|
75
|
+
name: str
|
|
76
|
+
#: done, failed, cancelled, or not done as asked -- a verify that found
|
|
77
|
+
#: differences, which is not a failure of the probe.
|
|
78
|
+
outcome: str
|
|
79
|
+
#: How long it took, in seconds.
|
|
80
|
+
took: float
|
|
81
|
+
#: What it failed with, where it did, as the Event Log said it.
|
|
82
|
+
said: str = ""
|
|
83
|
+
|
|
84
|
+
def line(self) -> str:
|
|
85
|
+
why = f": {self.said.splitlines()[0]}" if self.said else ""
|
|
86
|
+
return f"{self.when} {self.name}: {self.outcome}{why}, {self.took:.1f} s"
|
|
87
|
+
|
|
88
|
+
|
|
62
89
|
#: Progress is passed on when it has moved this far, and not otherwise: a
|
|
63
90
|
#: library reporting every page would otherwise queue a thousand signals to
|
|
64
91
|
#: move a bar a thousand times by less than a pixel.
|
|
@@ -128,6 +155,13 @@ class Flasher(QObject):
|
|
|
128
155
|
self.regions: list[Region] = []
|
|
129
156
|
self._regions_for = ""
|
|
130
157
|
self.busy = False
|
|
158
|
+
#: What is being done while busy: "Programming". Empty otherwise.
|
|
159
|
+
self.doing = ""
|
|
160
|
+
#: The last few operations, oldest first, for Help > Diagnostics: the
|
|
161
|
+
#: order things happened in is most of what a fault report needs.
|
|
162
|
+
self.history: deque[Operation] = deque(maxlen=REMEMBERED)
|
|
163
|
+
#: The probes found when they were last looked for.
|
|
164
|
+
self.probes_found: list = []
|
|
131
165
|
self._cancel = threading.Event()
|
|
132
166
|
self._worker = Worker(self)
|
|
133
167
|
self._last_progress = -1.0
|
|
@@ -156,14 +190,19 @@ class Flasher(QObject):
|
|
|
156
190
|
if self.busy:
|
|
157
191
|
self.events.warning(f"{name} was not started: something else is still running.")
|
|
158
192
|
return False
|
|
193
|
+
self.doing = name
|
|
159
194
|
self._set_busy(True)
|
|
160
195
|
self._cancel.clear()
|
|
161
196
|
self._last_progress = -1.0
|
|
197
|
+
asked, started = f"{datetime.now():%H:%M:%S}", time.monotonic()
|
|
198
|
+
|
|
199
|
+
def remember(outcome: str, said: str = "") -> None:
|
|
200
|
+
took = time.monotonic() - started
|
|
201
|
+
self.history.append(Operation(asked, name, outcome, took, said))
|
|
162
202
|
|
|
163
203
|
def job() -> tuple[Any, str | None, bool]:
|
|
164
204
|
# A backend's own error is a sentence already and is passed on as
|
|
165
|
-
# one. Anything else
|
|
166
|
-
# and says what kind it was.
|
|
205
|
+
# one. Anything else was not expected, and says what kind it was.
|
|
167
206
|
try:
|
|
168
207
|
return work(), None, False
|
|
169
208
|
except CancelledError as exc:
|
|
@@ -176,13 +215,16 @@ class Flasher(QObject):
|
|
|
176
215
|
if error is None:
|
|
177
216
|
result, error, cancelled = result
|
|
178
217
|
if not quiet:
|
|
218
|
+
self.doing = ""
|
|
179
219
|
self._set_busy(False)
|
|
180
220
|
if error is not None:
|
|
221
|
+
remember("cancelled" if cancelled else "failed", error)
|
|
181
222
|
(self.events.warning if cancelled else self.events.error)(error)
|
|
182
223
|
if not quiet:
|
|
183
224
|
self.finished.emit(name, False)
|
|
184
225
|
return
|
|
185
226
|
ok = done(result)
|
|
227
|
+
remember("done" if ok is not False else "not done as asked")
|
|
186
228
|
if not quiet:
|
|
187
229
|
self.finished.emit(name, ok is not False)
|
|
188
230
|
|
|
@@ -243,6 +285,7 @@ class Flasher(QObject):
|
|
|
243
285
|
|
|
244
286
|
def done(result: tuple[list, str]) -> None:
|
|
245
287
|
probes, error = result
|
|
288
|
+
self.probes_found = list(probes)
|
|
246
289
|
if error:
|
|
247
290
|
self.events.warning(error)
|
|
248
291
|
self.probes_changed.emit(probes)
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
# SPDX-FileCopyrightText: 2026 davhodg
|
|
3
|
+
"""What pyOCD and pylink say for themselves, kept for Help > Diagnostics.
|
|
4
|
+
|
|
5
|
+
Both write to Python's logging rather than raising everything they have to
|
|
6
|
+
say: a probe that did not answer on disconnecting, a flash routine that
|
|
7
|
+
reported a fault, the parts of the debug port found on connecting. With no
|
|
8
|
+
handler of their own those lines went to a console pyflashgui started with
|
|
9
|
+
pythonw does not have, and were lost.
|
|
10
|
+
|
|
11
|
+
So the last of them are kept, from information up, for the diagnostics
|
|
12
|
+
report -- which is where the question "what did pyOCD make of this target?"
|
|
13
|
+
is answered -- and errors are passed to the Event Log as they happen.
|
|
14
|
+
Warnings are only kept: pyOCD gives several on an ordinary connection to
|
|
15
|
+
some targets, and an Event Log that opens itself for those would soon be
|
|
16
|
+
closed and not opened again.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
import logging
|
|
22
|
+
from collections import deque
|
|
23
|
+
from datetime import datetime
|
|
24
|
+
|
|
25
|
+
from pyflashgui.core.events import EventLog
|
|
26
|
+
|
|
27
|
+
#: The loggers listened to, and what each library is called when it is named.
|
|
28
|
+
LIBRARIES = {"pyocd": "pyOCD", "pylink": "pylink"}
|
|
29
|
+
#: How many lines are kept for the report.
|
|
30
|
+
KEPT = 100
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def library_of(logger_name: str) -> str:
|
|
34
|
+
"""The library a logger belongs to, as it is written: pyocd.coresight.ap is pyOCD."""
|
|
35
|
+
return LIBRARIES.get(logger_name.split(".")[0], logger_name)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def text_of(record: logging.LogRecord) -> str:
|
|
39
|
+
"""The line a record says, with the exception it carries where it adds anything."""
|
|
40
|
+
try:
|
|
41
|
+
text = record.getMessage()
|
|
42
|
+
except Exception: # a library's own mistake in its format string
|
|
43
|
+
text = str(record.msg)
|
|
44
|
+
if record.exc_info and (exc := record.exc_info[1]) is not None:
|
|
45
|
+
said = str(exc).strip() or type(exc).__name__
|
|
46
|
+
if said not in text:
|
|
47
|
+
text = f"{text.rstrip(':')}: {said}" if text else said
|
|
48
|
+
return text.strip()
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class LibraryLog(logging.Handler):
|
|
52
|
+
"""Keeps what the libraries said, and passes their errors to the Event Log.
|
|
53
|
+
|
|
54
|
+
Called on whichever thread the library was on -- usually the worker
|
|
55
|
+
talking to the probe -- which is safe for both: a deque appends from any
|
|
56
|
+
thread, and the Event Log crosses to the window's thread itself.
|
|
57
|
+
"""
|
|
58
|
+
|
|
59
|
+
def __init__(self, events: EventLog, kept: int = KEPT) -> None:
|
|
60
|
+
super().__init__(logging.INFO)
|
|
61
|
+
self.events = events
|
|
62
|
+
#: (time, level, library, text), oldest first.
|
|
63
|
+
self.recent: deque[tuple[str, str, str, str]] = deque(maxlen=kept)
|
|
64
|
+
|
|
65
|
+
def emit(self, record: logging.LogRecord) -> None:
|
|
66
|
+
try:
|
|
67
|
+
library, text = library_of(record.name), text_of(record)
|
|
68
|
+
self.recent.append(
|
|
69
|
+
(f"{datetime.now():%H:%M:%S}", record.levelname.lower(), library, text)
|
|
70
|
+
)
|
|
71
|
+
if record.levelno >= logging.ERROR:
|
|
72
|
+
self.events.error(f"{library}: {text}")
|
|
73
|
+
except Exception:
|
|
74
|
+
# Nothing a library says may stop it doing what it was doing, and
|
|
75
|
+
# a handler that fails would be reported through logging again.
|
|
76
|
+
pass
|
|
77
|
+
|
|
78
|
+
def lines(self) -> list[str]:
|
|
79
|
+
return [f"{when} {library} {level}: {text}" for when, level, library, text in self.recent]
|
|
80
|
+
|
|
81
|
+
def install(self) -> None:
|
|
82
|
+
"""Listen to the libraries, from information up.
|
|
83
|
+
|
|
84
|
+
Their loggers are left at information once set there: one handler
|
|
85
|
+
removed and another added, as when a workspace is opened in a new
|
|
86
|
+
window, must not leave the next listening to less.
|
|
87
|
+
"""
|
|
88
|
+
for name in LIBRARIES:
|
|
89
|
+
logger = logging.getLogger(name)
|
|
90
|
+
if logger.getEffectiveLevel() > logging.INFO:
|
|
91
|
+
logger.setLevel(logging.INFO)
|
|
92
|
+
logger.addHandler(self)
|
|
93
|
+
|
|
94
|
+
def remove(self) -> None:
|
|
95
|
+
for name in LIBRARIES:
|
|
96
|
+
logging.getLogger(name).removeHandler(self)
|
|
@@ -46,8 +46,8 @@ class Region:
|
|
|
46
46
|
#: show the same flash at several addresses and take writes at only one.
|
|
47
47
|
alias_of: str = ""
|
|
48
48
|
#: What a byte of this flash reads as once erased, as far as the backend
|
|
49
|
-
#: knows: FF for most devices, 00 for some
|
|
50
|
-
#: says
|
|
49
|
+
#: knows: FF for most devices, 00 for some, an XMC4500 among them. FF
|
|
50
|
+
#: where nothing says, so a Memory pane lets it be chosen as well.
|
|
51
51
|
erased: int = 0xFF
|
|
52
52
|
|
|
53
53
|
@property
|
|
@@ -56,8 +56,7 @@ class Worker(QThread):
|
|
|
56
56
|
|
|
57
57
|
The ``done`` connection is dropped first: a queued result arriving
|
|
58
58
|
after the owner has been torn down would be delivered to a dead
|
|
59
|
-
object, which
|
|
60
|
-
exception.
|
|
59
|
+
object, which ends in an access violation rather than an exception.
|
|
61
60
|
"""
|
|
62
61
|
if self._delivering:
|
|
63
62
|
self._delivering = False
|
|
@@ -4,7 +4,31 @@
|
|
|
4
4
|
|
|
5
5
|
**Help > Documentation** opens this manual. It ships inside pyflashgui; *Back* and *Contents* get you around it.
|
|
6
6
|
|
|
7
|
-
**Help > Diagnostics...**
|
|
7
|
+
**Help > Diagnostics...** opens a report in a window of its own, and copies it to the
|
|
8
|
+
clipboard as it opens -- ready to paste into a bug report. **Refresh** makes it again,
|
|
9
|
+
**Copy** copies it again and **Save...** writes it to a file. It gives:
|
|
10
|
+
|
|
11
|
+
- the versions of pyflashgui, Python and every package it runs on;
|
|
12
|
+
- **the connection**: what is chosen under Using, Probe, Target and Speed, what is
|
|
13
|
+
connected, and whether anything is being done; through pyOCD, the options pyOCD was
|
|
14
|
+
given, whether the target is built in, from a pack or the generic Cortex-M, its cores and
|
|
15
|
+
the probe's wire protocol; through J-Link Commander, how Commander is run;
|
|
16
|
+
- the probes found when they were last looked for;
|
|
17
|
+
- the target's memory as pyflashgui has it, and what the memory map says of the files
|
|
18
|
+
ticked;
|
|
19
|
+
- each entry in the Files pane, and the Memory panes open;
|
|
20
|
+
- **the last twenty operations**, in order -- connecting, erasing, programming, reading,
|
|
21
|
+
resetting -- each with how it ended and how long it took;
|
|
22
|
+
- **what pyOCD and pylink said for themselves**: their last hundred lines, from information
|
|
23
|
+
up. Their errors are also put in the Event Log as they happen;
|
|
24
|
+
- the last twenty warnings and errors from the Event Log;
|
|
25
|
+
- J-Link Commander's location and every J-Link software folder found, and pyOCD's packs;
|
|
26
|
+
- the workspace, any confirmation not being asked, and the threads running;
|
|
27
|
+
- the panes out on their own, and how long starting took.
|
|
28
|
+
|
|
29
|
+
A part of it that cannot be read says so and the rest is still given. It names the probes'
|
|
30
|
+
serial numbers, the workspace folder and the files in the Files pane: remove any of that
|
|
31
|
+
from a bug report if you would rather.
|
|
8
32
|
|
|
9
33
|
The report names the version, and -- when pyflashgui is being run from a source checkout rather than an installed build -- the branch or tag and the commit it is on, read from `.git` without running git. Between two releases every build calls itself the same version, so that line is what tells one pull from another. It does not say whether any file has been edited since: that needs git itself, and the launcher runs without a console for it to use.
|
|
10
34
|
|
|
@@ -16,6 +40,6 @@ The report names the version, and -- when pyflashgui is being run from a source
|
|
|
16
40
|
|
|
17
41
|
## If pyflashgui will not start
|
|
18
42
|
|
|
19
|
-
Started from the source folder, `pyflashgui.cmd` and `pyflashgui.sh` install anything missing before they start pyflashgui, and a library that is still missing is reported in a window rather than as nothing happening. The entry made by *Tools > Settings > Add shortcut to Start menu* (*Add shortcut to applications menu* on Linux), or by *Add shortcut to desktop* on Windows, starts the launcher too, so starting from it after a pull does the same. Installed with pip, the same two make a shortcut that starts pyflashgui with the Python it is installed in. The first start says where they are in the Event Log; the launchers ask nothing. On Linux, a library Qt needs to draw the window is
|
|
43
|
+
Started from the source folder, `pyflashgui.cmd` and `pyflashgui.sh` install anything missing before they start pyflashgui, and a library that is still missing is reported in a window rather than as nothing happening. The entry made by *Tools > Settings > Add shortcut to Start menu* (*Add shortcut to applications menu* on Linux), or by *Add shortcut to desktop* on Windows, starts the launcher too, so starting from it after a pull does the same. Installed with pip, the same two make a shortcut that starts pyflashgui with the Python it is installed in. The first start says where they are in the Event Log; the launchers ask nothing. On Linux, a library Qt needs to draw the window is looked for before Qt is started, and one that is missing is named with the package it comes in. `python -m pyflashgui --selftest` imports everything a build is most likely to be missing, checks that pyOCD can find its probe drivers and targets, and exits with 0 if it is all there, which is how the build checks itself.
|
|
20
44
|
|
|
21
45
|
**If pyflashgui is slow to start**, the diagnostics report says where the seconds went: starting is measured on every run, step by step and package by package, so the breakdown is already there when somebody thinks to ask. A start that takes longer than usual says so in the Event Log and points to *Help > Diagnostics*. The first start after an update is the usual one: Python compiles every changed file again, and that start says how many it compiled. To have the whole report in the Event Log after every start, and in `startup-timing.txt` in pyflashgui's own folder, start pyflashgui with `--timing` -- `pyflashgui.cmd --timing`, or `pyflashgui.sh --timing` -- or set `PYFLASHGUI_TIMING=1`.
|