mpflash 1.25.0.post1__py3-none-any.whl → 1.25.0rc1__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.
Files changed (50) hide show
  1. mpflash/add_firmware.py +43 -16
  2. mpflash/ask_input.py +4 -3
  3. mpflash/basicgit.py +1 -1
  4. mpflash/bootloader/manual.py +1 -1
  5. mpflash/cli_download.py +8 -5
  6. mpflash/cli_flash.py +17 -33
  7. mpflash/cli_group.py +3 -0
  8. mpflash/cli_list.py +2 -2
  9. mpflash/cli_main.py +4 -0
  10. mpflash/common.py +1 -36
  11. mpflash/config.py +21 -0
  12. mpflash/db/__init__.py +2 -0
  13. mpflash/db/core.py +56 -0
  14. mpflash/db/gather_boards.py +112 -0
  15. mpflash/db/loader.py +120 -0
  16. mpflash/db/meta.py +78 -0
  17. mpflash/db/micropython_boards.zip +0 -0
  18. mpflash/db/models.py +93 -0
  19. mpflash/db/tools.py +27 -0
  20. mpflash/download/__init__.py +46 -64
  21. mpflash/download/from_web.py +26 -36
  22. mpflash/download/fwinfo.py +41 -0
  23. mpflash/download/jid.py +53 -0
  24. mpflash/downloaded.py +79 -93
  25. mpflash/flash/__init__.py +7 -3
  26. mpflash/flash/esp.py +2 -1
  27. mpflash/flash/worklist.py +16 -28
  28. mpflash/list.py +3 -3
  29. mpflash/logger.py +2 -2
  30. mpflash/mpboard_id/__init__.py +3 -9
  31. mpflash/mpboard_id/alternate.py +56 -0
  32. mpflash/mpboard_id/board_id.py +11 -94
  33. mpflash/mpboard_id/known.py +44 -56
  34. mpflash/mpboard_id/resolve.py +19 -0
  35. mpflash/mpremoteboard/__init__.py +1 -1
  36. mpflash/mpremoteboard/mpy_fw_info.py +1 -0
  37. mpflash/mpremoteboard/runner.py +5 -2
  38. mpflash/vendor/pydfu.py +4 -5
  39. mpflash/versions.py +3 -0
  40. {mpflash-1.25.0.post1.dist-info → mpflash-1.25.0rc1.dist-info}/METADATA +2 -2
  41. mpflash-1.25.0rc1.dist-info/RECORD +69 -0
  42. mpflash/db/boards.py +0 -63
  43. mpflash/db/downloads.py +0 -87
  44. mpflash/mpboard_id/add_boards.py +0 -260
  45. mpflash/mpboard_id/board.py +0 -40
  46. mpflash/mpboard_id/store.py +0 -47
  47. mpflash-1.25.0.post1.dist-info/RECORD +0 -62
  48. {mpflash-1.25.0.post1.dist-info → mpflash-1.25.0rc1.dist-info}/LICENSE +0 -0
  49. {mpflash-1.25.0.post1.dist-info → mpflash-1.25.0rc1.dist-info}/WHEEL +0 -0
  50. {mpflash-1.25.0.post1.dist-info → mpflash-1.25.0rc1.dist-info}/entry_points.txt +0 -0
