Skip to content

Commit c429e34

Browse files
committed
pybricksdev/flash: drop support for firmware.bin
We were allowing firmware.zip files with a "ready-to-go" firmware.bin file, but this file was modified just as the firmware-base.bin file, so there is really no difference between the two. To simplify things, we only use one file name, firmware-base.bin. This also makes having a main.py file optional so that we don't break compatibility with the firmware.zip generated for SPIKE hubs. Typings are added for the data contained in the firmware.metadata.json file to clarify what ships in the firmware.zip file and what is added.
1 parent 3296e92 commit c429e34

File tree

3 files changed

+98
-24
lines changed

3 files changed

+98
-24
lines changed

CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
66

77
## [Unreleased]
88

9+
### Added
10+
- Added typings for firmware metadata json structure.
11+
12+
### Changed
13+
- ``main.py`` in ``firmware.zip`` is now optional.
14+
15+
### Removed
16+
- Removed support for ``firmware.zip`` files with ``firmware.bin`` instead of
17+
``firmware-base.bin``.
18+
919
## [1.0.0-alpha.25] - 2022-03-17
1020

1121
### Added

pybricksdev/firmware.py

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
# SPDX-License-Identifier: MIT
2+
# Copyright (c) 2022 The Pybricks Authors
3+
4+
"""
5+
Utilities for working with Pybricks ``firmware.zip`` files.
6+
"""
7+
8+
from typing import Literal, TypedDict
9+
10+
11+
class FirmwareMetadataV100(
12+
TypedDict(
13+
"V100",
14+
{
15+
"metadata-version": Literal["1.0.0"],
16+
"firmware-version": str,
17+
"device-id": Literal[0x40] | Literal[0x41] | Literal[0x80] | Literal[0x81],
18+
"checksum-type": Literal["sum"] | Literal["crc32"],
19+
"mpy-abi-version": int,
20+
"mpy-cross-options": list[str],
21+
"user-mpy-offset": int,
22+
"max-firmware-size": int,
23+
},
24+
)
25+
):
26+
"""
27+
Type for data contained in v1.0.0 ``firmware.metadata.json`` files.
28+
"""
29+
30+
31+
class FirmwareMetadataV110(
32+
FirmwareMetadataV100,
33+
TypedDict(
34+
"V110",
35+
{
36+
# changed
37+
"metadata-version": Literal["1.1.0"],
38+
# added
39+
"hub-name-offset": int,
40+
"max-hub-name-size": int,
41+
},
42+
),
43+
):
44+
"""
45+
Type for data contained in v1.1.0 ``firmware.metadata.json`` files.
46+
"""
47+
48+
49+
AnyFirmwareMetadata = FirmwareMetadataV100 | FirmwareMetadataV110
50+
"""
51+
Type for data contained in ``firmware.metadata.json`` files of any version.
52+
"""
53+
54+
55+
class ExtendedFirmwareMetadata(
56+
FirmwareMetadataV110, TypedDict("Extended", {"firmware-sha256": str})
57+
):
58+
# NB: Ideally, this should inherit from AnyFirmwareMetadata instead of
59+
# FirmwareMetadataV110 but that is not technically possible because being
60+
# a Union it has a different meta class from TypedDict
61+
"""
62+
Type for data contained in ``firmware.metadata.json`` files of any version
63+
with extended data added.
64+
65+
The extended data is used by the ``install_pybricks.py`` script.
66+
"""

pybricksdev/flash.py

Lines changed: 22 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,15 @@
2222
from .ble.lwp3.bootloader import BootloaderCommand
2323
from .ble.lwp3.bytecodes import HubKind
2424
from .compile import compile_file, save_script
25+
from .firmware import ExtendedFirmwareMetadata, AnyFirmwareMetadata
2526
from .tools.checksum import crc32_checksum, sum_complement
2627

2728
logger = logging.getLogger(__name__)
2829

2930

3031
async def create_firmware(
3132
firmware_zip: Union[str, os.PathLike, BinaryIO], name: Optional[str] = None
32-
) -> Tuple[bytes, dict]:
33+
) -> Tuple[bytes, ExtendedFirmwareMetadata]:
3334
"""Creates a firmware blob from base firmware and main.mpy file.
3435
3536
Arguments:
@@ -39,8 +40,8 @@ async def create_firmware(
3940
A custom name for the hub.
4041
4142
Returns:
42-
bytes: Composite binary blob with correct padding and checksum.
43-
dict: Meta data for this firmware file.
43+
Composite binary blob with correct padding and checksum and
44+
extended metadata for this firmware file.
4445
4546
Raises:
4647
ValueError:
@@ -50,34 +51,31 @@ async def create_firmware(
5051
"""
5152

5253
archive = zipfile.ZipFile(firmware_zip)
53-
metadata = json.load(archive.open("firmware.metadata.json"))
54+
metadata: AnyFirmwareMetadata = json.load(archive.open("firmware.metadata.json"))
5455

55-
# Check if there's a complete firmware already
56-
if "firmware.bin" in archive.namelist():
57-
# If so, use it as-is, with the final checksum stripped off.
58-
# We'll add an updated checksum later on.
59-
firmware = bytearray(archive.open("firmware.bin").read()[:-4])
60-
else:
61-
# Otherwise, we have to take a base firmware and extend it.
62-
base = archive.open("firmware-base.bin").read()
56+
base = archive.open("firmware-base.bin").read()
57+
58+
if "main.py" in archive.namelist():
6359
main_py = io.TextIOWrapper(archive.open("main.py"))
6460

6561
mpy = await compile_file(
6662
save_script(main_py.read()),
6763
metadata["mpy-cross-options"],
6864
metadata["mpy-abi-version"],
6965
)
70-
71-
# start with base firmware binary blob
72-
firmware = bytearray(base)
73-
# pad with 0s until user-mpy-offset
74-
firmware.extend(0 for _ in range(metadata["user-mpy-offset"] - len(firmware)))
75-
# append 32-bit little-endian main.mpy file size
76-
firmware.extend(struct.pack("<I", len(mpy)))
77-
# append main.mpy file
78-
firmware.extend(mpy)
79-
# pad with 0s to align to 4-byte boundary
80-
firmware.extend(0 for _ in range(-len(firmware) % 4))
66+
else:
67+
mpy = b""
68+
69+
# start with base firmware binary blob
70+
firmware = bytearray(base)
71+
# pad with 0s until user-mpy-offset
72+
firmware.extend(0 for _ in range(metadata["user-mpy-offset"] - len(firmware)))
73+
# append 32-bit little-endian main.mpy file size
74+
firmware.extend(struct.pack("<I", len(mpy)))
75+
# append main.mpy file
76+
firmware.extend(mpy)
77+
# pad with 0s to align to 4-byte boundary
78+
firmware.extend(0 for _ in range(-len(firmware) % 4))
8179

8280
# Update hub name if given
8381
if name:
@@ -110,7 +108,7 @@ async def create_firmware(
110108
# Append checksum to the firmware
111109
firmware.extend(struct.pack("<I", checksum))
112110

113-
# Update sha256 of updated composite firmware
111+
# Add extended metadata needed by install_pybricks.py
114112
metadata["firmware-sha256"] = hashlib.sha256(firmware).hexdigest()
115113

116114
return firmware, metadata

0 commit comments

Comments
 (0)