Skip to content

Commit 11abd3b

Browse files
committed
flash: Refactor firmware extension.
Instead of looking at hub type to see if the firmware needs extending, just look at the file in the archive. Since the name setter is now used in one place again, we can also partially revert 97c2c24 to restore the original functionality. This way the docstring for create_firmware is correct again. Also fix some firmware size checks with/without checksum.
1 parent 97c2c24 commit 11abd3b

File tree

2 files changed

+52
-68
lines changed

2 files changed

+52
-68
lines changed

pybricksdev/flash.py

Lines changed: 49 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -26,27 +26,6 @@
2626
logger = logging.getLogger(__name__)
2727

2828

29-
def update_hub_name(firmware, metadata, name):
30-
"""Changes the hub name in the firmware blob."""
31-
32-
if not name:
33-
return
34-
35-
if semver.compare(metadata["metadata-version"], "1.1.0") < 0:
36-
raise ValueError("this firmware image does not support setting the hub name")
37-
38-
name = name.encode() + b"\0"
39-
40-
max_size = metadata["max-hub-name-size"]
41-
if len(name) > max_size:
42-
raise ValueError(
43-
f"name is too big - must be < {metadata['max-hub-name-size']} UTF-8 bytes"
44-
)
45-
46-
offset = metadata["hub-name-offset"]
47-
firmware[offset : offset + len(name)] = name
48-
49-
5029
async def create_firmware(
5130
firmware_zip: Union[str, os.PathLike, BinaryIO], name: Optional[str] = None
5231
) -> Tuple[bytes, dict]:
@@ -72,55 +51,64 @@ async def create_firmware(
7251
archive = zipfile.ZipFile(firmware_zip)
7352
metadata = json.load(archive.open("firmware.metadata.json"))
7453

75-
# For SPIKE Hubs, we can use the firmware without appending anything.
76-
if metadata["device-id"] in (HubKind.TECHNIC_SMALL, HubKind.TECHNIC_LARGE):
77-
firmware = bytearray(archive.open("firmware.bin").read())
78-
update_hub_name(firmware, metadata, name)
79-
return firmware, metadata
54+
# Check if there's a complete firmware already
55+
if "firmware.bin" in archive.namelist():
56+
# If so, use it as-is, with the final checksum stripped off.
57+
# We'll add an updated checksum later on.
58+
firmware = bytearray(archive.open("firmware.bin").read()[:-4])
59+
else:
60+
# Otherwise, we have to take a base firmware and extend it.
61+
base = archive.open("firmware-base.bin").read()
62+
main_py = io.TextIOWrapper(archive.open("main.py"))
63+
64+
mpy = await compile_file(
65+
save_script(main_py.read()),
66+
metadata["mpy-cross-options"],
67+
metadata["mpy-abi-version"],
68+
)
69+
70+
# start with base firmware binary blob
71+
firmware = bytearray(base)
72+
# pad with 0s until user-mpy-offset
73+
firmware.extend(0 for _ in range(metadata["user-mpy-offset"] - len(firmware)))
74+
# append 32-bit little-endian main.mpy file size
75+
firmware.extend(struct.pack("<I", len(mpy)))
76+
# append main.mpy file
77+
firmware.extend(mpy)
78+
# pad with 0s to align to 4-byte boundary
79+
firmware.extend(0 for _ in range(-len(firmware) % 4))
80+
81+
# Update hub name if given
82+
if name:
83+
84+
if semver.compare(metadata["metadata-version"], "1.1.0") < 0:
85+
raise ValueError(
86+
"this firmware image does not support setting the hub name"
87+
)
8088

81-
# For Powered Up hubs, we append a script and checksum to a base firmware.
82-
base = archive.open("firmware-base.bin").read()
83-
main_py = io.TextIOWrapper(archive.open("main.py"))
89+
name = name.encode() + b"\0"
8490

85-
mpy = await compile_file(
86-
save_script(main_py.read()),
87-
metadata["mpy-cross-options"],
88-
metadata["mpy-abi-version"],
89-
)
91+
max_size = metadata["max-hub-name-size"]
92+
if len(name) > max_size:
93+
raise ValueError(
94+
f"name is too big - must be < {metadata['max-hub-name-size']} UTF-8 bytes"
95+
)
96+
97+
offset = metadata["hub-name-offset"]
98+
firmware[offset : offset + len(name)] = name
9099

91-
# start with base firmware binary blob
92-
firmware = bytearray(base)
93-
# pad with 0s until user-mpy-offset
94-
firmware.extend(0 for _ in range(metadata["user-mpy-offset"] - len(firmware)))
95-
# append 32-bit little-endian main.mpy file size
96-
firmware.extend(struct.pack("<I", len(mpy)))
97-
# append main.mpy file
98-
firmware.extend(mpy)
99-
# pad with 0s to align to 4-byte boundary
100-
firmware.extend(0 for _ in range(-len(firmware) % 4))
101-
102-
# Update hub name
103-
update_hub_name(firmware, metadata, name)
104-
105-
# append 32-bit little-endian checksum
100+
# Get checksum for this firmware
106101
if metadata["checksum-type"] == "sum":
107-
firmware.extend(
108-
struct.pack(
109-
"<I",
110-
sum_complement(io.BytesIO(firmware), metadata["max-firmware-size"] - 4),
111-
)
112-
)
102+
checksum = sum_complement(io.BytesIO(firmware), metadata["max-firmware-size"])
113103
elif metadata["checksum-type"] == "crc32":
114-
firmware.extend(
115-
struct.pack(
116-
"<I",
117-
crc32_checksum(io.BytesIO(firmware), metadata["max-firmware-size"] - 4),
118-
)
119-
)
104+
checksum = crc32_checksum(io.BytesIO(firmware), metadata["max-firmware-size"])
120105
else:
121106
print(f'Unknown checksum type "{metadata["checksum-type"]}"', file=sys.stderr)
122107
exit(1)
123108

109+
# Append checksum to the firmware
110+
firmware.extend(struct.pack("<I", checksum))
111+
124112
return firmware, metadata
125113

126114

pybricksdev/tools/checksum.py

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -52,10 +52,10 @@ def sum_complement(data: BytesIO, max_size: int) -> int:
5252
checksum += int.from_bytes(word, "little")
5353
size += 4
5454

55-
if size > max_size:
55+
if size + 4 > max_size:
5656
raise ValueError("data is too large")
5757

58-
for _ in range(size, max_size, 4):
58+
for _ in range(size, max_size - 4, 4):
5959
checksum += 0xFFFFFFFF
6060

6161
checksum &= 0xFFFFFFFF
@@ -112,11 +112,7 @@ def crc32_checksum(data: BytesIO, max_size: int) -> int:
112112
The checksum.
113113
"""
114114

115-
# remove the last 4 bytes that are the placeholder for the checksum
116-
try:
117-
data = data.read()[:-4]
118-
except AttributeError:
119-
data = data[:-4]
115+
data = data.read()
120116

121117
if len(data) + 4 > max_size:
122118
raise ValueError("data is too large")

0 commit comments

Comments
 (0)