Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bugfix/lcg 631 xr firmware update fixes #302

Merged
merged 2 commits into from
May 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 3 additions & 2 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ v1.4.2 - XX/XX/202X
* Support to retrieve XBee statistics.
* Send/receive explicit data in 802.15.4.
(XBee 3 modules support this feature)
* Support for local and remote firmware update of XBee XR 868 and 900.
* Bug fixing:

* Fix order of nodes when creating a Zigbee source route (#278)
Expand All @@ -29,8 +30,8 @@ v1.4.1 - 12/22/2021
* XBee 3 Cellular LTE-M/NB-IoT (Telit)
* XBee 3 Reduced RAM
* S2C P5
* XB3-DMLR
* XB3-DMLR868
* XBee XR 900
* XBee XR 868
* OTA firmware update:

* Implementation of considerations for versions 1009, 300A, 200A or prior
Expand Down
91 changes: 68 additions & 23 deletions digi/xbee/firmware.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
from digi.xbee.models.hw import HardwareVersion
from digi.xbee.models.mode import APIOutputModeBit
from digi.xbee.models.options import RemoteATCmdOptions
from digi.xbee.models.protocol import XBeeProtocol, Role, Region
from digi.xbee.models.protocol import XBeeProtocol, Role, Region, OTAMethod
from digi.xbee.models.status import TransmitStatus, ATCommandStatus, \
EmberBootloaderMessageType, ModemStatus, UpdateProgressStatus, NodeUpdateType
from digi.xbee.packets.aft import ApiFrameType
Expand Down Expand Up @@ -81,12 +81,17 @@

_XBEE3_BL_DEF_PREFIX = "xb3-boot-rf_"
_XBEE3_RR_BL_DEF_PREFIX = "xb3-boot-rr_"
_XBEE3_XR_BL_DEF_PREFIX = "xb3-boot-lr_"
_XBEE3_BOOTLOADER_FILE_PREFIX = {
HardwareVersion.XBEE3.code: _XBEE3_BL_DEF_PREFIX,
HardwareVersion.XBEE3_SMT.code: _XBEE3_BL_DEF_PREFIX,
HardwareVersion.XBEE3_TH.code: _XBEE3_BL_DEF_PREFIX,
HardwareVersion.XBEE3_RR.code: _XBEE3_RR_BL_DEF_PREFIX,
HardwareVersion.XBEE3_RR_TH.code: _XBEE3_RR_BL_DEF_PREFIX
HardwareVersion.XBEE3_RR_TH.code: _XBEE3_RR_BL_DEF_PREFIX,
HardwareVersion.XBEE3_DM_LR.code: _XBEE3_XR_BL_DEF_PREFIX,
HardwareVersion.XBEE3_DM_LR_868.code: _XBEE3_XR_BL_DEF_PREFIX,
HardwareVersion.XBEE_XR_900_TH.code: _XBEE3_XR_BL_DEF_PREFIX,
HardwareVersion.XBEE_XR_868_TH.code: _XBEE3_XR_BL_DEF_PREFIX,
}

_GEN3_BOOTLOADER_ERROR_CHECKSUM = 0x12
Expand Down Expand Up @@ -297,14 +302,15 @@
HardwareVersion.XBEE3_SMT.code,
HardwareVersion.XBEE3_TH.code,
HardwareVersion.XBEE3_RR.code,
HardwareVersion.XBEE3_RR_TH.code,
HardwareVersion.XBEE3_DM_LR.code,
HardwareVersion.XBEE3_DM_LR_868.code,
HardwareVersion.XBEE_XR_900_TH.code,
HardwareVersion.XBEE_XR_868_TH.code)
HardwareVersion.XBEE3_RR_TH.code)

LOCAL_SUPPORTED_HW_VERSIONS = SX_HW_VERSIONS + XBEE3_HW_VERSIONS
REMOTE_SUPPORTED_HW_VERSIONS = SX_HW_VERSIONS + XBEE3_HW_VERSIONS + S2C_HW_VERSIONS
XR_HW_VERSIONS = (HardwareVersion.XBEE3_DM_LR.code,
HardwareVersion.XBEE3_DM_LR_868.code,
HardwareVersion.XBEE_XR_900_TH.code,
HardwareVersion.XBEE_XR_868_TH.code)

LOCAL_SUPPORTED_HW_VERSIONS = SX_HW_VERSIONS + XBEE3_HW_VERSIONS + XR_HW_VERSIONS
REMOTE_SUPPORTED_HW_VERSIONS = SX_HW_VERSIONS + XBEE3_HW_VERSIONS + S2C_HW_VERSIONS + XR_HW_VERSIONS

_log = logging.getLogger(__name__)

