Skip to content

Commit cb577cf

Browse files
committed
firmware: xbee xr: support for *.apponly.gbl firmware
Previously, firmware updates required separate files for the bootloader and the application. The update process first programmed the bootloader and then the application. XBee XR firmware packages also include two GBL files, but in a different structure: * `*.apponly.gbl` contains only the application. * `*.gbl` contains both the bootloader and the application. This commit adds support (both local and remote) for these files in: def update_firmware(self, xml_firmware_file, xbee_firmware_file=None, bootloader_firmware_file=None, timeout=None, progress_callback=None) For XR devices, the inputs are: * `xml_firmware_file`: absolute path to the firmware XML file. * `xbee_firmware_file`: absolute path to `*.apponly.gbl` (`*.gbl` is also accepted). * `bootloader_firmware_file`: absolute path to `*.gbl`. The update logic: * If a bootloader update is NOT required: * `xbee_firmware_file` is used. If not specified, the library searches for `*.apponly.gbl` in the same directory as `xml_firmware_file`. If defined, the library performs a basic check to ensure it is a valid GBL file. * `bootloader_firmware_file` is ignored. * If a bootloader update IS required: * `bootloader_firmware_file` is used. If not specified, the library searches for `*.gbl` in the same directory as `xml_firmware_file`. If defined, the library performs a basic check to ensure it is a valid GBL file (and not a `*.apponly.gbl` file). * `xbee_firmware_file` is not used for the update, but the library checks for its existence and format. This verification is useful because customers may not know whether a bootloader update is required. This behavior ensures flexibility while maintaining backward compatibility and correct bootloader programming when needed. This is related to commit d2c8a4a. https://onedigi.atlassian.net/browse/LCG-631 https://onedigi.atlassian.net/browse/XBPL-429 Signed-off-by: Tatiana Leon <Tatiana.Leon@digi.com>
1 parent f90bd78 commit cb577cf

File tree

1 file changed

+146
-10
lines changed

1 file changed

+146
-10
lines changed

digi/xbee/firmware.py

Lines changed: 146 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,8 @@
129129

130130
_ERROR_BOOTLOADER_MODE = "Could not enter in bootloader mode"
131131
_ERROR_BOOTLOADER_NOT_SUPPORTED = "XBee does not support firmware update process"
132+
_ERROR_BOOTLOADER_UPDATE_REQUIRED = "Bootloader update required, '%s' does not include the bootloader"
133+
_ERROR_CHECKING_GBL_FILE = "Error checking GBL file: %s"
132134
_ERROR_COMPATIBILITY_NUMBER = "Device compatibility number (%d) is greater " \
133135
"than the firmware one (%d)"
134136
_ERROR_COMMUNICATION_LOST = "Communication with the device was lost"
@@ -157,6 +159,7 @@
157159
"firmware one (%d)"
158160
_ERROR_IMAGE_VERIFICATION = "Image verification error"
159161
_ERROR_INITIALIZE_PROCESS = "Could not initialize firmware update process"
162+
_ERROR_INVALID_GBL_FILE = "Invalid gbl file: %s"
160163
_ERROR_INVALID_OTA_FILE = "Invalid OTA file: %s"
161164
_ERROR_INVALID_BLOCK = "Requested block index '%s' does not exits"
162165
_ERROR_INVALID_GPM_ANSWER = "Invalid GPM frame answer"
@@ -210,6 +213,7 @@
210213
EXTENSION_EBIN = ".ebin"
211214
EXTENSION_EBL = ".ebl"
212215
EXTENSION_GBL = ".gbl"
216+
EXTENSION_APPONLY_GBL = ".apponly.gbl"
213217
EXTENSION_EHX2 = ".ehx2"
214218
EXTENSION_OTA = ".ota"
215219
EXTENSION_OTB = ".otb"
@@ -234,6 +238,8 @@
234238
_OTA_DEFAULT_BLOCK_SIZE_ENC = 44
235239
_OTA_GBL_SIZE_BYTE_COUNT = 6
236240

241+
_GBL_FILE_IDENTIFIER = 0x03A617EB
242+
237243
_PACKET_DEFAULT_SEQ_NUMBER = 0x01
238244

239245
# Answer examples: 01 81 -> 1.8.1 - 0F 3E -> 15.3.14
@@ -397,6 +403,32 @@ def percent(self):
397403
return ((self._page_index + 1) * 100) // self._num_pages
398404

399405