mpflash/db/loader.py ADDED
@@ -0,0 +1,120 @@
1
+ import csv
2
+ import io
3
+ import json
4
+ import re
5
+ import zipfile
6
+ from pathlib import Path
7
+ from turtle import up
8
+
9
+ from loguru import logger as log
10
+
11
+ from mpflash.errors import MPFlashError
12
+
13
+ from .core import Session
14
+ from .meta import get_metadata, set_metadata_value
15
+ from .models import Board, Firmware
16
+
17
+ HERE = Path(__file__).parent.resolve()
18
+
19
+
20
+ def load_data_from_zip(zip_file: Path) -> int:
21
+ log.debug("Loading data from zip file")
22
+ csv_filename = "micropython_boards.csv" # name of the .csv inside the .zip
23
+ # Check if the zip file exists
24
+ if not zip_file.exists() or not zip_file.is_file():
25
+ log.error(f"Zip file {zip_file} not found.")
26
+ return 0
27
+ count = 0
28
+ # Load data directly from the zip file
29
+ with zipfile.ZipFile(zip_file, "r") as zipf:
30
+ # Read the CSV file from the zip
31
+ with zipf.open(csv_filename) as csv_file:
32
+ log.info("Reading CSV data...")
33
+ reader = csv.DictReader(io.TextIOWrapper(csv_file, "utf-8"))
34
+
35
+ # save to database
36
+ with Session() as session:
37
+ for row in reader:
38
+ # Create a board instance from the row data
39
+ board = Board(**row)
40
+ # Use merge to update existing or insert new record
41
+ # based on primary key (board_id and version)
42
+ session.merge(board)
43
+ count += 1
44
+ session.commit()
45
+ log.info(f"Loaded {count} boards from {zip_file}")
46
+ return count
47
+
48
+
49
+ def load_jsonl_to_db(jsonl_path: Path):
50
+ """
51
+ Load a JSONL file into a SQLite database
52
+
53
+ Args:
54
+ jsonl_path (Path): Path to the JSONL file
55
+ conn (sqlite3.Connection): SQLite database connection
56
+ table_name (str): Name of the table to insert data into
57
+
58
+ Returns:
59
+ int: Number of records imported
60
+ """
61
+ log.debug("Loading JSONL file into database")
62
+ # Ensure file exists
63
+ if not jsonl_path.exists():
64
+ raise FileNotFoundError(f"JSONL file not found: {jsonl_path}")
65
+ num_records = 0
66
+ with jsonl_path.open("r", encoding="utf-8") as file:
67
+ with Session() as session:
68
+ for line in file:
69
+ record = json.loads(line.strip())
70
+ # Clean up the record
71
+
72
+ if "variant" in record:
73
+ record["board_id"] = record.pop("variant") # Rename 'variant' to 'board_id'
74
+ if "firmware" in record:
75
+ record["source"] = record.pop("firmware") # Rename 'firmware' to 'source'
76
+ if "preview" in record:
77
+ record["version"] = f"{record['version']}-preview" if record["preview"] else record["version"]
78
+ record.pop("preview", None) # Remove 'preview' column
79
+ firmware_file = str(Path(record["filename"]).as_posix()) if record["filename"] else ""
80
+
81
+ # Check if Firmware with this firmware_file exists
82
+ existing_fw = session.query(Firmware).filter_by(firmware_file=firmware_file).first()
83
+ if existing_fw:
84
+ # Update fields
85
+ existing_fw.board_id = record["board_id"]
86
+ existing_fw.version = record["version"]
87
+ existing_fw.source = record["source"]
88
+ existing_fw.build = record["build"]
89
+ existing_fw.custom = record["custom"]
90
+ existing_fw.port = record["port"]
91
+ else:
92
+ # Add new Firmware
93
+ fw = Firmware(
94
+ board_id=record["board_id"],
95
+ version=record["version"],
96
+ firmware_file=firmware_file,
97
+ source=record["source"],
98
+ build=record["build"],
99
+ custom=record["custom"],
100
+ port=record["port"],
101
+ )
102
+ session.merge(fw)
103
+ num_records += 1
104
+ # commit once after all records are processed
105
+ session.commit()
106
+ return num_records
107
+
108
+
109
+ def update_boards():
110
+ try:
111
+ meta = get_metadata()
112
+ log.debug(f"Metadata: {meta}")
113
+ if meta.get("boards_version", "") < "v1.25.0":
114
+ log.info("Update boards from CSV to SQLite database.")
115
+ # Load data from the zip file into the database
116
+ load_data_from_zip(HERE / "micropython_boards.zip")
117
+ set_metadata_value("boards_version", "v1.25.0")
118
+ meta = get_metadata()
119
+ except Exception as e:
120
+ raise MPFlashError(f"Error updating boards table: {e}") from e
mpflash/db/meta.py ADDED
@@ -0,0 +1,78 @@
1
+
2
+ from typing import Optional
3
+
4
+ from loguru import logger as log
5
+
6
+ from .core import DatabaseError, OperationalError, Session
7
+ from .models import Metadata
8
+
9
+
10
+ def get_metadata() -> dict:
11
+ """
12
+ Get all metadata from the database.
13
+
14
+ Returns:
15
+ dict: Dictionary of metadata name-value pairs.
16
+ """
17
+ try:
18
+ with Session() as session:
19
+ metadata = session.query(Metadata).all()
20
+ return {m.name: m.value for m in metadata}
21
+ except (DatabaseError, OperationalError) as e:
22
+ log.error(f"Error retrieving metadata: {e}")
23
+ return {}
24
+
25
+ def set_metadata(metadata: dict):
26
+ """
27
+ Set metadata in the database.
28
+ Args:
29
+ metadata (dict): Dictionary of metadata name-value pairs.
30
+ Returns:
31
+ None
32
+ """
33
+ with Session() as session:
34
+ for name, value in metadata.items():
35
+ existing_metadata = session.query(Metadata).filter(Metadata.name == name).first()
36
+ if existing_metadata:
37
+ existing_metadata.value = value
38
+ else:
39
+ new_metadata = Metadata(name=name, value=value)
40
+ session.add(new_metadata)
41
+ session.commit()
42
+
43
+
44
+ def get_metadata_value(name: str) -> Optional[str]:
45
+ """
46
+ Get metadata value by name.
47
+
48
+ Args:
49
+ session (Session): SQLAlchemy session.
50
+ name (str): Name of the metadata.
51
+
52
+ Returns:
53
+ Optional[str]: Metadata value or None if not found.
54
+ """
55
+ with Session() as session:
56
+ metadata = session.query(Metadata).filter(Metadata.name == name).first()
57
+ return metadata.value if metadata else None
58
+
59
+ def set_metadata_value(name: str, value: str):
60
+ """
61
+ Set metadata value by name.
62
+
63
+ Args:
64
+ session (Session): SQLAlchemy session.
65
+ name (str): Name of the metadata.
66
+ value (str): Value to set.
67
+
68
+ Returns:
69
+ None
70
+ """
71
+ with Session() as session:
72
+ metadata = session.query(Metadata).filter(Metadata.name == name).first()
73
+ if metadata:
74
+ metadata.value = value
75
+ else:
76
+ new_metadata = Metadata(name=name, value=value)
77
+ session.add(new_metadata)
78
+ session.commit()
Binary file
mpflash/db/models.py ADDED
@@ -0,0 +1,93 @@
1
+ from pathlib import Path
2
+
3
+ import sqlalchemy as sa
4
+ from sqlalchemy import Index, String
5
+ from sqlalchemy.orm import DeclarativeBase, Mapped, composite, mapped_column, relationship
6
+
7
+
8
+ class Base(DeclarativeBase):
9
+ pass
10
+
11
+
12
+ class Metadata(Base):
13
+ """
14
+ Configuration information.
15
+ """
16
+
17
+ __tablename__ = "metadata"
18
+ name: Mapped[str] = mapped_column(primary_key=True, unique=True)
19
+ value: Mapped[str] = mapped_column()
20
+
21
+ def __repr__(self) -> str:
22
+ return f"Config(boards_version={self.name!r}, schema_version={self.value!r})"
23
+
24
+
25
+ class Board(Base):
26
+ """
27
+ All known Boards model for storing board information.
28
+ """
29
+
30
+ __tablename__ = "boards"
31
+ __table_args__ = (sa.UniqueConstraint("board_id", "version"),)
32
+
33
+ board_id: Mapped[str] = mapped_column(String(40), primary_key=True, unique=False)
34
+ version: Mapped[str] = mapped_column(String(12), primary_key=True, unique=False)
35
+ board_name: Mapped[str] = mapped_column()
36
+ mcu: Mapped[str] = mapped_column()
37
+ variant: Mapped[str] = mapped_column(default="")
38
+ port: Mapped[str] = mapped_column(String(30))
39
+ path: Mapped[str] = mapped_column(comment="Path in micropyton repo as_posix()")
40
+ description: Mapped[str] = mapped_column()
41
+ family: Mapped[str] = mapped_column(default="micropython")
42
+ custom: Mapped[bool] = mapped_column(default=False, comment="True if this is a custom board")
43
+ firmwares = relationship(
44
+ "Firmware",
45
+ back_populates="board",
46
+ lazy="joined",
47
+ )
48
+
49
+ board_key = composite(lambda board_id, version: f"{board_id}_{version}", board_id, version)
50
+
51
+ def __repr__(self) -> str:
52
+ return f"Board(board_id={self.board_id!r}, version={self.version!r}, board_name={self.board_name!r})"
53
+
54
+
55
+ class Firmware(Base):
56
+ """
57
+ Firmware model for storing firmware information.
58
+ """
59
+
60
+ __tablename__ = "firmwares"
61
+ __table_args__ = (
62
+ sa.ForeignKeyConstraint(["board_id", "version"], ["boards.board_id", "boards.version"]),
63
+ {"sqlite_autoincrement": False},
64
+ )
65
+
66
+ board_id: Mapped[str] = mapped_column(String(40), primary_key=True)
67
+ version: Mapped[str] = mapped_column(String(12), primary_key=True)
68
+ firmware_file: Mapped[str] = mapped_column(String, primary_key=True, index=True, comment="Path to the firmware file")
69
+ # Relationship to Board
70
+ board: Mapped["Board"] = relationship(
71
+ "Board",
72
+ back_populates="firmwares",
73
+ lazy="joined",
74
+ primaryjoin="and_(Firmware.board_id==Board.board_id, Firmware.version==Board.version)",
75
+ )
76
+ port: Mapped[str] = mapped_column(String(20), default="") # duplicate of board.port
77
+ description: Mapped[str] = mapped_column(default="")
78
+ source: Mapped[str] = mapped_column()
79
+ build: Mapped[int] = mapped_column(default=0, comment="Build number")
80
+ custom: Mapped[bool] = mapped_column(default=False, comment="True if this is a custom firmware")
81
+
82
+ @property
83
+ def preview(self) -> bool:
84
+ "Check if the firmware is a preview version."
85
+ return "preview" in self.firmware_file
86
+
87
+ @property
88
+ def ext(self) -> str:
89
+ "Get the file extension of the firmware file."
90
+ return Path(self.firmware_file).suffix
91
+
92
+ def __repr__(self) -> str:
93
+ return f"Firmware(board_id={self.board_id!r}, version={self.version!r}, firmware_file={self.firmware_file!r})"
mpflash/db/tools.py ADDED
@@ -0,0 +1,27 @@
1
+ import sqlite3
2
+ from pathlib import Path
3
+ from typing import List
4
+
5
+ from mpflash.config import config
6
+ from mpflash.errors import MPFlashError
7
+ from mpflash.logger import log
8
+
9
+
10
+ def backup_db(source_db: Path, backup_path: Path):
11
+ """
12
+ Backup the SQLite database to a specified path.
13
+
14
+ Args:
15
+ conn (sqlite3.Connection): SQLite connection object
16
+ backup_path (str or Path): Path to save the backup file
17
+ """
18
+
19
+ # Ensure the backup directory exists
20
+ backup_path.parent.mkdir(parents=True, exist_ok=True)
21
+ with sqlite3.connect(source_db) as conn:
22
+ # Perform the backup
23
+ with open(backup_path, "wb") as f:
24
+ for line in conn.iterdump():
25
+ f.write(f"{line}\n".encode("utf-8"))
26
+
27
+ log.info(f"Backup created at {backup_path}")
@@ -5,7 +5,6 @@ Uses the micropython.org website to get the available versions and locations to
5
5
 