Expand Down Expand Up @@ -1118,13 +1124,15 @@ class _BootloaderType(Enum):
| **name** (String): The name of this _BootloaderType.
| **value** (Integer): The ID of this _BootloaderType.
"""
GEN3_BOOTLOADER = (0x01, "Generation 3 bootloader")
GECKO_BOOTLOADER = (0x02, "Gecko bootloader")
EMBER_BOOTLOADER = (0x03, "Ember bootloader")
GEN3_BOOTLOADER = (0x01, "Generation 3 bootloader", OTAMethod.GPM)
GECKO_BOOTLOADER = (0x02, "Gecko bootloader", OTAMethod.ZCL)
EMBER_BOOTLOADER = (0x03, "Ember bootloader", OTAMethod.EMBER)
GECKO_BOOTLOADER_XR = (0x04, "Gecko bootloader with GPM OTA", OTAMethod.GPM)

def __init__(self, identifier, description):
def __init__(self, identifier, description, ota_method):
self.__id = identifier
self.__desc = description
self.__ota_method = ota_method

@classmethod
def get(cls, identifier):
Expand Down Expand Up @@ -1160,6 +1168,8 @@ def determine_bootloader_type(cls, hw_version):
return _BootloaderType.GEN3_BOOTLOADER
if hw_version in XBEE3_HW_VERSIONS:
return _BootloaderType.GECKO_BOOTLOADER
if hw_version in XR_HW_VERSIONS:
return _BootloaderType.GECKO_BOOTLOADER_XR
if hw_version in S2C_HW_VERSIONS:
return _BootloaderType.EMBER_BOOTLOADER

Expand All @@ -1185,6 +1195,16 @@ def description(self):
"""
return self.__desc

@property
def ota_method(self):
"""
Returns the over-the-air update method for this bootloader type.

Returns:
:class:`OTAMethod`: OTA method to use with this bootloader.
"""
return self.__ota_method