406+
class _GBLFile(_EbinFile):
407+
"""
408+
Helper class that represents a local firmware file in 'GBL' format.
409+
"""
410+
411+
@staticmethod
412+
def check_format(file_path):
413+
"""
414+
Verifies that the file is a GBL file.
415+
416+
Raises:
417+
_GBLFormatException: If the file in not a GBL file.
418+
"""
419+
_log.debug("Checking GBL file %s:", file_path)
420+
if os.path.splitext(file_path.lower())[1] != EXTENSION_GBL:
421+
raise _GBLFormatException(_ERROR_INVALID_GBL_FILE % file_path)
422+
try:
423+
with open(file_path, "rb") as file:
424+
identifier = utils.bytes_to_int(_reverse_bytearray(file.read(_BUFFER_SIZE_INT)))
425+
if identifier != _GBL_FILE_IDENTIFIER:
426+
raise _GBLFormatException(_ERROR_INVALID_GBL_FILE % file_path)
427+
_log.debug(" - Identifier: %04X (%d)", identifier, identifier)
428+
except IOError as exc:
429+
raise _GBLFormatException(_ERROR_CHECKING_GBL_FILE % str(exc)) from exc
430+
431+
400432
class _EBLFile:
401433
"""
402434
Helper class that represents a local firmware file in 'ebl' format.
@@ -761,6 +793,16 @@ def max_hw_version(self):
761793
return self._max_hw_version
762794

763795

796+
class _GBLFormatException(Exception):
797+
"""
798+
This exception will be thrown when any problem related with the parsing of
799+
GBL files occurs.
800+
801+
All functionality of this class is the inherited from `Exception
802+
<https://docs.python.org/2/library/exceptions.html?highlight=exceptions.exception#exceptions.Exception>`_.
803+
"""
804+
805+
764806
class _XBee3OTAClientDescription:
765807
"""
766808
Helper class used to get OTA client capabilities depending on its firmware version.
@@ -3617,6 +3659,7 @@ class _LocalXBee3FirmwareUpdater(_LocalFirmwareUpdater):
36173659
"""
36183660

36193661
def __init__(self, target, xml_fw_file, xbee_fw_file=None, bootloader_fw_file=None,
3662+
bootloader_type=_BootloaderType.GECKO_BOOTLOADER,
36203663
timeout=_READ_DATA_TIMEOUT, progress_cb=None):
36213664
"""
36223665
Class constructor. Instantiates a new
@@ -3629,6 +3672,7 @@ def __init__(self, target, xml_fw_file, xbee_fw_file=None, bootloader_fw_file=No
36293672
xml_fw_file (String): Location of the XML firmware file.
36303673
xbee_fw_file (String, optional): Location of the XBee binary firmware file.
36313674
bootloader_fw_file (String, optional): Location of the bootloader binary firmware file.
3675+
bootloader_type (:class:`_BootloaderType`): Bootloader type of the node.
36323676
timeout (Integer, optional): Serial port read data operation timeout.
36333677
progress_cb (Function, optional): Function to receive progress
36343678
information. Receives two arguments:
@@ -3640,6 +3684,7 @@ def __init__(self, target, xml_fw_file, xbee_fw_file=None, bootloader_fw_file=No
36403684
timeout=timeout, progress_cb=progress_cb)
36413685

36423686
self._bootloader_fw_file = bootloader_fw_file
3687+
self._bootloader_type = bootloader_type
36433688

36443689
def _is_bootloader_active(self):
36453690
"""
@@ -3740,15 +3785,64 @@ def _get_fw_binary_file_extension(self):
37403785
.. seealso::
37413786
| :meth:`._LocalFirmwareUpdater._get_fw_binary_file_extension`
37423787
"""
3788+
if self._bootloader_type == _BootloaderType.GECKO_BOOTLOADER_XR:
3789+
return EXTENSION_APPONLY_GBL
37433790
return EXTENSION_GBL
37443791

3792+
def _check_fw_binary_file(self):
3793+
"""
3794+
Override.
3795+
3796+
.. seealso::
3797+
| :meth:`._LocalFirmwareUpdater._check_fw_binary_file`
3798+
"""
3799+
super()._check_fw_binary_file()
3800+
3801+
try:
3802+
_GBLFile.check_format(self._fw_file)
3803+
except _GBLFormatException as exc:
3804+
self._exit_with_error(str(exc), restore_updater=False)
3805+
37453806
def _check_bootloader_binary_file(self):
37463807
"""
37473808
Override.
37483809
37493810
.. seealso::
37503811
| :meth:`._XBeeFirmwareUpdater._check_bootloader_binary_file`
37513812
"""
3813+
# For XR devices, the bootloader is not a separated file. There are:
3814+
# - '*.apponly.gbl' file with only the application
3815+
# - '*.gbl' file with the bootloader and the application
3816+
# Make sure the '*.gbl' file is used and not the '*.apponly.gbl' file
3817+
if self._bootloader_type == _BootloaderType.GECKO_BOOTLOADER_XR:
3818+
if self._bootloader_fw_file is None:
3819+
path = Path(self._xml_fw_file)
3820+
self._bootloader_fw_file = str(
3821+
Path(path.parent).joinpath(path.stem + EXTENSION_GBL))
3822+
3823+
if not _file_exists(self._bootloader_fw_file):
3824+
self._exit_with_error(
3825+
_ERROR_FILE_NOT_FOUND % ("XBee firmware", self._bootloader_fw_file),
3826+
restore_updater=False)
3827+
3828+
try:
3829+
_GBLFile.check_format(self._bootloader_fw_file)
3830+
except _GBLFormatException as exc:
3831+
self._exit_with_error(str(exc), restore_updater=False)
3832+
3833+
# File '*.apponly.gbl' does not include the bootloader
3834+
if self._bootloader_fw_file.lower().endswith(EXTENSION_APPONLY_GBL):
3835+
self._exit_with_error(
3836+
_ERROR_BOOTLOADER_UPDATE_REQUIRED % self._bootloader_fw_file,
3837+
restore_updater=False)
3838+
3839+
# Checking the bootloader file means a bootloader update is required, so
3840+
# replace the firmware file ('*.apponly.gbl') with the bootloader one ('*.gbl')
3841+
self._fw_file = self._bootloader_fw_file
3842+
self._bootloader_fw_file = None
3843+
self._bootloader_update_required = False
3844+
return
3845+
37523846
# If not already specified, the bootloader firmware file is usually in
37533847
# the same folder as the XML firmware file.
37543848
# The file filename starts with a fixed prefix and includes the
@@ -3763,7 +3857,7 @@ def _check_bootloader_binary_file(self):
37633857
+ EXTENSION_GBL))
37643858

