mpflash 1.25.0__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.
- mpflash/add_firmware.py +43 -16
- mpflash/ask_input.py +4 -3
- mpflash/basicgit.py +2 -2
- mpflash/bootloader/manual.py +1 -1
- mpflash/cli_download.py +8 -5
- mpflash/cli_flash.py +17 -33
- mpflash/cli_group.py +3 -0
- mpflash/cli_list.py +2 -2
- mpflash/cli_main.py +4 -0
- mpflash/common.py +1 -36
- mpflash/config.py +21 -0
- mpflash/db/__init__.py +2 -0
- mpflash/db/core.py +56 -0
- mpflash/db/gather_boards.py +112 -0
- mpflash/db/loader.py +120 -0
- mpflash/db/meta.py +78 -0
- mpflash/db/micropython_boards.zip +0 -0
- mpflash/db/models.py +93 -0
- mpflash/db/tools.py +27 -0
- mpflash/download/__init__.py +46 -64
- mpflash/download/from_web.py +26 -36
- mpflash/download/fwinfo.py +41 -0
- mpflash/download/jid.py +53 -0
- mpflash/downloaded.py +79 -93
- mpflash/flash/__init__.py +7 -3
- mpflash/flash/esp.py +2 -1
- mpflash/flash/worklist.py +16 -28
- mpflash/list.py +3 -3
- mpflash/logger.py +2 -2
- mpflash/mpboard_id/__init__.py +3 -9
- mpflash/mpboard_id/alternate.py +56 -0
- mpflash/mpboard_id/board_id.py +11 -94
- mpflash/mpboard_id/known.py +44 -72
- mpflash/mpboard_id/resolve.py +19 -0
- mpflash/mpremoteboard/__init__.py +1 -1
- mpflash/mpremoteboard/mpy_fw_info.py +1 -0
- mpflash/mpremoteboard/runner.py +5 -2
- mpflash/vendor/pydfu.py +4 -5
- mpflash/versions.py +3 -0
- {mpflash-1.25.0.dist-info → mpflash-1.25.0rc1.dist-info}/METADATA +2 -2
- mpflash-1.25.0rc1.dist-info/RECORD +69 -0
- mpflash/db/boards.py +0 -63
- mpflash/db/downloads.py +0 -87
- mpflash/mpboard_id/add_boards.py +0 -260
- mpflash/mpboard_id/board.py +0 -40
- mpflash/mpboard_id/store.py +0 -47
- mpflash-1.25.0.dist-info/RECORD +0 -62
- {mpflash-1.25.0.dist-info → mpflash-1.25.0rc1.dist-info}/LICENSE +0 -0
- {mpflash-1.25.0.dist-info → mpflash-1.25.0rc1.dist-info}/WHEEL +0 -0
- {mpflash-1.25.0.dist-info → mpflash-1.25.0rc1.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,112 @@
|
|
1
|
+
from os import path
|
2
|
+
from pathlib import Path
|
3
|
+
from mpflash.mpremoteboard import HERE
|
4
|
+
from mpflash.vendor.board_database import Database
|
5
|
+
from mpflash.logger import log
|
6
|
+
|
7
|
+
from typing import List
|
8
|
+
import mpflash.basicgit as git
|
9
|
+
from mpflash.versions import micropython_versions
|
10
|
+
|
11
|
+
HERE = Path(__file__).parent.resolve()
|
12
|
+
|
13
|
+
|
14
|
+
## iterator to flatten the board database into a list of tuples
|
15
|
+
def iter_boards(db: Database, version: str = ""):
|
16
|
+
"""Iterate over the boards in the database and yield tuples of board information."""
|
17
|
+
version = version.strip()
|
18
|
+
for b in db.boards:
|
19
|
+
board = db.boards[b]
|
20
|
+
yield (
|
21
|
+
version,
|
22
|
+
board.name,
|
23
|
+
board.name,
|
24
|
+
board.mcu,
|
25
|
+
"", # no variant
|
26
|
+
board.port.name if board.port else "",
|
27
|
+
board.path.split("/micropython/", 1)[1], # TODO - remove hack
|
28
|
+
board.description,
|
29
|
+
"micropython", # family
|
30
|
+
)
|
31
|
+
if board.variants:
|
32
|
+
for v in board.variants:
|
33
|
+
yield (
|
34
|
+
version,
|
35
|
+
f"{board.name}-{v.name}",
|
36
|
+
board.name,
|
37
|
+
board.mcu,
|
38
|
+
v.name,
|
39
|
+
board.port.name if board.port else "",
|
40
|
+
board.path.split("/micropython/", 1)[1], # TODO - remove hack
|
41
|
+
v.description,
|
42
|
+
"micropython", # family
|
43
|
+
)
|
44
|
+
|
45
|
+
|
46
|
+
def boardlist_from_repo(
|
47
|
+
versions: List[str],
|
48
|
+
mpy_dir: Path,
|
49
|
+
):
|
50
|
+
longlist = []
|
51
|
+
if not mpy_dir.is_dir():
|
52
|
+
log.error(f"Directory {mpy_dir} not found")
|
53
|
+
return longlist
|
54
|
+
for version in versions:
|
55
|
+
build_nr = ""
|
56
|
+
if "preview" in version:
|
57
|
+
ok = git.checkout_tag("master", mpy_dir)
|
58
|
+
if describe := git.get_git_describe(mpy_dir):
|
59
|
+
parts = describe.split("-", 3)
|
60
|
+
if len(parts) >= 3:
|
61
|
+
build_nr = parts[2]
|
62
|
+
else:
|
63
|
+
ok = git.checkout_tag(version, mpy_dir)
|
64
|
+
if not ok:
|
65
|
+
log.warning(f"Failed to checkout {version} in {mpy_dir}")
|
66
|
+
continue
|
67
|
+
|
68
|
+
log.info(f"{git.get_git_describe(mpy_dir)} - {build_nr}")
|
69
|
+
# un-cached database
|
70
|
+
db = Database(mpy_dir)
|
71
|
+
shortlist = list(iter_boards(db, version=version))
|
72
|
+
log.info(f"boards found {len(db.boards.keys())}")
|
73
|
+
log.info(f"boards-variants found {len(shortlist) - len(db.boards.keys())}")
|
74
|
+
longlist.extend(shortlist)
|
75
|
+
return longlist
|
76
|
+
|
77
|
+
|
78
|
+
def create_zip_file(longlist, zip_file: Path):
|
79
|
+
"""Create a ZIP file containing the CSV data."""
|
80
|
+
# lazy import
|
81
|
+
import zipfile
|
82
|
+
import pandas as pd
|
83
|
+
|
84
|
+
csv_filename = "micropython_boards.csv"
|
85
|
+
|
86
|
+
columns = ["version", "board_id", "board_name", "mcu", "variant", "port", "path", "description", "family"]
|
87
|
+
df = pd.DataFrame(longlist, columns=columns)
|
88
|
+
|
89
|
+
# Create the ZIP file and add the CSV data directly without creating an intermediate file
|
90
|
+
with zipfile.ZipFile(zip_file, "w", zipfile.ZIP_DEFLATED) as zipf:
|
91
|
+
# Create a temporary in-memory CSV string
|
92
|
+
csv_data = df.to_csv(index=False)
|
93
|
+
# Write the CSV data directly to the zip file
|
94
|
+
zipf.writestr(csv_filename, csv_data)
|
95
|
+
|
96
|
+
|
97
|
+
def package_repo(mpy_path: Path):
|
98
|
+
mpy_path = mpy_path or Path("../repos/micropython")
|
99
|
+
mp_versions = micropython_versions(minver="1.18")
|
100
|
+
longlist = boardlist_from_repo(
|
101
|
+
versions=mp_versions,
|
102
|
+
mpy_dir=mpy_path,
|
103
|
+
)
|
104
|
+
log.info(f"Total boards-variants: {len(longlist)}")
|
105
|
+
zip_file = HERE / "micropython_boards.zip"
|
106
|
+
create_zip_file(longlist, zip_file=zip_file)
|
107
|
+
|
108
|
+
assert zip_file.is_file(), f"Failed to create {zip_file}"
|
109
|
+
|
110
|
+
|
111
|
+
if __name__ == "__main__":
|
112
|
+
package_repo(Path("D:\\mypython\\mpflash\\repos\\micropython"))
|
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}")
|
mpflash/download/__init__.py
CHANGED
@@ -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
|
18
|
-
from .
|
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
|
-
|
23
|
-
from
|
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
|
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
|
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
|
-
|
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.
|
69
|
+
log.debug(b.firmware_file)
|
71
70
|
# relevant
|
72
71
|
|
73
|
-
log.info(f"Found {len(available_firmwares)} relevant
|
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[
|
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
|
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
|
-
|
102
|
-
|
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(
|
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
|
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[
|
144
|
-
for _, g in itertools.groupby(relevant, key=
|
145
|
-
# list is aleady sorted by build so we can just get the
|
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[
|
148
|
-
log.debug(f"
|
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
|