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"
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"
210213EXTENSION_EBIN = ".ebin"
211214EXTENSION_EBL = ".ebl"
212215EXTENSION_GBL = ".gbl"
216+ EXTENSION_APPONLY_GBL = ".apponly.gbl"
213217EXTENSION_EHX2 = ".ehx2"
214218EXTENSION_OTA = ".ota"
215219EXTENSION_OTB = ".otb"
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+
400432class _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+
764806class _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