37653859
if not _file_exists(self._bootloader_fw_file):
3766-
self._exit_with_error(_ERROR_FILE_NOT_FOUND % ("booloader firmware", self._bootloader_fw_file),
3860+
self._exit_with_error(_ERROR_FILE_NOT_FOUND % ("bootloader firmware", self._bootloader_fw_file),
37673861
restore_updater=False)
37683862

37693863
def _transfer_firmware(self):
@@ -5782,6 +5876,7 @@ class _RemoteGPMFirmwareUpdater(_RemoteFirmwareUpdater):
57825876
__DEFAULT_TIMEOUT = 20 # Seconds.
57835877

57845878
def __init__(self, remote, xml_fw_file, xbee_fw_file=None,
5879+
bootloader_file=None,
57855880
timeout=__DEFAULT_TIMEOUT, progress_cb=None,
57865881
bootloader_type=_BootloaderType.GEN3_BOOTLOADER):
57875882
"""
@@ -5792,6 +5887,7 @@ def __init__(self, remote, xml_fw_file, xbee_fw_file=None,
57925887
remote (:class:`.RemoteXBeeDevice`): Remote XBee to upload its firmware.
57935888
xml_fw_file (String): Path of the XML file that describes the firmware.
57945889
xbee_fw_file (String, optional): Path of the binary firmware file.
5890+
bootloader_file (String, optional): Path of the binary bootloader file.
57955891
timeout (Integer, optional): Timeout to wait for remote frame answers.
57965892
progress_cb (Function, optional): Function to receive progress
57975893
information. Receives two arguments:
@@ -5809,6 +5905,7 @@ def __init__(self, remote, xml_fw_file, xbee_fw_file=None,
58095905
super().__init__(remote, xml_fw_file, timeout=timeout, progress_cb=progress_cb)
58105906

58115907
self._fw_file = xbee_fw_file
5908+
self._bl_fw_file = bootloader_file
58125909
self._gpm_answer_payload = None
58135910
self._gpm_frame_sent = False
58145911
self._gpm_frame_received = False
@@ -5840,7 +5937,7 @@ def _check_fw_binary_file(self):
58405937
path.stem + (
58415938
EXTENSION_EBIN
58425939
if self._bootloader_type == _BootloaderType.GEN3_BOOTLOADER
5843-
else EXTENSION_GBL
5940+
else EXTENSION_APPONLY_GBL
58445941
)
58455942
)
58465943
)
@@ -5849,14 +5946,48 @@ def _check_fw_binary_file(self):
58495946
self._exit_with_error(_ERROR_FILE_NOT_FOUND % ("XBee firmware", self._fw_file),
58505947
restore_updater=False)
58515948

5949+
if self._bootloader_type == _BootloaderType.GECKO_BOOTLOADER_XR:
5950+
try:
5951+
_GBLFile.check_format(self._fw_file)
5952+
except _GBLFormatException as exc:
5953+
self._exit_with_error(str(exc), restore_updater=False)
5954+
58525955
def _check_bootloader_binary_file(self):
58535956
"""
58545957
Override.
58555958
58565959
.. seealso::
58575960
| :meth:`._XBeeFirmwareUpdater._check_bootloader_binary_file`
58585961
"""
5859-
# General Purpose Memory devices do not have bootloader update file.
5962+
if self._bootloader_type != _BootloaderType.GECKO_BOOTLOADER_XR:
5963+
# General Purpose Memory devices do not have bootloader update file.
5964+
return
5965+
5966+
# For XR devices, the bootloader is not a separated file. There are:
5967+
# - '*.apponly.gbl' file with only the application
5968+
# - '*.gbl' file with the bootloader and the application
5969+
# Make sure the '*.gbl' file is used and not the '*.apponly.gbl' file
5970+
if self._bl_fw_file is None:
5971+
path = Path(self._xml_fw_file)
5972+
self._bl_fw_file = str(Path(path.parent).joinpath(path.stem + EXTENSION_GBL))
5973+
5974+
if not _file_exists(self._bl_fw_file):
5975+
self._exit_with_error(_ERROR_FILE_NOT_FOUND % ("XBee firmware", self._bl_fw_file),
5976+
restore_updater=False)
5977+
try:
5978+
_GBLFile.check_format(self._bl_fw_file)
5979+
except _GBLFormatException as exc:
5980+
self._exit_with_error(str(exc), restore_updater=False)
5981+
5982+
# File '*.apponly.gbl' does not include the bootloader
5983+
if self._bl_fw_file.lower().endswith(EXTENSION_APPONLY_GBL):
5984+
self._exit_with_error(_ERROR_BOOTLOADER_UPDATE_REQUIRED % self._bl_fw_file,
5985+
restore_updater=False)
5986+
5987+
# Checking the bootloader file means a bootloader update is required, so
5988+
# replace the firmware file ('*.apponly.gbl') with the bootloader one ('*.gbl')
5989+
self._fw_file = self._bl_fw_file
5990+
self._bl_fw_file = None
58605991

58615992
def _configure_ao_parameter(self):
58625993
"""
@@ -6143,16 +6274,19 @@ def _transfer_firmware(self):
61436274
_log.info("%s - %s", self._remote, _PROGRESS_TASK_UPDATE_REMOTE_XBEE)
61446275
self._progress_task = _PROGRESS_TASK_UPDATE_REMOTE_XBEE
61456276
# Perform file transfer.
6146-
ebin_file = _EbinFile(self._fw_file, self.__DEFAULT_PAGE_SIZE)
6277+
if self._bootloader_type == _BootloaderType.GECKO_BOOTLOADER_XR:
6278+
fw_file = _GBLFile(self._fw_file, self.__DEFAULT_PAGE_SIZE)
6279+
else:
6280+
fw_file = _EbinFile(self._fw_file, self.__DEFAULT_PAGE_SIZE)
61476281
previous_percent = None
61486282
block_index = 0
61496283
byte_index = 0
6150-
for data_chunk in ebin_file.get_next_mem_page():
6151-
if ebin_file.percent != previous_percent:
6152-
self._notify_progress(self._progress_task, ebin_file.percent)
6153-
previous_percent = ebin_file.percent
6154-
_log.debug("Sending chunk %d/%d %d%%", ebin_file.page_index + 1,
6155-
ebin_file.num_pages, ebin_file.percent)
6284+
for data_chunk in fw_file.get_next_mem_page():
6285+
if fw_file.percent != previous_percent:
6286+
self._notify_progress(self._progress_task, fw_file.percent)
6287+
previous_percent = fw_file.percent
6288+
_log.debug("Sending chunk %d/%d %d%%", fw_file.page_index + 1,
6289+
fw_file.num_pages, fw_file.percent)
61566290
self._write_data(block_index, byte_index, data_chunk, 3)
61576291
byte_index += len(data_chunk)
61586292
# Increment block index if required.
@@ -7168,6 +7302,7 @@ def update_local_firmware(target, xml_fw_file, xbee_firmware_file=None,
71687302
update_process = _LocalXBee3FirmwareUpdater(
71697303
target, xml_fw_file, xbee_fw_file=xbee_firmware_file,
71707304
bootloader_fw_file=bootloader_firmware_file,
7305+
bootloader_type=bootloader_type,
71717306
timeout=timeout, progress_cb=progress_callback)
71727307
elif bootloader_type == _BootloaderType.GEN3_BOOTLOADER:
71737308
update_process = _LocalXBeeGEN3FirmwareUpdater(
@@ -7275,6 +7410,7 @@ def update_remote_firmware(remote, xml_fw_file, firmware_file=None, bootloader_f
72757410
elif bootloader_type.ota_method == OTAMethod.GPM:
72767411
update_process = _RemoteGPMFirmwareUpdater(
72777412
remote, xml_fw_file, xbee_fw_file=firmware_file,
7413+
bootloader_file=bootloader_file,
72787414
timeout=timeout, progress_cb=progress_callback,
72797415
bootloader_type=bootloader_type)
72807416
elif bootloader_type.ota_method == OTAMethod.EMBER:

0 commit comments

Comments
 (0)