6
6
  import itertools
7
7
  from pathlib import Path
8
- import sqlite3
9
8
  from typing import Dict, List, Optional
10
9
 
11
10
  # #########################################################################################################
@@ -14,27 +13,27 @@ import jsonlines
14
13
  from loguru import logger as log
15
14
  from rich.progress import track
16
15
 
17
- from mpflash.common import PORT_FWTYPES, FWInfo
18
- from .from_web import get_boards, fetch_firmware_files
16
+ from mpflash.common import PORT_FWTYPES
17
+ from mpflash.config import config
18
+ from mpflash.db.core import Session
19
+ from mpflash.db.models import Firmware
19
20
  from mpflash.downloaded import clean_downloaded_firmwares
20
21
  from mpflash.errors import MPFlashError
22
+ from mpflash.mpboard_id.alternate import add_renamed_boards
21
23
  from mpflash.versions import clean_version
22
- from mpflash.config import config
23
- from mpflash.db.downloads import upsert_download
24
+
25
+ from .from_web import fetch_firmware_files, get_boards
26
+ from .fwinfo import FWInfo
27
+
24
28
  # avoid conflict with the ujson used by MicroPython
25
29
  jsonlines.ujson = None # type: ignore
26
30
  # #########################################################################################################
27
31
 
28
32
 
