11# SPDX-License-Identifier: MIT
2- # Copyright (c) 2019-2021 The Pybricks Authors
2+ # Copyright (c) 2019-2022 The Pybricks Authors
33
44import asyncio
55import hashlib
99import os
1010import platform
1111import struct
12- import sys
1312import zipfile
1413from collections import namedtuple
1514from typing import BinaryIO , Dict , List , Optional , Tuple , Union
2221from .ble .lwp3 .bootloader import BootloaderCommand
2322from .ble .lwp3 .bytecodes import HubKind
2423from .compile import compile_file , save_script
25- from .firmware import ExtendedFirmwareMetadata , AnyFirmwareMetadata
24+ from .firmware import (
25+ AnyFirmwareV1Metadata ,
26+ AnyFirmwareV2Metadata ,
27+ ExtendedFirmwareMetadata ,
28+ AnyFirmwareMetadata ,
29+ firmware_metadata_is_v1 ,
30+ firmware_metadata_is_v2 ,
31+ )
2632from .tools .checksum import crc32_checksum , sum_complement
2733
2834logger = logging .getLogger (__name__ )
2935
3036
31- async def create_firmware (
32- firmware_zip : Union [str , os .PathLike , BinaryIO ], name : Optional [str ] = None
33- ) -> Tuple [bytes , ExtendedFirmwareMetadata ]:
34- """Creates a firmware blob from base firmware and main.mpy file.
35-
36- Arguments:
37- firmware_zip:
38- Path to the firmware zip file or a file-like object.
39- name:
40- A custom name for the hub.
41-
42- Returns:
43- Composite binary blob with correct padding and checksum and
44- extended metadata for this firmware file.
45-
46- Raises:
47- ValueError:
48- A name is given but the firmware does not support it or the name
49- exceeds the alloted space in the firmware.
50-
51- """
52-
53- archive = zipfile .ZipFile (firmware_zip )
54- metadata : AnyFirmwareMetadata = json .load (archive .open ("firmware.metadata.json" ))
55-
37+ async def _create_firmware_v1 (
38+ metadata : AnyFirmwareV1Metadata , archive : zipfile .ZipFile , name : Optional [str ]
39+ ) -> bytearray :
5640 base = archive .open ("firmware-base.bin" ).read ()
5741
5842 if "main.py" in archive .namelist ():
@@ -79,7 +63,6 @@ async def create_firmware(
7963
8064 # Update hub name if given
8165 if name :
82-
8366 if semver .compare (metadata ["metadata-version" ], "1.1.0" ) < 0 :
8467 raise ValueError (
8568 "this firmware image does not support setting the hub name"
@@ -88,6 +71,7 @@ async def create_firmware(
8871 name = name .encode () + b"\0 "
8972
9073 max_size = metadata ["max-hub-name-size" ]
74+
9175 if len (name ) > max_size :
9276 raise ValueError (
9377 f"name is too big - must be < { metadata ['max-hub-name-size' ]} UTF-8 bytes"
@@ -102,16 +86,90 @@ async def create_firmware(
10286 elif metadata ["checksum-type" ] == "crc32" :
10387 checksum = crc32_checksum (io .BytesIO (firmware ), metadata ["max-firmware-size" ])
10488 else :
105- print (f'Unknown checksum type "{ metadata ["checksum-type" ]} "' , file = sys .stderr )
106- exit (1 )
89+ raise ValueError (f"unsupported checksum type: { metadata ['checksum-type' ]} " )
90+
91+ # Append checksum to the firmware
92+ firmware .extend (struct .pack ("<I" , checksum ))
93+
94+ return firmware
95+
96+
97+ async def _create_firmware_v2 (
98+ metadata : AnyFirmwareV2Metadata , archive : zipfile .ZipFile , name : Optional [str ]
99+ ) -> bytearray :
100+ base = archive .open ("firmware-base.bin" ).read ()
101+
102+ # start with base firmware binary blob
103+ firmware = bytearray (base )
104+
105+ # Update hub name if given
106+ if name :
107+ name = name .encode () + b"\0 "
108+
109+ max_size = metadata ["hub-name-size" ]
110+
111+ if len (name ) > max_size :
112+ raise ValueError (
113+ f"name is too big - must be < { metadata ['hub-name-size' ]} UTF-8 bytes"
114+ )
115+
116+ offset = metadata ["hub-name-offset" ]
117+ firmware [offset : offset + len (name )] = name
118+
119+ # Get checksum for this firmware
120+ if metadata ["checksum-type" ] == "sum" :
121+ checksum = sum_complement (io .BytesIO (firmware ), metadata ["checksum-size" ])
122+ elif metadata ["checksum-type" ] == "crc32" :
123+ checksum = crc32_checksum (io .BytesIO (firmware ), metadata ["checksum-size" ])
124+ else :
125+ raise ValueError (f"unsupported checksum type: { metadata ['checksum-type' ]} " )
107126
108127 # Append checksum to the firmware
109128 firmware .extend (struct .pack ("<I" , checksum ))
110129
111- # Add extended metadata needed by install_pybricks.py
112- metadata ["firmware-sha256" ] = hashlib .sha256 (firmware ).hexdigest ()
130+ return firmware
131+
132+
133+ async def create_firmware (
134+ firmware_zip : Union [str , os .PathLike , BinaryIO ], name : Optional [str ] = None
135+ ) -> Tuple [bytes , ExtendedFirmwareMetadata ]:
136+ """Creates a firmware blob from base firmware and an optional custom name.
137+
138+ Arguments:
139+ firmware_zip:
140+ Path to the firmware zip file or a file-like object.
141+ name:
142+ A custom name for the hub.
143+
144+ Returns:
145+ Composite binary blob with correct padding and checksum and
146+ extended metadata for this firmware file.
147+
148+ Raises:
149+ ValueError:
150+ A name is given but the firmware does not support it or the name
151+ exceeds the alloted space in the firmware.
152+
153+ """
154+
155+ with zipfile .ZipFile (firmware_zip ) as archive :
156+ metadata : AnyFirmwareMetadata = json .load (
157+ archive .open ("firmware.metadata.json" )
158+ )
159+
160+ if firmware_metadata_is_v1 (metadata ):
161+ firmware = await _create_firmware_v1 (metadata , archive , name )
162+ elif firmware_metadata_is_v2 (metadata ):
163+ firmware = await _create_firmware_v2 (metadata , archive , name )
164+ else :
165+ raise ValueError (
166+ f"unsupported metadata version: { metadata ['metadata-version' ]} "
167+ )
168+
169+ # Add extended metadata needed by install_pybricks.py
170+ metadata ["firmware-sha256" ] = hashlib .sha256 (firmware ).hexdigest ()
113171
114- return firmware , metadata
172+ return firmware , metadata
115173
116174
117175# NAME, PAYLOAD_SIZE requirement
0 commit comments