@unique
class _Gen3BootloaderCmd(Enum):
Expand Down Expand Up @@ -1225,7 +1245,7 @@ def get(cls, identifier):
:class:`._Gen3BootloaderCommand`: _Gen3BootloaderCommand with the
given identifier, `None` if not found.
"""
for value in _BootloaderType:
for value in _Gen3BootloaderCmd:
if value.identifier == identifier:
return value

Expand Down Expand Up @@ -5760,7 +5780,8 @@ class _RemoteGPMFirmwareUpdater(_RemoteFirmwareUpdater):
__DEFAULT_TIMEOUT = 20 # Seconds.

def __init__(self, remote, xml_fw_file, xbee_fw_file=None,
timeout=__DEFAULT_TIMEOUT, progress_cb=None):
timeout=__DEFAULT_TIMEOUT, progress_cb=None,
bootloader_type=_BootloaderType.GEN3_BOOTLOADER):
"""
Class constructor. Instantiates a new
:class:`._RemoteGPMFirmwareUpdater` with the given parameters.
Expand All @@ -5776,6 +5797,9 @@ def __init__(self, remote, xml_fw_file, xbee_fw_file=None,
* The current update task as a String
* The current update task percentage as an Integer

bootloader_type (:class:`_BootloaderType`): Bootloader type of the
remote node.

Raises:
FirmwareUpdateException: If there is any error performing the
remote firmware update.
Expand All @@ -5787,6 +5811,7 @@ def __init__(self, remote, xml_fw_file, xbee_fw_file=None,
self._gpm_frame_sent = False
self._gpm_frame_received = False
self._num_bytes_per_blocks = 0
self._bootloader_type = bootloader_type

def _get_default_reset_timeout(self):
"""
Expand All @@ -5808,7 +5833,15 @@ def _check_fw_binary_file(self):
# same folder as the XML firmware file.
if self._fw_file is None:
path = Path(self._xml_fw_file)
self._fw_file = str(Path(path.parent).joinpath(path.stem + EXTENSION_EBIN))
self._fw_file = str(
Path(path.parent).joinpath(
path.stem + (
EXTENSION_EBIN
if self._bootloader_type == _BootloaderType.GEN3_BOOTLOADER
else EXTENSION_GBL
)
)
)

if not _file_exists(self._fw_file):
self._exit_with_error(_ERROR_FILE_XBEE_FW_NOT_FOUND
Expand Down Expand Up @@ -7103,7 +7136,9 @@ def update_local_firmware(target, xml_fw_file, xbee_firmware_file=None,
hw_version = target.get_hardware_version()
if hw_version and hw_version.code not in LOCAL_SUPPORTED_HW_VERSIONS:
raise OperationNotSupportedException(
"Firmware update only supported in XBee 3 and XBee SX 868/900")
"Firmware update only supported in XBee 3, XBee SX 868/900, "
"and XBee XR 868/900"
)

# Launch the update process.
if not timeout:
Expand All @@ -7120,7 +7155,10 @@ def update_local_firmware(target, xml_fw_file, xbee_firmware_file=None,
if isinstance(target, XBeeDevice) and not target._active_update_type:
target._active_update_type = NodeUpdateType.FIRMWARE
bootloader_type = _determine_bootloader_type(target)
if bootloader_type == _BootloaderType.GECKO_BOOTLOADER:
if bootloader_type in (
_BootloaderType.GECKO_BOOTLOADER,
_BootloaderType.GECKO_BOOTLOADER_XR,
):
update_process = _LocalXBee3FirmwareUpdater(
target, xml_fw_file, xbee_fw_file=xbee_firmware_file,
bootloader_fw_file=bootloader_firmware_file,
Expand Down Expand Up @@ -7196,7 +7234,9 @@ def update_remote_firmware(remote, xml_fw_file, firmware_file=None, bootloader_f
hw_version = remote.get_hardware_version()
if hw_version and hw_version.code not in REMOTE_SUPPORTED_HW_VERSIONS:
raise OperationNotSupportedException(
"Firmware update only supported in XBee 3, XBee SX 868/900, and XBee S2C devices")
"Firmware update only supported in XBee 3, XBee SX 868/900, "
"XBee S2C, and XBee XR 868/900 devices"
)

# Launch the update process.
if not timeout:
Expand All @@ -7217,16 +7257,17 @@ def update_remote_firmware(remote, xml_fw_file, firmware_file=None, bootloader_f
remote.set_sync_ops_timeout(max(orig_op_timeout, timeout))
bootloader_type = _determine_bootloader_type(remote)
remote.set_sync_ops_timeout(orig_op_timeout)
if bootloader_type == _BootloaderType.GECKO_BOOTLOADER:
if bootloader_type.ota_method == OTAMethod.ZCL:
update_process = _RemoteXBee3FirmwareUpdater(
remote, xml_fw_file, ota_fw_file=firmware_file,
otb_fw_file=bootloader_file, timeout=timeout,
max_block_size=max_block_size, progress_cb=progress_callback)
elif bootloader_type == _BootloaderType.GEN3_BOOTLOADER:
elif bootloader_type.ota_method == OTAMethod.GPM:
update_process = _RemoteGPMFirmwareUpdater(
remote, xml_fw_file, xbee_fw_file=firmware_file,
timeout=timeout, progress_cb=progress_callback)
elif bootloader_type == _BootloaderType.EMBER_BOOTLOADER:
timeout=timeout, progress_cb=progress_callback,
bootloader_type=bootloader_type)
elif bootloader_type.ota_method == OTAMethod.EMBER:
update_process = _RemoteEmberFirmwareUpdater(
remote, xml_fw_file, xbee_fw_file=firmware_file,
timeout=timeout, force_update=True, progress_cb=progress_callback)
Expand Down Expand Up @@ -7474,6 +7515,10 @@ def _get_bootloader_version(xbee):
_PARAM_READ_RETRIES)
if bootloader_version is None or len(bootloader_version) < 2:
return None
if len(bootloader_version) == 3:
# XR returns VH value as three bytes: 0XYYZZ.
return bootloader_version

bootloader_version_array[0] = bootloader_version[0] & 0x0F
bootloader_version_array[1] = (bootloader_version[1] & 0xF0) >> 4
bootloader_version_array[2] = bootloader_version[1] & 0x0F
Expand Down
14 changes: 13 additions & 1 deletion digi/xbee/models/protocol.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

from enum import Enum, unique
from enum import Enum, unique, auto
from digi.xbee.models.hw import HardwareVersion
from digi.xbee.util import utils

Expand Down Expand Up @@ -521,3 +521,15 @@ def allows_any(self):


Region.__doc__ += utils.doc_enum(Region)


@unique
class OTAMethod(Enum):
"""
Enumerates the over-the-air firmware update mechanisms of XBee modules.
"""

UNDEFINED = auto()
EMBER = auto()
GPM = auto()
ZCL = auto()
4 changes: 3 additions & 1 deletion digi/xbee/profile.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,9 @@
_WILDCARD_CELLULAR_BOOTLOADER = "bl_.*"
_WILDCARD_XML = "*%s" % EXTENSION_XML
_WILDCARDS_FW_LOCAL_BINARY_FILES = (EXTENSION_EBIN, EXTENSION_EHX2, EXTENSION_GBL)
_WILDCARDS_FW_REMOTE_BINARY_FILES = (EXTENSION_OTA, EXTENSION_OTB, EXTENSION_EBL, EXTENSION_EBIN)
_WILDCARDS_FW_REMOTE_BINARY_FILES = (
EXTENSION_OTA, EXTENSION_OTB, EXTENSION_EBL, EXTENSION_EBIN, EXTENSION_GBL,
)

_XML_COMMAND = "command"
_XML_CONTROL_TYPE = "control_type"
Expand Down
10 changes: 9 additions & 1 deletion digi/xbee/util/xmodem.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
_XMODEM_BLOCK_SIZE_1K = 1024
_XMODEM_READ_HEADER_TIMEOUT = 3 # Seconds
_XMODEM_READ_DATA_TIMEOUT = 1 # Seconds.
_XMODEM_READ_DATA_TIMEOUT_EXTENDED = 5 # Seconds.
_XMODEM_READ_RETRIES = 10
_XMODEM_WRITE_RETRIES = 10

Expand Down Expand Up @@ -540,7 +541,14 @@ def _send_next_block(self, data):
if not self._write_cb(packet):
retries -= 1
continue
answer = self._read_cb(1, timeout=_XMODEM_READ_DATA_TIMEOUT)
answer = self._read_cb(1, timeout=(
# On XBee 3 Cellular, XBee RR, XBee 3 BLU, and XBee XR,
# the first XModem chunk of a firmware upload to the bootloader
# takes about 4 seconds.
_XMODEM_READ_DATA_TIMEOUT_EXTENDED
if self._transfer_file.chunk_index == 1
else _XMODEM_READ_DATA_TIMEOUT
))
if not answer:
retries -= 1
continue
Expand Down
17 changes: 11 additions & 6 deletions doc/user_doc/update_the_xbee.rst
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ profiles:
* **XBee S2C**
* Remote firmware updates
* Remote profile updates
* **XBee XR 868/900**
* Local and remote firmware updates
* Local and remote profile updates


.. _updateFirmware:
Expand All @@ -44,6 +47,7 @@ and remote devices:
* **XBee 3**: Local and remote firmware updates
* **XBee SX 868/900 MHz**: Local and remote firmware updates
* **XBee S2C**: Remote firmware updates
* **XBee XR 868/900**: Local and remote firmware updates


.. _updateFirmwareLocal:
Expand All @@ -66,8 +70,8 @@ connection. For this operation, you need the following components:
not provided.

.. warning::
At the moment, local firmware update is only supported in **XBee 3** and
**XBee SX 868/900 MHz** devices.
At the moment, local firmware update is only supported in **XBee 3**,
**XBee SX 868/900 MHz** and **XBee XR 868/900** devices.


+------------------------------------------------------------------------------------------------------------------------------------------------------+
Expand Down Expand Up @@ -221,7 +225,7 @@ components:

.. warning::
At the moment, remote firmware update is only supported in **XBee 3**,
**XBee SX 868/900 MHz**, and **XBee S2C** devices.
**XBee SX 868/900 MHz**, **XBee S2C**, and **XBee XR 868/900** devices.

To perform the remote firmware update, call the ``update_firmware()`` method of
the ``RemoteXBeeDevice`` class providing the required parameters:
Expand Down Expand Up @@ -489,6 +493,7 @@ To configure individual settings see :ref:`configureXBee`.
* **XBee 3**: Local and remote profile updates
* **XBee SX 868/900 MHz**: Local and remote profile updates
* **XBee S2C**: Remote profile updates
* **XBee XR 868/900**: Local and remote profile updates


.. _readXBeeProfile:
Expand Down Expand Up @@ -641,8 +646,8 @@ Applying a profile to a local XBee requires the following components:
Use `XCTU <http://www.digi.com/xctu>`_ to create configuration profiles.

.. warning::
At the moment, local profile update is only supported in **XBee 3** and
**XBee SX 868/900 MHz** devices.
At the moment, local profile update is only supported in **XBee 3**,
**XBee SX 868/900 MHz**, and **XBee XR 868/900** devices.

To apply the XBee profile to a local XBee, call the ``apply_profile()`` method
of the ``XBeeDevice`` class providing the required parameters:
Expand Down Expand Up @@ -716,7 +721,7 @@ Applying a profile to a remote XBee requires the following components:

.. warning::
At the moment, remote profile update is only supported in **XBee 3**,
**XBee SX 868/900 MHz**, and **XBee S2C** devices.
**XBee SX 868/900 MHz**, **XBee S2C**, and **XBee XR 868/900** devices.

To apply the XBee profile to a remote XBee, call the ``apply_profile()`` method
of the ``RemoteXBeeDevice`` class providing the required parameters:
Expand Down