29
33
 
30
- def key_fw_ver_pre_ext_bld(x: FWInfo):
31
- "sorting key for the retrieved board urls"
32
- return x.variant, x.version, x.preview, x.ext, x.build
33
-
34
-
35
- def key_fw_var_pre_ext(x: FWInfo):
34
+ def key_fw_boardid_preview_ext(fw: Firmware):
36
35
  "Grouping key for the retrieved board urls"
37
- return x.variant, x.preview, x.ext
36
+ return fw.board_id, fw.preview, fw.ext
38
37
 
39
38
 
40
39
  def download_firmwares(
@@ -59,18 +58,18 @@ def download_firmwares(
59
58
  """
60
59
 
61
60
 
62
- skipped = downloaded = 0
61
+ downloaded = 0
63
62
  versions = [] if versions is None else [clean_version(v) for v in versions]
64
- # handle renamed boards
63
+ # handle downloading firmware for renamed boards
65
64
  boards = add_renamed_boards(boards)
66
65
 
67
66
  available_firmwares = get_firmware_list(ports, boards, versions, clean)
68
67
 
69
68
  for b in available_firmwares:
70
- log.debug(b.filename)
69
+ log.debug(b.firmware_file)
71
70
  # relevant
72
71
 
73
- log.info(f"Found {len(available_firmwares)} relevant unique firmwares")
72
+ log.info(f"Found {len(available_firmwares)} potentially relevant firmwares")
74
73
  if not available_firmwares:
75
74
  log.error("No relevant firmwares could be found on https://micropython.org/download")
76
75
  log.info(f"{versions=} {ports=} {boards=}")
@@ -83,7 +82,7 @@ def download_firmwares(
83
82
  log.success(f"Downloaded {downloaded} firmware images." )
84
83
  return downloaded
85
84
 
86
- def download_firmware_files(available_firmwares :List[FWInfo],firmware_folder:Path, force:bool ):
85
+ def download_firmware_files(available_firmwares :List[Firmware],firmware_folder:Path, force:bool ):
87
86
  """
88
87
  Downloads the firmware files to the specified folder.
89
88
  Args:
@@ -94,23 +93,23 @@ def download_firmware_files(available_firmwares :List[FWInfo],firmware_folder:Pa
94
93
  """
95
94
 
96
95
  # with jsonlines.open(firmware_folder / "firmware.jsonl", "a") as writer:
97
- with sqlite3.connect(config.db_path) as conn:
96
+ with Session() as session:
98
97
  # skipped, downloaded = fetch_firmware_files(available_firmwares, firmware_folder, force, requests, writer)
99
98
  downloaded = 0
100
99
  for fw in fetch_firmware_files(available_firmwares, firmware_folder, force):
101
- upsert_download(conn, fw)
102
- # writer.write(fw)
103
- log.debug(f" {fw.filename} downloaded")
100
+ session.merge(fw)
101
+ log.debug(f" {fw.firmware_file} downloaded")
104
102
  downloaded += 1
103
+ session.commit()
105
104
  if downloaded > 0:
106
- clean_downloaded_firmwares(firmware_folder)
105
+ clean_downloaded_firmwares()
107
106
  return downloaded
108
107
 
109
108
 
110
109
 
111
110
  def get_firmware_list(ports: List[str], boards: List[str], versions: List[str], clean: bool = True):
112
111
  """
113
- Retrieves a list of unique firmware files available om micropython.org > downloads
112
+ Retrieves a list of unique firmware files potentially available on micropython.org > downloads
114
113
  based on the specified ports, boards, versions, and clean flag.
115
114
 
116
115
  Args:
@@ -126,36 +125,43 @@ def get_firmware_list(ports: List[str], boards: List[str], versions: List[str],
126
125
 
127
126
  log.trace("Checking MicroPython download pages")
128
127
  versions = [clean_version(v, drop_v=False) for v in versions]
129
- preview = "preview" in versions
128
+ preview = any("preview" in v for v in versions)
130
129
 
131
- board_urls = sorted(get_boards(ports, boards, clean), key=key_fw_ver_pre_ext_bld)
130
+ # board_urls = sorted(get_boards(ports, boards, clean), key=key_fw_ver_pre_ext_bld)
131
+ board_urls = get_boards(ports, boards, clean)
132
132
 
133
133
  log.debug(f"Total {len(board_urls)} firmwares")
134
+ if versions:
135
+ # filter out the boards that are not in the versions list
136
+ relevant = [
137
+ board for board in board_urls if (
138
+ board.version in versions
139
+ # or (preview and board.preview )
140
+ # and board.board_id in boards
141
+ # and board.build == "0"
142
+ # and not board.preview
143
+ )
144
+ ]
145
+ else:
146
+ relevant = board_urls
134
147
 
135
- relevant = [
136
- board for board in board_urls if board.version in versions and board.build == "0" and board.board in boards and not board.preview
137
- ]
138
-
139
- if preview:
140
- relevant.extend([board for board in board_urls if board.board in boards and board.preview])
141
148
  log.debug(f"Matching firmwares: {len(relevant)}")
142
149
  # select the unique boards
143
- unique_boards: List[FWInfo] = []
144
- for _, g in itertools.groupby(relevant, key=key_fw_var_pre_ext):
145
- # list is aleady sorted by build so we can just get the last item
150
+ unique_boards: List[Firmware] = []
151
+ for _, g in itertools.groupby(relevant, key=key_fw_boardid_preview_ext):
152
+ # list is aleady sorted by build (desc) so we can just get the first item
146
153
  sub_list = list(g)
147
- unique_boards.append(sub_list[-1])
148
- log.debug(f"Last preview only: {len(unique_boards)}")
154
+ unique_boards.append(sub_list[0])
155
+ log.debug(f"Including preview: {len(unique_boards)}")
149
156
  return unique_boards
150
157
 
151
158
 
152
159
  def download(
153
- destination: Path,
154
160
  ports: List[str],
155
161
  boards: List[str],
156
162
  versions: List[str],
157
- force: bool,
158
- clean: bool,
163
+ force: bool = False,
164
+ clean: bool = True,
159
165
  ) -> int:
160
166
  """
161
167
  Downloads firmware files based on the specified destination, ports, boards, versions, force flag, and clean flag.
@@ -177,7 +183,7 @@ def download(
177
183
  """
178
184
  # Just in time import
179
185
  import requests
180
-
186
+ destination = config.firmware_folder
181
187
  if not boards:
182
188
  log.critical("No boards found, please connect a board or specify boards to download firmware for.")
183
189
  raise MPFlashError("No boards found")
@@ -195,27 +201,3 @@ def download(
195
201
 
196
202
  return result
197
203
 
198
-
199
- def add_renamed_boards(boards: List[str]) -> List[str]:
200
- """
201
- Adds the renamed boards to the list of boards.
202
-
203
- Args:
204
- boards : The list of boards to add the renamed boards to.
205
-
206
- Returns:
207
- List[str]: The list of boards with the renamed boards added.
208
- """
209
-
210
- renamed = {
211
- "PICO": ["RPI_PICO"],
212
- "PICO_W": ["RPI_PICO_W"],
213
- "GENERIC": ["ESP32_GENERIC", "ESP8266_GENERIC"], # just add both of them
214
- }
215
- _boards = boards.copy()
216
- for board in boards:
217
- if board in renamed and renamed[board] not in boards:
218
- _boards.extend(renamed[board])
219
- if board != board.upper() and board.upper() not in boards:
220
- _boards.append(board.upper())
221
- return _boards
@@ -5,18 +5,16 @@ from pathlib import Path
5
5
  from typing import Dict, List, Optional
6
6
  from urllib.parse import urljoin
7
7
 
8
-
9
8
  from loguru import logger as log
10
9
  from rich.progress import track
11
10
 
12
- from mpflash.common import PORT_FWTYPES, FWInfo
11
+ from mpflash.common import PORT_FWTYPES
12
+ from mpflash.db.models import Firmware
13
13
  from mpflash.downloaded import clean_downloaded_firmwares
14
14
  from mpflash.errors import MPFlashError
15
- from mpflash.mpboard_id import get_known_ports
15
+ from mpflash.mpboard_id import known_ports
16
16
  from mpflash.versions import clean_version
17
17
 
18
-
19
-
20
18
  MICROPYTHON_ORG_URL = "https://micropython.org/"
21
19
 
22
20
 
@@ -24,8 +22,8 @@ MICROPYTHON_ORG_URL = "https://micropython.org/"
24
22
  RE_DATE = r"(-\d{8}-)"
25
23
  RE_HASH = r"(.g[0-9a-f]+\.)"
26
24
  # regex to extract the version and the build from the firmware filename
27
- # group 1 is the version, group 2 is the build
28
- RE_VERSION_PREVIEW = r"v([\d\.]+)-?(?:preview\.)?(\d+)?\."
25
+ # group 1 is the version+Preview , gr2 just the version, group 3 is the build
26
+ RE_VERSION_PREVIEW = r"v(([\d\.]+)(?:-preview)?)\.?(\d+)?\."
29
27
 
30
28
 
31
29
  # 'RPI_PICO_W-v1.23.uf2'
@@ -107,7 +105,7 @@ def board_firmware_urls(board_url: str, base_url: str, ext: str) -> List[str]:
107
105
  # boards we are interested in ( this avoids getting a lot of boards we don't care about)
108
106
  # The first run takes ~60 seconds to run for 4 ports , all boards
109
107
  # so it makes sense to cache the results and skip boards as soon as possible
110
- def get_boards(ports: List[str], boards: List[str], clean: bool) -> List[FWInfo]:
108
+ def get_boards(ports: List[str], boards: List[str], clean: bool) -> List[Firmware]:
111
109
  # sourcery skip: use-getitem-for-re-match-groups
112
110
  """
113
111
  Retrieves a list of firmware information for the specified ports and boards.
@@ -115,15 +113,15 @@ def get_boards(ports: List[str], boards: List[str], clean: bool) -> List[FWInfo]
115
113
  Args:
116
114
  ports (List[str]): The list of ports to check for firmware.
117
115
  boards (List[str]): The list of boards to retrieve firmware information for.
118
- clean (bool): A flag indicating whether to perform a clean retrieval.
116
+ clean (bool): Remove date and hash from the firmware name.
119
117
 
120
118
  Returns:
121
119
  List[FWInfo]: A list of firmware information for the specified ports and boards.
122
120
 
123
121
  """
124
- board_urls: List[FWInfo] = []
122
+ board_urls: List[Firmware] = []
125
123
  if ports is None:
126
- ports = get_known_ports()
124
+ ports = known_ports()
127
125
  for port in ports:
128
126
  download_page_url = f"{MICROPYTHON_ORG_URL}download/?port={port}"
129
127
  urls = get_board_urls(download_page_url)
@@ -152,52 +150,44 @@ def get_boards(ports: List[str], boards: List[str], clean: bool) -> List[FWInfo]
152
150
  fname = re.sub(RE_DATE, "-", fname)
153
151
  # remove hash from firmware name
154
152
  fname = re.sub(RE_HASH, ".", fname)
155
- fw_info = FWInfo(
156
- filename=fname,
153
+ fw_info = Firmware(
154
+ firmware_file=fname,
157
155
  port=port,
158
- board=board["board"],
159
- preview="preview" in _url,
160
- firmware=_url,
156
+ board_id=board["board"],
157
+ source=_url,
161
158
  version="",
159
+ custom=False,
160
+ description="", # todo : add description from download page
162
161
  )
163
- # board["firmware"] = _url
164
- # board["preview"] = "preview" in _url # type: ignore
165
162
  if ver_match := re.search(RE_VERSION_PREVIEW, _url):
166
- fw_info.version = clean_version(ver_match.group(1))
167
- fw_info.build = ver_match.group(2) or "0"
168
- fw_info.preview = fw_info.build != "0"
169
- # # else:
170
- # # board.$1= ""
171
- # if "preview." in fw_info.version:
172
- # fw_info.build = fw_info.version.split("preview.")[-1]
173
- # else:
174
- # fw_info.build = "0"
175
-
176
- fw_info.ext = Path(fw_info.firmware).suffix
177
- fw_info.variant = fw_info.filename.split("-v")[0] if "-v" in fw_info.filename else ""
163
+ fw_info.version = clean_version(ver_match[1])
164
+ fw_info.build = int(ver_match[3] or 0)
165
+ if "-v" in fname:
166
+ # get the full board_id[-variant] from the filename
167
+ # filename : 'ESP32_GENERIC-v1.25.0.bin'
168
+ fw_info.board_id = fname.split("-v")[0]
178
169
 
179
170
  board_urls.append(fw_info)
180
171
  return board_urls
181
172
 
182
173
 
183
-
184
- def fetch_firmware_files(available_firmwares: List[FWInfo], firmware_folder: Path, force: bool):
174
+ def fetch_firmware_files(available_firmwares: List[Firmware], firmware_folder: Path, force: bool):
185
175
  # Just in time import
186
176
  import requests
187
177
 
188
178
  for board in available_firmwares:
189
- filename = firmware_folder / board.port / board.filename
179
+ filename = firmware_folder / board.port / board.firmware_file
190
180
  filename.parent.mkdir(exist_ok=True)
191
181
  if filename.exists() and not force:
192
182
  log.debug(f" {filename} already exists, skip download")
193
183
  continue
194
- log.info(f"Downloading {board.firmware}")
184
+ log.info(f"Downloading {board.source}")
195
185
  log.info(f" to {filename}")
196
186
  try:
197
- r = requests.get(board.firmware, allow_redirects=True)
187
+ r = requests.get(board.source, allow_redirects=True)
198
188
  with open(filename, "wb") as fw:
199
189
  fw.write(r.content)
200
- board.filename = str(filename.relative_to(firmware_folder))
190
+ board.firmware_file = str(filename.relative_to(firmware_folder))
201
191
  except requests.RequestException as e:
202
192
  log.exception(e)
203
193
  continue