mpflash 0.8.5__py3-none-any.whl → 0.8.7__py3-none-any.whl
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.
- mpflash/add_firmware.py +98 -98
- mpflash/ask_input.py +236 -236
- mpflash/bootloader/__init__.py +37 -36
- mpflash/bootloader/manual.py +102 -102
- mpflash/bootloader/micropython.py +10 -10
- mpflash/bootloader/touch1200.py +45 -45
- mpflash/cli_download.py +129 -129
- mpflash/cli_flash.py +219 -219
- mpflash/cli_group.py +98 -98
- mpflash/cli_list.py +81 -81
- mpflash/cli_main.py +41 -41
- mpflash/common.py +164 -164
- mpflash/config.py +47 -47
- mpflash/connected.py +74 -74
- mpflash/download.py +360 -360
- mpflash/downloaded.py +129 -129
- mpflash/errors.py +9 -9
- mpflash/flash.py +52 -52
- mpflash/flash_esp.py +59 -59
- mpflash/flash_stm32.py +24 -24
- mpflash/flash_stm32_cube.py +111 -111
- mpflash/flash_stm32_dfu.py +101 -101
- mpflash/flash_uf2.py +67 -67
- mpflash/flash_uf2_boardid.py +15 -15
- mpflash/flash_uf2_linux.py +123 -123
- mpflash/flash_uf2_macos.py +34 -34
- mpflash/flash_uf2_windows.py +34 -34
- mpflash/list.py +89 -89
- mpflash/logger.py +41 -41
- mpflash/mpboard_id/__init__.py +93 -93
- mpflash/mpboard_id/add_boards.py +255 -255
- mpflash/mpboard_id/board.py +37 -37
- mpflash/mpboard_id/board_id.py +86 -86
- mpflash/mpboard_id/store.py +43 -43
- mpflash/mpremoteboard/__init__.py +221 -221
- mpflash/mpremoteboard/mpy_fw_info.py +141 -141
- mpflash/mpremoteboard/runner.py +140 -140
- mpflash/uf2disk.py +12 -12
- mpflash/vendor/basicgit.py +288 -288
- mpflash/vendor/click_aliases.py +91 -91
- mpflash/vendor/dfu.py +165 -165
- mpflash/vendor/pydfu.py +605 -605
- mpflash/vendor/readme.md +2 -2
- mpflash/vendor/versions.py +119 -117
- mpflash/worklist.py +170 -170
- {mpflash-0.8.5.dist-info → mpflash-0.8.7.dist-info}/LICENSE +20 -20
- {mpflash-0.8.5.dist-info → mpflash-0.8.7.dist-info}/METADATA +1 -1
- mpflash-0.8.7.dist-info/RECORD +52 -0
- mpflash-0.8.5.dist-info/RECORD +0 -52
- {mpflash-0.8.5.dist-info → mpflash-0.8.7.dist-info}/WHEEL +0 -0
- {mpflash-0.8.5.dist-info → mpflash-0.8.7.dist-info}/entry_points.txt +0 -0
mpflash/flash_uf2_linux.py
CHANGED
@@ -1,123 +1,123 @@
|
|
1
|
-
""" Flashing UF2 based MCU on Linux"""
|
2
|
-
|
3
|
-
# sourcery skip: snake-case-functions
|
4
|
-
from __future__ import annotations
|
5
|
-
|
6
|
-
import subprocess
|
7
|
-
import sys
|
8
|
-
import time
|
9
|
-
from pathlib import Path
|
10
|
-
from typing import List
|
11
|
-
|
12
|
-
from loguru import logger as log
|
13
|
-
from rich.progress import track
|
14
|
-
|
15
|
-
from .flash_uf2_boardid import get_board_id
|
16
|
-
from .uf2disk import UF2Disk
|
17
|
-
|
18
|
-
glb_dismount_me: List[UF2Disk] = []
|
19
|
-
|
20
|
-
|
21
|
-
def get_uf2_drives():
|
22
|
-
"""
|
23
|
-
Get a list of all the (un)mounted UF2 drives
|
24
|
-
"""
|
25
|
-
# import blkinfo only on linux
|
26
|
-
from blkinfo import BlkDiskInfo
|
27
|
-
|
28
|
-
myblkd = BlkDiskInfo()
|
29
|
-
filters = {
|
30
|
-
"tran": "usb",
|
31
|
-
}
|
32
|
-
usb_disks = myblkd.get_disks(filters)
|
33
|
-
for disk in usb_disks:
|
34
|
-
if disk["fstype"] == "vfat":
|
35
|
-
uf2_part = disk
|
36
|
-
# unpartioned usb disk or partition (e.g. /dev/sdb )
|
37
|
-
# SEEED WIO Terminal is unpartioned
|
38
|
-
# print( json.dumps(uf2_part, indent=4))
|
39
|
-
uf2 = UF2Disk()
|
40
|
-
uf2.device_path = "/dev/" + uf2_part["name"]
|
41
|
-
uf2.label = uf2_part["label"]
|
42
|
-
uf2.mountpoint = uf2_part["mountpoint"]
|
43
|
-
yield uf2
|
44
|
-
elif disk["type"] == "disk" and disk.get("children") and len(disk.get("children")) > 0:
|
45
|
-
if disk.get("children")[0]["type"] == "part" and disk.get("children")[0]["fstype"] == "vfat":
|
46
|
-
uf2_part = disk.get("children")[0]
|
47
|
-
# print( json.dumps(uf2_part, indent=4))
|
48
|
-
uf2 = UF2Disk()
|
49
|
-
uf2.device_path = "/dev/" + uf2_part["name"]
|
50
|
-
uf2.label = uf2_part["label"]
|
51
|
-
uf2.mountpoint = uf2_part["mountpoint"]
|
52
|
-
yield uf2
|
53
|
-
|
54
|
-
|
55
|
-
def pmount(disk: UF2Disk):
|
56
|
-
"""
|
57
|
-
Mount a UF2 drive if there is no mountpoint yet.
|
58
|
-
"""
|
59
|
-
global glb_dismount_me
|
60
|
-
if not disk.mountpoint:
|
61
|
-
if not disk.label:
|
62
|
-
disk.label = "UF2BOOT"
|
63
|
-
disk.mountpoint = f"/media/{disk.label}"
|
64
|
-
# capture error if pmount is not installed
|
65
|
-
try:
|
66
|
-
subprocess.run(["pmount", disk.device_path, disk.mountpoint])
|
67
|
-
except FileNotFoundError:
|
68
|
-
log.error("pmount not found, please install it using 'sudo apt install pmount'")
|
69
|
-
return
|
70
|
-
log.debug(f"Mounted {disk.label} at {disk.mountpoint}")
|
71
|
-
glb_dismount_me.append(disk)
|
72
|
-
else:
|
73
|
-
log.debug(f"\n{disk.label} already mounted at {disk.mountpoint}")
|
74
|
-
|
75
|
-
|
76
|
-
def pumount(disk: UF2Disk):
|
77
|
-
"""
|
78
|
-
Unmount a UF2 drive
|
79
|
-
"""
|
80
|
-
if sys.platform != "linux":
|
81
|
-
log.error("pumount only works on Linux")
|
82
|
-
return
|
83
|
-
if disk.mountpoint:
|
84
|
-
subprocess.run(["pumount", disk.mountpoint]) # ), f"/media/{disk.label}"])
|
85
|
-
log.info(f"Unmounted {disk.label} from {disk.mountpoint}")
|
86
|
-
disk.mountpoint = f""
|
87
|
-
else:
|
88
|
-
log.warning(f"{disk.label} already dismounted")
|
89
|
-
|
90
|
-
|
91
|
-
def dismount_uf2_linux():
|
92
|
-
global glb_dismount_me
|
93
|
-
for disk in glb_dismount_me:
|
94
|
-
pumount(disk)
|
95
|
-
glb_dismount_me = []
|
96
|
-
|
97
|
-
|
98
|
-
def wait_for_UF2_linux(s_max: int = 10):
|
99
|
-
destination = ""
|
100
|
-
wait = 10
|
101
|
-
uf2_drives = []
|
102
|
-
# while not destination and wait > 0:
|
103
|
-
for _ in track(
|
104
|
-
range(s_max), description="Waiting for mcu to mount as a drive", transient=True, refresh_per_second=2
|
105
|
-
):
|
106
|
-
# log.info(f"Waiting for mcu to mount as a drive : {wait} seconds left")
|
107
|
-
uf2_drives += list(get_uf2_drives())
|
108
|
-
for drive in get_uf2_drives():
|
109
|
-
pmount(drive)
|
110
|
-
time.sleep(1)
|
111
|
-
try:
|
112
|
-
if Path(drive.mountpoint, "INFO_UF2.TXT").exists():
|
113
|
-
board_id = get_board_id(Path(drive.mountpoint)) # type: ignore
|
114
|
-
destination = Path(drive.mountpoint)
|
115
|
-
break
|
116
|
-
except PermissionError:
|
117
|
-
log.debug(f"Permission error on {drive.mountpoint}")
|
118
|
-
continue
|
119
|
-
if destination:
|
120
|
-
break
|
121
|
-
time.sleep(1)
|
122
|
-
wait -= 1
|
123
|
-
return destination
|
1
|
+
""" Flashing UF2 based MCU on Linux"""
|
2
|
+
|
3
|
+
# sourcery skip: snake-case-functions
|
4
|
+
from __future__ import annotations
|
5
|
+
|
6
|
+
import subprocess
|
7
|
+
import sys
|
8
|
+
import time
|
9
|
+
from pathlib import Path
|
10
|
+
from typing import List
|
11
|
+
|
12
|
+
from loguru import logger as log
|
13
|
+
from rich.progress import track
|
14
|
+
|
15
|
+
from .flash_uf2_boardid import get_board_id
|
16
|
+
from .uf2disk import UF2Disk
|
17
|
+
|
18
|
+
glb_dismount_me: List[UF2Disk] = []
|
19
|
+
|
20
|
+
|
21
|
+
def get_uf2_drives():
|
22
|
+
"""
|
23
|
+
Get a list of all the (un)mounted UF2 drives
|
24
|
+
"""
|
25
|
+
# import blkinfo only on linux
|
26
|
+
from blkinfo import BlkDiskInfo
|
27
|
+
|
28
|
+
myblkd = BlkDiskInfo()
|
29
|
+
filters = {
|
30
|
+
"tran": "usb",
|
31
|
+
}
|
32
|
+
usb_disks = myblkd.get_disks(filters)
|
33
|
+
for disk in usb_disks:
|
34
|
+
if disk["fstype"] == "vfat":
|
35
|
+
uf2_part = disk
|
36
|
+
# unpartioned usb disk or partition (e.g. /dev/sdb )
|
37
|
+
# SEEED WIO Terminal is unpartioned
|
38
|
+
# print( json.dumps(uf2_part, indent=4))
|
39
|
+
uf2 = UF2Disk()
|
40
|
+
uf2.device_path = "/dev/" + uf2_part["name"]
|
41
|
+
uf2.label = uf2_part["label"]
|
42
|
+
uf2.mountpoint = uf2_part["mountpoint"]
|
43
|
+
yield uf2
|
44
|
+
elif disk["type"] == "disk" and disk.get("children") and len(disk.get("children")) > 0:
|
45
|
+
if disk.get("children")[0]["type"] == "part" and disk.get("children")[0]["fstype"] == "vfat":
|
46
|
+
uf2_part = disk.get("children")[0]
|
47
|
+
# print( json.dumps(uf2_part, indent=4))
|
48
|
+
uf2 = UF2Disk()
|
49
|
+
uf2.device_path = "/dev/" + uf2_part["name"]
|
50
|
+
uf2.label = uf2_part["label"]
|
51
|
+
uf2.mountpoint = uf2_part["mountpoint"]
|
52
|
+
yield uf2
|
53
|
+
|
54
|
+
|
55
|
+
def pmount(disk: UF2Disk):
|
56
|
+
"""
|
57
|
+
Mount a UF2 drive if there is no mountpoint yet.
|
58
|
+
"""
|
59
|
+
global glb_dismount_me
|
60
|
+
if not disk.mountpoint:
|
61
|
+
if not disk.label:
|
62
|
+
disk.label = "UF2BOOT"
|
63
|
+
disk.mountpoint = f"/media/{disk.label}"
|
64
|
+
# capture error if pmount is not installed
|
65
|
+
try:
|
66
|
+
subprocess.run(["pmount", disk.device_path, disk.mountpoint])
|
67
|
+
except FileNotFoundError:
|
68
|
+
log.error("pmount not found, please install it using 'sudo apt install pmount'")
|
69
|
+
return
|
70
|
+
log.debug(f"Mounted {disk.label} at {disk.mountpoint}")
|
71
|
+
glb_dismount_me.append(disk)
|
72
|
+
else:
|
73
|
+
log.debug(f"\n{disk.label} already mounted at {disk.mountpoint}")
|
74
|
+
|
75
|
+
|
76
|
+
def pumount(disk: UF2Disk):
|
77
|
+
"""
|
78
|
+
Unmount a UF2 drive
|
79
|
+
"""
|
80
|
+
if sys.platform != "linux":
|
81
|
+
log.error("pumount only works on Linux")
|
82
|
+
return
|
83
|
+
if disk.mountpoint:
|
84
|
+
subprocess.run(["pumount", disk.mountpoint]) # ), f"/media/{disk.label}"])
|
85
|
+
log.info(f"Unmounted {disk.label} from {disk.mountpoint}")
|
86
|
+
disk.mountpoint = f""
|
87
|
+
else:
|
88
|
+
log.warning(f"{disk.label} already dismounted")
|
89
|
+
|
90
|
+
|
91
|
+
def dismount_uf2_linux():
|
92
|
+
global glb_dismount_me
|
93
|
+
for disk in glb_dismount_me:
|
94
|
+
pumount(disk)
|
95
|
+
glb_dismount_me = []
|
96
|
+
|
97
|
+
|
98
|
+
def wait_for_UF2_linux(s_max: int = 10):
|
99
|
+
destination = ""
|
100
|
+
wait = 10
|
101
|
+
uf2_drives = []
|
102
|
+
# while not destination and wait > 0:
|
103
|
+
for _ in track(
|
104
|
+
range(s_max), description="Waiting for mcu to mount as a drive", transient=True, refresh_per_second=2
|
105
|
+
):
|
106
|
+
# log.info(f"Waiting for mcu to mount as a drive : {wait} seconds left")
|
107
|
+
uf2_drives += list(get_uf2_drives())
|
108
|
+
for drive in get_uf2_drives():
|
109
|
+
pmount(drive)
|
110
|
+
time.sleep(1)
|
111
|
+
try:
|
112
|
+
if Path(drive.mountpoint, "INFO_UF2.TXT").exists():
|
113
|
+
board_id = get_board_id(Path(drive.mountpoint)) # type: ignore
|
114
|
+
destination = Path(drive.mountpoint)
|
115
|
+
break
|
116
|
+
except PermissionError:
|
117
|
+
log.debug(f"Permission error on {drive.mountpoint}")
|
118
|
+
continue
|
119
|
+
if destination:
|
120
|
+
break
|
121
|
+
time.sleep(1)
|
122
|
+
wait -= 1
|
123
|
+
return destination
|
mpflash/flash_uf2_macos.py
CHANGED
@@ -1,34 +1,34 @@
|
|
1
|
-
""" Flashing UF2 based MCU on macos"""
|
2
|
-
|
3
|
-
# sourcery skip: snake-case-functions
|
4
|
-
from __future__ import annotations
|
5
|
-
|
6
|
-
import time
|
7
|
-
from pathlib import Path
|
8
|
-
from typing import Optional
|
9
|
-
|
10
|
-
from rich.progress import track
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
def wait_for_UF2_macos(s_max: int = 10) -> Optional[Path]:
|
15
|
-
"""Wait for the MCU to mount as a drive"""
|
16
|
-
if s_max < 1:
|
17
|
-
s_max = 10
|
18
|
-
destination = None
|
19
|
-
for _ in track(
|
20
|
-
range(s_max), description="Waiting for mcu to mount as a drive", transient=True, refresh_per_second=2
|
21
|
-
):
|
22
|
-
# log.info(f"Waiting for mcu to mount as a drive : {n} seconds left")
|
23
|
-
vol_mounts = Path("/Volumes").iterdir()
|
24
|
-
for vol in vol_mounts:
|
25
|
-
try:
|
26
|
-
if Path(vol, "INFO_UF2.TXT").exists():
|
27
|
-
destination = Path(vol)
|
28
|
-
break
|
29
|
-
except OSError:
|
30
|
-
pass
|
31
|
-
if destination:
|
32
|
-
break
|
33
|
-
time.sleep(1)
|
34
|
-
return destination
|
1
|
+
""" Flashing UF2 based MCU on macos"""
|
2
|
+
|
3
|
+
# sourcery skip: snake-case-functions
|
4
|
+
from __future__ import annotations
|
5
|
+
|
6
|
+
import time
|
7
|
+
from pathlib import Path
|
8
|
+
from typing import Optional
|
9
|
+
|
10
|
+
from rich.progress import track
|
11
|
+
|
12
|
+
|
13
|
+
|
14
|
+
def wait_for_UF2_macos(s_max: int = 10) -> Optional[Path]:
|
15
|
+
"""Wait for the MCU to mount as a drive"""
|
16
|
+
if s_max < 1:
|
17
|
+
s_max = 10
|
18
|
+
destination = None
|
19
|
+
for _ in track(
|
20
|
+
range(s_max), description="Waiting for mcu to mount as a drive", transient=True, refresh_per_second=2
|
21
|
+
):
|
22
|
+
# log.info(f"Waiting for mcu to mount as a drive : {n} seconds left")
|
23
|
+
vol_mounts = Path("/Volumes").iterdir()
|
24
|
+
for vol in vol_mounts:
|
25
|
+
try:
|
26
|
+
if Path(vol, "INFO_UF2.TXT").exists():
|
27
|
+
destination = Path(vol)
|
28
|
+
break
|
29
|
+
except OSError:
|
30
|
+
pass
|
31
|
+
if destination:
|
32
|
+
break
|
33
|
+
time.sleep(1)
|
34
|
+
return destination
|
mpflash/flash_uf2_windows.py
CHANGED
@@ -1,34 +1,34 @@
|
|
1
|
-
# sourcery skip: snake-case-functions
|
2
|
-
"""Flash a MCU with a UF2 bootloader on Windows"""
|
3
|
-
|
4
|
-
from __future__ import annotations
|
5
|
-
|
6
|
-
import time
|
7
|
-
from pathlib import Path
|
8
|
-
from typing import Optional
|
9
|
-
|
10
|
-
import psutil
|
11
|
-
from rich.progress import track
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
def wait_for_UF2_windows(s_max: int = 10) -> Optional[Path]:
|
17
|
-
"""Wait for the MCU to mount as a drive"""
|
18
|
-
if s_max < 1:
|
19
|
-
s_max = 10
|
20
|
-
destination = None
|
21
|
-
for _ in track(range(s_max), description="Waiting for mcu to mount as a drive", transient=True,refresh_per_second=2):
|
22
|
-
# log.info(f"Waiting for mcu to mount as a drive : {n} seconds left")
|
23
|
-
drives = [drive.device for drive in psutil.disk_partitions()]
|
24
|
-
for drive in drives:
|
25
|
-
try:
|
26
|
-
if Path(drive, "INFO_UF2.TXT").exists():
|
27
|
-
destination = Path(drive)
|
28
|
-
break
|
29
|
-
except OSError:
|
30
|
-
pass
|
31
|
-
if destination:
|
32
|
-
break
|
33
|
-
time.sleep(1)
|
34
|
-
return destination
|
1
|
+
# sourcery skip: snake-case-functions
|
2
|
+
"""Flash a MCU with a UF2 bootloader on Windows"""
|
3
|
+
|
4
|
+
from __future__ import annotations
|
5
|
+
|
6
|
+
import time
|
7
|
+
from pathlib import Path
|
8
|
+
from typing import Optional
|
9
|
+
|
10
|
+
import psutil
|
11
|
+
from rich.progress import track
|
12
|
+
|
13
|
+
|
14
|
+
|
15
|
+
|
16
|
+
def wait_for_UF2_windows(s_max: int = 10) -> Optional[Path]:
|
17
|
+
"""Wait for the MCU to mount as a drive"""
|
18
|
+
if s_max < 1:
|
19
|
+
s_max = 10
|
20
|
+
destination = None
|
21
|
+
for _ in track(range(s_max), description="Waiting for mcu to mount as a drive", transient=True,refresh_per_second=2):
|
22
|
+
# log.info(f"Waiting for mcu to mount as a drive : {n} seconds left")
|
23
|
+
drives = [drive.device for drive in psutil.disk_partitions()]
|
24
|
+
for drive in drives:
|
25
|
+
try:
|
26
|
+
if Path(drive, "INFO_UF2.TXT").exists():
|
27
|
+
destination = Path(drive)
|
28
|
+
break
|
29
|
+
except OSError:
|
30
|
+
pass
|
31
|
+
if destination:
|
32
|
+
break
|
33
|
+
time.sleep(1)
|
34
|
+
return destination
|
mpflash/list.py
CHANGED
@@ -1,89 +1,89 @@
|
|
1
|
-
from typing import List
|
2
|
-
|
3
|
-
from rich.progress import track
|
4
|
-
from rich.table import Table
|
5
|
-
|
6
|
-
from mpflash.mpremoteboard import MPRemoteBoard
|
7
|
-
from mpflash.vendor.versions import clean_version
|
8
|
-
|
9
|
-
from .logger import console
|
10
|
-
|
11
|
-
|
12
|
-
def show_mcus(
|
13
|
-
conn_mcus: List[MPRemoteBoard],
|
14
|
-
title: str = "Connected boards",
|
15
|
-
refresh: bool = True,
|
16
|
-
):
|
17
|
-
console.print(mcu_table(conn_mcus, title, refresh))
|
18
|
-
|
19
|
-
|
20
|
-
def abbrv_family(family: str, is_wide: bool) -> str:
|
21
|
-
if not is_wide:
|
22
|
-
ABRV = {"micropython": "upy", "circuitpython": "cpy", "unknown": "?"}
|
23
|
-
return ABRV.get(family, family[:4])
|
24
|
-
return family
|
25
|
-
|
26
|
-
|
27
|
-
def mcu_table(
|
28
|
-
conn_mcus: List[MPRemoteBoard],
|
29
|
-
title: str = "Connected boards",
|
30
|
-
refresh: bool = True,
|
31
|
-
):
|
32
|
-
"""
|
33
|
-
builds a rich table with the connected boards information
|
34
|
-
The columns of the table are adjusted to the terminal width
|
35
|
-
the columns are :
|
36
|
-
Narrow Wide
|
37
|
-
- Serial Yes Yes
|
38
|
-
- Family abbrv. Yes
|
39
|
-
- Port - yes
|
40
|
-
- Board Yes Yes BOARD_ID and Description
|
41
|
-
- CPU - Yes
|
42
|
-
- Version Yes Yes
|
43
|
-
- Build * * only if any of the mcus have a build
|
44
|
-
"""
|
45
|
-
table = Table(
|
46
|
-
title=title,
|
47
|
-
title_style="magenta",
|
48
|
-
header_style="bold magenta",
|
49
|
-
collapse_padding=True,
|
50
|
-
padding=(0, 0),
|
51
|
-
)
|
52
|
-
# check if the terminal is wide enough to show all columns or if we need to collapse some
|
53
|
-
is_wide = console.width > 99
|
54
|
-
needs_build = any(mcu.build for mcu in conn_mcus)
|
55
|
-
|
56
|
-
table.add_column("Serial" if is_wide else "Ser.", overflow="fold")
|
57
|
-
table.add_column("Family" if is_wide else "Fam.", overflow="crop", max_width=None if is_wide else 4)
|
58
|
-
if is_wide:
|
59
|
-
table.add_column("Port")
|
60
|
-
table.add_column("Board", overflow="fold")
|
61
|
-
# table.add_column("Variant") # TODO: add variant
|
62
|
-
if is_wide:
|
63
|
-
table.add_column("CPU")
|
64
|
-
table.add_column("Version", overflow="fold", min_width=5, max_width=16)
|
65
|
-
if needs_build:
|
66
|
-
table.add_column("Build" if is_wide else "Bld", justify="right")
|
67
|
-
|
68
|
-
for mcu in track(conn_mcus, description="Updating board info", transient=True, refresh_per_second=2):
|
69
|
-
if refresh:
|
70
|
-
try:
|
71
|
-
mcu.get_mcu_info()
|
72
|
-
except ConnectionError:
|
73
|
-
continue
|
74
|
-
description = f"[italic bright_cyan]{mcu.description}" if mcu.description else ""
|
75
|
-
row = [
|
76
|
-
mcu.serialport.replace("/dev/", ""),
|
77
|
-
abbrv_family(mcu.family, is_wide),
|
78
|
-
]
|
79
|
-
if is_wide:
|
80
|
-
row.append(mcu.port)
|
81
|
-
row.append(f"{mcu.board}\n{description}".strip())
|
82
|
-
if is_wide:
|
83
|
-
row.append(mcu.cpu)
|
84
|
-
row.append(clean_version(mcu.version))
|
85
|
-
if needs_build:
|
86
|
-
row.append(mcu.build)
|
87
|
-
|
88
|
-
table.add_row(*row)
|
89
|
-
return table
|
1
|
+
from typing import List
|
2
|
+
|
3
|
+
from rich.progress import track
|
4
|
+
from rich.table import Table
|
5
|
+
|
6
|
+
from mpflash.mpremoteboard import MPRemoteBoard
|
7
|
+
from mpflash.vendor.versions import clean_version
|
8
|
+
|
9
|
+
from .logger import console
|
10
|
+
|
11
|
+
|
12
|
+
def show_mcus(
|
13
|
+
conn_mcus: List[MPRemoteBoard],
|
14
|
+
title: str = "Connected boards",
|
15
|
+
refresh: bool = True,
|
16
|
+
):
|
17
|
+
console.print(mcu_table(conn_mcus, title, refresh))
|
18
|
+
|
19
|
+
|
20
|
+
def abbrv_family(family: str, is_wide: bool) -> str:
|
21
|
+
if not is_wide:
|
22
|
+
ABRV = {"micropython": "upy", "circuitpython": "cpy", "unknown": "?"}
|
23
|
+
return ABRV.get(family, family[:4])
|
24
|
+
return family
|
25
|
+
|
26
|
+
|
27
|
+
def mcu_table(
|
28
|
+
conn_mcus: List[MPRemoteBoard],
|
29
|
+
title: str = "Connected boards",
|
30
|
+
refresh: bool = True,
|
31
|
+
):
|
32
|
+
"""
|
33
|
+
builds a rich table with the connected boards information
|
34
|
+
The columns of the table are adjusted to the terminal width
|
35
|
+
the columns are :
|
36
|
+
Narrow Wide
|
37
|
+
- Serial Yes Yes
|
38
|
+
- Family abbrv. Yes
|
39
|
+
- Port - yes
|
40
|
+
- Board Yes Yes BOARD_ID and Description
|
41
|
+
- CPU - Yes
|
42
|
+
- Version Yes Yes
|
43
|
+
- Build * * only if any of the mcus have a build
|
44
|
+
"""
|
45
|
+
table = Table(
|
46
|
+
title=title,
|
47
|
+
title_style="magenta",
|
48
|
+
header_style="bold magenta",
|
49
|
+
collapse_padding=True,
|
50
|
+
padding=(0, 0),
|
51
|
+
)
|
52
|
+
# check if the terminal is wide enough to show all columns or if we need to collapse some
|
53
|
+
is_wide = console.width > 99
|
54
|
+
needs_build = any(mcu.build for mcu in conn_mcus)
|
55
|
+
|
56
|
+
table.add_column("Serial" if is_wide else "Ser.", overflow="fold")
|
57
|
+
table.add_column("Family" if is_wide else "Fam.", overflow="crop", max_width=None if is_wide else 4)
|
58
|
+
if is_wide:
|
59
|
+
table.add_column("Port")
|
60
|
+
table.add_column("Board", overflow="fold")
|
61
|
+
# table.add_column("Variant") # TODO: add variant
|
62
|
+
if is_wide:
|
63
|
+
table.add_column("CPU")
|
64
|
+
table.add_column("Version", overflow="fold", min_width=5, max_width=16)
|
65
|
+
if needs_build:
|
66
|
+
table.add_column("Build" if is_wide else "Bld", justify="right")
|
67
|
+
|
68
|
+
for mcu in track(conn_mcus, description="Updating board info", transient=True, refresh_per_second=2):
|
69
|
+
if refresh:
|
70
|
+
try:
|
71
|
+
mcu.get_mcu_info()
|
72
|
+
except ConnectionError:
|
73
|
+
continue
|
74
|
+
description = f"[italic bright_cyan]{mcu.description}" if mcu.description else ""
|
75
|
+
row = [
|
76
|
+
mcu.serialport.replace("/dev/", ""),
|
77
|
+
abbrv_family(mcu.family, is_wide),
|
78
|
+
]
|
79
|
+
if is_wide:
|
80
|
+
row.append(mcu.port)
|
81
|
+
row.append(f"{mcu.board}\n{description}".strip())
|
82
|
+
if is_wide:
|
83
|
+
row.append(mcu.cpu)
|
84
|
+
row.append(clean_version(mcu.version))
|
85
|
+
if needs_build:
|
86
|
+
row.append(mcu.build)
|
87
|
+
|
88
|
+
table.add_row(*row)
|
89
|
+
return table
|
mpflash/logger.py
CHANGED
@@ -1,41 +1,41 @@
|
|
1
|
-
"""Logging."""
|
2
|
-
|
3
|
-
from loguru import logger as log
|
4
|
-
from rich.console import Console
|
5
|
-
|
6
|
-
from .config import config
|
7
|
-
|
8
|
-
console = Console()
|
9
|
-
|
10
|
-
|
11
|
-
def _log_formatter(record: dict) -> str:
|
12
|
-
"""Log message formatter to combine loguru and rich formatting."""
|
13
|
-
color_map = {
|
14
|
-
"TRACE": "dim blue",
|
15
|
-
"DEBUG": "cyan",
|
16
|
-
"INFO": "bold",
|
17
|
-
"SUCCESS": "bold green",
|
18
|
-
"WARNING": "yellow",
|
19
|
-
"ERROR": "bold red",
|
20
|
-
"CRITICAL": "bold white on red",
|
21
|
-
}
|
22
|
-
lvl_color = color_map.get(record["level"].name, "cyan")
|
23
|
-
return (
|
24
|
-
"[not bold green]{time:HH:mm:ss}[/not bold green] | {level.icon} " + f"[{lvl_color}]{{message}}[/{lvl_color}]"
|
25
|
-
)
|
26
|
-
|
27
|
-
|
28
|
-
def set_loglevel(loglevel: str):
|
29
|
-
"""Set the log level for the logger"""
|
30
|
-
try:
|
31
|
-
log.remove()
|
32
|
-
except ValueError:
|
33
|
-
pass
|
34
|
-
log.add(console.print, level=loglevel.upper(), colorize=False, format=_log_formatter) # type: ignore
|
35
|
-
|
36
|
-
|
37
|
-
def make_quiet():
|
38
|
-
"""Make the logger quiet"""
|
39
|
-
config.quiet = True
|
40
|
-
console.quiet = True
|
41
|
-
set_loglevel("CRITICAL")
|
1
|
+
"""Logging."""
|
2
|
+
|
3
|
+
from loguru import logger as log
|
4
|
+
from rich.console import Console
|
5
|
+
|
6
|
+
from .config import config
|
7
|
+
|
8
|
+
console = Console()
|
9
|
+
|
10
|
+
|
11
|
+
def _log_formatter(record: dict) -> str:
|
12
|
+
"""Log message formatter to combine loguru and rich formatting."""
|
13
|
+
color_map = {
|
14
|
+
"TRACE": "dim blue",
|
15
|
+
"DEBUG": "cyan",
|
16
|
+
"INFO": "bold",
|
17
|
+
"SUCCESS": "bold green",
|
18
|
+
"WARNING": "yellow",
|
19
|
+
"ERROR": "bold red",
|
20
|
+
"CRITICAL": "bold white on red",
|
21
|
+
}
|
22
|
+
lvl_color = color_map.get(record["level"].name, "cyan")
|
23
|
+
return (
|
24
|
+
"[not bold green]{time:HH:mm:ss}[/not bold green] | {level.icon} " + f"[{lvl_color}]{{message}}[/{lvl_color}]"
|
25
|
+
)
|
26
|
+
|
27
|
+
|
28
|
+
def set_loglevel(loglevel: str):
|
29
|
+
"""Set the log level for the logger"""
|
30
|
+
try:
|
31
|
+
log.remove()
|
32
|
+
except ValueError:
|
33
|
+
pass
|
34
|
+
log.add(console.print, level=loglevel.upper(), colorize=False, format=_log_formatter) # type: ignore
|
35
|
+
|
36
|
+
|
37
|
+
def make_quiet():
|
38
|
+
"""Make the logger quiet"""
|
39
|
+
config.quiet = True
|
40
|
+
console.quiet = True
|
41
|
+
set_loglevel("CRITICAL")
|