From f5534d000f1e2569ae9aecfc9a1a6caf31fa9c63 Mon Sep 17 00:00:00 2001 From: Tatiana Leon Date: Wed, 16 Feb 2022 10:13:29 +0100 Subject: [PATCH 01/36] Fix value of 'ENABLE_UNICAST_TRACE_ROUTE' (must be 0x08) Signed-off-by: Tatiana Leon --- digi/xbee/models/options.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/digi/xbee/models/options.py b/digi/xbee/models/options.py index 5f1636e..a4ce5e0 100644 --- a/digi/xbee/models/options.py +++ b/digi/xbee/models/options.py @@ -171,15 +171,6 @@ class TransmitOptions(Enum): Only valid for DigiMesh 868/900 protocol, and XBee 3 DigiMesh. """ - ENABLE_UNICAST_TRACE_ROUTE = 0x04 - """ - Enables unicast trace route messages. - - Trace route is enabled on the packets. - - Only valid for DigiMesh 868/900 protocol. - """ - INDIRECT_TRANSMISSION = 0x04 """ Used for binding transmissions. @@ -202,6 +193,15 @@ class TransmitOptions(Enum): Only valid for DigiMesh XBee protocol. """ + ENABLE_UNICAST_TRACE_ROUTE = 0x08 + """ + Enables unicast trace route messages. + + Trace route is enabled on the packets. + + Only valid for DigiMesh 868/900 protocol. + """ + SECURE_SESSION_ENC = 0x10 """ Encrypt payload for transmission across a Secure Session. From 26e15b319d1b1e16f01c50091cdfd5117adccfcd Mon Sep 17 00:00:00 2001 From: Tatiana Leon Date: Fri, 18 Feb 2022 13:22:42 +0100 Subject: [PATCH 02/36] update collections import to support Python 3.10 https://onedigi.atlassian.net/browse/DAL-5918 Signed-off-by: Tatiana Leon --- digi/xbee/util/xmodem.py | 5 ++++- setup.py | 1 + 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/digi/xbee/util/xmodem.py b/digi/xbee/util/xmodem.py index 07eae81..a14f8ca 100644 --- a/digi/xbee/util/xmodem.py +++ b/digi/xbee/util/xmodem.py @@ -12,7 +12,10 @@ # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -import collections +try: + import collections.abc as collections +except ImportError: + import collections import os import time diff --git a/setup.py b/setup.py index afeedeb..9a255b3 100644 --- a/setup.py +++ b/setup.py @@ -73,6 +73,7 @@ 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.9', + 'Programming Language :: Python :: 3.10', 'Operating System :: OS Independent', ], project_urls={ From 41823acdd5aecef625ec90da9fac53fd816f77ea Mon Sep 17 00:00:00 2001 From: Isaac Hermida Date: Mon, 21 Mar 2022 18:15:36 +0100 Subject: [PATCH 03/36] devices: add support for XBee statistics Initial support to report basic XBee statistics: * TX/RX packets * TX/RX Bytes * Remote AT command errors * TX errors This commit also updates the documentation and adds a basic example. Signed-off-by: Isaac Hermida --- CHANGELOG.rst | 5 + digi/xbee/comm_interface.py | 12 +- digi/xbee/devices.py | 35 ++++- digi/xbee/models/statistics.py | 131 ++++++++++++++++++ digi/xbee/packets/base.py | 12 +- digi/xbee/packets/common.py | 86 +++++++++++- digi/xbee/packets/raw.py | 62 ++++++++- digi/xbee/packets/wifi.py | 32 ++++- digi/xbee/sender.py | 5 +- doc/api/digi.xbee.models.statistics.rst | 7 + doc/examples.rst | 22 +++ .../communicating_with_xbee_devices.rst | 65 +++++++++ .../GetXBeeStatisticsSample.py | 75 ++++++++++ .../GetXBeeStatisticsSample/readme.txt | 66 +++++++++ 14 files changed, 607 insertions(+), 8 deletions(-) create mode 100644 digi/xbee/models/statistics.py create mode 100644 doc/api/digi.xbee.models.statistics.rst create mode 100644 examples/statistics/GetXBeeStatisticsSample/GetXBeeStatisticsSample.py create mode 100644 examples/statistics/GetXBeeStatisticsSample/readme.txt diff --git a/CHANGELOG.rst b/CHANGELOG.rst index be1f46d..fde06e0 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,6 +1,11 @@ Changelog ========= +v1.4.2 - XX/XX/202X + +* Support to retrieve XBee statistics. + + v1.4.1 - 12/22/2021 ------------------- diff --git a/digi/xbee/comm_interface.py b/digi/xbee/comm_interface.py index a48705f..838cadc 100644 --- a/digi/xbee/comm_interface.py +++ b/digi/xbee/comm_interface.py @@ -1,4 +1,4 @@ -# Copyright 2019, 2020, Digi International Inc. +# Copyright 2019-2022, Digi International Inc. # # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this @@ -138,6 +138,16 @@ def get_local_xbee_info(self): """ return None + def get_stats(self): + """ + Returns a statistics object. + + Returns: + :class: `.Statistics`: `None` if not implemented, + otherwise a `Statistics` object. + """ + return None + def supports_update_firmware(self): """ Returns if the interface supports the firmware update feature. diff --git a/digi/xbee/devices.py b/digi/xbee/devices.py index abd5cc0..9c3c3a2 100644 --- a/digi/xbee/devices.py +++ b/digi/xbee/devices.py @@ -1,4 +1,4 @@ -# Copyright 2017-2021, Digi International Inc. +# Copyright 2017-2022, Digi International Inc. # # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this @@ -24,6 +24,7 @@ from digi.xbee import serial from digi.xbee.filesystem import FileSystemManager +from digi.xbee.models.statistics import Statistics from digi.xbee.packets.cellular import TXSMSPacket from digi.xbee.models.accesspoint import AccessPoint, WiFiEncryptionType from digi.xbee.models.atcomm import ATCommandResponse, ATCommand, ATStringCommand @@ -2420,6 +2421,7 @@ def __init__(self, port=None, baud_rate=None, data_bits=serial.EIGHTBITS, self.__tmp_dm_to_insert = [] self.__tmp_dm_routes_lock = threading.Lock() self.__route_received = RouteReceived() + self.__stats = Statistics() @classmethod def create_xbee_device(cls, comm_port_data): @@ -2596,6 +2598,16 @@ def operating_mode(self): """ return super()._get_operating_mode() + @property + def stats(self): + """ + Gets the statistics for this XBee. + + Returns: + :class:`.Statistics`. XBee statistics. + """ + return self._comm_iface.get_stats() if self._comm_iface.get_stats() else self.__stats + @AbstractXBeeDevice._before_send_method def get_parameter(self, parameter, parameter_value=None, apply=None): """ @@ -3559,6 +3571,7 @@ def get_xbee_device_callbacks(self): if self.serial_port: api_callbacks.append(self._packet_sender.at_response_received_cb) + api_callbacks.append(self._update_rx_stats_cb) if not self._network: return api_callbacks @@ -4565,6 +4578,24 @@ def route_cb(src, dest, hops): return status, (self, remote, node_list[1:]) + def _update_rx_stats_cb(self, rx_packet): + """ + Callback to increase the XBee statistics related with received packets. + + Args: + rx_packet (:class: `.XBeeAPIPacket`): The received API packet. + """ + self.__stats._update_rx_stats(rx_packet) + + def _update_tx_stats(self, tx_packet): + """ + Increments the XBee statistics related with transmitted packets. + + Args: + tx_packet (:class: `.XBeeAPIPacket`): The sent API packet. + """ + self.__stats._update_tx_stats(tx_packet) + class Raw802Device(XBeeDevice): """ @@ -10409,7 +10440,7 @@ def _discover_neighbors(self, requester, nodes_queue, active_processes, node_tim def __discover_devices(self, node_id=None): """ Blocking method. Performs a device discovery in the network and waits - until it finish (timeout or 'end' packet for 802.15.4) + until it finishes (timeout or 'end' packet for 802.15.4) Args: node_id (String, optional, default=`None`): Node identifier of the diff --git a/digi/xbee/models/statistics.py b/digi/xbee/models/statistics.py new file mode 100644 index 0000000..cad872b --- /dev/null +++ b/digi/xbee/models/statistics.py @@ -0,0 +1,131 @@ +# Copyright 2022, Digi International Inc. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +import copy + +from digi.xbee.models.status import TransmitStatus, ATCommandStatus +from digi.xbee.packets.aft import ApiFrameType +from digi.xbee.packets.base import DictKeys + + +class Statistics: + """ + This class represents all available XBee statistics. + """ + + def __init__(self): + _dict = {} + for elem in ApiFrameType: + _dict[elem.name] = {} + _dict[elem.name]["pkts"] = 0 + _dict[elem.name]["bytes"] = 0 + _dict[elem.name]["errors"] = 0 + self._tx_dict = copy.deepcopy(_dict) + self._rx_dict = copy.deepcopy(_dict) + + def _update_rx_stats(self, rx_packet): + """ + Increases the XBee statistics related with received packets. + + Args: + rx_packet (:class: `.XBeeAPIPacket`): The received API packet. + """ + _key = rx_packet.get_frame_type().name + if _key not in self._rx_dict.keys(): + return + + self._rx_dict[_key]["pkts"] += 1 + self._rx_dict[_key]["bytes"] += rx_packet.effective_len + + if _key in (ApiFrameType.TRANSMIT_STATUS.name, ApiFrameType.TX_STATUS.name): + tx_status = rx_packet._get_api_packet_spec_data_dict()[DictKeys.TS_STATUS] + if tx_status != TransmitStatus.SUCCESS: + self._rx_dict[_key]["errors"] += 1 + + if _key == ApiFrameType.REMOTE_AT_COMMAND_RESPONSE.name: + if rx_packet.status != ATCommandStatus.OK: + self._rx_dict[_key]["errors"] += 1 + + def _update_tx_stats(self, tx_packet): + """ + Increments the XBee statistics related with transmitted packets. + + Args: + tx_packet (:class: `.XBeeAPIPacket`): The sent API packet. + """ + _key = tx_packet.get_frame_type().name + if _key in self._tx_dict.keys(): + self._tx_dict[_key]["pkts"] += 1 + self._tx_dict[_key]["bytes"] += tx_packet.effective_len + + @property + def tx_packets(self): + """ + Gets the current amount of TX packets. + + Returns: + Integer: Number of TX packets. + """ + return sum(self._tx_dict[item]["pkts"] for item in self._tx_dict) + + @property + def rx_packets(self): + """ + Gets the current amount of RX packets. + + Returns: + Integer: Number of RX packets. + """ + return sum(self._rx_dict[item]["pkts"] for item in self._rx_dict) + + @property + def tx_bytes(self): + """ + Gets the current amount of TX bytes. + + Returns: + Integer: Number of TX bytes. + """ + return sum(self._tx_dict[item]["bytes"] for item in self._tx_dict) + + @property + def rx_bytes(self): + """ + Gets the current amount of RX bytes. + + Returns: + Integer: Number of RX bytes. + """ + return sum(self._rx_dict[item]["bytes"] for item in self._rx_dict) + + @property + def rmt_cmd_errors(self): + """ + Gets the current amount of remote AT command errors. + + Returns: + Integer: Number of remote AT command errors. + """ + return self._rx_dict[ApiFrameType.REMOTE_AT_COMMAND_RESPONSE.name]["errors"] + + @property + def tx_errors(self): + """ + Gets the current amount of transmit errors. + + Returns: + Integer: Number of transmit errors. + """ + return (self._rx_dict[ApiFrameType.TRANSMIT_STATUS.name]["errors"] + + self._rx_dict[ApiFrameType.TX_STATUS.name]["errors"]) diff --git a/digi/xbee/packets/base.py b/digi/xbee/packets/base.py index c4872fe..151b565 100644 --- a/digi/xbee/packets/base.py +++ b/digi/xbee/packets/base.py @@ -1,4 +1,4 @@ -# Copyright 2017-2021, Digi International Inc. +# Copyright 2017-2022, Digi International Inc. # # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this @@ -453,6 +453,16 @@ def is_broadcast(self): """ return False + @property + def effective_len(self): + """ + Returns the effective length of the packet. + + Returns: + Integer: Effective length of the packet. + """ + return len(self) + @property def frame_id(self): """ diff --git a/digi/xbee/packets/common.py b/digi/xbee/packets/common.py index 69fbabc..993b25b 100644 --- a/digi/xbee/packets/common.py +++ b/digi/xbee/packets/common.py @@ -1,4 +1,4 @@ -# Copyright 2017-2021, Digi International Inc. +# Copyright 2017-2022, Digi International Inc. # # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this @@ -733,6 +733,16 @@ def _get_api_packet_spec_data_dict(self): DictKeys.RECEIVE_OPTIONS: self.__rx_opts, DictKeys.RF_DATA: list(self.__data) if self.__data is not None else None} + @property + def effective_len(self): + """ + Override method. + + .. seealso:: + | :meth:`.XBeeAPIPacket.effective_len` + """ + return len(self) - 2 - 8 # Remove 16-bit and 64-bit addresses + @property def x64bit_source_addr(self): """ @@ -979,6 +989,16 @@ def _get_api_packet_spec_data_dict(self): DictKeys.COMMAND: self.__cmd, DictKeys.PARAMETER: list(self.__param) if self.__param is not None else None} + @property + def effective_len(self): + """ + Override method. + + .. seealso:: + | :meth:`.XBeeAPIPacket.effective_len` + """ + return len(self) - 2 - 8 # Remove 16-bit and 64-bit addresses + @property def x64bit_dest_addr(self): """ @@ -1253,6 +1273,16 @@ def _get_api_packet_spec_data_dict(self): DictKeys.AT_CMD_STATUS: self.__resp_st, DictKeys.RF_DATA: list(self.__comm_val) if self.__comm_val is not None else None} + @property + def effective_len(self): + """ + Override method. + + .. seealso:: + | :meth:`.XBeeAPIPacket.effective_len` + """ + return len(self) - 2 - 8 # Remove 16-bit and 64-bit addresses + @property def command(self): """ @@ -1563,6 +1593,16 @@ def _get_api_packet_spec_data_dict(self): DictKeys.TRANSMIT_OPTIONS: self.__tx_opts, DictKeys.RF_DATA: list(self.__data) if self.__data is not None else None} + @property + def effective_len(self): + """ + Override method. + + .. seealso:: + | :meth:`.XBeeAPIPacket.effective_len` + """ + return len(self) - 2 - 8 # Remove 16-bit and 64-bit addresses + @property def rf_data(self): """ @@ -1821,6 +1861,16 @@ def _get_api_packet_spec_data_dict(self): DictKeys.TS_STATUS: self.__tx_status, DictKeys.DS_STATUS: self.__discovery_status} + @property + def effective_len(self): + """ + Override method. + + .. seealso:: + | :meth:`.XBeeAPIPacket.effective_len` + """ + return len(self) - 2 # Remove 16-bit address + @property def x16bit_dest_addr(self): """ @@ -2203,6 +2253,16 @@ def is_broadcast(self): """ return utils.is_bit_enabled(self.__rx_opts, 1) + @property + def effective_len(self): + """ + Override method. + + .. seealso:: + | :meth:`.XBeeAPIPacket.effective_len` + """ + return len(self) - 2 - 8 # Remove 16-bit and 64-bit addresses + @property def x64bit_source_addr(self): """ @@ -2539,6 +2599,18 @@ def _get_api_packet_spec_data_dict(self): DictKeys.TRANSMIT_OPTIONS: self.__tx_opts, DictKeys.RF_DATA: self.__data} + @property + def effective_len(self): + """ + Override method. + + .. seealso:: + | :meth:`.XBeeAPIPacket.effective_len` + """ + # Remove 16-bit and 64-bit addresses, the source and destination + # endpoints, the cluster ID and the profile ID. + return len(self) - 2 - 8 - 1 - 1 - 2 - 2 + @property def source_endpoint(self): """ @@ -2914,6 +2986,18 @@ def _get_api_packet_spec_data_dict(self): DictKeys.RECEIVE_OPTIONS: self.__rx_opts, DictKeys.RF_DATA: self.__data} + @property + def effective_len(self): + """ + Override method. + + .. seealso:: + | :meth:`.XBeeAPIPacket.effective_len` + """ + # Remove 16-bit and 64-bit addresses, the source and destination + # endpoints, the cluster ID and the profile ID. + return len(self) - 2 - 8 - 1 - 1 - 2 - 2 + @property def x64bit_source_addr(self): """ diff --git a/digi/xbee/packets/raw.py b/digi/xbee/packets/raw.py index b57b360..8c7b9e3 100644 --- a/digi/xbee/packets/raw.py +++ b/digi/xbee/packets/raw.py @@ -1,4 +1,4 @@ -# Copyright 2017-2021, Digi International Inc. +# Copyright 2017-2022, Digi International Inc. # # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this @@ -139,6 +139,16 @@ def _get_api_packet_spec_data_dict(self): DictKeys.TRANSMIT_OPTIONS: self.__tx_opts, DictKeys.RF_DATA: self.__data} + @property + def effective_len(self): + """ + Override method. + + .. seealso:: + | :meth:`.XBeeAPIPacket.effective_len` + """ + return len(self) - 8 # Remove 64-bit address + @property def x64bit_dest_addr(self): """ @@ -334,6 +344,16 @@ def _get_api_packet_spec_data_dict(self): DictKeys.TRANSMIT_OPTIONS: self.__tx_opts, DictKeys.RF_DATA: self.__data} + @property + def effective_len(self): + """ + Override method. + + .. seealso:: + | :meth:`.XBeeAPIPacket.effective_len` + """ + return len(self) - 2 # Remove 16-bit address + @property def x16bit_dest_addr(self): """ @@ -674,6 +694,16 @@ def _get_api_packet_spec_data_dict(self): DictKeys.RECEIVE_OPTIONS: self.__rx_opts, DictKeys.RF_DATA: self.__data} + @property + def effective_len(self): + """ + Override method. + + .. seealso:: + | :meth:`.XBeeAPIPacket.effective_len` + """ + return len(self) - 8 # Remove 64-bit address + @property def x64bit_source_addr(self): """ @@ -901,6 +931,16 @@ def _get_api_packet_spec_data_dict(self): DictKeys.RECEIVE_OPTIONS: self.__rx_opts, DictKeys.RF_DATA: self.__data} + @property + def effective_len(self): + """ + Override method. + + .. seealso:: + | :meth:`.XBeeAPIPacket.effective_len` + """ + return len(self) - 2 # Remove 16-bit address + @property def x16bit_source_addr(self): """ @@ -1146,6 +1186,16 @@ def _get_api_packet_spec_data_dict(self): return base + @property + def effective_len(self): + """ + Override method. + + .. seealso:: + | :meth:`.XBeeAPIPacket.effective_len` + """ + return len(self) - 8 # Remove 64-bit address + @property def x64bit_source_addr(self): """ @@ -1424,6 +1474,16 @@ def _get_api_packet_spec_data_dict(self): return base + @property + def effective_len(self): + """ + Override method. + + .. seealso:: + | :meth:`.XBeeAPIPacket.effective_len` + """ + return len(self) - 2 # Remove 16-bit address + @property def x16bit_source_addr(self): """ diff --git a/digi/xbee/packets/wifi.py b/digi/xbee/packets/wifi.py index 41bad42..f72f587 100644 --- a/digi/xbee/packets/wifi.py +++ b/digi/xbee/packets/wifi.py @@ -1,4 +1,4 @@ -# Copyright 2017-2021, Digi International Inc. +# Copyright 2017-2022, Digi International Inc. # # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this @@ -175,6 +175,16 @@ def _get_api_packet_spec_data_dict(self): return base + @property + def effective_len(self): + """ + Override method. + + .. seealso:: + | :meth:`.XBeeAPIPacket.effective_len` + """ + return len(self) - 8 # Remove 64-bit address + @property def source_address(self): """ @@ -445,6 +455,16 @@ def _get_api_packet_spec_data_dict(self): DictKeys.COMMAND: self.__cmd, DictKeys.PARAMETER: list(self.__param) if self.__param is not None else None} + @property + def effective_len(self): + """ + Override method. + + .. seealso:: + | :meth:`.XBeeAPIPacket.effective_len` + """ + return len(self) - 8 # Destination address + @property def dest_address(self): """ @@ -672,6 +692,16 @@ def _get_api_packet_spec_data_dict(self): DictKeys.AT_CMD_STATUS: self.__resp_status, DictKeys.RF_DATA: list(self.__comm_val) if self.__comm_val is not None else None} + @property + def effective_len(self): + """ + Override method. + + .. seealso:: + | :meth:`.XBeeAPIPacket.effective_len` + """ + return len(self) - 8 # Remove source address + @property def source_address(self): """ diff --git a/digi/xbee/sender.py b/digi/xbee/sender.py index 560288e..70a973e 100644 --- a/digi/xbee/sender.py +++ b/digi/xbee/sender.py @@ -1,4 +1,4 @@ -# Copyright 2020, 2021, Digi International Inc. +# Copyright 2020-2022, Digi International Inc. # # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this @@ -83,6 +83,9 @@ def send_packet(self, packet): comm_iface = self.__xbee.comm_iface op_mode = self.__xbee.operating_mode + if self.__xbee._serial_port: + self.__xbee._update_tx_stats(packet) + out = packet.output(escaped=op_mode == OperatingMode.ESCAPED_API_MODE) comm_iface.write_frame(out) self._log.debug(self._LOG_PATTERN.format(comm_iface=str(comm_iface), diff --git a/doc/api/digi.xbee.models.statistics.rst b/doc/api/digi.xbee.models.statistics.rst new file mode 100644 index 0000000..7934673 --- /dev/null +++ b/doc/api/digi.xbee.models.statistics.rst @@ -0,0 +1,7 @@ +digi\.xbee\.models\.statistics module +===================================== + +.. automodule:: digi.xbee.models.statistics + :members: + :inherited-members: + :show-inheritance: diff --git a/doc/examples.rst b/doc/examples.rst index 834644f..ab54e65 100644 --- a/doc/examples.rst +++ b/doc/examples.rst @@ -25,6 +25,7 @@ Examples are split by categories: * :ref:`samplesFirmware` * :ref:`samplesFilesystem` * :ref:`samplesProfile` +* :ref:`samplesStatistics` .. _samplesConfiguration: @@ -164,6 +165,7 @@ You can locate the example in the following path: For more information about how to listen to network modifications, see :ref:`listenToNetworkCacheModifications`. + .. _samplesCommunication: Communication samples @@ -803,3 +805,23 @@ file and prints all the accessible settings and properties. You can locate the example in the following path: **examples/profile/ReadXBeeProfileSample** + + +.. _samplesStatistics: + +Statistics samples +------------------ + +Get XBee statistics sample +`````````````````````````` +This sample application demonstrates how to get XBee statistics. + +The application sets and gets some local parameters. After that, it retrieves +the XBee statistics. + +You can locate the example in the following path: +**examples/statistics/GetXBeeStatisticsSample** + +.. note:: + For more information about how to use the XBee statistics, see + :ref:`getXBeeStatistics`. diff --git a/doc/user_doc/communicating_with_xbee_devices.rst b/doc/user_doc/communicating_with_xbee_devices.rst index 5a7bb01..cd15a51 100644 --- a/doc/user_doc/communicating_with_xbee_devices.rst +++ b/doc/user_doc/communicating_with_xbee_devices.rst @@ -2074,3 +2074,68 @@ the sequence ``bind()``, ``listen()``, ``accept()``. | | | **examples/communication/socket/SocketUDPServerClientSample** | +---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ + +.. _getXBeeStatistics: + +Get XBee statistics +------------------- + +XBee statistics are collected automatically when it receives or transmits data. +These statistics are only available for the local XBee device, they are not +available for remote nodes. + +You can access the statistics information of a local XBee using its ``stats`` +attribute, which returns a ``Statistics`` object: + ++--------------+---------------------------------------------------------+ +| Attribute | Description | ++==============+=========================================================+ +| **stats** | Attribute with XBee statistic, a ``Statistics`` object. | ++--------------+---------------------------------------------------------+ + +Available statistics are attributes of the ``Statistics`` object: + ++--------------------------------+--------------------+--------------------------------------------------+ +| Statistics | Attribute | Description | ++============+===================+====================+==================================================+ +| Transmit | TX packets | **tx_packets** | Number of transmitted packets via serial | +| +-------------------+--------------------+--------------------------------------------------+ +| | TX bytes | **tx_bytes** | Number of effective transmitted bytes via serial | ++------------+-------------------+--------------------+--------------------------------------------------+ +| Receive | RX packets | **rx_packets** | Number of received packets via serial | +| +-------------------+--------------------+--------------------------------------------------+ +| | RX bytes | **rx_bytes** | Number of effective received bytes via serial | ++------------+-------------------+--------------------+--------------------------------------------------+ +| Errors | Remote cmd errors | **rmt_cmd_errors** | Number of failed remote AT commands | +| +-------------------+--------------------+--------------------------------------------------+ +| | TX errors | **tx_errors** | Number of transmission errors | ++------------+-------------------+--------------------+--------------------------------------------------+ + +**Get XBee statistics** + +.. code:: python + + [...] + + # Instantiate a local XBee object. + xbee = XBeeDevice("COM1", 9600) + xbee.open() + + # Perform any action. + [...] + + # Get and print XBee stats + print(xbee.stats.tx_packets) + print(xbee.stats.tx_bytes) + print(xbee.stats.rx_packets) + print(xbee.stats.rx_bytes) + print(xbee.stats.rmt_cmd_errors) + print(xbee.stats.tx_errors) + ++--------------------------------------------------------------------------------------------------------------------------------------------+ +| Example: Get XBee statistics | ++============================================================================================================================================+ +| The XBee Python Library includes a sample application that shows how to get XBee statistics. The example is located in the following path: | +| | +| **examples/statistics/GetXBeeStatisticsSample** | ++--------------------------------------------------------------------------------------------------------------------------------------------+ diff --git a/examples/statistics/GetXBeeStatisticsSample/GetXBeeStatisticsSample.py b/examples/statistics/GetXBeeStatisticsSample/GetXBeeStatisticsSample.py new file mode 100644 index 0000000..8bdcb9e --- /dev/null +++ b/examples/statistics/GetXBeeStatisticsSample/GetXBeeStatisticsSample.py @@ -0,0 +1,75 @@ +# Copyright 2022, Digi International Inc. +# +# Permission to use, copy, modify, and/or distribute this software for any +# purpose with or without fee is hereby granted, provided that the above +# copyright notice and this permission notice appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +from digi.xbee.devices import XBeeDevice +from digi.xbee.util import utils + +# TODO: Replace with the serial port where your local module is connected to. +PORT = "COM1" +# TODO: Replace with the baud rate of your local module. +BAUD_RATE = 9600 + +PARAM_NODE_ID = "NI" +PARAM_PAN_ID = "ID" +PARAM_DEST_ADDRESS_H = "DH" +PARAM_DEST_ADDRESS_L = "DL" + +PARAM_VALUE_NODE_ID = "Yoda" +PARAM_VALUE_PAN_ID = utils.hex_string_to_bytes("1234") +PARAM_VALUE_DEST_ADDRESS_H = utils.hex_string_to_bytes("00") +PARAM_VALUE_DEST_ADDRESS_L = utils.hex_string_to_bytes("FFFF") + + +def main(): + print(" +------------------------------------------------+") + print(" | XBee Python Library Get XBee Statistics Sample |") + print(" +------------------------------------------------+\n") + + device = XBeeDevice(PORT, BAUD_RATE) + + try: + device.open() + + # Set parameters. + device.set_parameter(PARAM_NODE_ID, bytearray(PARAM_VALUE_NODE_ID, 'utf8')) + device.set_parameter(PARAM_PAN_ID, PARAM_VALUE_PAN_ID) + device.set_parameter(PARAM_DEST_ADDRESS_H, PARAM_VALUE_DEST_ADDRESS_H) + device.set_parameter(PARAM_DEST_ADDRESS_L, PARAM_VALUE_DEST_ADDRESS_L) + + # Get parameters. + print("Node ID: %s" % device.get_parameter(PARAM_NODE_ID).decode()) + print("PAN ID: %s" % utils.hex_to_string(device.get_parameter(PARAM_PAN_ID))) + print("Destination address high: %s" % utils.hex_to_string(device.get_parameter(PARAM_DEST_ADDRESS_H))) + print("Destination address low: %s" % utils.hex_to_string(device.get_parameter(PARAM_DEST_ADDRESS_L))) + + print("") + print("All parameters were set correctly!") + + print("Showing XBee statistics") + print(" RX: packets=%d, bytes=%d" % (device.stats.rx_packets, + device.stats.rx_bytes)) + print(" TX: packets=%d, bytes=%d" % (device.stats.tx_packets, + device.stats.tx_bytes)) + print("Showing network errors") + print(" Remote AT commands errors=%d" % device.stats.rmt_cmd_errors) + print(" TX errors=%d" % device.stats.tx_errors) + print("All XBee statistics displayed!") + + finally: + if device is not None and device.is_open(): + device.close() + + +if __name__ == '__main__': + main() diff --git a/examples/statistics/GetXBeeStatisticsSample/readme.txt b/examples/statistics/GetXBeeStatisticsSample/readme.txt new file mode 100644 index 0000000..0c1d29e --- /dev/null +++ b/examples/statistics/GetXBeeStatisticsSample/readme.txt @@ -0,0 +1,66 @@ + Introduction + ------------ + This sample Python application shows how to get XBee statistics using + the XBee Python Library. + + The application sets the value of four parameters with different value types: + string, byte array and integer. Then it reads them from the device to verify + that the read values are the same as the values that were set. After that, it + gets the current XBee statistics. + + NOTE: This example uses the generic XBee device (XBeeDevice) class, + but it can be applied to any other local XBee device class. + + Requirements + ------------ + To run this example you will need: + + * One XBee radio in API mode and its corresponding carrier board (XBIB + or XBee Development Board). + * The XCTU application (available at www.digi.com/xctu). + + + Compatible protocols + -------------------- + * 802.15.4 + * DigiMesh + * Point-to-Multipoint + * Zigbee + + + Example setup + ------------- + 1) Plug the XBee radio into the XBee adapter and connect it to your + computer's USB or serial port. + + 2) Ensure that the module is in API mode. + For further information on how to perform this task, read the + 'Configuring Your XBee Modules' topic of the Getting Started guide. + + 3) Set the port and baud rate of the XBee radio in the sample file class. + If you configured the module in the previous step with the XCTU, you + will see the port number and baud rate in the 'Port' label of the device + on the left view. + + + Running the example + ------------------- + First, build and launch the application. To test the functionality, check + that the output console displays the parameters set and then states the + following message: + + "All parameters were set correctly!" + + That message indicates that all the parameters could be set and their read + values are the same as the values that were set. + Next, the XBee statistics are read and the next message appers: + + "All XBee Statistics displayed!" + + That message indicates that all XBee statistics were displayed. The statistics + shown are: + * RX info: Number of receive packets and bytes. + * TX info: Number of transmitted packets and bytes. + * Remote AT command errors: Number of packets of remote AT commands + that failed. + * TX numbers: Number of packets which transmission failed. From 2663692c13e1a26f7d4ed0433a2d334853a3d5a7 Mon Sep 17 00:00:00 2001 From: Ruben Moral Date: Mon, 23 May 2022 16:04:22 +0200 Subject: [PATCH 04/36] hardware: add new versions to the supported hardware list - 0x54: XBee 3 Cellular Global LTE Cat 1 - 0x55: XBee 3 Cellular North America LTE Cat 1 https://onedigi.atlassian.net/browse/XBPL-381 Signed-off-by: Ruben Moral --- CHANGELOG.rst | 4 ++++ digi/xbee/models/hw.py | 4 +++- digi/xbee/models/protocol.py | 6 ++++-- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index fde06e0..d76bfc7 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -3,6 +3,10 @@ Changelog v1.4.2 - XX/XX/202X +* Support for new hardware variants: + + * XBee 3 Cellular Global LTE Cat 1 + * XBee 3 Cellular North America LTE Cat 1 * Support to retrieve XBee statistics. diff --git a/digi/xbee/models/hw.py b/digi/xbee/models/hw.py index 92b4724..0bc9c8b 100644 --- a/digi/xbee/models/hw.py +++ b/digi/xbee/models/hw.py @@ -1,4 +1,4 @@ -# Copyright 2017-2021, Digi International Inc. +# Copyright 2017-2022, Digi International Inc. # # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this @@ -98,6 +98,8 @@ class HardwareVersion(Enum): XBEE3_DM_LR_868 = (0x51, "XB3-DMLR868") XBEE3_RR = (0x52, "XBee 3 Reduced RAM") S2C_P5 = (0x53, "S2C P5") + CELLULAR_3_GLOBAL_LTE_CAT1 = (0x54, "XBee 3 Cellular Global LTE Cat 1") + CELLULAR_3_NA_LTE_CAT1 = (0x55, "XBee 3 Cellular North America LTE Cat 1") def __init__(self, code, description): self.__code = code diff --git a/digi/xbee/models/protocol.py b/digi/xbee/models/protocol.py index f9188b0..083a47f 100644 --- a/digi/xbee/models/protocol.py +++ b/digi/xbee/models/protocol.py @@ -1,4 +1,4 @@ -# Copyright 2017-2021, Digi International Inc. +# Copyright 2017-2022, Digi International Inc. # # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this @@ -257,7 +257,9 @@ def determine_protocol(hw_version, fw_version, br_value=None): HardwareVersion.CELLULAR_3_LTE_M_VERIZON.code, HardwareVersion.CELLULAR_3_LTE_M_ATT.code, HardwareVersion.CELLULAR_3_CAT1_LTE_VERIZON.code, - HardwareVersion.CELLULAR_3_LTE_M_TELIT.code): + HardwareVersion.CELLULAR_3_LTE_M_TELIT.code, + HardwareVersion.CELLULAR_3_GLOBAL_LTE_CAT1.code, + HardwareVersion.CELLULAR_3_NA_LTE_CAT1.code): return XBeeProtocol.CELLULAR if hw_version == HardwareVersion.CELLULAR_NBIOT_EUROPE.code: From 4b5336d46120f244b459f9bbbc8bd368a1a57eed Mon Sep 17 00:00:00 2001 From: Tatiana Leon Date: Wed, 6 Jul 2022 10:34:04 +0200 Subject: [PATCH 05/36] explicit data: add explicit data to 802.15.4 XBee 3 802.15.4 modules allows to send and receive explicit data (other module hardware does not include this feature, i. e., S2C) This commits includes methods to read and send explicit data to the 'XBeeDevice' class so they are inherited by 'Raw802Device'. It removes the equivalent methods from 'ZigBeeDevice', 'DigiMeshDevice', and 'DigiPointDevice' classes since now they are defined in the super class. It also overrides the methods in the 'IPDevice' class so they throw an exception if used as they are not supported for Cellular nor Wi-Fi XBee modules. The related documentation is also updated. https://onedigi.atlassian.net/browse/DAL-6334 Signed-off-by: Tatiana Leon --- CHANGELOG.rst | 2 + digi/xbee/devices.py | 888 ++++++------------ .../communicating_with_xbee_devices.rst | 290 +++--- doc/user_doc/working_with_xbee_classes.rst | 2 +- 4 files changed, 442 insertions(+), 740 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index d76bfc7..3b1e62b 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,6 +8,8 @@ v1.4.2 - XX/XX/202X * XBee 3 Cellular Global LTE Cat 1 * XBee 3 Cellular North America LTE Cat 1 * Support to retrieve XBee statistics. +* Send/receive explicit data in 802.15.4. + (XBee 3 modules support this feature) v1.4.1 - 12/22/2021 diff --git a/digi/xbee/devices.py b/digi/xbee/devices.py index 9c3c3a2..61ee4cc 100644 --- a/digi/xbee/devices.py +++ b/digi/xbee/devices.py @@ -1661,6 +1661,7 @@ def set_api_output_mode_value(self, api_output_mode): raise ValueError("API output mode cannot be None") if self.get_protocol() not in (XBeeProtocol.ZIGBEE, XBeeProtocol.DIGI_MESH, + XBeeProtocol.RAW_802_15_4, XBeeProtocol.DIGI_POINT, XBeeProtocol.XLR, XBeeProtocol.XLR_DM): raise OperationNotSupportedException( @@ -3693,6 +3694,181 @@ def _init_network(self): """ return XBeeNetwork(self) + def read_expl_data(self, timeout=None): + """ + Reads new explicit data received by this XBee. + + If `timeout` is specified, this method blocks until new data is received + or the timeout expires, throwing a :class:`.TimeoutException` in this case. + + Args: + timeout (Integer, optional): Read timeout in seconds. If `None`, + this method is non-blocking and returns `None` if there is no + explicit data available. + + Returns: + :class:`.ExplicitXBeeMessage`: Read message or `None` if this XBee + did not receive new explicit data. + + Raises: + ValueError: If a timeout is specified and is less than 0. + TimeoutException: If a timeout is specified and no explicit data was + received during that time. + InvalidOperatingModeException: If the XBee's operating mode is not + API or ESCAPED API. This method only checks the cached value of + the operating mode. + XBeeException: If the XBee's communication interface is closed. + + .. seealso:: + | :class:`.ExplicitXBeeMessage` + """ + return self._read_expl_data(timeout=timeout) + + def read_expl_data_from(self, remote_xbee, timeout=None): + """ + Reads new explicit data received from the given remote XBee. + + If `timeout` is specified, this method blocks until new data is received + or the timeout expires, throwing a :class:`.TimeoutException` in this case. + + Args: + remote_xbee (:class:`.RemoteXBeeDevice`): Remote XBee that sent the explicit data. + timeout (Integer, optional): Read timeout in seconds. If `None`, + this method is non-blocking and returns `None` if there is no + data available. + + Returns: + :class:`.ExplicitXBeeMessage`: Read message sent by `remote_xbee` + or `None` if this XBee did not receive new data from that node. + + Raises: + ValueError: If a timeout is specified and is less than 0. + TimeoutException: If a timeout is specified and no explicit data was + received during that time. + InvalidOperatingModeException: If the XBee's operating mode is not + API or ESCAPED API. This method only checks the cached value of + the operating mode. + XBeeException: If the XBee's communication interface is closed. + + .. seealso:: + | :class:`.ExplicitXBeeMessage` + | :class:`.RemoteXBeeDevice` + """ + return self._read_expl_data_from(remote_xbee, timeout=timeout) + + def send_expl_data(self, remote_xbee, data, src_endpoint, dest_endpoint, + cluster_id, profile_id, transmit_options=TransmitOptions.NONE.value): + """ + Blocking method. Sends the provided explicit data to the given XBee, + source and destination end points, cluster and profile ids. + + This method blocks until a success or error response arrives or the + configured receive timeout expires. The default timeout is + :attr:`.XBeeDevice._DEFAULT_TIMEOUT_SYNC_OPERATIONS`. + + Args: + remote_xbee (:class:`.RemoteXBeeDevice`): Remote XBee to send data to. + data (String or Bytearray): Raw data to send. + src_endpoint (Integer): Source endpoint of the transmission. 1 byte. + dest_endpoint (Integer): Destination endpoint of the transmission. 1 byte. + cluster_id (Integer): Cluster ID of the transmission (between 0x0 and 0xFFFF) + profile_id (Integer): Profile ID of the transmission (between 0x0 and 0xFFFF) + transmit_options (Integer, optional): Transmit options, bitfield of + :class:`.TransmitOptions`. Default to `TransmitOptions.NONE.value`. + + Returns: + :class:`.XBeePacket`: Response packet obtained after sending data. + + Raises: + TimeoutException: If response is not received before the read + timeout expires. + InvalidOperatingModeException: If the XBee's operating mode is not + API or ESCAPED API. This method only checks the cached value of + the operating mode. + TransmitException: If the status of the response received is not OK. + XBeeException: If the XBee's communication interface is closed. + ValueError: if `cluster_id` or `profile_id` is less than 0x0 or + greater than 0xFFFF. + + .. seealso:: + | :class:`.RemoteXBeeDevice` + | :class:`.XBeePacket` + """ + return self._send_expl_data( + remote_xbee, data, src_endpoint, dest_endpoint, cluster_id, + profile_id, transmit_options=transmit_options) + + def send_expl_data_broadcast(self, data, src_endpoint, dest_endpoint, cluster_id, + profile_id, transmit_options=TransmitOptions.NONE.value): + """ + Sends the provided explicit data to all the XBee nodes of the network + (broadcast) using provided source and destination end points, cluster + and profile ids. + + This method blocks until a success or error transmit status arrives or + the configured receive timeout expires. The received timeout is + configured using the :meth:`.AbstractXBeeDevice.set_sync_ops_timeout` + method and can be consulted with method + :meth:`.AbstractXBeeDevice.get_sync_ops_timeout`. + + Args: + data (String or Bytearray): Raw data to send. + src_endpoint (Integer): Source endpoint of the transmission. 1 byte. + dest_endpoint (Integer): Destination endpoint of the transmission. 1 byte. + cluster_id (Integer): Cluster ID of the transmission (between 0x0 and 0xFFFF) + profile_id (Integer): Profile ID of the transmission (between 0x0 and 0xFFFF) + transmit_options (Integer, optional): Transmit options, bitfield of + :class:`.TransmitOptions`. Default to `TransmitOptions.NONE.value`. + + Raises: + TimeoutException: If response is not received before the read + timeout expires. + InvalidOperatingModeException: If the XBee's operating mode is not + API or ESCAPED API. This method only checks the cached value of + the operating mode. + TransmitException: If the status of the response received is not OK. + XBeeException: If the XBee's communication interface is closed. + ValueError: if `cluster_id` or `profile_id` is less than 0x0 or + greater than 0xFFFF. + + .. seealso:: + | :meth:`.XBeeDevice._send_expl_data` + """ + return self._send_expl_data_broadcast( + data, src_endpoint, dest_endpoint, cluster_id, profile_id, + transmit_options=transmit_options) + + def send_expl_data_async(self, remote_xbee, data, src_endpoint, dest_endpoint, + cluster_id, profile_id, transmit_options=TransmitOptions.NONE.value): + """ + Non-blocking method. Sends the provided explicit data to the given XBee, + source and destination end points, cluster and profile ids. + + Args: + remote_xbee (:class:`.RemoteXBeeDevice`): Remote XBee to send data to. + data (String or Bytearray): Raw data to send. + src_endpoint (Integer): Source endpoint of the transmission. 1 byte. + dest_endpoint (Integer): Destination endpoint of the transmission. 1 byte. + cluster_id (Integer): Cluster ID of the transmission (between 0x0 and 0xFFFF) + profile_id (Integer): Profile ID of the transmission (between 0x0 and 0xFFFF) + transmit_options (Integer, optional): Transmit options, bitfield of + :class:`.TransmitOptions`. Default to `TransmitOptions.NONE.value`. + + Raises: + InvalidOperatingModeException: If the XBee's operating mode is not + API or ESCAPED API. This method only checks the cached value of + the operating mode. + XBeeException: If the XBee's communication interface is closed. + ValueError: if `cluster_id` or `profile_id` is less than 0x0 or + greater than 0xFFFF. + + .. seealso:: + | :class:`.RemoteXBeeDevice` + """ + self._send_expl_data_async(remote_xbee, data, src_endpoint, + dest_endpoint, cluster_id, profile_id, + transmit_options=transmit_options) + @AbstractXBeeDevice._before_send_method @AbstractXBeeDevice._after_send_method def _send_expl_data(self, remote_xbee, data, src_endpoint, dest_endpoint, @@ -4959,242 +5135,67 @@ def send_data_async_64(self, x64addr, data, transmit_options=TransmitOptions.NON """ self._send_data_async_64(x64addr, data, transmit_options=transmit_options) - def read_expl_data(self, timeout=None): + def get_neighbors(self, neighbor_cb=None, finished_cb=None, timeout=None): """ - Reads new explicit data received by this XBee. - - If `timeout` is specified, this method blocks until new data is received - or the timeout expires, throwing a :class:`.TimeoutException` in this case. + Returns the neighbors of this XBee. If `neighbor_cb` is not + defined, the process blocks during the specified timeout. Args: - timeout (Integer, optional): Read timeout in seconds. If `None`, - this method is non-blocking and returns `None` if there is no - explicit data available. + neighbor_cb (Function, optional, default=`None`): Method called + when a new neighbor is received. Receives two arguments: + + * The XBee that owns this new neighbor. + * The new neighbor. + + finished_cb (Function, optional, default=`None`): Method to execute + when the process finishes. Receives two arguments: + + * The XBee that is searching for its neighbors. + * A list with the discovered neighbors. + * An error message if something went wrong. + timeout (Float, optional, default=`NeighborFinder.DEFAULT_TIMEOUT`): The timeout + in seconds. Returns: - :class:`.ExplicitXBeeMessage`: Read message or `None` if this XBee - did not receive new explicit data. + List: List of :class:`.Neighbor` when `neighbor_cb` is not defined, + `None` otherwise (in this case neighbors are received in the callback). Raises: - ValueError: If a timeout is specified and is less than 0. - TimeoutException: If a timeout is specified and no explicit data was - received during that time. - InvalidOperatingModeException: If the XBee's operating mode is not - API or ESCAPED API. This method only checks the cached value of - the operating mode. - XBeeException: If the XBee's communication interface is closed. + OperationNotSupportedException: If XBee protocol is not DigiMesh. .. seealso:: - | :class:`.ExplicitXBeeMessage` + | :class:`com.digi.models.zdo.Neighbor` """ - return self._read_expl_data(timeout=timeout) + from digi.xbee.models.zdo import NeighborFinder + return super()._get_neighbors( + neighbor_cb=neighbor_cb, finished_cb=finished_cb, + timeout=timeout if timeout else NeighborFinder.DEFAULT_TIMEOUT) - def read_expl_data_from(self, remote_xbee, timeout=None): - """ - Reads new explicit data received from the given remote XBee. - If `timeout` is specified, this method blocks until new data is received - or the timeout expires, throwing a :class:`.TimeoutException` in this case. +class DigiPointDevice(XBeeDevice): + """ + This class represents a local DigiPoint XBee. + """ - Args: - remote_xbee (:class:`.RemoteXBeeDevice`): Remote XBee that sent the explicit data. - timeout (Integer, optional): Read timeout in seconds. If `None`, - this method is non-blocking and returns `None` if there is no - data available. + def __init__(self, port=None, baud_rate=None, data_bits=serial.EIGHTBITS, + stop_bits=serial.STOPBITS_ONE, parity=serial.PARITY_NONE, + flow_control=FlowControl.NONE, + _sync_ops_timeout=AbstractXBeeDevice._DEFAULT_TIMEOUT_SYNC_OPERATIONS, + comm_iface=None): + """ + Class constructor. Instantiates a new :class:`.DigiPointDevice` with + the provided parameters. - Returns: - :class:`.ExplicitXBeeMessage`: Read message sent by `remote_xbee` - or `None` if this XBee did not receive new data from that node. - - Raises: - ValueError: If a timeout is specified and is less than 0. - TimeoutException: If a timeout is specified and no explicit data was - received during that time. - InvalidOperatingModeException: If the XBee's operating mode is not - API or ESCAPED API. This method only checks the cached value of - the operating mode. - XBeeException: If the XBee's communication interface is closed. - - .. seealso:: - | :class:`.ExplicitXBeeMessage` - | :class:`.RemoteXBeeDevice` - """ - return self._read_expl_data_from(remote_xbee, timeout=timeout) - - def send_expl_data(self, remote_xbee, data, src_endpoint, dest_endpoint, - cluster_id, profile_id, transmit_options=TransmitOptions.NONE.value): - """ - Blocking method. Sends the provided explicit data to the given XBee, - source and destination end points, cluster and profile ids. - - This method blocks until a success or error response arrives or the - configured receive timeout expires. The default timeout is - :attr:`.XBeeDevice._DEFAULT_TIMEOUT_SYNC_OPERATIONS`. - - Args: - remote_xbee (:class:`.RemoteXBeeDevice`): Remote XBee to send data to. - data (String or Bytearray): Raw data to send. - src_endpoint (Integer): Source endpoint of the transmission. 1 byte. - dest_endpoint (Integer): Destination endpoint of the transmission. 1 byte. - cluster_id (Integer): Cluster ID of the transmission (between 0x0 and 0xFFFF) - profile_id (Integer): Profile ID of the transmission (between 0x0 and 0xFFFF) - transmit_options (Integer, optional): Transmit options, bitfield of - :class:`.TransmitOptions`. Default to `TransmitOptions.NONE.value`. - - Returns: - :class:`.XBeePacket`: Response packet obtained after sending data. - - Raises: - TimeoutException: If response is not received before the read - timeout expires. - InvalidOperatingModeException: If the XBee's operating mode is not - API or ESCAPED API. This method only checks the cached value of - the operating mode. - TransmitException: If the status of the response received is not OK. - XBeeException: If the XBee's communication interface is closed. - ValueError: if `cluster_id` or `profile_id` is less than 0x0 or - greater than 0xFFFF. - - .. seealso:: - | :class:`.RemoteXBeeDevice` - | :class:`.XBeePacket` - """ - return self._send_expl_data( - remote_xbee, data, src_endpoint, dest_endpoint, cluster_id, - profile_id, transmit_options=transmit_options) - - def send_expl_data_broadcast(self, data, src_endpoint, dest_endpoint, cluster_id, - profile_id, transmit_options=TransmitOptions.NONE.value): - """ - Sends the provided explicit data to all the XBee nodes of the network - (broadcast) using provided source and destination end points, cluster - and profile ids. - - This method blocks until a success or error transmit status arrives or - the configured receive timeout expires. The received timeout is - configured using the :meth:`.AbstractXBeeDevice.set_sync_ops_timeout` - method and can be consulted with method - :meth:`.AbstractXBeeDevice.get_sync_ops_timeout`. - - Args: - data (String or Bytearray): Raw data to send. - src_endpoint (Integer): Source endpoint of the transmission. 1 byte. - dest_endpoint (Integer): Destination endpoint of the transmission. 1 byte. - cluster_id (Integer): Cluster ID of the transmission (between 0x0 and 0xFFFF) - profile_id (Integer): Profile ID of the transmission (between 0x0 and 0xFFFF) - transmit_options (Integer, optional): Transmit options, bitfield of - :class:`.TransmitOptions`. Default to `TransmitOptions.NONE.value`. - - Raises: - TimeoutException: If response is not received before the read - timeout expires. - InvalidOperatingModeException: If the XBee's operating mode is not - API or ESCAPED API. This method only checks the cached value of - the operating mode. - TransmitException: If the status of the response received is not OK. - XBeeException: If the XBee's communication interface is closed. - ValueError: if `cluster_id` or `profile_id` is less than 0x0 or - greater than 0xFFFF. - - .. seealso:: - | :meth:`.XBeeDevice._send_expl_data` - """ - return self._send_expl_data_broadcast( - data, src_endpoint, dest_endpoint, cluster_id, profile_id, - transmit_options=transmit_options) - - def send_expl_data_async(self, remote_xbee, data, src_endpoint, dest_endpoint, - cluster_id, profile_id, transmit_options=TransmitOptions.NONE.value): - """ - Non-blocking method. Sends the provided explicit data to the given XBee, - source and destination end points, cluster and profile ids. - - Args: - remote_xbee (:class:`.RemoteXBeeDevice`): Remote XBee to send data to. - data (String or Bytearray): Raw data to send. - src_endpoint (Integer): Source endpoint of the transmission. 1 byte. - dest_endpoint (Integer): Destination endpoint of the transmission. 1 byte. - cluster_id (Integer): Cluster ID of the transmission (between 0x0 and 0xFFFF) - profile_id (Integer): Profile ID of the transmission (between 0x0 and 0xFFFF) - transmit_options (Integer, optional): Transmit options, bitfield of - :class:`.TransmitOptions`. Default to `TransmitOptions.NONE.value`. - - Raises: - InvalidOperatingModeException: If the XBee's operating mode is not - API or ESCAPED API. This method only checks the cached value of - the operating mode. - XBeeException: If the XBee's communication interface is closed. - ValueError: if `cluster_id` or `profile_id` is less than 0x0 or - greater than 0xFFFF. - - .. seealso:: - | :class:`.RemoteXBeeDevice` - """ - self._send_expl_data_async(remote_xbee, data, src_endpoint, - dest_endpoint, cluster_id, profile_id, - transmit_options=transmit_options) - - def get_neighbors(self, neighbor_cb=None, finished_cb=None, timeout=None): - """ - Returns the neighbors of this XBee. If `neighbor_cb` is not - defined, the process blocks during the specified timeout. - - Args: - neighbor_cb (Function, optional, default=`None`): Method called - when a new neighbor is received. Receives two arguments: - - * The XBee that owns this new neighbor. - * The new neighbor. - - finished_cb (Function, optional, default=`None`): Method to execute - when the process finishes. Receives two arguments: - - * The XBee that is searching for its neighbors. - * A list with the discovered neighbors. - * An error message if something went wrong. - - timeout (Float, optional, default=`NeighborFinder.DEFAULT_TIMEOUT`): The timeout - in seconds. - Returns: - List: List of :class:`.Neighbor` when `neighbor_cb` is not defined, - `None` otherwise (in this case neighbors are received in the callback). - - Raises: - OperationNotSupportedException: If XBee protocol is not DigiMesh. - - .. seealso:: - | :class:`com.digi.models.zdo.Neighbor` - """ - from digi.xbee.models.zdo import NeighborFinder - return super()._get_neighbors( - neighbor_cb=neighbor_cb, finished_cb=finished_cb, - timeout=timeout if timeout else NeighborFinder.DEFAULT_TIMEOUT) - - -class DigiPointDevice(XBeeDevice): - """ - This class represents a local DigiPoint XBee. - """ - - def __init__(self, port=None, baud_rate=None, data_bits=serial.EIGHTBITS, - stop_bits=serial.STOPBITS_ONE, parity=serial.PARITY_NONE, - flow_control=FlowControl.NONE, - _sync_ops_timeout=AbstractXBeeDevice._DEFAULT_TIMEOUT_SYNC_OPERATIONS, - comm_iface=None): - """ - Class constructor. Instantiates a new :class:`.DigiPointDevice` with - the provided parameters. - - Args: - port (String): Serial port identifier. Depends on operating system. - e.g. '/dev/ttyUSB0' on 'GNU/Linux' or 'COM3' on Windows. - baud_rate (Integer): Serial port baud rate. - data_bits (Integer, default: :attr:`.serial.EIGHTBITS`): Port bitsize. - stop_bits (Integer, default: :attr:`.serial.STOPBITS_ONE`): Port stop bits. - parity (Character, default: :attr:`.serial.PARITY_NONE`): Port parity. - flow_control (Integer, default: :attr:`.FlowControl.NONE`): Port flow control. - _sync_ops_timeout (Integer, default: 3): Read timeout (in seconds). - comm_iface (:class:`.XBeeCommunicationInterface`): Communication interface. + Args: + port (String): Serial port identifier. Depends on operating system. + e.g. '/dev/ttyUSB0' on 'GNU/Linux' or 'COM3' on Windows. + baud_rate (Integer): Serial port baud rate. + data_bits (Integer, default: :attr:`.serial.EIGHTBITS`): Port bitsize. + stop_bits (Integer, default: :attr:`.serial.STOPBITS_ONE`): Port stop bits. + parity (Character, default: :attr:`.serial.PARITY_NONE`): Port parity. + flow_control (Integer, default: :attr:`.FlowControl.NONE`): Port flow control. + _sync_ops_timeout (Integer, default: 3): Read timeout (in seconds). + comm_iface (:class:`.XBeeCommunicationInterface`): Communication interface. Raises: All exceptions raised by :meth:`.XBeeDevice.__init__` constructor. @@ -5263,182 +5264,7 @@ def send_data_64_16(self, x64addr, x16addr, data, :class:`.XBeePacket`: The response. Raises: - ValueError: If `x64addr`, `x16addr` or `data` is `None`. - TimeoutException: If response is not received before the read - timeout expires. - InvalidOperatingModeException: If the XBee's operating mode is not - API or ESCAPED API. This method only checks the cached value of - the operating mode. - TransmitException: If the status of the response received is not OK. - XBeeException: If the XBee's communication interface is closed. - - .. seealso:: - | :class:`.XBee64BitAddress` - | :class:`.XBee16BitAddress` - | :class:`.XBeePacket` - """ - return self._send_data_64_16(x64addr, x16addr, data, - transmit_options=transmit_options) - - def send_data_async_64_16(self, x64addr, x16addr, data, - transmit_options=TransmitOptions.NONE.value): - """ - Non-blocking method. This method sends data to a remote XBee with the - given 64-bit/16-bit address. - - This method does not wait for a response. - - Args: - x64addr (:class:`.XBee64BitAddress`): 64-bit address of the - destination XBee. - x16addr (:class:`.XBee16BitAddress`): 16-bit address of the - destination XBee, :attr:`.XBee16BitAddress.UNKNOWN_ADDRESS` if unknown. - data (String or Bytearray): Raw data to send. - transmit_options (Integer, optional): Transmit options, bitfield of - :class:`.TransmitOptions`. Default to `TransmitOptions.NONE.value`. - - Raises: - ValueError: If `x64addr`, `x16addr` or `data` is `None`. - InvalidOperatingModeException: If the XBee's operating mode is not - API or ESCAPED API. This method only checks the cached value of - the operating mode. - XBeeException: If the XBee's communication interface is closed. - - .. seealso:: - | :class:`.XBee64BitAddress` - | :class:`.XBee16BitAddress` - | :class:`.XBeePacket` - """ - self._send_data_async_64_16(x64addr, x16addr, data, - transmit_options=transmit_options) - - def read_expl_data(self, timeout=None): - """ - Reads new explicit data received by this XBee. - - If `timeout` is specified, this method blocks until new data is received - or the timeout expires, throwing a :class:`.TimeoutException` in this case. - - Args: - timeout (Integer, optional): Read timeout in seconds. If `None`, - this method is non-blocking and returns `None` if there is no - explicit data available. - - Returns: - :class:`.ExplicitXBeeMessage`: Read message or `None` if this XBee - did not receive new explicit data. - - Raises: - ValueError: If a timeout is specified and is less than 0. - TimeoutException: If a timeout is specified and no explicit data was - received during that time. - InvalidOperatingModeException: If the XBee's operating mode is not - API or ESCAPED API. This method only checks the cached value of - the operating mode. - XBeeException: If the XBee's communication interface is closed. - - .. seealso:: - | :class:`.ExplicitXBeeMessage` - """ - return self._read_expl_data(timeout=timeout) - - def read_expl_data_from(self, remote_xbee, timeout=None): - """ - Reads new explicit data received from the given remote XBee. - - If `timeout` is specified, this method blocks until new data is received - or the timeout expires, throwing a :class:`.TimeoutException` in this case. - - Args: - remote_xbee (:class:`.RemoteXBeeDevice`): Remote XBee that sent the explicit data. - timeout (Integer, optional): Read timeout in seconds. If `None`, - this method is non-blocking and returns `None` if there is no - data available. - - Returns: - :class:`.ExplicitXBeeMessage`: Read message sent by `remote_xbee` - or `None` if this XBee did not receive new data from that node. - - Raises: - ValueError: If a timeout is specified and is less than 0. - TimeoutException: If a timeout is specified and no explicit data was - received during that time. - InvalidOperatingModeException: If the XBee's operating mode is not - API or ESCAPED API. This method only checks the cached value of - the operating mode. - XBeeException: If the XBee's communication interface is closed. - - .. seealso:: - | :class:`.ExplicitXBeeMessage` - | :class:`.RemoteXBeeDevice` - """ - return self._read_expl_data_from(remote_xbee, timeout=timeout) - - def send_expl_data(self, remote_xbee, data, src_endpoint, dest_endpoint, - cluster_id, profile_id, transmit_options=TransmitOptions.NONE.value): - """ - Blocking method. Sends the provided explicit data to the given XBee, - source and destination end points, cluster and profile ids. - - This method blocks until a success or error response arrives or the - configured receive timeout expires. The default timeout is - :attr:`.XBeeDevice._DEFAULT_TIMEOUT_SYNC_OPERATIONS`. - - Args: - remote_xbee (:class:`.RemoteXBeeDevice`): Remote XBee to send data to. - data (String or Bytearray): Raw data to send. - src_endpoint (Integer): Source endpoint of the transmission. 1 byte. - dest_endpoint (Integer): Destination endpoint of the transmission. 1 byte. - cluster_id (Integer): Cluster ID of the transmission (between 0x0 and 0xFFFF) - profile_id (Integer): Profile ID of the transmission (between 0x0 and 0xFFFF) - transmit_options (Integer, optional): Transmit options, bitfield of - :class:`.TransmitOptions`. Default to `TransmitOptions.NONE.value`. - - Returns: - :class:`.XBeePacket`: Response packet obtained after sending data. - - Raises: - TimeoutException: If response is not received before the read - timeout expires. - InvalidOperatingModeException: If the XBee's operating mode is not - API or ESCAPED API. This method only checks the cached value of - the operating mode. - TransmitException: If the status of the response received is not OK. - XBeeException: If the XBee's communication interface is closed. - ValueError: if `cluster_id` or `profile_id` is less than 0x0 or - greater than 0xFFFF. - - .. seealso:: - | :class:`.RemoteXBeeDevice` - | :class:`.XBeePacket` - """ - return self._send_expl_data(remote_xbee, data, src_endpoint, - dest_endpoint, cluster_id, profile_id, - transmit_options=transmit_options) - - def send_expl_data_broadcast(self, data, src_endpoint, dest_endpoint, cluster_id, profile_id, - transmit_options=TransmitOptions.NONE.value): - """ - Sends the provided explicit data to all the XBee nodes of the network - (broadcast) using provided source and destination end points, cluster - and profile ids. - - This method blocks until a success or error transmit status arrives or - the configured receive timeout expires. The received timeout is - configured using the :meth:`.AbstractXBeeDevice.set_sync_ops_timeout` - method and can be consulted with method - :meth:`.AbstractXBeeDevice.get_sync_ops_timeout`. - - Args: - data (String or Bytearray): Raw data to send. - src_endpoint (Integer): Source endpoint of the transmission. 1 byte. - dest_endpoint (Integer): Destination endpoint of the transmission. 1 byte. - cluster_id (Integer): Cluster ID of the transmission (between 0x0 and 0xFFFF) - profile_id (Integer): Profile ID of the transmission (between 0x0 and 0xFFFF) - transmit_options (Integer, optional): Transmit options, bitfield of - :class:`.TransmitOptions`. Default to `TransmitOptions.NONE.value`. - - Raises: + ValueError: If `x64addr`, `x16addr` or `data` is `None`. TimeoutException: If response is not received before the read timeout expires. InvalidOperatingModeException: If the XBee's operating mode is not @@ -5446,46 +5272,46 @@ def send_expl_data_broadcast(self, data, src_endpoint, dest_endpoint, cluster_id the operating mode. TransmitException: If the status of the response received is not OK. XBeeException: If the XBee's communication interface is closed. - ValueError: if `cluster_id` or `profile_id` is less than 0x0 or - greater than 0xFFFF. .. seealso:: - | :meth:`.XBeeDevice._send_expl_data` + | :class:`.XBee64BitAddress` + | :class:`.XBee16BitAddress` + | :class:`.XBeePacket` """ - return self._send_expl_data_broadcast(data, src_endpoint, dest_endpoint, - cluster_id, profile_id, - transmit_options=transmit_options) + return self._send_data_64_16(x64addr, x16addr, data, + transmit_options=transmit_options) - def send_expl_data_async(self, remote_xbee, data, src_endpoint, dest_endpoint, - cluster_id, profile_id, transmit_options=TransmitOptions.NONE.value): + def send_data_async_64_16(self, x64addr, x16addr, data, + transmit_options=TransmitOptions.NONE.value): """ - Non-blocking method. Sends the provided explicit data to the given XBee, - source and destination end points, cluster and profile ids. + Non-blocking method. This method sends data to a remote XBee with the + given 64-bit/16-bit address. + + This method does not wait for a response. Args: - remote_xbee (:class:`.RemoteXBeeDevice`): Remote XBee to send data to. + x64addr (:class:`.XBee64BitAddress`): 64-bit address of the + destination XBee. + x16addr (:class:`.XBee16BitAddress`): 16-bit address of the + destination XBee, :attr:`.XBee16BitAddress.UNKNOWN_ADDRESS` if unknown. data (String or Bytearray): Raw data to send. - src_endpoint (Integer): Source endpoint of the transmission. 1 byte. - dest_endpoint (Integer): Destination endpoint of the transmission. 1 byte. - cluster_id (Integer): Cluster ID of the transmission (between 0x0 and 0xFFFF) - profile_id (Integer): Profile ID of the transmission (between 0x0 and 0xFFFF) transmit_options (Integer, optional): Transmit options, bitfield of :class:`.TransmitOptions`. Default to `TransmitOptions.NONE.value`. Raises: + ValueError: If `x64addr`, `x16addr` or `data` is `None`. InvalidOperatingModeException: If the XBee's operating mode is not API or ESCAPED API. This method only checks the cached value of the operating mode. XBeeException: If the XBee's communication interface is closed. - ValueError: if `cluster_id` or `profile_id` is less than 0x0 or - greater than 0xFFFF. .. seealso:: - | :class:`.RemoteXBeeDevice` + | :class:`.XBee64BitAddress` + | :class:`.XBee16BitAddress` + | :class:`.XBeePacket` """ - self._send_expl_data_async(remote_xbee, data, src_endpoint, - dest_endpoint, cluster_id, profile_id, - transmit_options=transmit_options) + self._send_data_async_64_16(x64addr, x16addr, data, + transmit_options=transmit_options) class ZigBeeDevice(XBeeDevice): @@ -5723,182 +5549,6 @@ def send_data_async_64_16(self, x64addr, x16addr, data, """ self._send_data_async_64_16(x64addr, x16addr, data, transmit_options=transmit_options) - def read_expl_data(self, timeout=None): - """ - Reads new explicit data received by this XBee. - - If `timeout` is specified, this method blocks until new data is received - or the timeout expires, throwing a :class:`.TimeoutException` in this case. - - Args: - timeout (Integer, optional): Read timeout in seconds. If `None`, - this method is non-blocking and returns `None` if there is no - explicit data available. - - Returns: - :class:`.ExplicitXBeeMessage`: Read message or `None` if this XBee - did not receive new explicit data. - - Raises: - ValueError: If a timeout is specified and is less than 0. - TimeoutException: If a timeout is specified and no explicit data was - received during that time. - InvalidOperatingModeException: If the XBee's operating mode is not - API or ESCAPED API. This method only checks the cached value of - the operating mode. - XBeeException: If the XBee's communication interface is closed. - - .. seealso:: - | :class:`.ExplicitXBeeMessage` - """ - return self._read_expl_data(timeout=timeout) - - def read_expl_data_from(self, remote_xbee, timeout=None): - """ - Reads new explicit data received from the given remote XBee. - - If `timeout` is specified, this method blocks until new data is received - or the timeout expires, throwing a :class:`.TimeoutException` in this case. - - Args: - remote_xbee (:class:`.RemoteXBeeDevice`): Remote XBee that sent the explicit data. - timeout (Integer, optional): Read timeout in seconds. If `None`, - this method is non-blocking and returns `None` if there is no - data available. - - Returns: - :class:`.ExplicitXBeeMessage`: Read message sent by `remote_xbee` - or `None` if this XBee did not receive new data from that node. - - Raises: - ValueError: If a timeout is specified and is less than 0. - TimeoutException: If a timeout is specified and no explicit data was - received during that time. - InvalidOperatingModeException: If the XBee's operating mode is not - API or ESCAPED API. This method only checks the cached value of - the operating mode. - XBeeException: If the XBee's communication interface is closed. - - .. seealso:: - | :class:`.ExplicitXBeeMessage` - | :class:`.RemoteXBeeDevice` - """ - return self._read_expl_data_from(remote_xbee, timeout=timeout) - - def send_expl_data(self, remote_xbee, data, src_endpoint, dest_endpoint, - cluster_id, profile_id, transmit_options=TransmitOptions.NONE.value): - """ - Blocking method. Sends the provided explicit data to the given XBee, - source and destination end points, cluster and profile ids. - - This method blocks until a success or error response arrives or the - configured receive timeout expires. The default timeout is - :attr:`.XBeeDevice._DEFAULT_TIMEOUT_SYNC_OPERATIONS`. - - Args: - remote_xbee (:class:`.RemoteXBeeDevice`): Remote XBee to send data to. - data (String or Bytearray): Raw data to send. - src_endpoint (Integer): Source endpoint of the transmission. 1 byte. - dest_endpoint (Integer): Destination endpoint of the transmission. 1 byte. - cluster_id (Integer): Cluster ID of the transmission (between 0x0 and 0xFFFF) - profile_id (Integer): Profile ID of the transmission (between 0x0 and 0xFFFF) - transmit_options (Integer, optional): Transmit options, bitfield of - :class:`.TransmitOptions`. Default to `TransmitOptions.NONE.value`. - - Returns: - :class:`.XBeePacket`: Response packet obtained after sending data. - - Raises: - TimeoutException: If response is not received before the read - timeout expires. - InvalidOperatingModeException: If the XBee's operating mode is not - API or ESCAPED API. This method only checks the cached value of - the operating mode. - TransmitException: If the status of the response received is not OK. - XBeeException: If the XBee's communication interface is closed. - ValueError: if `cluster_id` or `profile_id` is less than 0x0 or - greater than 0xFFFF. - - .. seealso:: - | :class:`.RemoteXBeeDevice` - | :class:`.XBeePacket` - """ - return self._send_expl_data(remote_xbee, data, src_endpoint, - dest_endpoint, cluster_id, profile_id, - transmit_options=transmit_options) - - def send_expl_data_broadcast(self, data, src_endpoint, dest_endpoint, - cluster_id, profile_id, - transmit_options=TransmitOptions.NONE.value): - """ - Sends the provided explicit data to all the XBee nodes of the network - (broadcast) using provided source and destination end points, cluster - and profile ids. - - This method blocks until a success or error transmit status arrives or - the configured receive timeout expires. The received timeout is - configured using the :meth:`.AbstractXBeeDevice.set_sync_ops_timeout` - method and can be consulted with method - :meth:`.AbstractXBeeDevice.get_sync_ops_timeout`. - - Args: - data (String or Bytearray): Raw data to send. - src_endpoint (Integer): Source endpoint of the transmission. 1 byte. - dest_endpoint (Integer): Destination endpoint of the transmission. 1 byte. - cluster_id (Integer): Cluster ID of the transmission (between 0x0 and 0xFFFF) - profile_id (Integer): Profile ID of the transmission (between 0x0 and 0xFFFF) - transmit_options (Integer, optional): Transmit options, bitfield of - :class:`.TransmitOptions`. Default to `TransmitOptions.NONE.value`. - - Raises: - TimeoutException: If response is not received before the read - timeout expires. - InvalidOperatingModeException: If the XBee's operating mode is not - API or ESCAPED API. This method only checks the cached value of - the operating mode. - TransmitException: If the status of the response received is not OK. - XBeeException: If the XBee's communication interface is closed. - ValueError: if `cluster_id` or `profile_id` is less than 0x0 or - greater than 0xFFFF. - - .. seealso:: - | :meth:`.XBeeDevice._send_expl_data` - """ - return self._send_expl_data_broadcast(data, src_endpoint, dest_endpoint, - cluster_id, profile_id, - transmit_options=transmit_options) - - def send_expl_data_async(self, remote_xbee, data, src_endpoint, dest_endpoint, - cluster_id, profile_id, transmit_options=TransmitOptions.NONE.value): - """ - Non-blocking method. Sends the provided explicit data to the given XBee, - source and destination end points, cluster and profile ids. - - Args: - remote_xbee (:class:`.RemoteXBeeDevice`): Remote XBee to send data to. - data (String or Bytearray): Raw data to send. - src_endpoint (Integer): Source endpoint of the transmission. 1 byte. - dest_endpoint (Integer): Destination endpoint of the transmission. 1 byte. - cluster_id (Integer): Cluster ID of the transmission (between 0x0 and 0xFFFF) - profile_id (Integer): Profile ID of the transmission (between 0x0 and 0xFFFF) - transmit_options (Integer, optional): Transmit options, bitfield of - :class:`.TransmitOptions`. Default to `TransmitOptions.NONE.value`. - - Raises: - InvalidOperatingModeException: If the XBee's operating mode is not - API or ESCAPED API. This method only checks the cached value of - the operating mode. - XBeeException: If the XBee's communication interface is closed. - ValueError: if `cluster_id` or `profile_id` is less than 0x0 or - greater than 0xFFFF. - - .. seealso:: - | :class:`.RemoteXBeeDevice` - """ - self._send_expl_data_async(remote_xbee, data, src_endpoint, - dest_endpoint, cluster_id, profile_id, - transmit_options=transmit_options) - @AbstractXBeeDevice._before_send_method @AbstractXBeeDevice._after_send_method def send_multicast_data(self, group_id, data, src_endpoint, dest_endpoint, @@ -6823,6 +6473,54 @@ def send_data_async(self, remote_xbee, data, transmit_options=TransmitOptions.NO """ raise AttributeError(self.__OPERATION_EXCEPTION) + def read_expl_data(self, timeout=None): + """ + Override. + + Operation not supported in this protocol. This method raises an + :class:`.AttributeError`. + """ + raise AttributeError(self.__OPERATION_EXCEPTION) + + def read_expl_data_from(self, remote_xbee, timeout=None): + """ + Override. + + Operation not supported in this protocol. This method raises an + :class:`.AttributeError`. + """ + raise AttributeError(self.__OPERATION_EXCEPTION) + + def send_expl_data(self, remote_xbee, data, src_endpoint, dest_endpoint, + cluster_id, profile_id, transmit_options=TransmitOptions.NONE.value): + """ + Override. + + Operation not supported in this protocol. This method raises an + :class:`.AttributeError`. + """ + raise AttributeError(self.__OPERATION_EXCEPTION) + + def send_expl_data_broadcast(self, data, src_endpoint, dest_endpoint, cluster_id, + profile_id, transmit_options=TransmitOptions.NONE.value): + """ + Override. + + Operation not supported in this protocol. This method raises an + :class:`.AttributeError`. + """ + raise AttributeError(self.__OPERATION_EXCEPTION) + + def send_expl_data_async(self, remote_xbee, data, src_endpoint, dest_endpoint, + cluster_id, profile_id, transmit_options=TransmitOptions.NONE.value): + """ + Override. + + Operation not supported in this protocol. This method raises an + :class:`.AttributeError`. + """ + raise AttributeError(self.__OPERATION_EXCEPTION) + class CellularDevice(IPDevice): """ diff --git a/doc/user_doc/communicating_with_xbee_devices.rst b/doc/user_doc/communicating_with_xbee_devices.rst index cd15a51..8443936 100644 --- a/doc/user_doc/communicating_with_xbee_devices.rst +++ b/doc/user_doc/communicating_with_xbee_devices.rst @@ -11,13 +11,17 @@ reception of data. applicable for local XBee devices. Remote XBee classes do not include methods for transmitting or receiving data. +.. warning:: + Only Zigbee, DigiMesh, 802.15.4, and Point-to-Multipoint protocols support the + transmission and reception of data from other devices in the network. + Send and receive data --------------------- XBee modules can communicate with other devices that are on the same network and use the same radio frequency. The XBee Python Library provides several methods -to send and receive data between the local XBee and any remote on the network. +to send and receive data between the local XBee and any remote in the network. * :ref:`communicateSendData` * :ref:`communicateReceiveData` @@ -29,7 +33,7 @@ Send data ````````` A data transmission operation sends data from your local (attached) XBee to a -remote device on the network. The operation sends data in API frames. The XBee +remote device in the network. The operation sends data in API frames. The XBee Python Library abstracts the process so you only have to specify the device to send data to and the data itself. @@ -56,30 +60,30 @@ This type of operation is blocking. This means the method waits until the transmit status response is received or the default timeout is reached. The ``XBeeDevice`` class of the API provides the following method to perform a -synchronous unicast transmission with a remote node of the network: +synchronous unicast transmission with a remote node in the network: -+---------------------------------------------------------------+-----------------------------------------------------------------------------------------------------+ -| Method | Description | -+===============================================================+=====================================================================================================+ -| **send_data(RemoteXBeeDevice, String or Bytearray, Integer)** | Specifies the remote XBee destination object, the data to send and optionally the transmit options. | -+---------------------------------------------------------------+-----------------------------------------------------------------------------------------------------+ ++---------------------------------------------------------------+--------------------------------------------------------------------------------------------------------+ +| Method | Description | ++===============================================================+========================================================================================================+ +| **send_data(RemoteXBeeDevice, String or Bytearray, Integer)** | Specifies the remote XBee destination object, the data to send, and, optionally, the transmit options. | ++---------------------------------------------------------------+--------------------------------------------------------------------------------------------------------+ Protocol-specific classes offer additional synchronous unicast transmission methods apart from the one provided by the ``XBeeDevice`` object: -+-----------------+---------------------------------------------------------------------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ -| XBee class | Method | Description | -+=================+=======================================================================================+===================================================================================================================================================================================================+ -| ZigBeeDevice | **send_data_64_16(XBee64BitAddress, XBee16BitAddress, String or Bytearray, Integer)** | Specifies the 64-bit and 16-bit destination addresses, the data to send and optionally the transmit options. If you do not know the 16-bit address, use the ``XBee16BitAddress.UNKNOWN_ADDRESS``. | -+-----------------+---------------------------------------------------------------------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ -| Raw802Device | **send_data_16(XBee16BitAddress, String or Bytearray, Integer)** | Specifies the 16-bit destination address, the data to send and optionally the transmit options. | -+ +---------------------------------------------------------------------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ -| | **send_data_64(XBee64BitAddress, String or Bytearray, Integer)** | Specifies the 64-bit destination address, the data to send and optionally the transmit options. | -+-----------------+---------------------------------------------------------------------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ -| DigiMeshDevice | **send_data_64(XBee64BitAddress, String or Bytearray, Integer)** | Specifies the 64-bit destination address, the data to send and optionally the transmit options. | -+-----------------+---------------------------------------------------------------------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ -| DigiPointDevice | **send_data_64_16(XBee64BitAddress, XBee16BitAddress, String or Bytearray, Integer)** | Specifies the 64-bit and 16-bit destination addresses, the data to send and optionally the transmit options. If you do not know the 16-bit address, use the ``XBee16BitAddress.UNKNOWN_ADDRESS``. | -+-----------------+---------------------------------------------------------------------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ ++-----------------+---------------------------------------------------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +| XBee class | Method | Description | ++=================+=======================================================================================+==================================================================================================================================================================================================+ +| ZigBeeDevice | **send_data_64_16(XBee64BitAddress, XBee16BitAddress, String or Bytearray, Integer)** | Specifies the 64-bit and 16-bit destination addresses, the data to send, and, optionally, the transmit options. If you do not know the 16-bit address, use ``XBee16BitAddress.UNKNOWN_ADDRESS``. | ++-----------------+---------------------------------------------------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +| Raw802Device | **send_data_16(XBee16BitAddress, String or Bytearray, Integer)** | Specifies the 16-bit destination address, the data to send, and, optionally, the transmit options. | ++ +---------------------------------------------------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +| | **send_data_64(XBee64BitAddress, String or Bytearray, Integer)** | Specifies the 64-bit destination address, the data to send, and, optionally, the transmit options. | ++-----------------+---------------------------------------------------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +| DigiMeshDevice | **send_data_64(XBee64BitAddress, String or Bytearray, Integer)** | Specifies the 64-bit destination address, the data to send, and, optionally, the transmit options. | ++-----------------+---------------------------------------------------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +| DigiPointDevice | **send_data_64_16(XBee64BitAddress, XBee16BitAddress, String or Bytearray, Integer)** | Specifies the 64-bit and 16-bit destination addresses, the data to send, and, optionally, the transmit options. If you do not know the 16-bit address, use ``XBee16BitAddress.UNKNOWN_ADDRESS``. | ++-----------------+---------------------------------------------------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ **Send data synchronously** @@ -87,11 +91,11 @@ methods apart from the one provided by the ``XBeeDevice`` object: [...] - # Instantiate a local XBee object. + # Instantiate a local XBee node. xbee = XBeeDevice("COM1", 9600) xbee.open() - # Instantiate a remote XBee object. + # Instantiate a remote XBee node. remote = RemoteXBeeDevice(xbee, XBee64BitAddress.from_hex_string("0013A20040XXXXXX")) # Send data using the remote object. @@ -113,7 +117,7 @@ The previous methods may fail for the following reasons: ``XBeeException``. The default timeout to wait for the send status is two seconds. However, you -can configure the timeout using the ``get_sync_ops_timeout()`` and +can configure the timeout using ``get_sync_ops_timeout()`` and ``set_sync_ops_timeout()`` methods of an XBee class. **Get/set the timeout for synchronous operations** @@ -139,7 +143,7 @@ can configure the timeout using the ``get_sync_ops_timeout()`` and +----------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | Example: Synchronous unicast transmission | +============================================================================================================================================================================+ -| The XBee Python Library includes a sample application that shows you how to send data to another XBee on the network. The example is located in the following path: | +| The XBee Python Library includes a sample application that shows you how to send data to another XBee in the network. The example is located in the following path: | | | | **examples/communication/SendDataSample** | +----------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ @@ -153,30 +157,30 @@ during the transmit process. However, you cannot ensure that the data was successfully sent to the remote node. The ``XBeeDevice`` class of the API provides the following method to perform -an asynchronous unicast transmission with a remote node on the network: +an asynchronous unicast transmission with a remote node in the network: -+---------------------------------------------------------------------+-----------------------------------------------------------------------------------------------------+ -| Method | Description | -+=====================================================================+=====================================================================================================+ -| **send_data_async(RemoteXBeeDevice, String or Bytearray, Integer)** | Specifies the remote XBee destination object, the data to send and optionally the transmit options. | -+---------------------------------------------------------------------+-----------------------------------------------------------------------------------------------------+ ++---------------------------------------------------------------------+--------------------------------------------------------------------------------------------------------+ +| Method | Description | ++=====================================================================+========================================================================================================+ +| **send_data_async(RemoteXBeeDevice, String or Bytearray, Integer)** | Specifies the remote XBee destination object, the data to send, and, optionally, the transmit options. | ++---------------------------------------------------------------------+--------------------------------------------------------------------------------------------------------+ Protocol-specific classes offer some other asynchronous unicast transmission methods in addition to the one provided by the XBeeDevice object: -+-----------------+---------------------------------------------------------------------------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ -| XBee class | Method | Description | -+=================+=============================================================================================+===================================================================================================================================================================================================+ -| ZigBeeDevice | **send_data_async_64_16(XBee64BitAddress, XBee16BitAddress, String or Bytearray, Integer)** | Specifies the 64-bit and 16-bit destination addresses, the data to send and optionally the transmit options. If you do not know the 16-bit address, use the ``XBee16BitAddress.UNKNOWN_ADDRESS``. | -+-----------------+---------------------------------------------------------------------------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ -| Raw802Device | **send_data_async_16(XBee16BitAddress, String or Bytearray, Integer)** | Specifies the 16-bit destination address, the data to send and optionally the transmit options. | -+ +---------------------------------------------------------------------------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ -| | **send_data_async_64(XBee64BitAddress, String or Bytearray, Integer)** | Specifies the 64-bit destination address, the data to send and optionally the transmit options. | -+-----------------+---------------------------------------------------------------------------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ -| DigiMeshDevice | **send_data_async_64(XBee64BitAddress, String or Bytearray, Integer)** | Specifies the 64-bit destination address, the data to send and optionally the transmit options. | -+-----------------+---------------------------------------------------------------------------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ -| DigiPointDevice | **send_data_async_64_16(XBee64BitAddress, XBee16BitAddress, String or Bytearray, Integer)** | Specifies the 64-bit and 16-bit destination addresses, the data to send and optionally the transmit options. If you do not know the 16-bit address, use the ``XBee16BitAddress.UNKNOWN_ADDRESS``. | -+-----------------+---------------------------------------------------------------------------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ ++-----------------+---------------------------------------------------------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +| XBee class | Method | Description | ++=================+=============================================================================================+==================================================================================================================================================================================================+ +| ZigBeeDevice | **send_data_async_64_16(XBee64BitAddress, XBee16BitAddress, String or Bytearray, Integer)** | Specifies the 64-bit and 16-bit destination addresses, the data to send, and, optionally, the transmit options. If you do not know the 16-bit address, use ``XBee16BitAddress.UNKNOWN_ADDRESS``. | ++-----------------+---------------------------------------------------------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +| Raw802Device | **send_data_async_16(XBee16BitAddress, String or Bytearray, Integer)** | Specifies the 16-bit destination address, the data to send, and, optionally, the transmit options. | ++ +---------------------------------------------------------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +| | **send_data_async_64(XBee64BitAddress, String or Bytearray, Integer)** | Specifies the 64-bit destination address, the data to send, and, optionally, the transmit options. | ++-----------------+---------------------------------------------------------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +| DigiMeshDevice | **send_data_async_64(XBee64BitAddress, String or Bytearray, Integer)** | Specifies the 64-bit destination address, the data to send, and, optionally, the transmit options. | ++-----------------+---------------------------------------------------------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +| DigiPointDevice | **send_data_async_64_16(XBee64BitAddress, XBee16BitAddress, String or Bytearray, Integer)** | Specifies the 64-bit and 16-bit destination addresses, the data to send, and, optionally, the transmit options. If you do not know the 16-bit address, use ``XBee16BitAddress.UNKNOWN_ADDRESS``. | ++-----------------+---------------------------------------------------------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ **Send data asynchronously** @@ -184,11 +188,11 @@ methods in addition to the one provided by the XBeeDevice object: [...] - # Instantiate a local XBee object. + # Instantiate a local XBee node. xbee = XBeeDevice("COM1", 9600) xbee.open() - # Instantiate a remote XBee object. + # Instantiate a remote XBee node. remote = RemoteXBeeDevice(xbee, XBee64BitAddress.from_hex_string("0013A20040XXXXXX")) # Send data using the remote object. @@ -220,16 +224,16 @@ Send data to all devices of the network ''''''''''''''''''''''''''''''''''''''' Broadcast transmissions are sent from one source device to all the other -devices on the network. +devices in the network. All the XBee classes (generic and protocol specific) provide the same method to send broadcast data: -+-------------------------------------------------------+-----------------------------------------------------------------+ -| Method | Description | -+=======================================================+=================================================================+ -| **send_data_broadcast(String or Bytearray, Integer)** | Specifies the data to send and optionally the transmit options. | -+-------------------------------------------------------+-----------------------------------------------------------------+ ++-------------------------------------------------------+--------------------------------------------------------------------+ +| Method | Description | ++=======================================================+====================================================================+ +| **send_data_broadcast(String or Bytearray, Integer)** | Specifies the data to send, and, optionally, the transmit options. | ++-------------------------------------------------------+--------------------------------------------------------------------+ **Send broadcast data** @@ -237,7 +241,7 @@ send broadcast data: [...] - # Instantiate a local XBee object. + # Instantiate a local XBee node. xbee = XBeeDevice("COM1", 9600) xbee.open() @@ -248,7 +252,7 @@ send broadcast data: The ``send_data_broadcast()`` method may fail for the following reasons: -* Transmit status is not received in the configured timeout, throwing a +* A Transmit status is not received in the configured timeout, throwing a ``TimeoutException`` exception. * Error types catch as ``XBeeException``: @@ -261,7 +265,7 @@ The ``send_data_broadcast()`` method may fail for the following reasons: +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | Example: Broadcast transmission | +====================================================================================================================================================================================+ -| The XBee Python Library includes a sample application that shows you how to send data to all the devices on the network (broadcast). The example is located in the following path: | +| The XBee Python Library includes a sample application that shows you how to send data to all the devices in the network (broadcast). The example is located in the following path: | | | | **examples/communication/SendBroadcastDataSample** | +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ @@ -272,7 +276,7 @@ The ``send_data_broadcast()`` method may fail for the following reasons: Receive data ```````````` -The data reception operation allows you to receive and handle data sent by +The data reception operation allows you to receive and handle sent data by other remote nodes of the network. There are two different ways to read data from the device: @@ -282,7 +286,8 @@ There are two different ways to read data from the device: configurable timeout has expired. * **Data reception callback**. In this case, you must register a listener that executes a callback each time new data is received by the local XBee (that is, - the device attached to your PC) providing data and other related information. + the device attached to your PC) providing received data and other related + information. .. _communicateReceiveDataPolling: @@ -292,7 +297,7 @@ Polling for data The simplest way to read for data is by executing the ``read_data()`` method of the local XBee. This method blocks your application until data from any XBee -on the network is received or the provided timeout expires: +in the network is received or the provided timeout expires: +------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | Method | Description | @@ -306,7 +311,7 @@ on the network is received or the provided timeout expires: [...] - # Instantiate a local XBee object. + # Instantiate a local XBee node. xbee = XBeeDevice("COM1", 9600) xbee.open() @@ -342,7 +347,7 @@ the ``XBeeMessage`` object: [...] You can also read data from a specific remote XBee of the network. For that -purpose, the XBee object provides the ``read_data_from()`` method: +purpose, ``XBeeDevice`` object provides the ``read_data_from()`` method: +-----------------------------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | Method | Description | @@ -356,14 +361,14 @@ purpose, the XBee object provides the ``read_data_from()`` method: [...] - # Instantiate a local XBee object. + # Instantiate a local XBee node. xbee = XBeeDevice("COM1", 9600) xbee.open() - # Instantiate a remote XBee object. + # Instantiate a remote XBee node. remote = RemoteXBeeDevice(xbee, XBee64BitAddress.from_hex_string("0013A200XXXXXX")) - # Read data sent by the remote device. + # Read sent data by the remote device. xbee_message = xbee.read_data(remote) [...] @@ -401,11 +406,11 @@ parameter. [...] - # Instantiate a local XBee object. + # Instantiate a local XBee node. xbee = XBeeDevice("COM1", 9600) xbee.open() - # Define callback. + # Define the callback. def my_data_received_callback(xbee_message): address = xbee_message.remote_device.get_64bit_addr() data = xbee_message.data.decode("utf8") @@ -468,10 +473,8 @@ layer-specific fields—the source and destination endpoints, profile ID, and cluster ID. .. warning:: - Only Zigbee, DigiMesh, and Point-to-Multipoint protocols support the - transmission and reception of data in explicit format. This means you cannot - transmit or receive explicit data using a generic ``XBeeDevice`` object. You - must use a protocol-specific XBee object such as a ``ZigBeeDevice``. + Only Zigbee, DigiMesh, 802.15.4, and Point-to-Multipoint protocols support the + transmission and reception of data from other devices in the network. * :ref:`communicateSendExplicitData` * :ref:`communicateReceiveExplicitData` @@ -509,11 +512,11 @@ All local XBee classes that support explicit data transmission provide a method to transmit unicast and synchronous explicit data to a remote node of the network: -+--------------------------------------------------------------------------------------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ -| Method | Description | -+========================================================================================================+====================================================================================================================================================================================================+ -| **send_expl_data(RemoteXBeeDevice, Integer, Integer, Integer, Integer, String or Bytearray, Integer)** | Specifies remote XBee destination object, four application layer fields (source endpoint, destination endpoint, cluster ID, and profile ID), the data to send and optionally the transmit options. | -+--------------------------------------------------------------------------------------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ ++--------------------------------------------------------------------------------------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +| Method | Description | ++========================================================================================================+=======================================================================================================================================================================================================+ +| **send_expl_data(RemoteXBeeDevice, Integer, Integer, Integer, Integer, String or Bytearray, Integer)** | Specifies remote XBee destination object, four application layer fields (source endpoint, destination endpoint, cluster ID, and profile ID), the data to send, and, optionally, the transmit options. | ++--------------------------------------------------------------------------------------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ **Send unicast explicit data synchronously** @@ -521,19 +524,19 @@ network: [...] - # Instantiate a local Zigbee object. - xbee = ZigBeeDevice("COM1", 9600) + # Instantiate a local node. + xbee = XBeeDevice("COM1", 9600) xbee.open() - # Instantiate a remote Zigbee object. - remote = RemoteZigBeeDevice(xbee, XBee64BitAddress.from_hex_string("0013A20040XXXXXX")) + # Instantiate a remote node. + remote = RemoteXBeeDevice(xbee, XBee64BitAddress.from_hex_string("0013A20040XXXXXX")) # Send explicit data using the remote object. xbee.send_expl_data(remote, 0xA0, 0xA1, 0x1554, 0xC105, "Hello XBee!") [...] -The previous methods may fail for the following reasons: +The previous method may fail for the following reasons: * The method throws a ``TimeoutException`` exception if the response is not received in the configured timeout. @@ -547,7 +550,7 @@ The previous methods may fail for the following reasons: generic ``XBeeException``. The default timeout to wait for the send status is two seconds. However, you -can configure the timeout using the ``get_sync_ops_timeout()`` and +can configure the timeout using ``get_sync_ops_timeout()`` and ``set_sync_ops_timeout()`` methods of an XBee class. +-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ @@ -570,11 +573,11 @@ All local XBee classes that support explicit data transmission provide a method to transmit unicast and asynchronous explicit data to a remote node of the network: -+--------------------------------------------------------------------------------------------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ -| Method | Description | -+==============================================================================================================+====================================================================================================================================================================================================+ -| **send_expl_data_async(RemoteXBeeDevice, Integer, Integer, Integer, Integer, String or Bytearray, Integer)** | Specifies remote XBee destination object, four application layer fields (source endpoint, destination endpoint, cluster ID, and profile ID), the data to send and optionally the transmit options. | -+--------------------------------------------------------------------------------------------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ ++--------------------------------------------------------------------------------------------------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +| Method | Description | ++==============================================================================================================+======================================================================================================================================================================================================+ +| **send_expl_data_async(RemoteXBeeDevice, Integer, Integer, Integer, Integer, String or Bytearray, Integer)** | Specifies remote XBee destination object, four application layer fields (source endpoint, destination endpoint, cluster ID, and profile ID), the data to send and, optionally, the transmit options. | ++--------------------------------------------------------------------------------------------------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ **Send unicast explicit data asynchronously** @@ -582,19 +585,19 @@ of the network: [...] - # Instantiate a local Zigbee object. - xbee = ZigBeeDevice("COM1", 9600) + # Instantiate a local XBee node. + xbee = XBeeDevice("COM1", 9600) xbee.open() - # Instantiate a remote Zigbee object. - remote = RemoteZigBeeDevice(xbee, XBee64BitAddress.from_hex_string("0013A20040XXXXXX")) + # Instantiate a remote XBee node. + remote = RemoteXBeeDevice(xbee, XBee64BitAddress.from_hex_string("0013A20040XXXXXX")) # Send explicit data asynchronously using the remote object. xbee.send_expl_data_async(remote, 0xA0, 0xA1, 0x1554, 0xC105, "Hello XBee!") [...] -The previous methods may fail for the following reasons: +The previous method may fail for the following reasons: * All the possible errors are caught as an ``XBeeException``: @@ -623,11 +626,11 @@ the network. All protocol-specific XBee classes that support the transmission of explicit data provide the same method to send broadcast explicit data: -+------------------------------------------------------------------------------------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ -| Method | Description | -+================================================================================================+========================================================================================================================================================================+ -| **send_expl_data_broadcast(Integer, Integer, Integer, Integer, String or Bytearray, Integer)** | Specifies the four application layer fields (source endpoint, destination endpoint, cluster ID, and profile ID), the data to send and optionally the transmit options. | -+------------------------------------------------------------------------------------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ ++------------------------------------------------------------------------------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +| Method | Description | ++================================================================================================+===========================================================================================================================================================================+ +| **send_expl_data_broadcast(Integer, Integer, Integer, Integer, String or Bytearray, Integer)** | Specifies the four application layer fields (source endpoint, destination endpoint, cluster ID, and profile ID), the data to send, and, optionally, the transmit options. | ++------------------------------------------------------------------------------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ **Send broadcast data** @@ -635,8 +638,8 @@ data provide the same method to send broadcast explicit data: [...] - # Instantiate a local Zigbee object. - xbee = ZigBeeDevice("COM1", 9600) + # Instantiate a local XBee node. + xbee = XBeeDevice("COM1", 9600) xbee.open() # Send broadcast data. @@ -676,14 +679,14 @@ To receive data in explicit format, configure the data output mode of the receiver XBee to explicit format using the ``set_api_output_mode_value()`` method. -+----------------------------------------+----------------------------------------------------------------------------------------------+ -| Method | Description | -+========================================+==============================================================================================+ -| **get_api_output_mode_value()** | Returns the API output mode of the data received by the XBee. | -+----------------------------------------+----------------------------------------------------------------------------------------------+ -| **set_api_output_mode_value(Integer)** | Specifies the API output mode of the data received by the XBee. Calculate the mode | -| | with the method `calculate_api_output_mode_value` with a set of `APIOutputModeBit`. | -+----------------------------------------+----------------------------------------------------------------------------------------------+ ++----------------------------------------+-------------------------------------------------------------------------------------+ +| Method | Description | ++========================================+=====================================================================================+ +| **get_api_output_mode_value()** | Returns the API output mode of the data received by the XBee. | ++----------------------------------------+-------------------------------------------------------------------------------------+ +| **set_api_output_mode_value(Integer)** | Specifies the API output mode of the data received by the XBee. Calculate the mode | +| | with the method `calculate_api_output_mode_value` with a set of `APIOutputModeBit`. | ++----------------------------------------+-------------------------------------------------------------------------------------+ **Set API output mode** @@ -691,8 +694,8 @@ method. [...] - # Instantiate a local Zigbee object. - xbee = ZigBeeDevice("COM1", 9600) + # Instantiate a local XBee node. + xbee = XBeeDevice("COM1", 9600) xbee.open() # Set explicit output mode @@ -704,7 +707,7 @@ method. mode = 0 xbee.set_api_output_mode_value(mode) - # Set explicit plus unsupported ZDO request pass-through + # Set explicit plus unsupported ZDO request pass-through (only for Zigbee) mode = APIOutputModeBit.calculate_api_output_mode_value(xbee.get_protocol(), {APIOutputModeBit.EXPLICIT, APIOutputModeBit.UNSUPPORTED_ZDO_PASSTHRU}) xbee.set_api_output_mode_value(mode) @@ -738,8 +741,8 @@ or the provided timeout has expired: [...] - # Instantiate a local Zigbee object. - xbee = ZigBeeDevice("COM1", 9600) + # Instantiate a local XBee node. + xbee = XBeeDevice("COM1", 9600) xbee.open() # Read data. @@ -782,7 +785,7 @@ the ``ExplicitXBeeMessage`` object: [...] You can also read explicit data from a specific remote XBee of the network. For -that purpose, the XBee object provides the ``read_expl_data_from()`` method: +that purpose, ``XBeeDevice`` provides the ``read_expl_data_from()`` method: +----------------------------------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | Method | Description | @@ -796,14 +799,14 @@ that purpose, the XBee object provides the ``read_expl_data_from()`` method: [...] - # Instantiate a local Zigbee object. - xbee = ZigBeeDevice("COM1", 9600) + # Instantiate a local XBee node. + xbee = BeeDevice("COM1", 9600) xbee.open() - # Instantiate a remote Zigbee object. - remote = RemoteZigBeeDevice(xbee, XBee64BitAddress.from_hex_string("0013A200XXXXXX")) + # Instantiate a remote XBee node. + remote = RemoteXBeeDevice(xbee, XBee64BitAddress.from_hex_string("0013A200XXXXXX")) - # Read data sent by the remote device. + # Read sent data by the remote device. expl_xbee_message = xbee.read_expl_data(remote) [...] @@ -812,7 +815,7 @@ As in the previous method, this method also returns an ``ExplicitXBeeMessage`` object with all the information inside. The default timeout to wait for data is two seconds. However, you -can configure the timeout using the ``get_sync_ops_timeout()`` and +can configure the timeout using ``get_sync_ops_timeout()`` and ``set_sync_ops_timeout()`` methods of an XBee class. +----------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ @@ -830,8 +833,8 @@ Explicit data reception callback '''''''''''''''''''''''''''''''' This mechanism for reading explicit data does not block your application. -Instead, you can be notified when new explicit data has been received if you -are subscribed or registered to the explicit data reception service by using the +Instead, you are notified when new explicit data has been received if you are +subscribed or registered to the explicit data reception service by using ``add_expl_data_received_callback()``. **Explicit data reception registration** @@ -840,11 +843,11 @@ are subscribed or registered to the explicit data reception service by using the [...] - # Instantiate a local Zigbee object. - xbee = ZigBeeDevice("COM1", 9600) + # Instantiate a local XBee node. + xbee = XBeeDevice("COM1", 9600) xbee.open() - # Define callback. + # Define the callback. def my_expl_data_received_callback(expl_xbee_message): address = expl_xbee_message.remote_device.get_64bit_addr() source_endpoint = expl_xbee_message.source_endpoint @@ -938,8 +941,8 @@ already-registered callback. Send and receive IP data ------------------------ -In contrast to XBee protocols like Zigbee, DigiMesh or 802.15.4, where the -devices are connected each other, in cellular and Wi-Fi protocols the modules +In contrast to XBee protocols like Zigbee, DigiMesh, or 802.15.4, where the +devices are connected to each other, in Cellular and Wi-Fi protocols, modules are part of the Internet. XBee Cellular and Wi-Fi modules offer a special type of frame for communicating @@ -974,11 +977,11 @@ default timeout. The ``CellularDevice`` and ``WiFiDevice`` classes include several methods to transmit IP data synchronously: -+----------------------------------------------------------------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ -| Method | Description | -+==================================================================================+=============================================================================================================================================================================================================+ -| **send_ip_data(IPv4Address, Integer, IPProtocol, String or Bytearray, Boolean)** | Specifies the destination IP address, destination port, IP protocol (UDP, TCP or TCP SSL), data to send for transmissions and whether the socket should be closed after the transmission or not (optional). | -+----------------------------------------------------------------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ ++----------------------------------------------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +| Method | Description | ++==================================================================================+==============================================================================================================================================================================================================+ +| **send_ip_data(IPv4Address, Integer, IPProtocol, String or Bytearray, Boolean)** | Specifies the destination IP address, destination port, IP protocol (UDP, TCP or TCP SSL), data to send for transmissions, and whether the socket should be closed after the transmission or not (optional). | ++----------------------------------------------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ **Send network data synchronously** @@ -1046,11 +1049,11 @@ successfully sent. The ``CellularDevice`` and ``WiFiDevice`` classes include several methods to transmit IP data asynchronously: -+----------------------------------------------------------------------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ -| Method | Description | -+========================================================================================+=============================================================================================================================================================================================================+ -| **send_ip_data_async(IPv4Address, Integer, IPProtocol, String or Bytearray, Boolean)** | Specifies the destination IP address, destination port, IP protocol (UDP, TCP or TCP SSL), data to send for transmissions and whether the socket should be closed after the transmission or not (optional). | -+----------------------------------------------------------------------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ ++----------------------------------------------------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +| Method | Description | ++========================================================================================+==============================================================================================================================================================================================================+ +| **send_ip_data_async(IPv4Address, Integer, IPProtocol, String or Bytearray, Boolean)** | Specifies the destination IP address, destination port, IP protocol (UDP, TCP or TCP SSL), data to send for transmissions, and whether the socket should be closed after the transmission or not (optional). | ++----------------------------------------------------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ **Send network data asynchronously** @@ -1093,14 +1096,13 @@ receive IP data. XBee Cellular and Wi-Fi modules operate the same way as other TCP/IP devices. They can initiate communications with other devices or listen for TCP or UDP transmissions at a specific port. In either case, you must apply any of the -receive methods explained in this section in order to read IP data from other -devices. +receive methods explained in this section to read IP data from other devices. Listen for incoming transmissions ''''''''''''''''''''''''''''''''' -If the cellular or Wi-Fi module operates as a server, listening for incoming +If the Cellular or Wi-Fi module operates as a server, listening for incoming TCP or UDP transmissions, you must start listening at a specific port, similar to the bind operation of a socket. The XBee Python Library provides a method to listen for incoming transmissions: @@ -1242,7 +1244,7 @@ the ``IPMessage`` object: [...] You can also read IP data that comes from a specific IP address. For that -purpose, the cellular and Wi-Fi device objects provide the +purpose, the Cellular and Wi-Fi device objects provide the ``read_ip_data_from()`` method: **Read IP data from a specific IP address (polling)** @@ -1580,7 +1582,7 @@ during the transmit process. [...] - # Instantiate a local XBee object. + # Instantiate a local XBee node. xbee = XBeeDevice("COM1", 9600) xbee.open() @@ -1625,7 +1627,7 @@ reception service by using the ``add_bluetooth_data_received_callback()`` method [...] - # Instantiate a local XBee object. + # Instantiate a local XBee node. xbee = XBeeDevice("COM1", 9600) xbee.open() @@ -1714,7 +1716,7 @@ during the transmit process. [...] - # Instantiate a local XBee object. + # Instantiate a local XBee node. xbee = XBeeDevice("COM1", 9600) xbee.open() @@ -1759,7 +1761,7 @@ service by using the ``add_micropython_data_received_callback()`` method. [...] - # Instantiate a local XBee object. + # Instantiate a local XBee node. xbee = XBeeDevice("COM1", 9600) xbee.open() @@ -1832,7 +1834,7 @@ using a modem status listener as parameter with the method [...] - # Instantiate a local XBee object. + # Instantiate a local XBee node. xbee = XBeeDevice("COM1", 9600) xbee.open() @@ -2117,7 +2119,7 @@ Available statistics are attributes of the ``Statistics`` object: [...] - # Instantiate a local XBee object. + # Instantiate a local XBee node. xbee = XBeeDevice("COM1", 9600) xbee.open() diff --git a/doc/user_doc/working_with_xbee_classes.rst b/doc/user_doc/working_with_xbee_classes.rst index d80b408..a73c86f 100644 --- a/doc/user_doc/working_with_xbee_classes.rst +++ b/doc/user_doc/working_with_xbee_classes.rst @@ -26,7 +26,7 @@ The XBee Python Library supports one XBee class per protocol, as follows: * XBee Wi-Fi (``WiFiDevice``) All these XBee classes allow you to configure the physical XBee, communicate -with the device, send data to other nodes on the network, receive data from +with the device, send data to other nodes in the network, receive data from remote devices, and so on. Depending on the class, you may have additional methods to execute protocol-specific features or similar methods. From 0cb53bd4facb5641a3d3912105615b3b083c76ac Mon Sep 17 00:00:00 2001 From: Tatiana Leon Date: Wed, 17 Aug 2022 09:43:45 +0200 Subject: [PATCH 06/36] zigbee: source route: reverse hops order for a 'Create Source Route' packet 'Create Source Route' packet (0x21) must include the hops to a destination node from closest node to destination to closest neighbor to source. See: https://www.digi.com/resources/documentation/digidocs/90001539/#reference/r_frame_0x21.htm https://github.com/digidotcom/xbee-python/issues/278 Signed-off-by: Tatiana Leon --- CHANGELOG.rst | 3 +++ digi/xbee/devices.py | 3 +++ 2 files changed, 6 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 3b1e62b..0d2ec4e 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -2,6 +2,7 @@ Changelog ========= v1.4.2 - XX/XX/202X +------------------- * Support for new hardware variants: @@ -10,7 +11,9 @@ 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) +* Bug fixing: + * Fix order of nodes when creating a Zigbee source route (#278) v1.4.1 - 12/22/2021 ------------------- diff --git a/digi/xbee/devices.py b/digi/xbee/devices.py index 61ee4cc..3cebe3c 100644 --- a/digi/xbee/devices.py +++ b/digi/xbee/devices.py @@ -5901,6 +5901,9 @@ def create_source_route(self, dest_node, hops): dest_node, dest_node.get_local_xbee_device(), " >>> " if hops else "", " >>> ".join(map(str, hops)), dest_node, len(hops) + 1) + # Reverse addresses to create the packet: + # from closest to destination to closest to source + addresses.reverse() self.send_packet( CreateSourceRoutePacket(0x00, x64, x16, route_options=0, hops=addresses), sync=False) From 9c08721ccc53c9ba1d9330ca78b876b495958f4e Mon Sep 17 00:00:00 2001 From: Ruben Moral Date: Fri, 16 Sep 2022 12:35:44 +0200 Subject: [PATCH 07/36] hardware: add new versions to the supported hardware list - 0x56: XBee 3 Cellular LTE-M/NB-IoT Low Power - 0x57: XBee RR TH Pro/Non-Pro https://onedigi.atlassian.net/browse/XBPL-384 https://onedigi.atlassian.net/browse/XBPL-385 Signed-off-by: Ruben Moral --- CHANGELOG.rst | 2 ++ digi/xbee/filesystem.py | 3 ++- digi/xbee/firmware.py | 7 +++++-- digi/xbee/models/hw.py | 4 +++- digi/xbee/models/protocol.py | 6 ++++-- digi/xbee/recovery.py | 3 ++- 6 files changed, 18 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 0d2ec4e..2111e42 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,6 +8,8 @@ v1.4.2 - XX/XX/202X * XBee 3 Cellular Global LTE Cat 1 * XBee 3 Cellular North America LTE Cat 1 + * XBee 3 Cellular LTE-M/NB-IoT Low Power + * XBee RR TH Pro/Non-Pro * Support to retrieve XBee statistics. * Send/receive explicit data in 802.15.4. (XBee 3 modules support this feature) diff --git a/digi/xbee/filesystem.py b/digi/xbee/filesystem.py index 2675f18..c94ba63 100644 --- a/digi/xbee/filesystem.py +++ b/digi/xbee/filesystem.py @@ -86,7 +86,8 @@ HardwareVersion.XBEE3_SMT.code, HardwareVersion.XBEE3_TH.code) LOCAL_SUPPORTED_HW_VERSIONS = REMOTE_SUPPORTED_HW_VERSIONS \ - + (HardwareVersion.XBEE3_RR.code,) + + (HardwareVersion.XBEE3_RR.code, + HardwareVersion.XBEE3_RR_TH.code) # Update this value when File System API frames are supported XB3_MIN_FW_VERSION_FS_API_SUPPORT = { diff --git a/digi/xbee/firmware.py b/digi/xbee/firmware.py index d538249..c629552 100644 --- a/digi/xbee/firmware.py +++ b/digi/xbee/firmware.py @@ -80,11 +80,13 @@ "^.*Gecko Bootloader v([0-9a-fA-F]{1,}\\.[0-9a-fA-F]{1,}\\.[0-9a-fA-F]{1,}).*$" _XBEE3_BL_DEF_PREFIX = "xb3-boot-rf_" +_XBEE3_RR_BL_DEF_PREFIX = "xb3-boot-rr_" _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: "xb3-boot-rr_" + HardwareVersion.XBEE3_RR.code: _XBEE3_RR_BL_DEF_PREFIX, + HardwareVersion.XBEE3_RR_TH.code: _XBEE3_RR_BL_DEF_PREFIX } _GEN3_BOOTLOADER_ERROR_CHECKSUM = 0x12 @@ -296,7 +298,8 @@ XBEE3_HW_VERSIONS = (HardwareVersion.XBEE3.code, HardwareVersion.XBEE3_SMT.code, HardwareVersion.XBEE3_TH.code, - HardwareVersion.XBEE3_RR.code) + HardwareVersion.XBEE3_RR.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 diff --git a/digi/xbee/models/hw.py b/digi/xbee/models/hw.py index 0bc9c8b..705dc53 100644 --- a/digi/xbee/models/hw.py +++ b/digi/xbee/models/hw.py @@ -96,10 +96,12 @@ class HardwareVersion(Enum): CELLULAR_3_LTE_M_TELIT = (0x4E, "XBee 3 Cellular LTE-M/NB-IoT (Telit)") XBEE3_DM_LR = (0x50, "XB3-DMLR") XBEE3_DM_LR_868 = (0x51, "XB3-DMLR868") - XBEE3_RR = (0x52, "XBee 3 Reduced RAM") + XBEE3_RR = (0x52, "XBee RR SMT/MMT, Pro/Non-Pro") S2C_P5 = (0x53, "S2C P5") CELLULAR_3_GLOBAL_LTE_CAT1 = (0x54, "XBee 3 Cellular Global LTE Cat 1") CELLULAR_3_NA_LTE_CAT1 = (0x55, "XBee 3 Cellular North America LTE Cat 1") + CELLULAR_3_LTE_M_LOW_POWER = (0x56, "XBee 3 Cellular LTE-M/NB-IoT Low Power") + XBEE3_RR_TH = (0x57, "XBee RR TH Pro/Non-Pro") def __init__(self, code, description): self.__code = code diff --git a/digi/xbee/models/protocol.py b/digi/xbee/models/protocol.py index 083a47f..a90ba66 100644 --- a/digi/xbee/models/protocol.py +++ b/digi/xbee/models/protocol.py @@ -259,7 +259,8 @@ def determine_protocol(hw_version, fw_version, br_value=None): HardwareVersion.CELLULAR_3_CAT1_LTE_VERIZON.code, HardwareVersion.CELLULAR_3_LTE_M_TELIT.code, HardwareVersion.CELLULAR_3_GLOBAL_LTE_CAT1.code, - HardwareVersion.CELLULAR_3_NA_LTE_CAT1.code): + HardwareVersion.CELLULAR_3_NA_LTE_CAT1.code, + HardwareVersion.CELLULAR_3_LTE_M_LOW_POWER.code): return XBeeProtocol.CELLULAR if hw_version == HardwareVersion.CELLULAR_NBIOT_EUROPE.code: @@ -268,7 +269,8 @@ def determine_protocol(hw_version, fw_version, br_value=None): if hw_version in (HardwareVersion.XBEE3.code, HardwareVersion.XBEE3_SMT.code, HardwareVersion.XBEE3_TH.code, - HardwareVersion.XBEE3_RR.code): + HardwareVersion.XBEE3_RR.code, + HardwareVersion.XBEE3_RR_TH.code): if fw_version.startswith("2"): return XBeeProtocol.RAW_802_15_4 if fw_version.startswith("3"): diff --git a/digi/xbee/recovery.py b/digi/xbee/recovery.py index 2813b65..0909fd2 100644 --- a/digi/xbee/recovery.py +++ b/digi/xbee/recovery.py @@ -31,7 +31,8 @@ SUPPORTED_HARDWARE_VERSIONS = (HardwareVersion.XBEE3.code, HardwareVersion.XBEE3_SMT.code, HardwareVersion.XBEE3_TH.code, - HardwareVersion.XBEE3_RR.code) + HardwareVersion.XBEE3_RR.code, + HardwareVersion.XBEE3_RR_TH.code) _BAUDRATE_KEY = "baudrate" _PARITY_KEY = "parity" From 1024d8954e1b445fe6aacd7f0880de65978c351b Mon Sep 17 00:00:00 2001 From: soterboat <96389364+soterboat@users.noreply.github.com> Date: Wed, 8 Feb 2023 09:58:07 -0500 Subject: [PATCH 08/36] Update devices.py Byte shifting bug --- digi/xbee/devices.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/digi/xbee/devices.py b/digi/xbee/devices.py index 3cebe3c..57615bb 100644 --- a/digi/xbee/devices.py +++ b/digi/xbee/devices.py @@ -1544,7 +1544,7 @@ def set_dio_change_detection(self, io_lines_set): if i < 8: flags[1] = flags[1] | (1 << i) else: - flags[0] = flags[0] | ((1 << i) - 8) + flags[0] = flags[0] | (1 << (i - 8)) self.set_parameter(ATStringCommand.IC, flags, apply=self.is_apply_changes_enabled()) From badd8787f9a401cf9b68788e0622b23002a4fcc1 Mon Sep 17 00:00:00 2001 From: Ruben Moral Date: Wed, 1 Mar 2023 12:27:26 +0100 Subject: [PATCH 09/36] hardware: add new versions to the supported hardware list - 0x58: XBee 3 Cellular Global Cat 4 - 0x59: XBee 3 Cellular North America Cat 4 - 0x5A: XBee XR 900 TH - 0x5B: XBee XR 868 TH https://onedigi.atlassian.net/browse/XBPL-386 Signed-off-by: Ruben Moral --- CHANGELOG.rst | 4 ++++ digi/xbee/firmware.py | 8 ++++++-- digi/xbee/models/hw.py | 10 +++++++--- digi/xbee/models/protocol.py | 10 +++++++--- digi/xbee/recovery.py | 8 ++++++-- 5 files changed, 30 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 2111e42..06d0459 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -10,6 +10,10 @@ v1.4.2 - XX/XX/202X * XBee 3 Cellular North America LTE Cat 1 * XBee 3 Cellular LTE-M/NB-IoT Low Power * XBee RR TH Pro/Non-Pro + * XBee 3 Cellular Global Cat 4 + * XBee 3 Cellular North America Cat 4 + * XBee XR 900 TH + * XBee XR 868 TH * Support to retrieve XBee statistics. * Send/receive explicit data in 802.15.4. (XBee 3 modules support this feature) diff --git a/digi/xbee/firmware.py b/digi/xbee/firmware.py index c629552..8051068 100644 --- a/digi/xbee/firmware.py +++ b/digi/xbee/firmware.py @@ -1,4 +1,4 @@ -# Copyright 2019-2021, Digi International Inc. +# Copyright 2019-2023, Digi International Inc. # # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this @@ -299,7 +299,11 @@ HardwareVersion.XBEE3_SMT.code, HardwareVersion.XBEE3_TH.code, HardwareVersion.XBEE3_RR.code, - HardwareVersion.XBEE3_RR_TH.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) LOCAL_SUPPORTED_HW_VERSIONS = SX_HW_VERSIONS + XBEE3_HW_VERSIONS REMOTE_SUPPORTED_HW_VERSIONS = SX_HW_VERSIONS + XBEE3_HW_VERSIONS + S2C_HW_VERSIONS diff --git a/digi/xbee/models/hw.py b/digi/xbee/models/hw.py index 705dc53..26568ea 100644 --- a/digi/xbee/models/hw.py +++ b/digi/xbee/models/hw.py @@ -1,4 +1,4 @@ -# Copyright 2017-2022, Digi International Inc. +# Copyright 2017-2023, Digi International Inc. # # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this @@ -94,14 +94,18 @@ class HardwareVersion(Enum): CELLULAR_3_LTE_M_ATT = (0x4B, "XBee Cellular 3 LTE-M AT&T") CELLULAR_3_CAT1_LTE_VERIZON = (0x4D, "XBee Cellular 3 Cat 1 LTE Verizon") CELLULAR_3_LTE_M_TELIT = (0x4E, "XBee 3 Cellular LTE-M/NB-IoT (Telit)") - XBEE3_DM_LR = (0x50, "XB3-DMLR") - XBEE3_DM_LR_868 = (0x51, "XB3-DMLR868") + XBEE3_DM_LR = (0x50, "XBee XR 900") + XBEE3_DM_LR_868 = (0x51, "XBee XR 868") XBEE3_RR = (0x52, "XBee RR SMT/MMT, Pro/Non-Pro") S2C_P5 = (0x53, "S2C P5") CELLULAR_3_GLOBAL_LTE_CAT1 = (0x54, "XBee 3 Cellular Global LTE Cat 1") CELLULAR_3_NA_LTE_CAT1 = (0x55, "XBee 3 Cellular North America LTE Cat 1") CELLULAR_3_LTE_M_LOW_POWER = (0x56, "XBee 3 Cellular LTE-M/NB-IoT Low Power") XBEE3_RR_TH = (0x57, "XBee RR TH Pro/Non-Pro") + CELLULAR_3_GLOBAL_CAT4 = (0x58, "XBee 3 Cellular Global Cat 4") + CELLULAR_3_NA_CAT4 = (0x59, "XBee 3 Cellular North America Cat 4") + XBEE_XR_900_TH = (0x5A, "XBee XR 900 TH") + XBEE_XR_868_TH = (0x5B, "XBee XR 868 TH") def __init__(self, code, description): self.__code = code diff --git a/digi/xbee/models/protocol.py b/digi/xbee/models/protocol.py index a90ba66..c2a1f4e 100644 --- a/digi/xbee/models/protocol.py +++ b/digi/xbee/models/protocol.py @@ -1,4 +1,4 @@ -# Copyright 2017-2022, Digi International Inc. +# Copyright 2017-2023, Digi International Inc. # # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this @@ -260,7 +260,9 @@ def determine_protocol(hw_version, fw_version, br_value=None): HardwareVersion.CELLULAR_3_LTE_M_TELIT.code, HardwareVersion.CELLULAR_3_GLOBAL_LTE_CAT1.code, HardwareVersion.CELLULAR_3_NA_LTE_CAT1.code, - HardwareVersion.CELLULAR_3_LTE_M_LOW_POWER.code): + HardwareVersion.CELLULAR_3_LTE_M_LOW_POWER.code, + HardwareVersion.CELLULAR_3_GLOBAL_CAT4.code, + HardwareVersion.CELLULAR_3_NA_CAT4.code): return XBeeProtocol.CELLULAR if hw_version == HardwareVersion.CELLULAR_NBIOT_EUROPE.code: @@ -282,7 +284,9 @@ def determine_protocol(hw_version, fw_version, br_value=None): if br_value != 0 else XBeeProtocol.DIGI_POINT) if hw_version in (HardwareVersion.XBEE3_DM_LR.code, - HardwareVersion.XBEE3_DM_LR_868.code): + HardwareVersion.XBEE3_DM_LR_868.code, + HardwareVersion.XBEE_XR_900_TH.code, + HardwareVersion.XBEE_XR_868_TH.code): return XBeeProtocol.DIGI_MESH if hw_version == HardwareVersion.S2C_P5.code: diff --git a/digi/xbee/recovery.py b/digi/xbee/recovery.py index 0909fd2..8188f21 100644 --- a/digi/xbee/recovery.py +++ b/digi/xbee/recovery.py @@ -1,4 +1,4 @@ -# Copyright 2019-2021, Digi International Inc. +# Copyright 2019-2023, Digi International Inc. # # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this @@ -32,7 +32,11 @@ HardwareVersion.XBEE3_SMT.code, HardwareVersion.XBEE3_TH.code, HardwareVersion.XBEE3_RR.code, - HardwareVersion.XBEE3_RR_TH.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) _BAUDRATE_KEY = "baudrate" _PARITY_KEY = "parity" From 5051a321e2e609f3fb12ecfd30ba2c23f94c497b Mon Sep 17 00:00:00 2001 From: David Escalona Date: Fri, 6 Oct 2023 10:28:58 +0200 Subject: [PATCH 10/36] comm_interface: serial: capture serial exceptions while writing frames Signed-off-by: David Escalona --- digi/xbee/comm_interface.py | 5 ++++- digi/xbee/sender.py | 5 +++-- digi/xbee/serial.py | 22 +++++++++++++++------- 3 files changed, 22 insertions(+), 10 deletions(-) diff --git a/digi/xbee/comm_interface.py b/digi/xbee/comm_interface.py index 838cadc..2e03231 100644 --- a/digi/xbee/comm_interface.py +++ b/digi/xbee/comm_interface.py @@ -1,4 +1,4 @@ -# Copyright 2019-2022, Digi International Inc. +# Copyright 2019-2023, Digi International Inc. # # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this @@ -99,6 +99,9 @@ def write_frame(self, frame): frame (Bytearray): The XBee API frame packet to write. If the bytearray does not correctly represent an XBee frame, the behaviour is undefined. + + Raises: + CommunicationException: If there is any error writing the frame. """ def get_network(self, local_xbee): diff --git a/digi/xbee/sender.py b/digi/xbee/sender.py index 70a973e..5fafa85 100644 --- a/digi/xbee/sender.py +++ b/digi/xbee/sender.py @@ -1,4 +1,4 @@ -# Copyright 2020-2022, Digi International Inc. +# Copyright 2020-2023, Digi International Inc. # # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this @@ -67,7 +67,8 @@ def send_packet(self, packet): InvalidOperatingModeException: If the XBee device's operating mode is not API or ESCAPED API. This method only checks the cached value of the operating mode. - XBeeException: if the XBee device's communication interface is closed. + XBeeException: if the XBee device's communication interface is closed + or there is any error sending the packet. .. seealso:: | :class:`.XBeePacket` diff --git a/digi/xbee/serial.py b/digi/xbee/serial.py index 84f44d1..e606647 100644 --- a/digi/xbee/serial.py +++ b/digi/xbee/serial.py @@ -1,4 +1,4 @@ -# Copyright 2017-2021, Digi International Inc. +# Copyright 2017-2023, Digi International Inc. # # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this @@ -16,10 +16,10 @@ import os import time -from serial import Serial, EIGHTBITS, STOPBITS_ONE, PARITY_NONE +from serial import Serial, EIGHTBITS, STOPBITS_ONE, PARITY_NONE, PortNotOpenError, SerialException -import digi.xbee.exception from digi.xbee.comm_interface import XBeeCommunicationInterface +from digi.xbee.exception import CommunicationException, TimeoutException from digi.xbee.models.atcomm import SpecialByte from digi.xbee.models.mode import OperatingMode from digi.xbee.packets.base import XBeeAPIPacket, XBeePacket @@ -126,8 +126,16 @@ def write_frame(self, frame): frame (Bytearray): The XBee API frame packet to write. If the bytearray does not correctly represent an XBee frame, the behaviour is undefined. + + Raises: + CommunicationException: If there is any error writing the frame. """ - self.write(frame) + try: + self.write(frame) + except PortNotOpenError as error: + raise CommunicationException("Serial port not open") from error + except SerialException as exc: + raise CommunicationException(str(exc)) from exc def read_byte(self): """ @@ -141,7 +149,7 @@ def read_byte(self): """ byte = bytearray(self.read(1)) if len(byte) == 0: - raise digi.xbee.exception.TimeoutException() + raise TimeoutException() return byte[0] @@ -160,7 +168,7 @@ def read_bytes(self, num_bytes): """ read_bytes = bytearray(self.read(num_bytes)) if len(read_bytes) != num_bytes: - raise digi.xbee.exception.TimeoutException() + raise TimeoutException() return read_bytes def __read_next_byte(self, operating_mode=OperatingMode.API_MODE): @@ -263,7 +271,7 @@ def wait_for_frame(self, operating_mode): return XBeeAPIPacket.unescape_data(xbee_packet) return xbee_packet - except digi.xbee.exception.TimeoutException: + except TimeoutException: return None def read_existing(self): From 3bb1ee86a5d3c4c5f1dd820a9ff0ecc463353f83 Mon Sep 17 00:00:00 2001 From: David Escalona Date: Tue, 10 Oct 2023 11:14:47 +0200 Subject: [PATCH 11/36] serial: do not capture 'PortNotOpenError' to maintain backwards compatibility The 'PortNotOpenError' exception was not introduced until PySerial v3.5. This was causing some compatibility issues with software targeting a Pyserial version prior to 3.5. By removing the 'PortNotOpenError' exception capture block, we do not break backwards compatibility with Pyserial. Signed-off-by: David Escalona --- digi/xbee/serial.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/digi/xbee/serial.py b/digi/xbee/serial.py index e606647..78c2ac9 100644 --- a/digi/xbee/serial.py +++ b/digi/xbee/serial.py @@ -16,7 +16,7 @@ import os import time -from serial import Serial, EIGHTBITS, STOPBITS_ONE, PARITY_NONE, PortNotOpenError, SerialException +from serial import Serial, EIGHTBITS, STOPBITS_ONE, PARITY_NONE, SerialException from digi.xbee.comm_interface import XBeeCommunicationInterface from digi.xbee.exception import CommunicationException, TimeoutException @@ -132,8 +132,6 @@ def write_frame(self, frame): """ try: self.write(frame) - except PortNotOpenError as error: - raise CommunicationException("Serial port not open") from error except SerialException as exc: raise CommunicationException(str(exc)) from exc From d27f2dad5024f375b95318a3306b961fa8be03f5 Mon Sep 17 00:00:00 2001 From: Tatiana Leon Date: Wed, 25 Oct 2023 14:58:43 +0200 Subject: [PATCH 12/36] firmware: do not check the region for skip code If firmware XML contains a 'skip' region code, do not check the region code of the device before programming the firmware. https://onedigi.atlassian.net/browse/XBPL-394 Signed-off-by: Tatiana Leon --- digi/xbee/firmware.py | 24 +++++------ digi/xbee/models/protocol.py | 83 ++++++++++++++++++++++++++++++++++++ 2 files changed, 95 insertions(+), 12 deletions(-) diff --git a/digi/xbee/firmware.py b/digi/xbee/firmware.py index 8051068..0d377b7 100644 --- a/digi/xbee/firmware.py +++ b/digi/xbee/firmware.py @@ -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 +from digi.xbee.models.protocol import XBeeProtocol, Role, Region from digi.xbee.models.status import TransmitStatus, ATCommandStatus, \ EmberBootloaderMessageType, ModemStatus, UpdateProgressStatus, NodeUpdateType from digi.xbee.packets.aft import ApiFrameType @@ -243,8 +243,6 @@ _PROGRESS_TASK_UPDATE_REMOTE_FILESYSTEM = "Updating remote XBee filesystem" _PROGRESS_TASK_UPDATE_XBEE = "Updating XBee firmware" -_REGION_ALL = 0 - _REMOTE_FW_UPDATE_DEFAULT_TIMEOUT = 30 # Seconds _TIME_DAYS_1970TO_2000 = 10957 @@ -2454,7 +2452,9 @@ def _parse_xml_firmware_file(self): element = root.find(_XML_REGION_LOCK) if element is None: self._exit_with_error(_ERROR_XML_PARSE % self._xml_fw_file, restore_updater=False) - self._xml_region_lock = int(element.text) + self._xml_region_lock = Region.get(int(element.text)) + if self._xml_region_lock is None: + self._xml_region_lock = Region.ALL _log.debug(" - Region lock: %d", self._xml_region_lock) # Update timeout, optional. element = root.find(_XML_UPDATE_TIMEOUT) @@ -2554,8 +2554,8 @@ def _check_target_compatibility(self): # Check region lock for compatibility numbers greater than 1. if self._target_compat_number and self._target_compat_number > 1 and \ self._target_region_lock is not None: - if (self._target_region_lock != _REGION_ALL - and self._target_region_lock != self._xml_region_lock): + if (not self._target_region_lock.allows_any() + and self._xml_region_lock not in (Region.SKIP, self._target_region_lock)): self._exit_with_error( _ERROR_REGION_LOCK % (self._target_region_lock, self._xml_region_lock), restore_updater=False) @@ -2752,7 +2752,7 @@ def _get_target_region_lock(self): Returns the update target region lock number. Returns: - Integer: Update target region lock number as integer, `None` if it + :class:`.Region`: Update target region lock, `None` if it could not be read. """ @@ -3293,7 +3293,7 @@ def _get_target_region_lock_bootloader(self): Returns the update target region lock number from the bootloader. Returns: - Integer: Update target region lock number as integer read from the + :class:`.Region`: Update target region lock read from the bootloader, `None` if it could not be read. """ @@ -4103,9 +4103,9 @@ def _get_target_region_lock_bootloader(self): # Assume the device is already in bootloader mode. region_info = self._execute_bootloader_cmd(_Gen3BootloaderCmd.REGION_LOCK) if not region_info: - return _REGION_ALL + return Region.ALL - return region_info[0] + return Region.get(region_info[0]) def _get_target_hw_version_bootloader(self): """ @@ -7506,14 +7506,14 @@ def _get_region_lock(xbee): xbee (:class:`.AbstractXBeeDevice`): XBee to read the parameter from. Returns: - Integer: XBee region lock number as integer, `None` if it could not be read. + :class:`.Region`: XBee region lock, `None` if it could not be read. """ region_lock = _get_parameter_with_retries( xbee, ATStringCommand.R_QUESTION, _PARAM_READ_RETRIES) if region_lock is None: return None - return int(region_lock[0]) + return Region.get(int(region_lock[0])) def _get_hw_version(xbee): diff --git a/digi/xbee/models/protocol.py b/digi/xbee/models/protocol.py index c2a1f4e..edacbcb 100644 --- a/digi/xbee/models/protocol.py +++ b/digi/xbee/models/protocol.py @@ -438,3 +438,86 @@ def get(cls, identifier): Role.__doc__ += utils.doc_enum(Role) + + +@unique +class Region(Enum): + """ + Enumerates the available regions for an XBee. + + | Inherited properties: + | **name** (String): the name (id) of this Region. + | **value** (String): the value of this Region. + """ + + ALL = (0, "Any") + USA = (1, "USA") + AUSTRALIA = (2, "Australia") + BRAZIL = (3, "Brazil") + MEXICO = (4, "Mexico") + PERU = (5, "Peru") + NEW_ZEALAND = (6, "New Zealand") + SINGAPORE = (7, "Singapore") + CHILE = (8, "Chile") + FRANCE = (9, "France") + EUROPE = (10, "Europe") + SKIP = (99, "Skip") + UNKNOWN = (999, "Unknown") + ALL2 = (65535, "Any") + + def __init__(self, identifier, description): + self.__id = identifier + self.__desc = description + + def __str__(self): + return "%s (%s)" % (self.__desc, self.__id) + + @property + def id(self): + """ + Gets the identifier of the region. + + Returns: + Integer: the region identifier. + """ + return self.__id + + @property + def description(self): + """ + Gets the description of the region. + + Returns: + String: the region description. + """ + return self.__desc + + @classmethod + def get(cls, identifier): + """ + Returns the Region for the given identifier. + + Args: + identifier (Integer): the id value of the region to get. + + Returns: + :class:`.Region`: the Region with the given identifier. `None` if it + does not exist. + """ + for item in cls: + if identifier == item.id: + return item + + return None + + def allows_any(self): + """ + Returns whether this region accepts any region specified in the firmware. + + Returns: + Boolean: `True` if this region accepts any region `False` otherwise. + """ + return self in (Region.ALL, Region.ALL2) + + +Region.__doc__ += utils.doc_enum(Region) From 612f8843014d5c57db09b011d2aa56399d5bd090 Mon Sep 17 00:00:00 2001 From: Tatiana Leon Date: Fri, 27 Oct 2023 09:54:23 +0200 Subject: [PATCH 13/36] firmware: fix bad format in debug message Signed-off-by: Tatiana Leon --- digi/xbee/firmware.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/digi/xbee/firmware.py b/digi/xbee/firmware.py index 0d377b7..843b579 100644 --- a/digi/xbee/firmware.py +++ b/digi/xbee/firmware.py @@ -167,7 +167,7 @@ _ERROR_RECEIVE_FRAME_TIMEOUT = "Timeout waiting for response" _ERROR_RECOVERY_MODE = "Could not put updater device in recovery mode" _ERROR_READ_OTA_FILE = "Error reading OTA file: %s" -_ERROR_REGION_LOCK = "Device region (%d) differs from the firmware one (%d)" +_ERROR_REGION_LOCK = "Device region (%s) differs from the firmware one (%s)" _ERROR_REMOTE_DEVICE_INVALID = "Invalid remote XBee device" _ERROR_RESTORE_TARGET_CONNECTION = "Could not restore target connection: %s" _ERROR_RESTORE_LOCAL_CONNECTION = "Could not restore local connection: %s" @@ -2455,7 +2455,7 @@ def _parse_xml_firmware_file(self): self._xml_region_lock = Region.get(int(element.text)) if self._xml_region_lock is None: self._xml_region_lock = Region.ALL - _log.debug(" - Region lock: %d", self._xml_region_lock) + _log.debug(" - Region lock: %s", self._xml_region_lock) # Update timeout, optional. element = root.find(_XML_UPDATE_TIMEOUT) if element is not None: From 36a5b637821e6941aa489d3235867b4f44e33eca Mon Sep 17 00:00:00 2001 From: Mike Wadsten Date: Wed, 13 Dec 2023 13:47:54 -0600 Subject: [PATCH 14/36] firmware: fix AttributeError when update_local_firmware is called with a string This finally block assumed that target was always an XBeeDevice, while all of the code above it did the right thing and properly handled the case of it being a str. The "Bootloader not supported" block also assumed target was always an XBeeDevice, so that code has been fixed as well. --- digi/xbee/firmware.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/digi/xbee/firmware.py b/digi/xbee/firmware.py index 843b579..25c2849 100644 --- a/digi/xbee/firmware.py +++ b/digi/xbee/firmware.py @@ -7131,7 +7131,8 @@ def update_local_firmware(target, xml_fw_file, xbee_firmware_file=None, timeout=timeout, progress_cb=progress_callback) else: # Bootloader not supported. - if target._active_update_type == NodeUpdateType.FIRMWARE: + if (isinstance(target, XBeeDevice) + and target._active_update_type == NodeUpdateType.FIRMWARE): target._active_update_type = None _log.error("ERROR: %s", _ERROR_BOOTLOADER_NOT_SUPPORTED) raise FirmwareUpdateException(_ERROR_BOOTLOADER_NOT_SUPPORTED) @@ -7143,7 +7144,8 @@ def update_local_firmware(target, xml_fw_file, xbee_firmware_file=None, msg = "Error: %s" % exc raise exc finally: - finished = (target._active_update_type == NodeUpdateType.FIRMWARE) + finished = (isinstance(target, str) + or target._active_update_type == NodeUpdateType.FIRMWARE) if finished or msg != "Success": update_process._notify_progress(msg, 100, finished=finished) From 054942ce2b182fa79be0b0f1d6d05c2f57d52530 Mon Sep 17 00:00:00 2001 From: Ruben Moral Date: Wed, 22 Nov 2023 13:31:50 +0100 Subject: [PATCH 15/36] filesystem: several fixes in the API file system for Cellular devices - Cellular devices do not have NP setting, so the default block size for them should be 1490. - It's not possible to specify a write offset when uploading secure files, 0xFFFFFFFF (use current position) must be used. - Cellular devices return a file size of 0 bytes when creating a new file. - When uploading files bigger than the block size, the last offset must be used. Otherwise, the file is overwritten at offset 0. Signed-off-by: Ruben Moral --- digi/xbee/filesystem.py | 30 +++++++++++++++++++++++------- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/digi/xbee/filesystem.py b/digi/xbee/filesystem.py index c94ba63..167ae95 100644 --- a/digi/xbee/filesystem.py +++ b/digi/xbee/filesystem.py @@ -1,4 +1,4 @@ -# Copyright 2019-2021, Digi International Inc. +# Copyright 2019-2023, Digi International Inc. # # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this @@ -104,6 +104,7 @@ } _DEFAULT_BLOCK_SIZE = 64 +_DEFAULT_BLOCK_SIZE_CELLULAR = 1490 _TRANSFER_TIMEOUT = 5 # Seconds. @@ -589,6 +590,10 @@ def _start_process(self): self._status, self._fid, self._fsize = self._f_mng.popen_file( f_path, options=self._get_open_flags(), path_id=self._cpid, timeout=self._timeout) + # RF file systems return 0xFFFFFFFF file size for new files, + # while Cellular file systems return 0. + if self._fsize == 0 and (self._get_open_flags() & FileOpenRequestOption.CREATE) > 0: + self._fsize = 0xFFFFFFFF self._opened = bool(self._status == FSCommandStatus.SUCCESS.code) if not self._opened: @@ -646,6 +651,10 @@ def _get_open_flags(self): def _exec_specific_cmd(self): """ Executes the specific file process (read or write). + + Returns: + Boolean: `True` if this was the last command to execute, `False` + otherwise. """ @abstractmethod @@ -923,7 +932,8 @@ def _exec_specific_cmd(self): | :meth:`._FileProcess._exec_specific_cmd` """ self._status = FSCommandStatus.SUCCESS.code - if not self.__data or self.__offset + 1 >= self._fsize: + if not self.__data or (self.__offset != WriteFileCmdRequest.USE_CURRENT_OFFSET + and self.__offset + 1 >= self._fsize): return True last_offset = self.__offset @@ -953,7 +963,8 @@ def _exec_specific_cmd(self): # Recalculate chunk length chunk_len = min(chunk_len, len(self.__data) - data_offset) - self.__offset = last_offset + if self.__offset != WriteFileCmdRequest.USE_CURRENT_OFFSET: + self.__offset += data_offset self.__n_bytes = 0 return False @@ -1444,8 +1455,8 @@ def p_cb(n_bytes, _percent, status): wr_opts = [] if overwrite: wr_opts.append("truncate") - w_proc = self.write_file(dest, offset=0, secure=secure, - options=wr_opts, progress_cb=p_cb) + w_proc = self.write_file(dest, offset=WriteFileCmdRequest.USE_CURRENT_OFFSET, + secure=secure, options=wr_opts, progress_cb=p_cb) try: size = w_proc.block_size data = src_file.read(size) @@ -1459,8 +1470,9 @@ def p_cb(n_bytes, _percent, status): if not overwrite or exc.status != FSCommandStatus.ALREADY_EXISTS.code: raise exc self.remove(dest, rm_children=False) - w_proc = self.write_file(dest, offset=0, secure=secure, - options=wr_opts, progress_cb=p_cb) + w_proc = self.write_file(dest, + offset=WriteFileCmdRequest.USE_CURRENT_OFFSET, + secure=secure, options=wr_opts, progress_cb=p_cb) w_proc.next(data, last=False) data = src_file.read(size) finally: @@ -2407,6 +2419,10 @@ def _get_np(self, refresh=False): """ if self.__np_val and not refresh: return self.__np_val + # Cellular devices do not have NP setting. + if self.__xbee.get_protocol() == XBeeProtocol.CELLULAR: + self.__np_val = _DEFAULT_BLOCK_SIZE_CELLULAR + return self.__np_val xbee = self.__xbee n_extra_bytes = 0 From 9c51c569f5048687ac33d7cd198467b250e56251 Mon Sep 17 00:00:00 2001 From: Tatiana Leon Date: Tue, 7 May 2024 18:55:58 +0200 Subject: [PATCH 16/36] doc: fix some methods signature https://onedigi.atlassian.net/browse/XBPL-424 Signed-off-by: Tatiana Leon --- doc/user_doc/communicating_with_xbee_devices.rst | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/doc/user_doc/communicating_with_xbee_devices.rst b/doc/user_doc/communicating_with_xbee_devices.rst index 8443936..8f69e6c 100644 --- a/doc/user_doc/communicating_with_xbee_devices.rst +++ b/doc/user_doc/communicating_with_xbee_devices.rst @@ -515,9 +515,10 @@ network: +--------------------------------------------------------------------------------------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | Method | Description | +========================================================================================================+=======================================================================================================================================================================================================+ -| **send_expl_data(RemoteXBeeDevice, Integer, Integer, Integer, Integer, String or Bytearray, Integer)** | Specifies remote XBee destination object, four application layer fields (source endpoint, destination endpoint, cluster ID, and profile ID), the data to send, and, optionally, the transmit options. | +| **send_expl_data(RemoteXBeeDevice, String or Bytearray, Integer, Integer, Integer, Integer, Integer)** | Specifies remote XBee destination object, the data to send, four application layer fields (source endpoint, destination endpoint, cluster ID, and profile ID), and, optionally, the transmit options. | +--------------------------------------------------------------------------------------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ + **Send unicast explicit data synchronously** .. code:: python @@ -532,7 +533,7 @@ network: remote = RemoteXBeeDevice(xbee, XBee64BitAddress.from_hex_string("0013A20040XXXXXX")) # Send explicit data using the remote object. - xbee.send_expl_data(remote, 0xA0, 0xA1, 0x1554, 0xC105, "Hello XBee!") + xbee.send_expl_data(remote, "Hello XBee!", 0xA0, 0xA1, 0x1554, 0xC105) [...] @@ -576,7 +577,7 @@ of the network: +--------------------------------------------------------------------------------------------------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | Method | Description | +==============================================================================================================+======================================================================================================================================================================================================+ -| **send_expl_data_async(RemoteXBeeDevice, Integer, Integer, Integer, Integer, String or Bytearray, Integer)** | Specifies remote XBee destination object, four application layer fields (source endpoint, destination endpoint, cluster ID, and profile ID), the data to send and, optionally, the transmit options. | +| **send_expl_data_async(RemoteXBeeDevice, String or Bytearray, Integer, Integer, Integer, Integer, Integer)** | Specifies remote XBee destination object, the data to send, four application layer fields (source endpoint, destination endpoint, cluster ID, and profile ID), and, optionally, the transmit options. | +--------------------------------------------------------------------------------------------------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ **Send unicast explicit data asynchronously** @@ -593,7 +594,7 @@ of the network: remote = RemoteXBeeDevice(xbee, XBee64BitAddress.from_hex_string("0013A20040XXXXXX")) # Send explicit data asynchronously using the remote object. - xbee.send_expl_data_async(remote, 0xA0, 0xA1, 0x1554, 0xC105, "Hello XBee!") + xbee.send_expl_data_async(remote, "Hello XBee!", 0xA0, 0xA1, 0x1554, 0xC105) [...] @@ -629,7 +630,7 @@ data provide the same method to send broadcast explicit data: +------------------------------------------------------------------------------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | Method | Description | +================================================================================================+===========================================================================================================================================================================+ -| **send_expl_data_broadcast(Integer, Integer, Integer, Integer, String or Bytearray, Integer)** | Specifies the four application layer fields (source endpoint, destination endpoint, cluster ID, and profile ID), the data to send, and, optionally, the transmit options. | +| **send_expl_data_broadcast(String or Bytearray, Integer, Integer, Integer, Integer, Integer)** | Specifies the data to send, the four application layer fields (source endpoint, destination endpoint, cluster ID, and profile ID), and, optionally, the transmit options. | +------------------------------------------------------------------------------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ **Send broadcast data** @@ -643,7 +644,7 @@ data provide the same method to send broadcast explicit data: xbee.open() # Send broadcast data. - xbee.send_expl_data_broadcast(0xA0, 0xA1, 0x1554, 0xC105, "Hello XBees!") + xbee.send_expl_data_broadcast("Hello XBees!", 0xA0, 0xA1, 0x1554, 0xC105) [...] From d2c8a4a0bcf431c6bd4956b2e332204e48688adc Mon Sep 17 00:00:00 2001 From: Mike Wadsten Date: Thu, 2 May 2024 16:24:58 -0500 Subject: [PATCH 17/36] firmware: add support for XBee XR modules * The ATVH command on XR returns the value differently than other XBee 3 or RR devices. * _XBEE3_BOOTLOADER_FILE_PREFIX was missing an entry for XR. * Profile code now will treat a .gbl file as a possible OTA firmware image. https://onedigi.atlassian.net/browse/LCG-631 --- CHANGELOG.rst | 5 +- digi/xbee/firmware.py | 91 ++++++++++++++++++++++++-------- digi/xbee/models/protocol.py | 14 ++++- digi/xbee/profile.py | 4 +- doc/user_doc/update_the_xbee.rst | 17 +++--- 5 files changed, 98 insertions(+), 33 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 06d0459..c603093 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -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) @@ -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 diff --git a/digi/xbee/firmware.py b/digi/xbee/firmware.py index 25c2849..259138e 100644 --- a/digi/xbee/firmware.py +++ b/digi/xbee/firmware.py @@ -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 @@ -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 @@ -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__) @@ -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): @@ -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 @@ -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): @@ -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 @@ -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. @@ -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. @@ -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): """ @@ -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 @@ -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: @@ -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, @@ -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: @@ -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) @@ -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 diff --git a/digi/xbee/models/protocol.py b/digi/xbee/models/protocol.py index edacbcb..b3c5913 100644 --- a/digi/xbee/models/protocol.py +++ b/digi/xbee/models/protocol.py @@ -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 @@ -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() diff --git a/digi/xbee/profile.py b/digi/xbee/profile.py index 6043b7a..0549f90 100644 --- a/digi/xbee/profile.py +++ b/digi/xbee/profile.py @@ -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" diff --git a/doc/user_doc/update_the_xbee.rst b/doc/user_doc/update_the_xbee.rst index 1cc1b0b..3b8851c 100644 --- a/doc/user_doc/update_the_xbee.rst +++ b/doc/user_doc/update_the_xbee.rst @@ -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: @@ -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: @@ -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. +------------------------------------------------------------------------------------------------------------------------------------------------------+ @@ -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: @@ -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: @@ -641,8 +646,8 @@ Applying a profile to a local XBee requires the following components: Use `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: @@ -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: From f95e0fa5c0a220009654126454199d56ff1a0736 Mon Sep 17 00:00:00 2001 From: Mike Wadsten Date: Thu, 2 May 2024 16:26:46 -0500 Subject: [PATCH 18/36] xmodem: Handle first-chunk delay when sending firmware into XR bootloader This change will also apply to XBee RR, XBee 3 BLU and XBee 3 Cellular. https://onedigi.atlassian.net/browse/LCG-631 --- digi/xbee/util/xmodem.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/digi/xbee/util/xmodem.py b/digi/xbee/util/xmodem.py index a14f8ca..dd6bf01 100644 --- a/digi/xbee/util/xmodem.py +++ b/digi/xbee/util/xmodem.py @@ -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 @@ -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 From 6c0ed9f2ff6b46499670466e0f1ac1fc6fa4f9cc Mon Sep 17 00:00:00 2001 From: Tatiana Leon Date: Mon, 8 Jul 2024 14:18:04 +0200 Subject: [PATCH 19/36] srp: remove 'pysrp' dependency 'pysrp' library includes 2 modules to implement SRP: * _ctsrp: Using OS libraries to implement SRP. * _pysrp: Pure Python implementation of SRP. The first option is always '_ctsrp'. If there is any problem loading the required libraries or looking for required methods inside, '_pysrp' is loaded. This last module, '_pysrp' has a bug in the generation of the verifier for certain combination of username and password (for example, user=apiservice, password=1257c) See https://github.com/cocagne/pysrp/issues/55. This commit removes the dependency to 'pysrp' to avoid this bug and includes the required implementation to calculate a salt and the corresponding verifier to configure XBee Bluetooth password. Signed-off-by: Tatiana Leon --- CHANGELOG.rst | 9 + digi/xbee/devices.py | 12 +- digi/xbee/util/srp.py | 410 +++++++++++++++++++++++++++++++++ doc/api/digi.xbee.util.rst | 1 + doc/api/digi.xbee.util.srp.rst | 7 + doc/faq.rst | 13 -- doc/index.rst | 1 - requirements.txt | 1 - setup.py | 2 - 9 files changed, 432 insertions(+), 24 deletions(-) create mode 100644 digi/xbee/util/srp.py create mode 100644 doc/api/digi.xbee.util.srp.rst diff --git a/CHANGELOG.rst b/CHANGELOG.rst index c603093..a26fad8 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -18,9 +18,18 @@ v1.4.2 - XX/XX/202X * 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. +* Remove 'pysrp' dependency: + + * The library includes support to generate salt and verifier required to + configure '$S', '$V', '$W', '$X', and '$Y' XBee parameters to establish + Bluetooth password. * Bug fixing: * Fix order of nodes when creating a Zigbee source route (#278) + * Salt/verifier generation using 'pysrp' was not working with certain + passwords (see https://github.com/cocagne/pysrp/issues/55) + Solved by removing 'pysrp' dependency and implementing the code to + generate them. v1.4.1 - 12/22/2021 ------------------- diff --git a/digi/xbee/devices.py b/digi/xbee/devices.py index 57615bb..10398a7 100644 --- a/digi/xbee/devices.py +++ b/digi/xbee/devices.py @@ -1,4 +1,4 @@ -# Copyright 2017-2022, Digi International Inc. +# Copyright 2017-2024, Digi International Inc. # # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this @@ -1780,12 +1780,10 @@ def update_bluetooth_password(self, new_password, apply=True, save=True): if not isinstance(new_password, (str, bytes, bytearray)): raise ValueError("Password must be a string, bytes, or bytearray") - import srp - - # Generate the salt and verifier using the SRP library. - salt, verifier = srp.create_salted_verification_key( - self._BLE_API_USERNAME, new_password, hash_alg=srp.SHA256, - ng_type=srp.NG_1024, salt_len=4) + import digi.xbee.util.srp + salt, verifier = digi.xbee.util.srp.create_salted_verification_key( + self._BLE_API_USERNAME, new_password, hash_alg=digi.xbee.util.srp.HAType.SHA256, + ng_type=digi.xbee.util.srp.NgGroupParams.NG_1024, salt_len=4) self.update_bluetooth_salt_verifier(salt, verifier, apply=apply, save=save) diff --git a/digi/xbee/util/srp.py b/digi/xbee/util/srp.py new file mode 100644 index 0000000..499fb05 --- /dev/null +++ b/digi/xbee/util/srp.py @@ -0,0 +1,410 @@ +# Copyright 2024, Digi International Inc. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +import os + +import hashlib + +from enum import unique, Enum + +from digi.xbee.util import utils + +@unique +class HAType(Enum): + """ + This class lists the available hash algorithms. + + | Inherited properties: + | **name** (String): the name (id) of the HAType. + | **value** (String): the value of the HAType. + """ + SHA1 = (0, hashlib.sha1) + SHA224 = (1, hashlib.sha224) + SHA256 = (2, hashlib.sha256) + SHA384 = (3, hashlib.sha384) + SHA512 = (4, hashlib.sha512) + + def __init__(self, code, alg): + self.__code = code + self.__alg = alg + + @property + def code(self): + """ + Returns the code of the HAType element. + + Returns: + Integer: the code of the HAType element. + """ + return self.__code + + @property + def alg(self): + """ + Returns the algorithm of the HAType element. + + Returns: + String: the algorithm of the HAType element. + """ + return self.__alg + + @classmethod + def get(cls, code): + """ + Returns the hash algorithm type for the given code. + + Args: + code (Integer): the code of the hash algorithm to get. + + Returns: + :class:`.HAType`: the type with the given code, `None` if not found. + """ + for hs_type in cls: + if code == hs_type.code: + return hs_type + return None + + +HAType.__doc__ += utils.doc_enum(HAType) + +@unique +class NgGroupParams(Enum): + """ + This class lists the available group parameters (N, g) algorithms. + + | Inherited properties: + | **name** (String): the name (id) of the HAType. + | **value** (String): the value of the HAType. + """ + NG_1024 = (0, '''\ +EEAF0AB9ADB38DD69C33F80AFA8FC5E86072618775FF3C0B9EA2314C\ +9C256576D674DF7496EA81D3383B4813D692C6E0E0D5D8E250B98BE4\ +8E495C1D6089DAD15DC7D7B46154D6B6CE8EF4AD69B15D4982559B29\ +7BCF1885C529F566660E57EC68EDBC3C05726CC02FD4CBF4976EAA9A\ +FD5138FE8376435B9FC61D2FC0EB06E3\ +''', + '0x2') + NG_1536 = (1, '''\ +9DEF3CAFB939277AB1F12A8617A47BBBDBA51DF499AC4C80BEEEA961\ +4B19CC4D5F4F5F556E27CBDE51C6A94BE4607A291558903BA0D0F843\ +80B655BB9A22E8DCDF028A7CEC67F0D08134B1C8B97989149B609E0B\ +E3BAB63D47548381DBC5B1FC764E3F4B53DD9DA1158BFD3E2B9C8CF5\ +6EDF019539349627DB2FD53D24B7C48665772E437D6C7F8CE442734A\ +F7CCB7AE837C264AE3A9BEB87F8A2FE9B8B5292E5A021FFF5E91479E\ +8CE7A28C2442C6F315180F93499A234DCF76E3FED135F9BB\ + ''', + '0x2') + NG_2048 = (1, '''\ +AC6BDB41324A9A9BF166DE5E1389582FAF72B6651987EE07FC319294\ +3DB56050A37329CBB4A099ED8193E0757767A13DD52312AB4B03310D\ +CD7F48A9DA04FD50E8083969EDB767B0CF6095179A163AB3661A05FB\ +D5FAAAE82918A9962F0B93B855F97993EC975EEAA80D740ADBF4FF74\ +7359D041D5C33EA71D281E446B14773BCA97B43A23FB801676BD207A\ +436C6481F1D2B9078717461A5B9D32E688F87748544523B524B0D57D\ +5EA77A2775D2ECFA032CFBDBF52FB3786160279004E57AE6AF874E73\ +03CE53299CCC041C7BC308D82A5698F3A8D0C38271AE35F8E9DBFBB6\ +94B5C803D89F7AE435DE236D525F54759B65E372FCD68EF20FA7111F\ +9E4AFF73\ +''', + '0x2') + NG_3072 = (2, '''\ +FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E08\ +8A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B\ +302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9\ +A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE6\ +49286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8\ +FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D\ +670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C\ +180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF695581718\ +3995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D\ +04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7D\ +B3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D226\ +1AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200C\ +BBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFC\ +E0FD108E4B82D120A93AD2CAFFFFFFFFFFFFFFFF\ +''', + '0x5') + NG_4096 = (2, '''\ +FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E08\ +8A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B\ +302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9\ +A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE6\ +49286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8\ +FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D\ +670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C\ +180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF695581718\ +3995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D\ +04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7D\ +B3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D226\ +1AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200C\ +BBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFC\ +E0FD108E4B82D120A92108011A723C12A787E6D788719A10BDBA5B26\ +99C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8DBBBC2DB\ +04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2\ +233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127\ +D5B05AA993B4EA988D8FDDC186FFB7DC90A6C08F4DF435C934063199\ +FFFFFFFFFFFFFFFF\ +''', + '0x5') + NG_6144 = (3, '''\ +FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E08\ +8A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B\ +302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9\ +A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE6\ +49286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8\ +FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D\ +670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C\ +180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF695581718\ +3995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D\ +04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7D\ +B3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D226\ +1AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200C\ +BBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFC\ +E0FD108E4B82D120A92108011A723C12A787E6D788719A10BDBA5B26\ +99C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8DBBBC2DB\ +04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2\ +233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127\ +D5B05AA993B4EA988D8FDDC186FFB7DC90A6C08F4DF435C934028492\ +36C3FAB4D27C7026C1D4DCB2602646DEC9751E763DBA37BDF8FF9406\ +AD9E530EE5DB382F413001AEB06A53ED9027D831179727B0865A8918\ +DA3EDBEBCF9B14ED44CE6CBACED4BB1BDB7F1447E6CC254B33205151\ +2BD7AF426FB8F401378CD2BF5983CA01C64B92ECF032EA15D1721D03\ +F482D7CE6E74FEF6D55E702F46980C82B5A84031900B1C9E59E7C97F\ +BEC7E8F323A97A7E36CC88BE0F1D45B7FF585AC54BD407B22B4154AA\ +CC8F6D7EBF48E1D814CC5ED20F8037E0A79715EEF29BE32806A1D58B\ +B7C5DA76F550AA3D8A1FBFF0EB19CCB1A313D55CDA56C9EC2EF29632\ +387FE8D76E3C0468043E8F663F4860EE12BF2D5B0B7474D6E694F91E\ +6DCC4024FFFFFFFFFFFFFFFF\ +''', + '0x5') + NG_8192 = (3, '''\ +FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E08\ +8A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B\ +302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9\ +A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE6\ +49286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8\ +FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D\ +670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C\ +180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF695581718\ +3995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D\ +04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7D\ +B3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D226\ +1AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200C\ +BBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFC\ +E0FD108E4B82D120A92108011A723C12A787E6D788719A10BDBA5B26\ +99C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8DBBBC2DB\ +04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2\ +233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127\ +D5B05AA993B4EA988D8FDDC186FFB7DC90A6C08F4DF435C934028492\ +36C3FAB4D27C7026C1D4DCB2602646DEC9751E763DBA37BDF8FF9406\ +AD9E530EE5DB382F413001AEB06A53ED9027D831179727B0865A8918\ +DA3EDBEBCF9B14ED44CE6CBACED4BB1BDB7F1447E6CC254B33205151\ +2BD7AF426FB8F401378CD2BF5983CA01C64B92ECF032EA15D1721D03\ +F482D7CE6E74FEF6D55E702F46980C82B5A84031900B1C9E59E7C97F\ +BEC7E8F323A97A7E36CC88BE0F1D45B7FF585AC54BD407B22B4154AA\ +CC8F6D7EBF48E1D814CC5ED20F8037E0A79715EEF29BE32806A1D58B\ +B7C5DA76F550AA3D8A1FBFF0EB19CCB1A313D55CDA56C9EC2EF29632\ +387FE8D76E3C0468043E8F663F4860EE12BF2D5B0B7474D6E694F91E\ +6DBE115974A3926F12FEE5E438777CB6A932DF8CD8BEC4D073B931BA\ +3BC832B68D9DD300741FA7BF8AFC47ED2576F6936BA424663AAB639C\ +5AE4F5683423B4742BF1C978238F16CBE39D652DE3FDB8BEFC848AD9\ +22222E04A4037C0713EB57A81A23F0C73473FC646CEA306B4BCBC886\ +2F8385DDFA9D4B7FA2C087E879683303ED5BDD3A062B3CF5B3A278A6\ +6D2A13F83F44F82DDF310EE074AB6A364597E899A0255DC164F31CC5\ +0846851DF9AB48195DED7EA1B1D510BD7EE74D73FAF36BC31ECFA268\ +359046F4EB879F924009438B481C6CD7889A002ED5EE382BC9190DA6\ +FC026E479558E4475677E9AA9E3050E2765694DFC81F56E880B96E71\ +60C980DD98EDD3DFFFFFFFFFFFFFFFFF\ +''', + '0x13') + NG_CUSTOM = (9999, "", -1) + + def __init__(self, code, n, g): + self.__code = code + self.__n = n + self.__g = g + + @property + def code(self): + """ + Returns the code of the NgGroupParams element. + + Returns: + Integer: the code of the NgGroupParams element. + """ + return self.__code + + @property + def n(self): + """ + Returns N of the NgGroupParams element. + + Returns: + String: N of the NgGroupParams element. + """ + return self.__n + + @property + def g(self): + """ + Returns g of the NgGroupParams element. + + Returns: + String: g of the NgGroupParams element. + """ + return self.__g + + @classmethod + def get(cls, code): + """ + Returns the group parameters for the given code. + + Args: + code (Integer): the code of the group parameters to get. + + Returns: + :class:`.NgGroupParams`: the parameters with the given code, `None` if not found. + """ + for group in cls: + if code == group.code: + return group + return None + + +NgGroupParams.__doc__ += utils.doc_enum(NgGroupParams) + + +def create_salted_verification_key(user, password, hash_alg=HAType.SHA256, + ng_type=NgGroupParams.NG_1024, salt_len=4): + """ + Generates a salted verification key for the provided username and password. + + Params: + user (String): Username string. + password (String): Plain text password. + hash_alg (:class:`.HAType`, optional, default=`HAType.SHA256`): Hash algorithm. + ng_type (:class:`.NgGroupParams`, optional, default=`NgGroupParams.NG_1024`): + Prime generator type. + salt_len (Integer, optional, default=`4`): Number of bytes of generated salt. + + Returns: + Tuple (bytes, bytes): Tuple with salt and verifier. + """ + s = generate_salt(l=salt_len) + v = generate_verifier(user, password, hash_alg=hash_alg, ng_type=ng_type, + salt=s, sep=":") + + return s, v + + +def generate_salt(l=4): + """ + Generates new salt. + + Params: + l (Integer, optional, default=`4`): Number of bytes. + + Returns: + Bytes: The generated salt. + """ + return os.urandom(l) + + +def generate_verifier(user, password, salt, hash_alg=HAType.SHA256, + ng_type=NgGroupParams.NG_1024, sep=":"): + """ + Calculates a verifier for the provided salt and configured password. + + Params: + user (String): Username string. + password (String): Plain text password. + salt (bytes): Salt to generate a verifier. + hash_alg (:class:`.HAType`, optional, default=`HAType.SHA256`): Hash algorithm. + ng_type (:class:`.NgGroupParams`, optional, default=`NgGroupParams.NG_1024`): + Prime generator type. + sep (String, optional, default= `:`): Separator string. + + Returns: + Bytes: The generated verifier. + """ + x = __calculate_x(user, password, salt, hash_alg=hash_alg, sep=sep) + n_val = int(ng_type.n, 16) # N + g_val = int(ng_type.g, 16) # g + + # v = g^x + v = pow(g_val, x, n_val) + + return bytes(utils.int_to_bytes(v)) + + +def __calculate_x(user, password, salt, hash_alg=HAType.SHA256, sep=":"): + """ + Calculates the user secret parameter. + + Params: + user (String): Username string. + password (String): Plain text password. + salt (bytes): Salt byte array. + hash_alg (:class:`.HAType`, optional, default=`HAType.SHA256`): Hash algorithm. + sep (String, optional, default= `:`): Separator string. + + Returns: + Integer: The user secret value. + """ + if None in (user, password, salt): + raise ValueError("User, password, and salt must be specified") + + # x = H(s | H (I | ":" | p)) + h_up = __hash(hash_alg, user, sep, password) + + return int.from_bytes( + __hash(hash_alg, utils.bytes_to_int(salt), h_up), "big") + + +def __hash(hash_alg, *args): + """ + Calculates the hash of the provided arguments. + + Params: + args: Variable argument list of object to use for hash. + + Returns: + Bytes: The calculated hash. + """ + hash_obj = hash_alg.alg() + for i in args: + hash_obj.update(__to_bytes(i)) + + return hash_obj.digest() + + +def __to_bytes(obj): + """ + Converts object to byte array, with optional context. + + Params: + obj: Object to convert. + + Returns: + Bytes: Bytes representation of object. + """ + if isinstance(obj, bytes): + return obj + if isinstance(obj, bytearray): + return bytes(obj) + if isinstance(obj, int): + return bytes(utils.int_to_bytes(obj)) + if isinstance(obj, str): + return bytes(obj, 'utf-8') + return None diff --git a/doc/api/digi.xbee.util.rst b/doc/api/digi.xbee.util.rst index 3b43b4c..7ff95aa 100644 --- a/doc/api/digi.xbee.util.rst +++ b/doc/api/digi.xbee.util.rst @@ -12,5 +12,6 @@ Submodules .. toctree:: digi.xbee.util.exportutils + digi.xbee.util.srp digi.xbee.util.utils digi.xbee.util.xmodem diff --git a/doc/api/digi.xbee.util.srp.rst b/doc/api/digi.xbee.util.srp.rst new file mode 100644 index 0000000..5e18ec7 --- /dev/null +++ b/doc/api/digi.xbee.util.srp.rst @@ -0,0 +1,7 @@ +digi\.xbee\.util\.srp module +=============================== + +.. automodule:: digi.xbee.util.srp + :members: + :inherited-members: + :show-inheritance: diff --git a/doc/faq.rst b/doc/faq.rst index aa6d852..85daa9a 100644 --- a/doc/faq.rst +++ b/doc/faq.rst @@ -55,16 +55,3 @@ You can install PySerial running this command in your terminal application: For further information about the installation of PySerial, refer to the `PySerial installation guide `_. - - -I get the Python error ``ImportError: No module named 'srp'`` -------------------------------------------------------------- - -This error means that Python cannot find the ``srp`` module, which is used by -the library to authenticate with XBee devices over Bluetooth Low Energy. - -You can install SRP running this command in your terminal application: - -.. code:: - - $ pip install srp diff --git a/doc/index.rst b/doc/index.rst index 35e184b..82a2b99 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -55,7 +55,6 @@ properly: the `PySerial installation guide `_ for further information about getting PySerial. -* **SRP** Install it with pip (``pip install srp``). Contents diff --git a/requirements.txt b/requirements.txt index 9e8c9f9..227e919 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1 @@ pyserial>=3 -srp \ No newline at end of file diff --git a/setup.py b/setup.py index 9a255b3..d0ced24 100644 --- a/setup.py +++ b/setup.py @@ -12,7 +12,6 @@ DEPENDENCIES = ( 'pyserial>=3', - 'srp', ) # 'setup.py build' shortcut. @@ -56,7 +55,6 @@ 'pyserial>=3', ], extras_require={ - "SRP": ["srp"], }, classifiers=[ 'Development Status :: 5 - Production/Stable', From 187afbfa215541f7c99efce82c9c1fcb7385f95b Mon Sep 17 00:00:00 2001 From: Scott Kilau Date: Wed, 31 Jan 2024 14:26:45 -0600 Subject: [PATCH 20/36] Add support for the new XBee BLU device/product. This product does not support the XBee protocol, but instead, only supports Bluetooth/BLE. The XBee BLU currently adds extra support over the existing Bluetooth support, by adding a few new API packet/frames. These frames are: Bluetooth GAP Scan Request - 0x34 Bluetooth GAP Scan Legacy Advertisement Response - 0xB4 Bluetooth GAP Scan Extended Advertisement Response - 0xB7 Bluetooth GAP Scan Status - 0xB5 In addition to creating a new device type called BluDevice that supports these new calls, there have also been a couple new functional tests to verify the support, along with a new Demo/Example application that will initiate a BLE GAP scan, and display all devices that are broadcasting BLE advertisements. --- CHANGELOG.rst | 3 + digi/xbee/ble.py | 231 ++++ digi/xbee/devices.py | 349 +++++ digi/xbee/filesystem.py | 6 +- digi/xbee/firmware.py | 8 +- digi/xbee/models/address.py | 174 ++- digi/xbee/models/hw.py | 4 +- digi/xbee/models/message.py | 396 +++++- digi/xbee/models/protocol.py | 7 +- digi/xbee/models/status.py | 122 +- digi/xbee/packets/aft.py | 8 +- digi/xbee/packets/base.py | 9 +- digi/xbee/packets/bluetooth.py | 1220 +++++++++++++++++ digi/xbee/packets/factory.py | 14 + digi/xbee/reader.py | 153 ++- digi/xbee/recovery.py | 4 +- doc/api/digi.xbee.ble.rst | 7 + doc/api/digi.xbee.packets.bluetooth.rst | 7 + doc/api/digi.xbee.packets.rst | 1 + doc/api/digi.xbee.rst | 1 + doc/examples.rst | 13 + ...tting_started_with_xbee_python_library.rst | 89 +- doc/user_doc/working_with_xbee_classes.rst | 7 +- .../GapScanCursesDemo/GapScanCursesDemo.py | 183 +++ .../bluetooth/GapScanCursesDemo/readme.txt | 80 ++ .../GapScanExample/GapScanExample.py | 75 + .../bluetooth/GapScanExample/readme.txt | 55 + functional_tests/Bluetooth/device_info.py | 57 + functional_tests/Bluetooth/test_gap_scan.py | 416 ++++++ 29 files changed, 3670 insertions(+), 29 deletions(-) create mode 100644 digi/xbee/ble.py create mode 100644 digi/xbee/packets/bluetooth.py create mode 100644 doc/api/digi.xbee.ble.rst create mode 100644 doc/api/digi.xbee.packets.bluetooth.rst create mode 100644 examples/communication/bluetooth/GapScanCursesDemo/GapScanCursesDemo.py create mode 100644 examples/communication/bluetooth/GapScanCursesDemo/readme.txt create mode 100644 examples/communication/bluetooth/GapScanExample/GapScanExample.py create mode 100644 examples/communication/bluetooth/GapScanExample/readme.txt create mode 100644 functional_tests/Bluetooth/device_info.py create mode 100644 functional_tests/Bluetooth/test_gap_scan.py diff --git a/CHANGELOG.rst b/CHANGELOG.rst index a26fad8..70d9a17 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -14,6 +14,7 @@ v1.4.2 - XX/XX/202X * XBee 3 Cellular North America Cat 4 * XBee XR 900 TH * XBee XR 868 TH + * XBee BLU * Support to retrieve XBee statistics. * Send/receive explicit data in 802.15.4. (XBee 3 modules support this feature) @@ -23,6 +24,8 @@ v1.4.2 - XX/XX/202X * The library includes support to generate salt and verifier required to configure '$S', '$V', '$W', '$X', and '$Y' XBee parameters to establish Bluetooth password. +* Support for sending BLE Generic Access Profile (GAP) scans. + (Only XBee BLU modules support this feature) * Bug fixing: * Fix order of nodes when creating a Zigbee source route (#278) diff --git a/digi/xbee/ble.py b/digi/xbee/ble.py new file mode 100644 index 0000000..bc677f4 --- /dev/null +++ b/digi/xbee/ble.py @@ -0,0 +1,231 @@ +# Copyright 2024, Digi International Inc. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +from digi.xbee.packets.bluetooth import BluetoothGAPScanRequestPacket +from digi.xbee.models.status import BLEGAPScanStatus + + +class BLEManager: + """ + Helper class used to manage the BLE Interface on the XBee. + + NOTE: For more information about Shortened and Local Names + when running a GAP scan, see: + 8: raise ValueError("Address cannot contain more than 8 bytes") - self.__address = bytearray(8) - diff = 8 - len(address) - for i in range(diff): - self.__address[i] = 0 - for i in range(diff, 8): - self.__address[i] = address[i - diff] + self.__address = bytearray(address.rjust(8, b'\x00')) @classmethod def from_hex_string(cls, address): @@ -617,3 +612,168 @@ def __eq__(self, other): return False return self.address == other.address + + +class XBeeBLEAddress: + """ + This class represents a 48-bit address (also known as MAC address). + + The 48-bit address is a unique device address assigned during + manufacturing. + This address is unique to each physical device. + """ + + PATTERN = "^(0[xX])?[0-9a-fA-F]{1,12}$" + """ + 48-bit address string pattern. + """ + + __REGEXP = re.compile(PATTERN) + + def __init__(self, address): + """ + Class constructor. Instantiates a new :class:`.XBeeBLEAddress` object + with the provided parameters. + + Args: + address (Bytearray): the XBee BLE 48-bit address as byte array. + + Raise: + ValueError: if `address` is `None` or its length less than 1 + or greater than 6. + """ + if not address: + raise ValueError("Address must contain at least 1 byte") + if len(address) > 6: + raise ValueError("Address cannot contain more than 6 bytes") + + self.__address = bytearray(address.rjust(6, b'\x00')) + + @classmethod + def from_hex_string(cls, address): + """ + Class constructor. Instantiates a new :class:`.XBeeBLEAddress` + object from the provided hex string. + + Args: + address (String): The XBee BLE 48-bit address as a string. + + Raises: + ValueError: if the address' length is less than 1 or does not match + with the pattern: `(0[xX])?[0-9a-fA-F]{1,12}`. + """ + if not address: + raise ValueError("Address must contain at least 1 byte") + if not cls.__REGEXP.match(address): + raise ValueError("Address must follow this pattern: " + + cls.PATTERN) + + return cls(utils.hex_string_to_bytes(address)) + + @classmethod + def from_bytes(cls, *args): + """ + Class constructor. Instantiates a new :class:`.XBeeBLEAddress` + object from the provided bytes. + + Args: + args (6 Integers): 6 integers that represent the bytes 1 to 6 of + this XBeeBLEAddress. + + Raises: + ValueError: if the amount of arguments is not 6 or if any of the + arguments is not between 0 and 255. + """ + if len(args) != 6: + raise ValueError("Number of bytes given as arguments must be 6.") + for i, val in enumerate(args): + if val > 255 or val < 0: + raise ValueError("Byte " + str(i + 1) + + " must be between 0 and 255") + + return cls(bytearray(args)) + + @classmethod + def is_valid(cls, address): + """ + Checks if the provided hex string is a valid BLE 48-bit address. + + Args: + address (String, Bytearray, or :class:`.XBeeBLEAddress`): + String: String with the address only with hex digits without + blanks. Minimum 1 character, maximum 12 (48-bit). + Bytearray: Address as byte array. Must be 1-6 digits. + + Returns + Boolean: `True` for a valid BLE 48-bit address, `False` otherwise. + """ + if isinstance(address, XBeeBLEAddress): + return True + + if isinstance(address, bytearray): + return 1 <= len(address) <= 6 + + if isinstance(address, str): + return bool(cls.__REGEXP.match(address)) + + return False + + def __str__(self): + """ + Called by the str() built-in function and by the print statement to + compute the "informal" string representation of an object. This differs + from __repr__() in that it does not have to be a valid Python + expression: a more convenient or concise representation may be + used instead. + + Returns: + String: "informal" representation of this XBeeBLEAddress. + """ + return utils.hex_to_string(self.__address, pretty=False) + + def __hash__(self): + """ + Returns a hash code value for the object. + + Returns: + Integer: hash code value for the object. + """ + res = 23 + for byte in self.__address: + res = 31 * (res + byte) + return res + + def __eq__(self, other): + """ + Operator == + + Args: + other: another XBeeBLEAddress. + + Returns: + Boolean: `True` if self and other have the same value and type, + `False` in other case. + """ + if not isinstance(other, XBeeBLEAddress): + return False + + return self.address == other.address + + def __iter__(self): + """ + Gets an iterator class of this instance address. + + Returns: + Iterator: iterator of this address. + """ + return self.__address.__iter__() + + @property + def address(self): + """ + Returns a bytearray representation of this XBeeBLEAddress. + + Returns: + Bytearray: bytearray representation of this XBeeBLEAddress. + """ + return bytearray(self.__address) diff --git a/digi/xbee/models/hw.py b/digi/xbee/models/hw.py index 26568ea..a84b287 100644 --- a/digi/xbee/models/hw.py +++ b/digi/xbee/models/hw.py @@ -1,4 +1,4 @@ -# Copyright 2017-2023, Digi International Inc. +# Copyright 2017-2024, Digi International Inc. # # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this @@ -106,6 +106,8 @@ class HardwareVersion(Enum): CELLULAR_3_NA_CAT4 = (0x59, "XBee 3 Cellular North America Cat 4") XBEE_XR_900_TH = (0x5A, "XBee XR 900 TH") XBEE_XR_868_TH = (0x5B, "XBee XR 868 TH") + XBEE_BLU = (0x5C, "XBee BLU") + XBEE_BLU_TH = (0x5D, "XBee BLU TH") def __init__(self, code, description): self.__code = code diff --git a/digi/xbee/models/message.py b/digi/xbee/models/message.py index ba15a92..21c5378 100644 --- a/digi/xbee/models/message.py +++ b/digi/xbee/models/message.py @@ -1,4 +1,4 @@ -# Copyright 2017-2021, Digi International Inc. +# Copyright 2017-2024, Digi International Inc. # # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this @@ -13,6 +13,7 @@ # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. import re +from digi.xbee.models.status import BLEMACAddressType, BLEGAPScanStatus class XBeeMessage: @@ -429,3 +430,396 @@ def to_dict(self): """ return {"XBee local interface: ": self.__local_iface, "Data: ": self.__data} + + +class _BLEGAPScanBaseAdvertisementMessage: + """ + This class represents a base BLE advertising message. + It will contain a common set of values that all BLE advertising + messages will have. + This includes: + The address that the message was received from. + The type of address it is. + Whether the device is connectable or not. + The RSSI of the advertisement. + The Local/Short name embedded into the payload, if any. + """ + + def __init__(self, address, address_type, advertisement_flags, rssi, + payload): + """ + Class constructor. + + Args: + address (:class:`.XBeeBLEAddress`): The BLE address the message + comes from. + address_type (Integer): The type of BLE address. + advertisement_flags (Integer): Flags. Includes whether + connectable or not. + rssi (Float): The received signal strength of the advertisement, + in dBm. + payload (Bytearray): The data payload of the advertisement. + + Raises: + ValueError: if `address` is `None`. + ValueError: if `address_type` is `None`. + ValueError: if `advertisement_flags` is `None`. + ValueError: if `rssi` is `None`. + ValueError: if `payload` is `None`. + """ + if address is None: + raise ValueError("BLE address cannot be None") + if address_type is None: + raise ValueError("BLE address type cannot be None") + if advertisement_flags is None: + raise ValueError("BLE advertisement flags cannot be None") + if rssi is None: + raise ValueError("RSSI cannot be None") + if payload is None: + raise ValueError("Payload cannot be None") + + self.__address = address + # Address type comes in as Integer, convert to BLEMACAddressType + self.__address_type = BLEMACAddressType.get(address_type) + self.__connectable = bool(advertisement_flags & 0x1) + self.__rssi = -float(rssi) + self.__payload = payload + self.__name = None + + # Attempt to find a Local or Short name, if it exists. + ltv = self.__find_advertising_str() + if ltv >= 0: + # Found a Local/Short name LTV (Length-Type-Value) + + # Length is the first byte + length = self.__payload[ltv] + # Jump over Length and Type to get to the name + start = ltv + 2 + # Mark finish + finish = ltv + length + 1 + # Extract name as a string from the payload + self.__name = self.__payload[start:finish].decode( + encoding='utf8', errors='ignore') + + def __find_advertising_str(self): + """ + The Advertising data(AD) is formatted as follows: 1st byte length, + 2nd byte AD type, and AD DATA. + + Returns: + Integer: Location in the payload of where to find the start of the + advertising data's LTV (Length-type-value). + Returns -1 if no advertising data LTV was found. + """ + # Check if payload length is less then 2 bytes, if so + # it can't possibly contain an advertising string. + if len(self.__payload) < 2: + return -1 + # Walk payload + offset = 0 + while offset < len(self.__payload): + # Get the LTV type + typ = self.__payload[offset + 1] + if typ in (0x08, 0x09): + # Found a Long/Short name LTV type, return it + return offset + if self.__payload[offset] == 0x00: + # NULL Byte, no LTV's, return -1 + return -1 + + # Jump to the next LTV + offset += self.__payload[offset] + 1 + + # Didn't find anything + return -1 + + @property + def address(self): + """ + Returns the BLE MAC address of the sender of the advertisement. + + Returns: + :class:`.XBeeBLEAddress`: the BLE address of the sender. + """ + return self.__address + + @property + def address_type(self): + """ + Returns the type of BLE address of the sender. + + Returns: + :class:`.BLEMACAddressType`: The type of BLE address. + """ + return self.__address_type + + @property + def connectable(self): + """ + Returns if the advertising device indicates that BLE central-mode + devices may connect to it. + + Returns: + Boolean: `True` if connectable, `False` otherwise. + """ + return self.__connectable + + @property + def rssi(self): + """ + Returns the received signal strength of the advertisement, in dBm. + + Returns: + Integer: The RSSI value. + """ + return self.__rssi + + @property + def name(self): + """ + Returns the Local/Short name, if the sender presented one. + + Returns: + Str: The Local/Short name. + """ + return self.__name + + @property + def payload(self): + """ + Returns a bytearray containing the data of the message. + + Returns: + Bytearray: the data of the message. + """ + return self.__payload + + def to_dict(self): + """ + Returns the message information as a dictionary. + """ + return {"Address": str(self.address), + "Type": self.address_type.description, + "Name": self.name, + "Connectable": self.connectable, + "RSSI": self.rssi, + "Payload": self.payload} + + +class BLEGAPScanLegacyAdvertisementMessage(_BLEGAPScanBaseAdvertisementMessage): + """ + This class represents a 'Legacy' BLE advertising message, that contains + the address that the message was received from, the type of address it is, + whether the device is connectable or not, the RSSI of the advertisement, + and the payload that was sent. + """ + + +class BLEGAPScanExtendedAdvertisementMessage(_BLEGAPScanBaseAdvertisementMessage): + """ + This class represents an 'Extended' BLE advertising message, that contains + the address that the message was received from, the type of address it is, + whether the device is connectable or not, the RSSI of the advertisement, + and the payload that was sent. + """ + + def __init__(self, address, address_type, advertisement_flags, rssi, + advertisement_set_id, primary_phy, secondary_phy, + tx_power, periodic_interval, data_completeness, payload): + """ + Class constructor. + + Args: + address (:class:`.XBeeBLEAddress`): The BLE address the message + comes from. + address_type (Integer): The type of BLE address. + advertisement_flags (Integer): Flags. Includes whether + connectable or not. + rssi (Float): The received signal strength of the advertisement, + in dBm. + advertisement_set_id (Integer): A device can broadcast multiple + advertisements at a time. + The set identifier will help identify + which advertisement you received. + primary_phy (Integer): This is the preferred PHY for connecting + with this device. Values are: + 0x1: 1M PHY + 0x2: 2M PHY + 0x4: LE Coded PHY 125k + 0x8: LE Coded PHY 500k + 0xFF : Any PHY supported + secondary_phy (Integer): This is the secondary PHY for connecting + with this device. + This has the same values as `primary_phy`. + tx_power (Integer): Transmission power of received advertisement. + This is a signed value. + periodic_interval (Integer): Interval for periodic advertising. + 0 indicates no periodic advertising. + Interval value is in increments of + 1.25 ms. + data_completeness (Integer): Values are: + 0x0: indicates all data of the advertisement has been reported. + 0x1: Data is incomplete, but more data will follow. + 0x2: Data is incomplete, but no more data is following. Data has be truncated. + payload (Bytearray): The data payload of the advertisement. + + Raises: + ValueError: if `address` is `None`. + ValueError: if `address_type` is `None`. + ValueError: if `advertisement_flags` is `None`. + ValueError: if `rssi` is `None`. + ValueError: if `advertisement_set_id` is `None`. + ValueError: if `primary_phy` is `None`. + ValueError: if `secondary_phy` is `None`. + ValueError: if `tx_power` is `None`. + ValueError: if `periodic_interval` is `None`. + ValueError: if `data_completeness` is `None`. + ValueError: if `payload` is `None`. + """ + if advertisement_set_id is None: + raise ValueError("BLE advertisement set ID cannot be None") + if primary_phy is None: + raise ValueError("BLE primary PHY cannot be None") + if secondary_phy is None: + raise ValueError("BLE secondary PHY cannot be None") + if tx_power is None: + raise ValueError("BLE tx power cannot be None") + if periodic_interval is None: + raise ValueError("BLE periodic interval cannot be None") + if data_completeness is None: + raise ValueError("BLE data completeness cannot be None") + if primary_phy not in (0x01, 0x02, 0x04, 0x08, 0xFF): + raise ValueError("primary_phy must be 1, 2, 4, 8 or 255") + if secondary_phy not in (0x01, 0x02, 0x04, 0x08, 0xFF): + raise ValueError("secondary_phy must be 1, 2, 4, 8 or 255") + if data_completeness not in (0x0, 0x1, 0x2): + raise ValueError("BLE data completeness must be 0, 1 or 2") + + super().__init__(address, address_type, advertisement_flags, rssi, + payload) + + self.__advertisement_set_id = advertisement_set_id + self.__primary_phy = primary_phy + self.__secondary_phy = secondary_phy + self.__tx_power = tx_power + self.__periodic_interval = periodic_interval + self.__data_completeness = data_completeness + + @property + def advertisement_set_id(self): + """ + Returns the advertisement set identifier used to help identify + which advertisement you received. + + Returns: + Integer: the advertisement set identifier. + """ + return self.__advertisement_set_id + + @property + def primary_phy(self): + """ + Returns the preferred PHY for connecting this device. + + Returns: + Integer: the primary PHY + """ + return self.__primary_phy + + @property + def secondary_phy(self): + """ + Returns the secondary PHY for connecting this device. + + Returns: + Integer: the secondary PHY. + """ + return self.__secondary_phy + + @property + def tx_power(self): + """ + Returns the transmission power of received advertisement. + This is a signed value. + + Returns: + Integer: transmission power. + """ + return self.__tx_power + + @property + def periodic_interval(self): + """ + Returns the interval for periodic advertising. + 0 indicates no periodic advertising. + + Returns: + Integer: periodic interval. + """ + return self.__periodic_interval + + @property + def data_completeness(self): + """ + Returns the data completeness field. + + Returns: + Integer: data completeness field. + """ + return self.__data_completeness + + def to_dict(self): + """ + Override. + + .. seealso:: + | :meth:`._BLEGAPScanBaseAdvertisementMessage.to_dict` + """ + msg_dict = super().to_dict() + msg_dict.update({ + "Advertisement set ID": self.__advertisement_set_id, + "Primary PHY": self.__primary_phy, + "Secondary PHY": self.__secondary_phy, + "TX power": self.__tx_power, + "Periodic interval": self.__periodic_interval, + "Data completeness": self.__data_completeness + }) + return msg_dict + + +class BLEGAPScanStatusMessage: + """ + This class represents a BLE GAP scan status message. + It will store the Status value received. + """ + + def __init__(self, status): + """ + Class constructor. + + Args: + status (Integer): The status of the GAP scan. + + Raises: + ValueError: if `status` is invalid. + """ + if status is None: + raise ValueError("status cannot be None") + + self.__status = BLEGAPScanStatus.get(status) + + @property + def status(self): + """ + Returns the status of the GAP scan. + + Returns: + :class:`.BLEGAPScanStatus`: The status of the GAP scan. + """ + return self.__status + + def to_dict(self): + """ + Returns the message information as a dictionary. + """ + return {"Status": self.__status.description} diff --git a/digi/xbee/models/protocol.py b/digi/xbee/models/protocol.py index b3c5913..b3b7ac4 100644 --- a/digi/xbee/models/protocol.py +++ b/digi/xbee/models/protocol.py @@ -1,4 +1,4 @@ -# Copyright 2017-2023, Digi International Inc. +# Copyright 2017-2024, Digi International Inc. # # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this @@ -45,6 +45,7 @@ class XBeeProtocol(Enum): XLR_MODULE = (14, "XLR Module") CELLULAR = (15, "Cellular") CELLULAR_NBIOT = (16, "Cellular NB-IoT") + BLE = (17, "BLE") UNKNOWN = (99, "Unknown") def __init__(self, code, description): @@ -296,6 +297,10 @@ def determine_protocol(hw_version, fw_version, br_value=None): return XBeeProtocol.DIGI_MESH return XBeeProtocol.ZIGBEE + if hw_version in (HardwareVersion.XBEE_BLU.code, + HardwareVersion.XBEE_BLU_TH.code): + return XBeeProtocol.BLE + return XBeeProtocol.ZIGBEE diff --git a/digi/xbee/models/status.py b/digi/xbee/models/status.py index 1341138..ed3c278 100644 --- a/digi/xbee/models/status.py +++ b/digi/xbee/models/status.py @@ -1,4 +1,4 @@ -# Copyright 2017-2021, Digi International Inc. +# Copyright 2017-2024, Digi International Inc. # # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this @@ -1351,3 +1351,123 @@ def finished(self): `False` otherwise. """ return self.__finished + + +@unique +class BLEMACAddressType(Enum): + """ + This class lists all the possible types of BLE Mac Addresses. + + | Inherited properties: + | **name** (String): The name of the BLEMACAddressType. + | **value** (Integer): The ID of the BLEMACAddressType. + """ + PUBLIC = (0x00, "Public") + STATIC = (0x01, "Static") + RANDOM_RESOLVABLE = (0x02, "Random Resolvable") + RANDOM_NONRESOLVABLE = (0x03, "Random Nonresolvable") + UNKNOWN = (0xFF, "Unknown") + + def __init__(self, code, description): + self.__code = code + self.__desc = description + + @property + def code(self): + """ + Returns the code of the BLEMACAddressType element. + + Returns: + Integer: the code of the BLEMACAddressType element. + """ + return self.__code + + @property + def description(self): + """ + Returns the description of the BLEMACAddressType element. + + Returns: + String: The description of the BLEMACAddressType element. + + """ + return self.__desc + + @classmethod + def get(cls, code): + """ + Returns the address type for the given code. + + Args: + code (Integer): the code of the address type to get. + + Returns: + :class:`.BLEMACAddressType`: the address type with the given code. + """ + for addr_type in cls: + if code == addr_type.code: + return addr_type + return BLEMACAddressType.UNKNOWN + + +BLEMACAddressType.__doc__ += utils.doc_enum(BLEMACAddressType) + + +@unique +class BLEGAPScanStatus(Enum): + """ + This class lists all the possible statuses of a BLE GAP scan + + | Inherited properties: + | **name** (String): The name of the BLEGAPScanStatus. + | **value** (Integer): The ID of the BLEGAPScanStatus. + """ + STARTED = (0x00, "Scanner has started") + RUNNING = (0x01, "Scanner is running") + STOPPED = (0x02, "Scanner has stopped") + ERROR = (0x03, "Scanner was unable to start or stop") + INVALID_PARAM = (0x04, "Invalid Parameters") + + def __init__(self, code, description): + self.__code = code + self.__desc = description + + @property + def code(self): + """ + Returns the code of the BLEGAPScanStatus element. + + Returns: + Integer: the code of the BLEGAPScanStatus element. + """ + return self.__code + + @property + def description(self): + """ + Returns the description of the BLEGAPScanStatus element. + + Returns: + String: The description of the BLEGAPScanStatus element. + + """ + return self.__desc + + @classmethod + def get(cls, code): + """ + Returns the address type for the given code. + + Args: + code (Integer): the code of the gap scan status. + + Returns: + :class:`.BLEGAPScanStatus`: the status type with the given code. + """ + for status in cls: + if code == status.code: + return status + return BLEGAPScanStatus.ERROR + + +BLEGAPScanStatus.__doc__ += utils.doc_enum(BLEGAPScanStatus) diff --git a/digi/xbee/packets/aft.py b/digi/xbee/packets/aft.py index 8551c3a..5ef9a81 100644 --- a/digi/xbee/packets/aft.py +++ b/digi/xbee/packets/aft.py @@ -1,4 +1,4 @@ -# Copyright 2017-2020, Digi International Inc. +# Copyright 2017-2024, Digi International Inc. # # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this @@ -43,6 +43,7 @@ class ApiFrameType(Enum): SEND_DATA_REQUEST = (0x28, "Send Data Request") DEVICE_RESPONSE = (0x2A, "Device Response") USER_DATA_RELAY_REQUEST = (0x2D, "User Data Relay Request") + BLUETOOTH_GAP_SCAN_REQUEST = (0x34, "Bluetooth GAP Scan Request") FILE_SYSTEM_REQUEST = (0x3B, "File System Request") REMOTE_FILE_SYSTEM_REQUEST = (0x3C, "Remote File System Request") SOCKET_CREATE = (0x40, "Socket Create") @@ -75,6 +76,11 @@ class ApiFrameType(Enum): REGISTER_JOINING_DEVICE_STATUS = (0xA4, "Register Joining Device Status") USER_DATA_RELAY_OUTPUT = (0xAD, "User Data Relay Output") RX_IPV4 = (0xB0, "RX IPv4") + BLUETOOTH_GAP_SCAN_LEGACY_ADVERTISEMENT_RESPONSE = ( + 0xB4, "Bluetooth GAP Scan Legacy Advertisement Response") + BLUETOOTH_GAP_SCAN_EXTENDED_ADVERTISEMENT_RESPONSE = ( + 0xB7, "Bluetooth GAP Scan Extended Advertisement Response") + BLUETOOTH_GAP_SCAN_STATUS = (0xB5, "Bluetooth GAP Scan Status") SEND_DATA_RESPONSE = (0xB8, "Send Data Response") DEVICE_REQUEST = (0xB9, "Device Request") DEVICE_RESPONSE_STATUS = (0xBA, "Device Response Status") diff --git a/digi/xbee/packets/base.py b/digi/xbee/packets/base.py index 151b565..c4db067 100644 --- a/digi/xbee/packets/base.py +++ b/digi/xbee/packets/base.py @@ -1,4 +1,4 @@ -# Copyright 2017-2022, Digi International Inc. +# Copyright 2017-2024, Digi International Inc. # # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this @@ -35,6 +35,13 @@ class DictKeys(Enum): API_DATA = "api_spec_data" AT_CMD_STATUS = "at_command_status" BROADCAST_RADIUS = "broadcast_radius" + BLE_ADDRESS = "ble_address" + BLE_ADDRESS_TYPE = "ble_address_type" + BLE_ADVERTISEMENT_FLAGS = "advertisement_flags" + BLE_SCAN_FILTER_TYPE = "scan_filter_type" + BLE_SCAN_INTERVAL = "scan_interval" + BLE_SCAN_START = "scan_start" + BLE_SCAN_WINDOW = "scan_window" BLOCK_NUMBER = "block_number" BOOTLOADER_MSG_TYPE = "bootloader_msg_type" BYTES_USED = "bytes_used" diff --git a/digi/xbee/packets/bluetooth.py b/digi/xbee/packets/bluetooth.py new file mode 100644 index 0000000..663dc67 --- /dev/null +++ b/digi/xbee/packets/bluetooth.py @@ -0,0 +1,1220 @@ +# Copyright 2024, Digi International Inc. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +from digi.xbee.packets.base import XBeeAPIPacket, DictKeys +from digi.xbee.exception import InvalidOperatingModeException, \ + InvalidPacketException +from digi.xbee.packets.aft import ApiFrameType +from digi.xbee.models.mode import OperatingMode +from digi.xbee.util import utils +from digi.xbee.models.address import XBeeBLEAddress + + +class BluetoothGAPScanRequestPacket(XBeeAPIPacket): + """ + This class represents a Bluetooth GAP scan request packet. Packet is built + using the parameters of the constructor or providing a valid byte array. + + .. seealso:: + | :class:`.BluetoothGAPScanRequestPacket` + | :class:`.XBeeAPIPacket` + """ + + # Various defines, max/mins + STOP_SCAN = 0x00 + """ + Constant value used to stop the scan + """ + START_SCAN = 0x01 + """ + Constant value used to start the scan + """ + INDEFINITE_SCAN_DURATION = 0x00 + """ + Constant value for an indefinite scan + """ + + # Internal only + __SCAN_DURATION_MINIMUM = 0x1 + __SCAN_DURATION_MAXIMUM = 0xFFFF + __SCAN_WINDOW_MINIMUM = 0x9C4 + __SCAN_WINDOW_MAXIMUM = 0x270FD8F + __SCAN_INTERVAL_MINIMUM = 0x9C4 + __SCAN_INTERVAL_MAXIMUM = 0x270FD8F + __SCAN_FILTER_DISABLED = 0x00 + __SCAN_FILTER_ENABLED = 0x01 + __SCAN_CUSTOM_FILTER_MAXIMUM_LENGTH = 22 + + __MIN_PACKET_LENGTH = 17 + + def __init__(self, start_command, scan_duration, scan_window, + scan_interval, filter_type, filter_data, + op_mode=OperatingMode.API_MODE): + """ + Class constructor. Instantiates a new :class:`.BluetoothGAPScanRequestPacket` + object with the provided parameters. + + Args: + start_command (Integer): To start scan set to 1. + To stop scan set to 0. + scan_duration (Integer): Scan duration in seconds. + Value must be between 0 and 0xFFFF. + A value of 0 indicates the scan will run indefinitely. + scan_window (Integer): Scan window in microseconds. + Value must be between 0x9C4 and 0x270FD8F and + value can't be larger than the scan_interval. + scan_interval (Integer): Scan interval in microseconds. + Value must be between 0x9C4 and + 0x270FD8F and + value can't be smaller than the + scan_window. + filter_type (Integer): Value of 0 disables filter. + Value of 1 enables filter looking for + Complete Local Name (0x09) and + Short Local Name (0x08) types. + filter_data (String or bytearray): The size of the filter_data is + from 0-22 bytes. + op_mode (:class:`.OperatingMode`, optional, default=`OperatingMode.API_MODE`): + The mode in which the frame was captured. + + + Raises: + ValueError: if `start_command` is not 0 nor 1. + ValueError: if `scan_duration` is not between 0 and 0xFFFF. + ValueError: if `scan_window` is not between 0x9C4 and 0x270FD8F + or larger than the scan_interval. + ValueError: if `scan_interval` is not between 0x9C4 and 0x270FD8F + or smaller than the scan_window. + ValueError: if `filter_type` is not 0 nor 1. + + .. seealso:: + | :class:`.XBeeAPIPacket` + """ + if start_command not in (self.START_SCAN, self.STOP_SCAN): + raise ValueError(f"Command must be {self.START_SCAN} or {self.STOP_SCAN}") + + if scan_duration is None: + raise ValueError("The scan duration cannot be None") + + if not (self.INDEFINITE_SCAN_DURATION <= scan_duration <= self.__SCAN_DURATION_MAXIMUM): + raise ValueError(f"scan_duration must be between {self.__SCAN_DURATION_INDEFINITELY} and {self.__SCAN_DURATION_MAXIMUM}") + + if scan_window is None: + raise ValueError("The scan window cannot be None") + + if not (self.__SCAN_WINDOW_MINIMUM <= scan_window <= self.__SCAN_WINDOW_MAXIMUM): + raise ValueError(f"scan_window must be between {self.__SCAN_WINDOW_MINIMUM} and {self.__SCAN_WINDOW_MAXIMUM}") + + if scan_interval is None: + raise ValueError("The scan interval cannot be None") + + if not (self.__SCAN_INTERVAL_MINIMUM <= scan_interval <= self.__SCAN_INTERVAL_MAXIMUM): + raise ValueError(f"scan_interval must be between {self.__SCAN_INTERVAL_MINIMUM} and {self.__SCAN_INTERVAL_MAXIMUM}") + + if scan_interval < scan_window: + raise ValueError("scan_interval can't be smaller than the scan_window") + + if filter_type not in (self.__SCAN_FILTER_DISABLED, self.__SCAN_FILTER_DISABLED): + raise ValueError(f"filter_type must be {self.__SCAN_FILTER_DISABLED} or {self.__SCAN_FILTER_DISABLED}") + + if filter_data and len(filter_data) > self.__SCAN_CUSTOM_FILTER_MAXIMUM_LENGTH: + raise ValueError(f"The length of the custom filter must be from 0-{self.__SCAN_CUSTOM_FILTER_MAXIMUM_LENGTH}") + + super().__init__(ApiFrameType.BLUETOOTH_GAP_SCAN_REQUEST, + op_mode=op_mode) + + self.__start_command = start_command + self.__scan_duration = scan_duration + self.__scan_window = scan_window + self.__scan_interval = scan_interval + self.__filter_type = filter_type + + if isinstance(filter_data, str): + self.__filter_data = filter_data.encode('utf8', errors='ignore') + else: + self.__filter_data = filter_data + + @staticmethod + def create_packet(raw, operating_mode): + """ + Override method. + + Returns: + :class:`.BluetoothGAPScanRequestPacket` + + Raises: + InvalidPacketException: if the bytearray length is less than 17. + (start delim, length (2 bytes), frame type, + start_command, scan_duration (2 bytes), scan_window (4 bytes), + scan_interval (4 bytes), filter_type, checksum = 17 bytes) + InvalidPacketException: if the length field of `raw` is different + from its real length. (length field: bytes 2 and 3) + InvalidPacketException: if the first byte of `raw` is not the + header byte. See :class:`.SPECIAL_BYTE`. + InvalidPacketException: if the calculated checksum is different + from the checksum field value (last byte). + InvalidPacketException: if the frame type is different from + :py:attr:`.ApiFrameType.BLUETOOTH_GAP_SCAN_REQUEST`. + InvalidOperatingModeException: if `operating_mode` + is not supported. + + .. seealso:: + | :meth:`.XBeePacket.create_packet` + """ + if operating_mode not in (OperatingMode.ESCAPED_API_MODE, + OperatingMode.API_MODE): + raise InvalidOperatingModeException(op_mode=operating_mode) + + XBeeAPIPacket._check_api_packet(raw, min_length=BluetoothGAPScanRequestPacket.__MIN_PACKET_LENGTH) + + if raw[3] != ApiFrameType.BLUETOOTH_GAP_SCAN_REQUEST.code: + raise InvalidPacketException(message="This packet is not a BluetoothGAPScanRequestPacket packet") + + filter_data = None + if len(raw) > BluetoothGAPScanRequestPacket.__MIN_PACKET_LENGTH: + filter_data = raw[17:-1] + return BluetoothGAPScanRequestPacket( + raw[4], utils.bytes_to_int(raw[5:7]), + utils.bytes_to_int(raw[7:11]), + utils.bytes_to_int(raw[11:15]), raw[15], + filter_data, op_mode=operating_mode) + + def needs_id(self): + """ + Override method. + + .. seealso:: + | :meth:`.XBeeAPIPacket.needs_id` + """ + return False + + def _get_api_packet_spec_data(self): + """ + Override method. + + .. seealso:: + | :meth:`.XBeeAPIPacket._get_api_packet_spec_data` + """ + ret = bytearray() + ret.append(self.__start_command) + ret += utils.int_to_bytes(self.__scan_duration, num_bytes=2) + ret += utils.int_to_bytes(self.__scan_window, num_bytes=4) + ret += utils.int_to_bytes(self.__scan_interval, num_bytes=4) + ret.append(self.__filter_type) + if self.__filter_data is not None: + ret += self.__filter_data + return ret + + def _get_api_packet_spec_data_dict(self): + """ + Override method. + + .. seealso:: + | :meth:`.XBeeAPIPacket._get_api_packet_spec_data_dict` + """ + return {DictKeys.BLE_SCAN_START: self.__start_command, + DictKeys.BLE_SCAN_DURATION.value: "%02X" % + self.__scan_duration, + DictKeys.BLE_SCAN_WINDOW.value: "%04X" % self.__scan_window, + DictKeys.BLE_SCAN_INTERVAL.value: "%04X" % + self.__scan_interval, + DictKeys.BLE_SCAN_FILTER_TYPE: self.__scan_interval, + DictKeys.PAYLOAD.value: utils.hex_to_string(self.__filter_data, + True) if self.__filter_data is not None else None} + + @property + def start_command(self): + """ + Returns the start_command. + + Returns: + Integer: the start_command. + """ + return self.__start_command + + @start_command.setter + def start_command(self, start_command): + """ + Sets the start_command. + + Args: + start_command (Integer): To start scan set to 1. + To stop scan set to 0. + + Raises: + ValueError: if `start_command` is less than 0 or greater than 1. + """ + if start_command < 0 or start_command > 1: + raise ValueError("start_command must be between 0 and 1.") + self.__start_command = start_command + + @property + def scan_duration(self): + """ + Returns the scan duration. + + Returns: + Integer: the scan duration. + """ + return self.__scan_duration + + @scan_duration.setter + def scan_duration(self, scan_duration): + """ + Sets the scan duration. + + Args: + scan_duration (Integer): Scan duration in seconds. + Value must be between 0 and 0xFFFF. + + Raises: + ValueError: if `scan_duration` is less than 0 or greater + than 0xFFFF. + """ + if scan_duration < 0 or scan_duration > 0xFFFF: + raise ValueError("scan_duration must be between 0 and 0xFFFF.") + self.__scan_duration = scan_duration + + @property + def scan_window(self): + """ + Returns the scan window. + + Returns: + Integer: the scan window. + """ + return self.__scan_window + + @scan_window.setter + def scan_window(self, scan_window): + """ + Sets the scan window. + + Args: + scan_window (Integer): Scan window in microseconds. + Scan window in microseconds. + Value must be between 0x9C4 and 0x270FD8F. + + Raises: + ValueError: if `scan_window` is less than 0x9C4 or greater + than 0x270FD8F. + """ + if scan_window < 0x9C4 or scan_window > 0x270FD8F: + raise ValueError(f"scan_window must be between {self.__SCAN_WINDOW_MINIMUM} and {self.__SCAN_WINDOW_MAXIMUM}.") + self.__scan_window = scan_window + + @property + def scan_interval(self): + """ + Returns the scan interval. + + Returns: + Integer: the scan interval. + """ + return self.__scan_interval + + @scan_interval.setter + def scan_interval(self, scan_interval): + """ + Sets the scan interval. + + Args: + scan_interval (Integer): Scan window in microseconds. + Scan window in microseconds. + Value must be between 0x9C4 and 0x270FD8F. + + Raises: + ValueError: if `scan_interval` is less than 0x9C4 or greater + than 0x270FD8F. + """ + if scan_interval < 0x9C4 or scan_interval > 0x270FD8F: + raise ValueError(f"scan_interval must be between {self.__SCAN_INTERVAL_MINIMUM} and {self.__SCAN_INTERVAL_MAXIMUM}.") + self.__scan_interval = scan_interval + + @property + def filter_type(self): + """ + Returns the filter_type. + + Returns: + Integer: the filter_type. + """ + return self.__filter_type + + @filter_type.setter + def filter_type(self, filter_type): + """ + Sets the filter_type. + Args: + filter_type (Integer): Value of 0 disables filter. + Value of 1 enables filter looking for + Complete Local Name (0x09) and Short Local Name (0x08) types. + + Raises: + ValueError: if `filter_type` is less than 0 or greater than 1. + """ + if filter_type < 0 or filter_type > 1: + raise ValueError("filter_type must be between 0 and 1.") + self.__filter_type = filter_type + + @property + def filter_data(self): + """ + Returns the filter data. + + Returns: + Bytearray: packet's filter data. + """ + return self.__filter_data + + @filter_data.setter + def filter_data(self, filter_data): + """ + Sets the filter data. + + Args: + filter_data (String or Bytearray): the new filter data. + """ + if isinstance(filter_data, str): + self.__filter_data = filter_data.encode('utf8', errors='ignore') + else: + self.__filter_data = filter_data + + +class BluetoothGAPScanLegacyAdvertisementResponsePacket(XBeeAPIPacket): + """ + This class represents a Bluetooth GAP scan legacy advertisement + response packet. + Packet is built using the parameters of the constructor or providing a + valid byte array. + + .. seealso:: + | :class:`.BluetoothGAPScanLegacyAdvertisementResponsePacket` + | :class:`.XBeeAPIPacket` + """ + + __MIN_PACKET_LENGTH = 16 + + def __init__(self, address, address_type, advertisement_flags, rssi, + payload, op_mode=OperatingMode.API_MODE): + """ + Class constructor. Instantiates a new :class:`.BluetoothGAPScanLegacyAdvertisementResponsePacket` + object with the provided parameters. + + Args: + address (:class:`.XBeeBLEAddress`): The Bluetooth MAC address of + the received advertisement + address_type (Integer): Indicates whether the Bluetooth address + is a public address or a randomly + generated address. + advertisement_flags (Integer): bitfield structure where bit0 + indicates whether the advertisement + is connectable. + Other bits are reserved. + rssi (Integer): The received signal strength of the advertisement, + in dBm. + payload (bytearray): The actual data payload of the advertisement + which can contain various information elements, + manufacturer-specific data, or other relevant + details about the advertising device. + op_mode (:class:`.OperatingMode`, optional, default=`OperatingMode.API_MODE`): + The mode in which the frame was captured. + + Raises: + ValueError: if length of `payload` is less than 16 or greater + than 255. + + .. seealso:: + | :class:`.XBeeBLEAddress` + """ + if len(payload) > 255: + raise ValueError("Response payload length must be less than 256 bytes") + + super().__init__(ApiFrameType.BLUETOOTH_GAP_SCAN_LEGACY_ADVERTISEMENT_RESPONSE, + op_mode=op_mode) + + self.__address = address + self.__address_type = address_type + self.__advertisement_flags = advertisement_flags + self.__rssi = rssi + self.__payload = payload + + @staticmethod + def create_packet(raw, operating_mode): + """ + Override method. + + Returns: + :class:`.BluetoothGAPScanLegacyAdvertisementResponsePacket` + + Raises: + InvalidPacketException: if the bytearray length is less than 32. + (start delim + length (2 bytes) + frame type + + address (6 bytes) + address_type + advertisement_flags + + rssi + reserved + payload_length + checksum = 16 bytes) + InvalidPacketException: if the length field of `raw` is different + from its real length. (length field: bytes 2 and 3) + InvalidPacketException: if the first byte of `raw` is not the + header byte. See :class:`.SPECIAL_BYTE`. + InvalidPacketException: if the calculated checksum is different + from the checksum field value (last byte). + InvalidPacketException: if the frame type is different from + :py:attr:`.ApiFrameType.BluetoothGAPScanLegacyAdvertisementResponsePacket`. + InvalidPacketException: if the payload_length field does not match + the actual + length of the payload. + InvalidOperatingModeException: if `operating_mode` is not + supported. + + .. seealso:: + | :meth:`.XBeePacket.create_packet` + """ + if operating_mode not in (OperatingMode.ESCAPED_API_MODE, + OperatingMode.API_MODE): + raise InvalidOperatingModeException(op_mode=operating_mode) + + XBeeAPIPacket._check_api_packet( + raw, min_length=BluetoothGAPScanLegacyAdvertisementResponsePacket.__MIN_PACKET_LENGTH) + + if raw[3] != ApiFrameType.BLUETOOTH_GAP_SCAN_LEGACY_ADVERTISEMENT_RESPONSE.code: + raise InvalidPacketException(message="This packet is not an BLUETOOTH_GAP_SCAN_LEGACY_ADVERTISEMENT_RESPONSE") + + payload = None + if len(raw) > BluetoothGAPScanLegacyAdvertisementResponsePacket.__MIN_PACKET_LENGTH: + payload = raw[15:-1] + + payload_length = raw[14] + if len(payload) != payload_length: + raise InvalidPacketException( + message="BluetoothGAPScanLegacyAdvertisementResponsePacket payload length field is %u and does not match actual length of payload that is %u".format( + payload_length, len(payload) + )) + + return BluetoothGAPScanLegacyAdvertisementResponsePacket( + XBeeBLEAddress(raw[4:10]), raw[10], raw[11], raw[12], payload, + op_mode=operating_mode) + + def needs_id(self): + """ + Override method. + + .. seealso:: + | :meth:`.XBeeAPIPacket.needs_id` + """ + return False + + def _get_api_packet_spec_data(self): + """ + Override method. + + .. seealso:: + | :meth:`.XBeeAPIPacket._get_API_packet_spec_data` + """ + ret = bytearray() + ret.append(len(self.__address.address)) + ret.append(self.__address_type) + ret.append(self.__advertisement_flags) + ret.append(self.__rssi) + ret.append(0) + if self.__payload is not None: + ret.append(len(self.__payload)) + ret += self.__payload + else: + ret.append(0) + return ret + + def _get_api_packet_spec_data_dict(self): + """ + Override method. + + .. seealso:: + | :meth:`.XBeeAPIPacket._get_API_packet_spec_data_dict` + """ + return {DictKeys.BLE_ADDRESS: "%s (%s)" % (self.__address.packed, + self.__address.exploded), + DictKeys.BLE_ADDRESS_TYPE: self.__address_type, + DictKeys.BLE_ADVERTISEMENT_FLAGS: self.__advertisement_flags, + DictKeys.RSSI: self.__rssi, + DictKeys.RESERVED: 0, + DictKeys.PAYLOAD.value: utils.hex_to_string(self.__payload, + True) if self.__payload is not None else None} + + @property + def address(self): + """ + Returns the Bluetooth MAC address of the received advertisement. + + Returns: + address (:class:`.XBeeBLEAddress`): returns the Bluetooth + MAC address. + """ + return self.__address + + @address.setter + def address(self, address): + """ + Sets the BLE MAC address of the received advertisement + + Args: + address (:class:`.XBeeBLEAddress`): the 48 bit BLE MAC address. + """ + if address is not None: + self.__address = address + + @property + def address_type(self): + """ + Returns the address type of the received advertisement. + + Returns: + Integer: the address type. + """ + return self.__address_type + + @address_type.setter + def address_type(self, address_type): + """ + Sets the address type indicating whether the Bluetooth address is a + public or randomly generated address. + + Args: + address_type (Integer): address type + """ + self.__address_type = address_type + + @property + def advertisement_flags(self): + """ + Returns the advertisement flags. + + Returns: + Integer: the advertisement flags. + """ + return self.__advertisement_flags + + @advertisement_flags.setter + def advertisement_flags(self, advertisement_flags): + """ + Sets the Advertisement flags type. + Bit 0 indicates whether the advertisement is connectable. + + Args: + advertisement_flags (Integer): advertisement flag + """ + self.__advertisement_flags = advertisement_flags + + @property + def rssi(self): + """ + Returns the RSSI of the advertisement. + + Returns: + Integer: the RSSI. + """ + return self.__rssi + + @rssi.setter + def rssi(self, rssi): + """ + Sets the RSSI of the advertisement in dBm. + + Args: + rssi (Integer): the RSSI + """ + self.__rssi = rssi + + @property + def payload(self): + """ + Returns the payload that was received. + + Returns: + Bytearray: the payload that was received. + """ + if self.__payload is None: + return None + return self.__payload.copy() + + @payload.setter + def payload(self, payload): + """ + Sets the payload that was received. + + Args: + payload (Bytearray): the new payload that was received. + """ + if payload is None: + self.__payload = None + else: + self.__payload = payload.copy() + + +class BluetoothGAPScanExtendedAdvertisementResponsePacket(XBeeAPIPacket): + """ + This class represents a Bluetooth GAP scan extended advertisement response + packet. Packet is built using the parameters of the constructor or + providing a valid byte array. + + .. seealso:: + | :class:`.BluetoothGAPScanExtendedAdvertisementResponsePacket` + | :class:`.XBeeAPIPacket` + """ + + __MIN_PACKET_LENGTH = 23 + + def __init__(self, address, address_type, advertisement_flags, rssi, + advertisement_set_id, primary_phy, secondary_phy, tx_power, + periodic_interval, data_completeness, payload, + op_mode=OperatingMode.API_MODE): + """ + Class constructor. Instantiates a new + :class:`.BluetoothGAPScanExtendedAdvertisementResponsePacket` object + with the provided parameters. + + Args: + address (:class:`.XBeeBLEAddress`): The Bluetooth MAC address of + the received advertisement + address_type (Integer): Indicates whether the Bluetooth address is + a public address or a randomly + generated address. + advertisement_flags (Integer): bitfield structure where bit0 + indicates whether the advertisement is connectable. + Other bits are reserved. + rssi (Integer) The received signal strength of the advertisement, + in dBm. + advertisement_set_id (Integer): A device can broadcast multiple + advertisements at a time. + The set identifier will help + identify which advertisement + you received. + primary_phy (Integer): This is the preferred PHY for connecting + with this device. Values are: + 0x1 : 1M PHY + 0x2 : 2M PHY + 0x4 : LE Coded PHY 125k + 0x8 : LE Coded PHY 500k + 0xFF : Any PHY supported + secondary_phy (Integer): This is the secondary PHY for connecting + with this device. + This has the same values as primary_phy. + tx_power (Integer): Transmission power of received advertisement. + This is a signed value. + periodic_interval (Integer): Interval for periodic advertising. + 0 indicates no periodic advertising. + Interval value is in increments of 1.25 ms. + data_completeness (Integer): Values are + 0x0 indicates all data of the advertisement has been reported. + 0x1 : Data is incomplete, but more data will follow. + 0x2 : Data is incomplete, but no more data is following. + Data has be truncated. + payload (bytearray): The actual data payload of the advertisement + which can contain various information elements, + manufacturer-specific data, or other relevant details + about the advertising device. + op_mode (:class:`.OperatingMode`, optional, default=`OperatingMode.API_MODE`): + The mode in which the frame was captured. + + Raises: + ValueError: if length of `payload` is less than 22 or + greater than 255. + """ + + if len(payload) > 255: + raise ValueError("Response payload length must be less than 256 bytes") + + super().__init__(ApiFrameType.BLUETOOTH_GAP_SCAN_EXTENDED_ADVERTISEMENT_RESPONSE, + op_mode=op_mode) + + self.__address = address + self.__address_type = address_type + self.__advertisement_flags = advertisement_flags + self.__rssi = rssi + self.__advertisement_set_id = advertisement_set_id + self.__primary_phy = primary_phy + self.__secondary_phy = secondary_phy + self.__tx_power = tx_power + self.__periodic_interval = periodic_interval + self.__data_completeness = data_completeness + self.__payload = payload + + @staticmethod + def create_packet(raw, operating_mode): + """ + Override method. + + Returns: + :class:`.BluetoothGAPScanExtendedAdvertisementResponsePacket` + + Raises: + InvalidPacketException: if the bytearray length is less than 45. + (start delim + length (2 bytes) + frame type + + address (6 bytes) + address_type + advertisement_flags + + rssi + reserved + advertisement_set_id + primary_phy + + secondary_phy + tx_power + periodic_interval (2 bytes) + + data_completeness + payload_length + checksum = 23 bytes) + InvalidPacketException: if the length field of `raw` is different + from its real length. (length field: bytes 2 and 3) + InvalidPacketException: if the first byte of `raw` is not the + header byte. See :class:`.SPECIAL_BYTE`. + InvalidPacketException: if the calculated checksum is different + from the checksum field value (last byte). + InvalidPacketException: if the frame type is different from + :py:attr:`.ApiFrameType.BluetoothGAPScanExtendedAdvertisementResponsePacket`. + InvalidOperatingModeException: if `operating_mode` is not + supported. + + .. seealso:: + | :meth:`.XBeePacket.create_packet` + """ + if operating_mode not in (OperatingMode.ESCAPED_API_MODE, + OperatingMode.API_MODE): + raise InvalidOperatingModeException(op_mode=operating_mode) + + XBeeAPIPacket._check_api_packet( + raw, min_length=BluetoothGAPScanExtendedAdvertisementResponsePacket.__MIN_PACKET_LENGTH) + + if raw[3] != ApiFrameType.BLUETOOTH_GAP_SCAN_EXTENDED_ADVERTISEMENT_RESPONSE.code: + raise InvalidPacketException(message="This packet is not an BLUETOOTH_GAP_SCAN_EXTENDED_ADVERTISEMENT_RESPONSE") + + payload = None + if len(raw) > BluetoothGAPScanExtendedAdvertisementResponsePacket.__MIN_PACKET_LENGTH: + payload = raw[22:-1] + + payload_length = raw[21] + if len(payload) != payload_length: + raise InvalidPacketException( + message="BluetoothGAPScanExtendedAdvertisementResponsePacket payload length field is %u and does not match actual length of payload that is %u".format( + payload_length, len(payload) + )) + + return BluetoothGAPScanExtendedAdvertisementResponsePacket( + XBeeBLEAddress(raw[4:10]), raw[10], raw[11], raw[12], raw[14], + raw[15], raw[16], raw[17], raw[18:19], raw[20], + payload, op_mode=operating_mode) + + def needs_id(self): + """ + Override method. + + .. seealso:: + | :meth:`.XBeeAPIPacket.needs_id` + """ + return False + + def _get_api_packet_spec_data(self): + """ + Override method. + + .. seealso:: + | :meth:`.XBeeAPIPacket._get_API_packet_spec_data` + """ + ret = bytearray() + ret.append(len(self.__address.address)) + ret.append(self.__address_type) + ret.append(self.__advertisement_flags) + ret.append(self.__rssi) + ret.append(self.__advertisement_set_id) + ret.append(self.__primary_phy) + ret.append(self.__secondary_phy) + ret.append(self.__tx_power) + ret += self.__periodic_interval + ret.append(self.__data_completeness) + ret.append(0) + if self.__payload is not None: + ret.append(len(self.__payload)) + ret += self.__payload + else: + ret.append(0) + return ret + + def _get_api_packet_spec_data_dict(self): + """ + Override method. + + .. seealso:: + | :meth:`.XBeeAPIPacket._get_API_packet_spec_data_dict` + + """ + return {DictKeys.BLE_ADDRESS: "%s (%s)" % (self.__address.packed, + self.__address.exploded), + DictKeys.BLE_ADDRESS_TYPE: self.__address_type, + DictKeys.BLE_ADVERTISEMENT_FLAGS: self.__advertisement_flags, + DictKeys.RSSI: self.__rssi, + DictKeys.RESERVED: 0, + DictKeys.ADVERTISEMENT_SET_ID: self.__advertisement_set_id, + DictKeys.PRIMARY_PHY: self.__primary_phy, + DictKeys.SECONDARY_PHY: self.__secondary_phy, + DictKeys.TX_POWER: self.__tx_power, + DictKeys.PERIODIC_INTERVAL: self.__periodic_interval, + DictKeys.DATA_COMPLETENESS: self.__data_completeness, + DictKeys.PAYLOAD.value: utils.hex_to_string(self.__payload, + True) if self.__payload is not None else None} + + @property + def address(self): + """ + Returns the Bluetooth MAC address of the received advertisement. + + Returns: + address (:class:`.XBeeBLEAddress`): returns the Bluetooth + MAC address. + """ + return self.__address + + @address.setter + def address(self, address): + """ + Sets the BLE MAC address of the received advertisement + + Args: + address (byt:class:`.XBeeBLEAddress`): the 48 bit BLE MAC address. + """ + self.__address = address + + @property + def address_type(self): + """ + Returns the address type + + Returns: + Integer: the address type. + """ + return self.__address_type + + @address_type.setter + def address_type(self, address_type): + """ + Sets the address type indicating whether the Bluetooth address is a + public or randomly generated address. + + Args: + address_type (Integer): address type + """ + self.__address_type = address_type + + @property + def advertisement_flags(self): + """ + Returns the Advertisement flags + + Returns: + Integer: the advertisement flags. + """ + return self.__advertisement_flags + + @advertisement_flags.setter + def advertisement_flags(self, advertisement_flags): + """ + Sets the Advertisement flags type. + Bit 0 indicates whether the advertisement is connectable. + + Args: + advertisement_flags (Integer): advertisement flag + """ + self.__advertisement_flags = advertisement_flags + + @property + def rssi(self): + """ + Returns the RSSI of the advertisement. + + Returns: + Integer: the RSSI. + """ + return self.__rssi + + @rssi.setter + def rssi(self, rssi): + """ + Sets the RSSI of the advertisement in dBm. + + Args: + rssi (Integer): the RSSI + """ + self.__rssi = rssi + + @property + def advertisement_set_id(self): + """ + Returns the advertisement set identifier used to help identify which + advertisement you received. + + Returns: + Integer: the advertisement set identifier. + """ + return self.__advertisement_set_id + + @advertisement_set_id.setter + def advertisement_set_id(self, advertisement_set_id): + """ + Sets the advertisement set identifier. + + Args: + advertisement_set_id (Integer): + """ + self.__advertisement_set_id = advertisement_set_id + + @property + def primary_phy(self): + """ + Returns the preferred PHY for connecting this device. + + Returns: + Integer: the primary PHY + """ + return self.__primary_phy + + @primary_phy.setter + def primary_phy(self, primary_phy): + """ + Sets the preferred PHY for connecting this device. + + Args: + primary_phy (Integer): primary PHY + """ + self.__primary_phy = primary_phy + + @property + def secondary_phy(self): + """ + Returns the secondary PHY for connecting this device. + + Returns: + Integer: the secondary PHY. + """ + return self.__secondary_phy + + @secondary_phy.setter + def secondary_phy(self, secondary_phy): + """ + Sets the secondary PHY for connecting this device. + + Args: + secondary_phy (Integer): secondary PHY + """ + self.__secondary_phy = secondary_phy + + @property + def tx_power(self): + """ + Returns the transmission power of received advertisement. + This is a signed value. + + Returns: + Integer: transmission power. + """ + return self.__tx_power + + @tx_power.setter + def tx_power(self, tx_power): + """ + Sets the transmission power of received advertisement. + + Args: + tx_power (Integer): transmission power. + """ + self.__tx_power = tx_power + + @property + def periodic_interval(self): + """ + Returns the interval for periodic advertising. + 0 indicates no periodic advertising. + + Returns: + Integer: periodic interval. + """ + return int.from_bytes(self.__periodic_interval, "big") + + @periodic_interval.setter + def periodic_interval(self, periodic_interval): + """ + Sets the Interval for periodic advertising. + + Args: + periodic_interval (Integer): periodic interval. + """ + self.__periodic_interval = periodic_interval + + @property + def data_completeness(self): + """ + Returns the data completeness field. + + Returns: + Integer: data completeness field. + """ + return self.__data_completeness + + @data_completeness.setter + def data_completeness(self, data_completeness): + """ + Sets the data completeness field. + + Args: + data_completeness (Integer): data completeness. + """ + self.__data_completeness = data_completeness + + @property + def payload(self): + """ + Returns the payload that was received. + + Returns: + Bytearray: the payload that was received. + """ + if self.__payload is None: + return None + return self.__payload.copy() + + @payload.setter + def payload(self, payload): + """ + Sets the payload that was received. + + Args: + payload (Bytearray): the new payload that was received. + """ + if payload is None: + self.__payload = None + else: + self.__payload = payload.copy() + + +class BluetoothGAPScanStatusPacket(XBeeAPIPacket): + """ + This class represents a Bluetooth GAP scan status packet. Packet is built + using the parameters of the constructor or providing a valid byte array. + + .. seealso:: + | :class:`.BluetoothGAPScanStatusPacket` + | :class:`.XBeeAPIPacket` + """ + + __MIN_PACKET_LENGTH = 6 + + def __init__(self, scan_status, op_mode=OperatingMode.API_MODE): + """ + Class constructor. Instantiates a new + :class:`.BluetoothGAPScanStatusPacket` object with the provided parameters. + + Args: + scan_status (Integer): Reflects the status of the + Bluetooth scanner. Values are: + 0x00: Scanner has successfully started. + 0x01: Scanner is currently running. + 0x02: Scanner has successfully stopped. + 0x03: Scanner was unable to start or stop. + 0x04: Invalid parameters. + + op_mode (:class:`.OperatingMode`, optional, default=`OperatingMode.API_MODE`): + The mode in which the frame was captured. + """ + + super().__init__(ApiFrameType.BLUETOOTH_GAP_SCAN_STATUS, + op_mode=op_mode) + + self.__scan_status = scan_status + + @staticmethod + def create_packet(raw, operating_mode): + """ + Override method. + + Returns: + :class:`.BluetoothGAPScanStatusPacket` + + Raises: + InvalidPacketException: if the bytearray length is not 6. + (start delim + length (2 bytes) + frame type + + scan_status + checksum = 6 bytes) + InvalidPacketException: if the length field of `raw` is different + from its real length. (length field: bytes 2 and 3) + InvalidPacketException: if the first byte of `raw` is not the + header byte. See :class:`.SPECIAL_BYTE`. + InvalidPacketException: if the calculated checksum is different + from the checksum field value (last byte). + InvalidPacketException: if the frame type is different from + :py:attr:`.ApiFrameType.BluetoothGAPScanLegacyAdvertisementResponsePacket`. + InvalidOperatingModeException: if `operating_mode` + is not supported. + + .. seealso:: + | :meth:`.XBeePacket.create_packet` + """ + if operating_mode not in (OperatingMode.ESCAPED_API_MODE, + OperatingMode.API_MODE): + raise InvalidOperatingModeException(op_mode=operating_mode) + + XBeeAPIPacket._check_api_packet( + raw, min_length=BluetoothGAPScanStatusPacket.__MIN_PACKET_LENGTH) + + if raw[3] != ApiFrameType.BLUETOOTH_GAP_SCAN_STATUS.code: + raise InvalidPacketException( + message="This packet is not an BLUETOOTH_GAP_SCAN_STATUS" + ) + + return BluetoothGAPScanStatusPacket( + raw[4], op_mode=operating_mode) + + def needs_id(self): + """ + Override method. + + .. seealso:: + | :meth:`.XBeeAPIPacket.needs_id` + """ + return False + + def _get_api_packet_spec_data(self): + """ + Override method. + + .. seealso:: + | :meth:`.XBeeAPIPacket._get_API_packet_spec_data` + """ + ret = bytearray() + ret.append(self.__scan_status) + ret.append(0) + return ret + + def _get_api_packet_spec_data_dict(self): + """ + Override method. + + .. seealso:: + | :meth:`.XBeeAPIPacket._get_API_packet_spec_data_dict` + """ + return {DictKeys.SCAN_STATUS: self.__scan_status} + + @property + def scan_status(self): + """ + Returns the scan status of the Bluetooth scanner. + + Returns: + Integer: scan status + """ + return self.__scan_status + + @scan_status.setter + def scan_status(self, scan_status): + """ + Sets the scan status. + + Args: + scan_status: (Integer) scan status + """ + self.__scan_status = scan_status diff --git a/digi/xbee/packets/factory.py b/digi/xbee/packets/factory.py index b78465b..99be143 100644 --- a/digi/xbee/packets/factory.py +++ b/digi/xbee/packets/factory.py @@ -123,6 +123,8 @@ RemoteATCommandResponseWifiPacket, IODataSampleRxIndicatorWifiPacket from digi.xbee.packets.zigbee import RegisterJoiningDevicePacket,\ RegisterDeviceStatusPacket, RouteRecordIndicatorPacket, OTAFirmwareUpdateStatusPacket +from digi.xbee.packets.bluetooth import BluetoothGAPScanLegacyAdvertisementResponsePacket, \ + BluetoothGAPScanExtendedAdvertisementResponsePacket, BluetoothGAPScanStatusPacket def build_frame(packet_bytearray, operating_mode=OperatingMode.API_MODE): @@ -319,4 +321,16 @@ def build_frame(packet_bytearray, operating_mode=OperatingMode.API_MODE): if frame_type == ApiFrameType.OTA_FIRMWARE_UPDATE_STATUS: return OTAFirmwareUpdateStatusPacket.create_packet(packet_bytearray, operating_mode) + if frame_type == ApiFrameType.BLUETOOTH_GAP_SCAN_LEGACY_ADVERTISEMENT_RESPONSE: + return BluetoothGAPScanLegacyAdvertisementResponsePacket.create_packet( + packet_bytearray, operating_mode) + + if frame_type == ApiFrameType.BLUETOOTH_GAP_SCAN_EXTENDED_ADVERTISEMENT_RESPONSE: + return BluetoothGAPScanExtendedAdvertisementResponsePacket.create_packet( + packet_bytearray, operating_mode) + + if frame_type == ApiFrameType.BLUETOOTH_GAP_SCAN_STATUS: + return BluetoothGAPScanStatusPacket.create_packet(packet_bytearray, + operating_mode) + return UnknownXBeePacket.create_packet(packet_bytearray, operating_mode=operating_mode) diff --git a/digi/xbee/reader.py b/digi/xbee/reader.py index a3c8054..2c344a9 100644 --- a/digi/xbee/reader.py +++ b/digi/xbee/reader.py @@ -1,4 +1,4 @@ -# Copyright 2017-2021, Digi International Inc. +# Copyright 2017-2024, Digi International Inc. # # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this @@ -25,8 +25,11 @@ from digi.xbee.models.address import XBee64BitAddress, XBee16BitAddress from digi.xbee.models.atcomm import ATStringCommand from digi.xbee.models.hw import HardwareVersion -from digi.xbee.models.message import XBeeMessage, ExplicitXBeeMessage, IPMessage, \ - SMSMessage, UserDataRelayMessage +from digi.xbee.models.message import ( + XBeeMessage, ExplicitXBeeMessage, IPMessage, SMSMessage, + UserDataRelayMessage, BLEGAPScanLegacyAdvertisementMessage, + BLEGAPScanExtendedAdvertisementMessage, BLEGAPScanStatusMessage +) from digi.xbee.models.mode import OperatingMode from digi.xbee.models.options import XBeeLocalInterface from digi.xbee.models.protocol import XBeeProtocol @@ -451,6 +454,31 @@ class FileSystemFrameReceived(XBeeEvent): """ +class BLEGAPScanReceived(XBeeEvent): + """ + This event is fired when an XBee receives data from the BLE scan interface. + + The callbacks to handle these events will receive the following arguments: + 1. data (Bytearray): Received Bluetooth data. + + .. seealso:: + | :class:`.XBeeEvent` + """ + + +class BLEGAPScanStatusReceived(XBeeEvent): + """ + This event is fired when an XBee receives status from the BLE + scan interface. + + The callbacks for handle this events will receive the following arguments: + 1. Status (:class:`.BLEGAPScanStatus`): Gap scan Status code. + + .. seealso:: + | :class:`.XBeeEvent` + """ + + class NetworkUpdateProgress(XBeeEvent): """ This event is fired when the progress of a running firmware update changes. @@ -546,6 +574,8 @@ def __init__(self, comm_iface, xbee_device, queue_max_size=None): self.__route_record_indicator_received_from = RouteRecordIndicatorReceived() self.__dm_route_information_received_from = RouteInformationReceived() self.__fs_frame_received = FileSystemFrameReceived() + self.__ble_gap_scan_received = BLEGAPScanReceived() + self.__ble_gap_scan_status_received = BLEGAPScanStatusReceived() # API internal callbacks: self.__packet_received_api = xbee_device.get_xbee_device_callbacks() @@ -985,6 +1015,36 @@ def add_fs_frame_received_callback(self, callback): elif callback: self.__fs_frame_received += callback + def add_ble_gap_advertisement_received_callback(self, callback): + """ + Adds a callback for the event :class:`.BluetoothDataReceived`. + + Args: + callback (Function or List of functions): Callback. Receives one + argument. + + * The data received as a Bytearray + """ + if isinstance(callback, list): + self.__ble_gap_scan_received.extend(callback) + elif callback: + self.__ble_gap_scan_received += callback + + def add_ble_gap_scan_status_received_callback(self, callback): + """ + Adds a callback for the event :class:`.BluetoothDataReceived`. + + Args: + callback (Function or List of functions): Callback. Receives one + argument. + + * The data received as a Bytearray + """ + if isinstance(callback, list): + self.__ble_gap_scan_status_received.extend(callback) + elif callback: + self.__ble_gap_scan_status_received += callback + def del_packet_received_callback(self, callback): """ Deletes a callback for the callback list of :class:`.PacketReceived` @@ -1222,6 +1282,34 @@ def del_fs_frame_received_callback(self, callback): """ self.__fs_frame_received -= callback + def del_ble_gap_advertisement_received_callback(self, callback): + """ + Deletes a callback for the callback list of + :class:`.BluetoothDataReceived` event. + + Args: + callback (Function): Callback to delete. + + Raises: + ValueError: If `callback` is not in the callback list of + :class:`.BluetoothDataReceived` event. + """ + self.__ble_gap_scan_received -= callback + + def del_ble_gap_scan_status_received_callback(self, callback): + """ + Deletes a callback for the callback list of + :class:`.BluetoothDataReceived` event. + + Args: + callback (Function): Callback to delete. + + Raises: + ValueError: If `callback` is not in the callback list of + :class:`.BluetoothDataReceived` event. + """ + self.__ble_gap_scan_status_received -= callback + def get_packet_received_callbacks(self): """ Returns the list of registered callbacks for received packets. @@ -1377,6 +1465,24 @@ def get_fs_frame_received_callbacks(self): """ return self.__fs_frame_received + def get_ble_gap_scan_received_callbacks(self): + """ + Returns the list of registered callbacks for received Bluetooth data. + + Returns: + List: List of :class:`.BluetoothDataReceived` events. + """ + return self.__ble_gap_scan_received + + def get_ble_gap_scan_status_received_callbacks(self): + """ + Returns the list of registered callbacks for received Bluetooth data. + + Returns: + List: List of :class:`.BluetoothDataReceived` events. + """ + return self.__ble_gap_scan_status_received + def __execute_user_callbacks(self, packet, remote=None): """ Executes callbacks corresponding to the received packet. @@ -1550,6 +1656,47 @@ def __execute_user_callbacks(self, packet, remote=None): packet.command.status, rcv_opts))) + # Bluetooth BLE GAP Scan Legacy Advertisement Response + elif f_type == ApiFrameType.BLUETOOTH_GAP_SCAN_LEGACY_ADVERTISEMENT_RESPONSE: + self.__ble_gap_scan_received(BLEGAPScanLegacyAdvertisementMessage( + packet.address, + packet.address_type, + packet.advertisement_flags, + packet.rssi, + packet.payload)) + self._log.debug(self._LOG_PATTERN.format( + comm_iface=str(self.__xbee.comm_iface), event="RECEIVED", + fr_type="BLE GAP SCAN LEGACY", sender=str(packet.address), + more_data=packet.payload.decode(encoding='utf8', errors='ignore'))) + + # Bluetooth BLE GAP Scan Extended Advertisement Response + elif f_type == ApiFrameType.BLUETOOTH_GAP_SCAN_EXTENDED_ADVERTISEMENT_RESPONSE: + self.__ble_gap_scan_received(BLEGAPScanExtendedAdvertisementMessage( + packet.address, + packet.address_type, + packet.advertisement_flags, + packet.rssi, + packet.advertisement_set_id, + packet.primary_phy, + packet.secondary_phy, + packet.tx_power, + packet.periodic_interval, + packet.data_completeness, + packet.payload)) + self._log.debug(self._LOG_PATTERN.format( + comm_iface=str(self.__xbee.comm_iface), event="RECEIVED", + fr_type="BLE GAP SCAN EXTENDED", sender=str(packet.address), + more_data=packet.payload.decode(encoding='utf8', errors='ignore'))) + + # Bluetooth BLE GAP Scan Status Response + elif f_type == ApiFrameType.BLUETOOTH_GAP_SCAN_STATUS: + self.__ble_gap_scan_status_received(BLEGAPScanStatusMessage( + packet.scan_status)) + self._log.debug(self._LOG_PATTERN.format( + comm_iface=str(self.__xbee.comm_iface), event="RECEIVED", + fr_type="BLE GAP SCAN STATUS", sender="None", + more_data=str(packet.scan_status))) + @staticmethod def __get_remote_device_data_from_packet(packet, uses_16bit_addr): """ diff --git a/digi/xbee/recovery.py b/digi/xbee/recovery.py index 8188f21..19a807a 100644 --- a/digi/xbee/recovery.py +++ b/digi/xbee/recovery.py @@ -1,4 +1,4 @@ -# Copyright 2019-2023, Digi International Inc. +# Copyright 2019-2024, Digi International Inc. # # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this @@ -33,6 +33,8 @@ HardwareVersion.XBEE3_TH.code, HardwareVersion.XBEE3_RR.code, HardwareVersion.XBEE3_RR_TH.code, + HardwareVersion.XBEE_BLU.code, + HardwareVersion.XBEE_BLU_TH.code, HardwareVersion.XBEE3_DM_LR.code, HardwareVersion.XBEE3_DM_LR_868.code, HardwareVersion.XBEE_XR_900_TH.code, diff --git a/doc/api/digi.xbee.ble.rst b/doc/api/digi.xbee.ble.rst new file mode 100644 index 0000000..8777906 --- /dev/null +++ b/doc/api/digi.xbee.ble.rst @@ -0,0 +1,7 @@ +digi\.xbee\.ble module +============================= + +.. automodule:: digi.xbee.ble + :members: + :inherited-members: + :show-inheritance: diff --git a/doc/api/digi.xbee.packets.bluetooth.rst b/doc/api/digi.xbee.packets.bluetooth.rst new file mode 100644 index 0000000..8d73b15 --- /dev/null +++ b/doc/api/digi.xbee.packets.bluetooth.rst @@ -0,0 +1,7 @@ +digi\.xbee\.packets\.bluetooth module +===================================== + +.. automodule:: digi.xbee.packets.bluetooth + :members: + :inherited-members: + :show-inheritance: diff --git a/doc/api/digi.xbee.packets.rst b/doc/api/digi.xbee.packets.rst index 09ae1b8..3d61ed9 100644 --- a/doc/api/digi.xbee.packets.rst +++ b/doc/api/digi.xbee.packets.rst @@ -13,6 +13,7 @@ Submodules digi.xbee.packets.aft digi.xbee.packets.base + digi.xbee.packets.bluetooth digi.xbee.packets.cellular digi.xbee.packets.common digi.xbee.packets.devicecloud diff --git a/doc/api/digi.xbee.rst b/doc/api/digi.xbee.rst index 16fa056..2248281 100644 --- a/doc/api/digi.xbee.rst +++ b/doc/api/digi.xbee.rst @@ -20,6 +20,7 @@ Submodules .. toctree:: + digi.xbee.ble digi.xbee.comm_interface digi.xbee.devices digi.xbee.exception diff --git a/doc/examples.rst b/doc/examples.rst index ab54e65..3ebbc62 100644 --- a/doc/examples.rst +++ b/doc/examples.rst @@ -332,6 +332,19 @@ You can find the example at the following path: :ref:`communicateSendBluetoothData`. +Generic Access Profile (GAP) Scan (BLU Devices) +``````````````````````````````````````````````` + +The two sample applications show how to issue a BLE GAP scan request, +and display its results. + +You can find the first example at the following path: +**examples/communication/bluetooth/GapScanExample** + +You can find the second example at the following path: +**examples/communication/bluetooth/GapScanCursesDemo** + + Send MicroPython Data ````````````````````` diff --git a/doc/getting_started_with_xbee_python_library.rst b/doc/getting_started_with_xbee_python_library.rst index 99f9722..7fb0dab 100644 --- a/doc/getting_started_with_xbee_python_library.rst +++ b/doc/getting_started_with_xbee_python_library.rst @@ -129,12 +129,8 @@ Zigbee, DigiMesh, Point-to-Multipoint, or Wi-Fi) and must be configured to operate in the same network. .. note:: - If you are getting started with cellular, you only need to configure one - device. Cellular protocol devices are connected directly to the Internet, - so there is no network of remote devices to communicate with them. For - the cellular protocol, the XBee application demonstrated in the getting - started guide differs from other protocols. The cellular protocol sends and - reads data from an echo server. + If you are getting started with cellular or BLU, you only need to configure + one device since there is no network of remote devices to communicate with. Use XCTU to configure the devices. Plug the devices into the XBee adapters and connect them to your computer’s USB or serial ports. @@ -155,6 +151,7 @@ Repeat these steps to configure your XBee devices using XCTU. * :ref:`gsgConfigDPdevices` * :ref:`gsgConfigCellulardevices` * :ref:`gsgConfigWiFidevices` +* :ref:`gsgConfigBludevices` .. _gsgConfig802devices: @@ -324,6 +321,20 @@ Wi-Fi devices verifying it has a value different than **0.0.0.0**. +.. _gsgConfigBludevices: + +BLU devices +``````````` + +#. Click **Load default firmware settings** in the **Radio Configuration** + toolbar to load the default values for the device firmware. +#. Ensure the API mode (API1 or API2) is enabled. To do so, the **AP** + parameter value must be **1** (API mode without escapes) or **2** (API mode + with escapes). +#. Click **Write radio settings** in the **Radio Configuration** toolbar to + apply the new values to the module. + + .. _gsgRunApp: Run your first XBee Python application @@ -341,6 +352,7 @@ the corresponding steps depending on the protocol of your XBee devices. * :ref:`gsgAppZBDMDP802` * :ref:`gsgAppWiFi` * :ref:`gsgAppCellular` +* :ref:`gsgAppBlu` .. _gsgAppZBDMDP802: @@ -563,3 +575,68 @@ execute it: .. code:: > device.close() + + +.. _gsgAppBlu: + +BLU devices +``````````` + +BLU devices are stand alone devices, so there is no network +of remote devices to communicate with them. For the BLE +protocol, the application demonstrated in this guide differs from other +protocols. + +The application issues a GAP Scan request and displays the received responses: + +#. Open the Python interpreter and write the application commands. + + #. Import the ``BluDevice`` class by executing the following command: + + .. code:: + + > from digi.xbee.devices import BluDevice + + #. Instantiate a Blu XBee device: + + .. code:: + + > device = BluDevice("COM1", 9600) + + .. note:: + Remember to replace the COM port with the one your XBee device + is connected to. In UNIX-based systems, the port usually starts with + ``/dev/tty``. + + #. Open the connection with the device: + + .. code:: + + > ble_manager = device.get_ble_manager() + > device.open() + + #. Create a callback function for live GAP scan results: + + .. code:: + + > def scan_callback(data): + > print(data.to_dict()) + + #. Register a callback to receive live GAP scan results: + + .. code:: + + > ble_manager.add_ble_gap_advertisement_received_callback(scan_callback) + + #. Start a 30 seconds BLE Gap scan: + + .. code:: + + > ble_manager.start_ble_gap_scan(30, 10000, 10000, False, "") + + #. Remove the callback and close the connection with the device: + + .. code:: + + > ble_manager.del_ble_gap_advertisement_received_callback(scan_callback) + > device.close() diff --git a/doc/user_doc/working_with_xbee_classes.rst b/doc/user_doc/working_with_xbee_classes.rst index a73c86f..346e085 100644 --- a/doc/user_doc/working_with_xbee_classes.rst +++ b/doc/user_doc/working_with_xbee_classes.rst @@ -4,7 +4,7 @@ Work with XBee classes When working with the XBee Python Library, start with an XBee object that represents a physical module. A physical XBee is the combination of hardware and firmware. Depending on that combination, the device runs a specific wireless -communication protocol such as Zigbee, 802.15.4, DigiMesh, Wi-Fi, or Cellular. +communication protocol such as Zigbee, 802.15.4, DigiMesh, BLE, Wi-Fi, or Cellular. An ``XBeeDevice`` class represents the XBee module in the API. These protocols share some features and settings, but there are some differences @@ -20,6 +20,7 @@ The XBee Python Library supports one XBee class per protocol, as follows: * XBee 802.15.4 (``Raw802Device``) * XBee DigiMesh (``DigiMeshDevice``) * XBee Point-to-multipoint (``DigiPointDevice``) +* XBee BLU (``BluDevice``) * XBee IP devices (This is a non-instantiable class) * XBee Cellular (``CellularDevice``) @@ -71,6 +72,8 @@ local device are listed in the following table: +-----------------+--------------------------------------+ | DigiPointDevice | Point-to-multipoint protocol | +-----------------+--------------------------------------+ +| BluDevice | BLE protocol | ++-----------------+--------------------------------------+ | CellularDevice | Cellular protocol | +-----------------+--------------------------------------+ | WiFiDevice | Wi-Fi protocol | @@ -132,7 +135,7 @@ table lists the remote XBee classes: .. note:: - XBee Cellular and Wi-Fi protocols do not support remote devices. + XBee Cellular, Wi-Fi, and BLE protocols do not support remote devices. To instantiate a remote XBee object, provide the following parameters: diff --git a/examples/communication/bluetooth/GapScanCursesDemo/GapScanCursesDemo.py b/examples/communication/bluetooth/GapScanCursesDemo/GapScanCursesDemo.py new file mode 100644 index 0000000..d2e5f8a --- /dev/null +++ b/examples/communication/bluetooth/GapScanCursesDemo/GapScanCursesDemo.py @@ -0,0 +1,183 @@ +# Copyright 2024, Digi International Inc. +# +# Permission to use, copy, modify, and/or distribute this software for any +# purpose with or without fee is hereby granted, provided that the above +# copyright notice and this permission notice appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +import copy +import curses +from digi.xbee.devices import BluDevice +from digi.xbee.models.message import ( + BLEGAPScanLegacyAdvertisementMessage, + BLEGAPScanExtendedAdvertisementMessage +) + +# TODO: Replace with the serial port where your local module is connected to. +PORT = "COM1" +# TODO: Replace with the baud rate of your local module. +BAUD_RATE = 9600 + + +class GapScanCursesDemo: + """ + BLE GAP Scan Demo using the python curses module + """ + + # Various screen offsets to draw on the screen + _HEADER_ROW_OFFSET = 0 + _DATA_ROW_OFFSET = 2 + _NAME_COL_OFFSET = 0 + _MAC_COL_OFFSET = 26 + _RSSI_COL_OFFSET = 48 + _CONNECTABLE_COL_OFFSET = 58 + _SEEN_COL_OFFSET = 74 + + def __init__(self, ble_manager=None): + """ + Class constructor for the GapScanCursesDemo + """ + self._ble_manager = ble_manager + self._list = [] + self._stdscr = None + + def run(self): + """ + Runs the BLE GAP scan demo + """ + curses.wrapper(self._main_loop) + + def _add_advertisement_to_list(self, new_item): + # Attempt to find the device in current list, if it exists + found = next((item for item in self._list if item['Address'] == new_item['Address']), None) + if found: + # Found it. Update data and increase counter + found['Name'] = new_item['Name'] + found['RSSI'] = new_item['RSSI'] + found['Connectable'] = new_item['Connectable'] + found['Count'] += 1 + else: + # New device. Add it to our current list + new_item["Count"] = 1 + self._list.append(new_item) + + def _scan_callback(self, data): + """ + BLE GAP Scan Callback + """ + if isinstance( + data, + (BLEGAPScanLegacyAdvertisementMessage, BLEGAPScanExtendedAdvertisementMessage) + ): + new_item = copy.deepcopy(data.to_dict()) + self._add_advertisement_to_list(new_item) + + count = 0 + for item in self._list: + name = item['Name'] if item['Name'] else "N/A" + # Clear name area, and then write the name + self._stdscr.addstr(self._DATA_ROW_OFFSET + count, + self._NAME_COL_OFFSET, ' ' * 22) + self._stdscr.addstr(self._DATA_ROW_OFFSET + count, + self._NAME_COL_OFFSET, name) + # Add rest of the data points + addr = ':'.join(item['Address'][i:i+2] for i in range(0, 12, 2)) + self._stdscr.addstr(self._DATA_ROW_OFFSET + count, + self._MAC_COL_OFFSET, addr) + self._stdscr.addstr(self._DATA_ROW_OFFSET + count, + self._RSSI_COL_OFFSET - 1, + str(int(item['RSSI'])) + ' dBm') + self._stdscr.addstr(self._DATA_ROW_OFFSET + count, + self._CONNECTABLE_COL_OFFSET, + str(item['Connectable'])) + self._stdscr.addstr(self._DATA_ROW_OFFSET + count, + self._SEEN_COL_OFFSET, str(item['Count'])) + count += 1 + + # Finally, refresh the screen + self._stdscr.refresh() + + def _setup_screen(self): + """ + Set up the initial screen + """ + + # Start colors in curses + curses.use_default_colors() + curses.start_color() + curses.init_pair(1, curses.COLOR_WHITE, curses.COLOR_BLACK) + + # Clear and set the bacoground screen for a blank canvas + self._stdscr.clear() + self._stdscr.bkgd(' ', curses.color_pair(1)) + + # Hide the cursor + curses.curs_set(0) + + # Set up our titles at the top of the screen + self._stdscr.addstr(self._HEADER_ROW_OFFSET, + self._NAME_COL_OFFSET, "Name") + self._stdscr.addstr(self._HEADER_ROW_OFFSET, + self._MAC_COL_OFFSET, "MAC Address") + self._stdscr.addstr(self._HEADER_ROW_OFFSET, + self._RSSI_COL_OFFSET, "RSSI") + self._stdscr.addstr(self._HEADER_ROW_OFFSET, + self._CONNECTABLE_COL_OFFSET, "Connectable") + self._stdscr.addstr(self._HEADER_ROW_OFFSET, + self._SEEN_COL_OFFSET, "Seen") + + # Refresh once more + self._stdscr.refresh() + + def _main_loop(self, stdscr): + """ + The GapScanCursesDemo's main loop + """ + self._stdscr = stdscr + + # Set up the basic screen + self._setup_screen() + + # Set up callback for any results that come in + self._ble_manager.add_ble_gap_advertisement_received_callback(self._scan_callback) + + # Start the GAP scan request + self._ble_manager.start_ble_gap_scan(0, 10000, 10000, False, '') + + # Loop where k is the last character pressed + k = 0 + while (k != ord('q')): + k = self._stdscr.getch() + + # Out of loop, stop scan + self._ble_manager.stop_ble_gap_scan() + self._ble_manager.del_ble_gap_advertisement_received_callback(self._scan_callback) + + +def main(): + ble_manager = None + device = BluDevice(PORT, BAUD_RATE) + try: + device.open() + ble_manager = device.get_ble_manager() + ret = GapScanCursesDemo(ble_manager) + ret.run() + except Exception as e: + print(str(e)) + exit(1) + finally: + if device.is_open(): + if ble_manager: + ble_manager.close() + device.close() + + +if __name__ == "__main__": + main() diff --git a/examples/communication/bluetooth/GapScanCursesDemo/readme.txt b/examples/communication/bluetooth/GapScanCursesDemo/readme.txt new file mode 100644 index 0000000..7a024dc --- /dev/null +++ b/examples/communication/bluetooth/GapScanCursesDemo/readme.txt @@ -0,0 +1,80 @@ + Introduction + ------------ + This demo Python application shows how to set up a BLE GAP scan, start it, + receive and process the scan results coming from the scan on the XBee device. + + The application registers a callback to be notified when new GAP scan data + coming from Bluetooth BLE is received and displays it on the screen. + + NOTE: This demo is currently only supported on the XBee BLU device + which uses 'XBeeBLU' device class. + + + Requirements + ------------ + To run this demo you will need: + + * One XBee BLU module in API mode and its corresponding carrier board + (XBIB or equivalent). + * The XCTU application (available at www.digi.com/xctu). + * The Python 'curses' library installed. + + + Compatible protocols + -------------------- + * Bluetooth/BLE + + + Demo setup + ------------- + 1) Plug the XBee BLU radio into the XBee adapter and connect it to your + computer's USB or serial port. + + 2) Ensure that the module is in API mode. + For further information on how to perform this task, read the + 'Configuring Your XBee Modules' topic of the Getting Started guide. + + 3) Enable the Bluetooth interface of the XBee BLU device using XCTU. + For further information on how to perform this task, refer to the + XCTU user manual. + + 4) Set the port and baud rate of the XBee BLU radio in the demo file. + If you configured the module in the previous step with XCTU, you will + see the port number and baud rate in the 'Port' label of the device + on the left view. + + 5) Install the Python 'curses' library in your Python enviroment. + - For Linux based installations, this typically has already been done + for you by your Linux distribution. + If not, use 'pip' to install the 'curses' package. + + - For Windows based installations, you will need to install the + 'windows-curses' library using PIP. + For example, the following steps should work, assuming the + Python enviroment has been properly set up: + + python.exe -m ensurepip --upgrade + python.exe -m pip install --upgrade pip setuptools wheel + python.exe -m pip install windows-curses + + + Running the demo + ------------------- + Launch the application by running it: + python3 GapScanCursesDemo.py + + The window/display will be cleared, and then a BLE GAP scan + will be started. + + As devices are discovered by the scan, they will be displayed + in the window/display. + + Each device will be displayed by: + - Short name, if the device provides it, otherwise "N/A". + - BLE MAC address. + - Current RSSI value. + - Whether the device is Connectable or not. + - Number of times they have been seen. + (Number of times we have seen them send a BLE packet over the air) + + To quit the application, press 'q'. diff --git a/examples/communication/bluetooth/GapScanExample/GapScanExample.py b/examples/communication/bluetooth/GapScanExample/GapScanExample.py new file mode 100644 index 0000000..0124e6a --- /dev/null +++ b/examples/communication/bluetooth/GapScanExample/GapScanExample.py @@ -0,0 +1,75 @@ +# Copyright 2024, Digi International Inc. +# +# Permission to use, copy, modify, and/or distribute this software for any +# purpose with or without fee is hereby granted, provided that the above +# copyright notice and this permission notice appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +import time +from digi.xbee.devices import BluDevice +from digi.xbee.models.message import ( + BLEGAPScanLegacyAdvertisementMessage, + BLEGAPScanExtendedAdvertisementMessage, +) + + +# TODO: Replace with the serial port where your local module is connected to. +PORT = "COM1" +# TODO: Replace with the baud rate of your local module. +BAUD_RATE = 9600 + +# Time for the BLE GAP scan to run for +TIME_TO_SCAN = 10 + + +def main(): + print(" +---------------------------------------------------+") + print(" | XBee Python Library Receive Bluetooth Data Sample |") + print(" +---------------------------------------------------+\n") + + device = BluDevice(PORT, BAUD_RATE) + + try: + ble_manager = device.get_ble_manager() + device.open() + + def scan_callback(data): + """ + BLE GAP scan Callback + + This function will get called whenever + a new GAP scan entry has been received. + """ + if isinstance( + data, + (BLEGAPScanLegacyAdvertisementMessage, BLEGAPScanExtendedAdvertisementMessage) + ): + print(data.to_dict()) + + ble_manager.add_ble_gap_advertisement_received_callback(scan_callback) + + print("Starting BLE GAP scan...\n") + ble_manager.start_ble_gap_scan(TIME_TO_SCAN, 10000, 10000, False, "") + + # Wait for a moment to allow the radio to start the scan + time.sleep(.5) + + # Wait until the scan finishes. + while ble_manager.is_scan_running(): + time.sleep(1) + + finally: + if device is not None and device.is_open(): + ble_manager.del_ble_gap_advertisement_received_callback(scan_callback) + device.close() + + +if __name__ == '__main__': + main() diff --git a/examples/communication/bluetooth/GapScanExample/readme.txt b/examples/communication/bluetooth/GapScanExample/readme.txt new file mode 100644 index 0000000..b8bd4a3 --- /dev/null +++ b/examples/communication/bluetooth/GapScanExample/readme.txt @@ -0,0 +1,55 @@ + Introduction + ------------ + This Python example shows how to set up a BLE GAP scan, start it, + receive and process the scan results and scan status coming from + the scan on the XBee device. + + The application registers a callback to be notified when new GAP scan data + coming from Bluetooth BLE is received and displays it on the screen, + and also registers a callback to be notified when the GAP scan status + changes. + + NOTE: This example is currently only supported on the XBee BLU device + which uses 'XBeeBLU' device class. + + + Requirements + ------------ + To run this example you will need: + + * One XBee BLU module in API mode and its corresponding carrier board + (XBIB or equivalent). + * The XCTU application (available at www.digi.com/xctu). + + + Compatible protocols + -------------------- + * Bluetooth/BLE + + + Example setup + ------------- + 1) Plug the XBee BLU radio into the XBee adapter and connect it to your + computer's USB or serial port. + + 2) Ensure that the module is in API mode. + For further information on how to perform this task, read the + 'Configuring Your XBee Modules' topic of the Getting Started guide. + + 3) Enable the Bluetooth interface of the XBee BLU device using XCTU. + For further information on how to perform this task, refer to the + XCTU user manual. + + 4) Set the port and baud rate of the XBee BLU radio in the example file. + If you configured the module in the previous step with XCTU, you will + see the port number and baud rate in the 'Port' label of the device + on the left view. + + + Running the example + ------------------- + Launch the application by running it: + python3 GapScanExample.py + + As devices are discovered by the scan, the scan data + will be displayed. diff --git a/functional_tests/Bluetooth/device_info.py b/functional_tests/Bluetooth/device_info.py new file mode 100644 index 0000000..901094f --- /dev/null +++ b/functional_tests/Bluetooth/device_info.py @@ -0,0 +1,57 @@ +# Copyright 2024, Digi International Inc. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +from digi.xbee.devices import BluDevice + +# Configure your test device port and baud rate +PORT_LOCAL = "COM1" +BAUD_RATE_LOCAL = 9600 + + +def main(): + print(" +---------------------------+") + print(" | XBee BLU device info test |") + print(" +---------------------------+\n") + + local = BluDevice(PORT_LOCAL, BAUD_RATE_LOCAL) + + try: + local.open() + local.read_device_info() + + print("Firmware Version:", local.get_firmware_version().hex()) + + hv = local.get_hardware_version() + print("Hardware Version: %s (0x%02X)" % (hv, hv.code)) + + print("Bluetooth MAC:", local.get_bluetooth_mac_addr()) + print( + "Bluetooth Identifier:", + ascii( + local.get_parameter("BI").decode( + "ascii", errors="backslashreplace" + ) + ) + ) + + print("Serial Number:", local.get_64bit_addr()) + print("Node ID:", ascii(local.get_node_id())) + + finally: + if local.is_open(): + local.close() + + +if __name__ == "__main__": + main() diff --git a/functional_tests/Bluetooth/test_gap_scan.py b/functional_tests/Bluetooth/test_gap_scan.py new file mode 100644 index 0000000..efba084 --- /dev/null +++ b/functional_tests/Bluetooth/test_gap_scan.py @@ -0,0 +1,416 @@ +# Copyright 2024, Digi International Inc. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +import time +from typing import cast +from digi.xbee.devices import BluDevice +from digi.xbee.models.message import BLEGAPScanLegacyAdvertisementMessage, BLEGAPScanExtendedAdvertisementMessage +from digi.xbee.models.status import BLEMACAddressType +from digi.xbee.exception import TimeoutException +from digi.xbee.models.status import BLEGAPScanStatus + + +# Configure your test device port and baud rate +PORT_LOCAL = "/dev/ttyUSB0" +BAUD_RATE_LOCAL = "9600" +# Configure a FILTER for testing that you only receive messages from this device +FILTER_TEST = None +# FILTER_TEST = 'ResMed' +# FILTER_TEST = 'ResMed 289272' + +# Set to True to check and verify that legacy advertisements are properly received +TEST_LEGACY_ADVERTISEMENTS = True + +# Set to True to check and verify that extended advertisements are properly received +TEST_EXTENDED_ADVERTISEMENTS = False + + +def flush_advertisements(ble_manager): + # Make sure scanning is off + ble_manager.stop_ble_gap_scan() + time.sleep(1) + print("Flushed advertisements") + + +def flush_gap_status_messages(ble_manager): + # Make sure scanning is off + ble_manager.stop_ble_gap_scan() + time.sleep(1) + print("Flushed gap status messages") + + +def test_gap_scan_request(ble_manager): + rx_msg = None + + def gap_scan_callback(msg): + nonlocal rx_msg + print(f"{msg.to_dict()}") + rx_msg = msg + + flush_advertisements(ble_manager) + + print("\n\nTest GAP scan request") + + print("Test scan with infinite duration") + ble_manager.add_ble_gap_advertisement_received_callback(gap_scan_callback) + print("Start scanning") + ble_manager.start_ble_gap_scan(0, 0x1111111, 0x1111111, False, "") + max_time = 60.0 + print("Wait {} seconds for messages".format(max_time)) + start_time = time.time() + while True: + time.sleep(0.1) + if ((time.time() - start_time > max_time) or (rx_msg is not None)): + break + print("Stop scanning") + ble_manager.stop_ble_gap_scan() + # Verify we got at least 1 message + if rx_msg is None: + raise RuntimeError("Failed to receive any Bluetooth advertisements") + + # Test GAP scan for short duration + duration = 2 + print("Test GAP scan of {} seconds".format(duration)) + ble_manager.start_ble_gap_scan(duration, 0x2222222, 0x2222222, False, "") + print("Wait for messages for {} seconds".format(duration + 1)) + time.sleep(duration + 1) + print("Clear messages, we don't expect to get any more") + rx_msg = None + time.sleep(2) + if rx_msg is not None: + raise RuntimeError("Got message past the duration time") + print("Stop scanning") + ble_manager.stop_ble_gap_scan() + ble_manager.del_ble_gap_advertisement_received_callback(gap_scan_callback) + + print("Success") + + +def test_gap_scan_parameters(ble_manager): + flush_advertisements(ble_manager) + + print("\n\nTest different parameter ranges for start_ble_gap_scan") + duration = 0xFFFF + print("Test maximum duration {}".format(duration)) + ble_manager.start_ble_gap_scan(duration, 0x1111111, 0x1111111, False, "") + ble_manager.stop_ble_gap_scan() + + duration += 1 + print("Test maximum duration plus 1 ({}) fails".format(duration)) + try: + ble_manager.start_ble_gap_scan(duration, 0x1111111, 0x1111111, False, "") + except Exception: + pass + else: + raise RuntimeError("Failed to see exception passing in duration value of {}".format(duration)) + + scan_window = 0x9C4 + print("Test scan window minimum value") + ble_manager.start_ble_gap_scan(0, scan_window, 0x1111111, False, "") + ble_manager.stop_ble_gap_scan() + + scan_window -= 1 + print("Test scan window minimum value minus 1 ({}) fails".format(scan_window)) + try: + ble_manager.start_ble_gap_scan(0, scan_window, 0x1111111, False, "") + except Exception: + pass + else: + raise RuntimeError("Failed to see exception passing in scan window value of {}".format(scan_window)) + + scan_window = 0x270FD8F + scan_interval = scan_window # Must be greater or equal to scan window + print("Test scan window max value of ({})".format(scan_window)) + ble_manager.start_ble_gap_scan(0, scan_window, scan_interval, False, "") + ble_manager.stop_ble_gap_scan() + + scan_window += 1 + print("Test scan window max value plus 1 ({}) fails".format(scan_window)) + try: + ble_manager.start_ble_gap_scan(0, scan_window, scan_interval, False, "") + except Exception: + pass + else: + raise RuntimeError("Failed to see exception passing in scan window value of {}".format(scan_window)) + + scan_interval = 0x9C4 + scan_window = scan_interval + print("Test scan interval minimum value") + ble_manager.start_ble_gap_scan(0, scan_window, scan_interval, False, "") + ble_manager.stop_ble_gap_scan() + + scan_interval -= 1 + print("Test scan interval minimum value minus 1 ({}) fails".format(scan_interval)) + try: + ble_manager.start_ble_gap_scan(0, scan_window, scan_interval, False, "") + except Exception: + pass + else: + raise RuntimeError("Failed to see exception passing in scan interval value of {}".format(scan_interval)) + + scan_interval = 0x270FD8F + scan_window = 0x1111111 + print("Test scan interval max value of ({})".format(scan_interval)) + ble_manager.start_ble_gap_scan(0, scan_window, scan_interval, False, "") + ble_manager.stop_ble_gap_scan() + + scan_interval += 1 + print("Test scan interval max value plus 1 ({}) fails".format(scan_interval)) + try: + ble_manager.start_ble_gap_scan(0, scan_window, scan_interval, False, "") + except Exception: + pass + else: + raise RuntimeError("Failed to see exception passing in scan interval value of {}".format(scan_interval)) + + print("Test scan window greater than scan interval fails") + scan_interval = 0x1111111 + scan_window = scan_interval + 1 + try: + ble_manager.start_ble_gap_scan(0, scan_window, scan_interval, False, "") + except Exception: + pass + else: + raise RuntimeError("Failed to see exception passing in scan window value of {} and scan interval value of {}".format( + scan_window, scan_interval)) + + print("Success") + + +def test_gap_scan_filter(ble_manager): + global FILTER_TEST + rx_msg = None + failed = False + + def gap_scan_callback(msg): + global FILTER_TEST + nonlocal rx_msg + nonlocal failed + print(f"{msg.to_dict()}") + name = msg.name + if name is None or FILTER_TEST not in name: + failed = True + raise RuntimeError("For filter test expected to get: {} but got {}".format(FILTER_TEST, name)) + rx_msg = msg + + flush_advertisements(ble_manager) + + print("\n\nTest scan with filtering") + ble_manager.add_ble_gap_advertisement_received_callback(gap_scan_callback) + print("Start scanning") + ble_manager.start_ble_gap_scan(0, 0x1111111, 0x1111111, True, FILTER_TEST) + min_time = 5.0 # Test should run for at least this time + max_time = 60.0 + print("Wait {} seconds for messages".format(max_time)) + start_time = time.time() + while True: + time.sleep(0.1) + current_time = time.time() + if ((current_time - start_time > max_time) or failed): + break + if ((current_time - start_time > min_time) and (rx_msg is not None)): + break + + print("Stop scanning") + ble_manager.stop_ble_gap_scan() + ble_manager.del_ble_gap_advertisement_received_callback(gap_scan_callback) + # Verify we got at least 1 message + if rx_msg is None: + raise RuntimeError("Failed to receive any Bluetooth advertisements") + if failed: + raise RuntimeError("Failed to properly filter advertisements") + + print("Success") + + +def test_legacy_advertisement(ble_manager): + rx_msg = None + + def gap_scan_callback(msg): + nonlocal rx_msg + print(f"{msg.to_dict()}") + if isinstance(msg, BLEGAPScanLegacyAdvertisementMessage): + rx_msg = msg + + flush_advertisements(ble_manager) + + print("\n\nTest that we can receive legacy advertisement and that the advertisement message class is correct") + ble_manager.add_ble_gap_advertisement_received_callback(gap_scan_callback) + print("Start scanning") + ble_manager.start_ble_gap_scan(0, 0x1111111, 0x1111111, False, "") + max_time = 60.0 + print("Wait {} seconds for a message".format(max_time)) + start_time = time.time() + while True: + time.sleep(0.1) + if ((time.time() - start_time > max_time) or (rx_msg is not None)): + break + print("Stop scanning") + ble_manager.stop_ble_gap_scan() + ble_manager.del_ble_gap_advertisement_received_callback(gap_scan_callback) + # Verify we got a message + if rx_msg is None: + raise RuntimeError("Failed to receive any Bluetooth legacy advertisements") + + msg = cast(BLEGAPScanLegacyAdvertisementMessage, rx_msg) + + # Verify address + address = msg.address + if len(address.address) != 6: + raise RuntimeError("Invalid address {}".format(address.address)) + + # Verify address type + address_type = msg.address_type + if (address_type not in ( + BLEMACAddressType.PUBLIC, + BLEMACAddressType.STATIC, + BLEMACAddressType.RANDOM_RESOLVABLE, + BLEMACAddressType.RANDOM_NONRESOLVABLE, + BLEMACAddressType.UNKNOWN)): + raise RuntimeError("Invalid address_type {}".format(address_type)) + + connectable = msg.connectable + if not isinstance(connectable, bool): + raise RuntimeError("Invalid connectable {}".format(connectable)) + + rssi = msg.rssi + if not isinstance(rssi, float): + raise TypeError("rssi is not a float, but {}".format(type(rssi))) + if ((rssi > 0.0) or (rssi < -255.0)): + raise ValueError("rssi value {} is out of range".format(rssi)) + + name = msg.name + if name is not None and not isinstance(name, str): + raise TypeError("name is not a string type or None, but {}".format(type(name))) + + +def test_extended_advertisement(ble_manager): + rx_msg = None + + def gap_scan_callback(msg): + nonlocal rx_msg + print(f"{msg.to_dict()}") + if isinstance(msg, BLEGAPScanExtendedAdvertisementMessage): + rx_msg = msg + + flush_advertisements(ble_manager) + + print("\n\nTest that we can receive extended advertisement and that the advertisement message class is correct") + ble_manager.add_ble_gap_advertisement_received_callback(gap_scan_callback) + print("Start scanning") + ble_manager.start_ble_gap_scan(0, 0x1111111, 0x1111111, False, "") + max_time = 60.0 + print("Wait {} seconds for a message".format(max_time)) + start_time = time.time() + while True: + time.sleep(0.1) + if ((time.time() - start_time > max_time) or (rx_msg is not None)): + break + print("Stop scanning") + ble_manager.stop_ble_gap_scan() + ble_manager.del_ble_gap_advertisement_received_callback(gap_scan_callback) + # Verify we got a message + if rx_msg is None: + raise RuntimeError("Failed to receive any Bluetooth extended advertisements") + + msg = cast(BLEGAPScanExtendedAdvertisementMessage, rx_msg) + + # Verify address + address = msg.address + if len(address.address) != 6: + raise RuntimeError("Invalid address {}".format(address.address)) + + # Verify address type + address_type = msg.address_type + if (address_type not in ( + BLEMACAddressType.PUBLIC, + BLEMACAddressType.STATIC, + BLEMACAddressType.RANDOM_RESOLVABLE, + BLEMACAddressType.RANDOM_NONRESOLVABLE, + BLEMACAddressType.UNKNOWN)): + raise RuntimeError("Invalid address_type {}".format(address_type)) + + connectable = msg.connectable + if not isinstance(connectable, bool): + raise RuntimeError("Invalid connectable {}".format(connectable)) + + rssi = msg.rssi + if not isinstance(rssi, float): + raise TypeError("rssi is not a float, but {}".format(type(rssi))) + if ((rssi > 0.0) or (rssi < -255.0)): + raise ValueError("rssi value {} is out of range".format(rssi)) + + name = msg.name + if name is not None and not isinstance(name, str): + raise TypeError("name is not a string type or None, but {}".format(type(name))) + + advertisement_set_id = msg.advertisement_set_id + if not isinstance(advertisement_set_id, int): + raise TypeError("advertisement_set_id is not type int, but {}".format(type(advertisement_set_id))) + + primary_phy = msg.primary_phy + if not isinstance(primary_phy, int): + raise TypeError("primary_phy is not type int, but {}".format(type(primary_phy))) + + secondary_phy = msg.secondary_phy + if not isinstance(secondary_phy, int): + raise TypeError("secondary_phy is not type int, but {}".format(type(secondary_phy))) + + periodic_interval = msg.periodic_interval + if not isinstance(periodic_interval, int): + raise TypeError("periodic_interval is not type int, but {}".format(type(periodic_interval))) + + data_completeness = msg.data_completeness + if not isinstance(data_completeness, int): + raise TypeError("data_completeness is not type int, but {}".format(type(data_completeness))) + + +def test_gap_scan_status_callback(ble_manager): + scan_started = 0 + scan_running = 0 + scan_stopped = 0 + scan_error = 0 + scan_invalid_param = 0 + scan_unknown = 0 + + +def main(): + + xbee = BluDevice(PORT_LOCAL, BAUD_RATE_LOCAL) + ble_manager = xbee.get_ble_manager() + + try: + xbee.open() + + test_gap_scan_request(ble_manager) + + if FILTER_TEST is not None: + test_gap_scan_filter(ble_manager) + + test_gap_scan_parameters(ble_manager) + + if TEST_LEGACY_ADVERTISEMENTS: + test_legacy_advertisement(ble_manager) + + if TEST_EXTENDED_ADVERTISEMENTS: + test_extended_advertisement(ble_manager) + + finally: + + if xbee.is_open(): + xbee.close() + + +if __name__ == "__main__": + main() From 24b6ae6b5d340a9f34e0c1f33967a122a0d6fc73 Mon Sep 17 00:00:00 2001 From: Tatiana Leon Date: Fri, 12 Jul 2024 10:30:31 +0200 Subject: [PATCH 21/36] xbee blue: Update name of product to be 'XBee 3 BLU' Signed-off-by: Tatiana Leon --- CHANGELOG.rst | 4 ++-- digi/xbee/models/hw.py | 4 ++-- .../bluetooth/GapScanCursesDemo/readme.txt | 14 +++++++------- .../bluetooth/GapScanExample/readme.txt | 10 +++++----- functional_tests/Bluetooth/device_info.py | 6 +++--- 5 files changed, 19 insertions(+), 19 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 70d9a17..8524ad0 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -14,7 +14,7 @@ v1.4.2 - XX/XX/202X * XBee 3 Cellular North America Cat 4 * XBee XR 900 TH * XBee XR 868 TH - * XBee BLU + * XBee 3 BLU * Support to retrieve XBee statistics. * Send/receive explicit data in 802.15.4. (XBee 3 modules support this feature) @@ -25,7 +25,7 @@ v1.4.2 - XX/XX/202X configure '$S', '$V', '$W', '$X', and '$Y' XBee parameters to establish Bluetooth password. * Support for sending BLE Generic Access Profile (GAP) scans. - (Only XBee BLU modules support this feature) + (Only XBee 3 BLU modules support this feature) * Bug fixing: * Fix order of nodes when creating a Zigbee source route (#278) diff --git a/digi/xbee/models/hw.py b/digi/xbee/models/hw.py index a84b287..a09afab 100644 --- a/digi/xbee/models/hw.py +++ b/digi/xbee/models/hw.py @@ -106,8 +106,8 @@ class HardwareVersion(Enum): CELLULAR_3_NA_CAT4 = (0x59, "XBee 3 Cellular North America Cat 4") XBEE_XR_900_TH = (0x5A, "XBee XR 900 TH") XBEE_XR_868_TH = (0x5B, "XBee XR 868 TH") - XBEE_BLU = (0x5C, "XBee BLU") - XBEE_BLU_TH = (0x5D, "XBee BLU TH") + XBEE_BLU = (0x5C, "XBee 3 BLU") + XBEE_BLU_TH = (0x5D, "XBee 3 BLU TH") def __init__(self, code, description): self.__code = code diff --git a/examples/communication/bluetooth/GapScanCursesDemo/readme.txt b/examples/communication/bluetooth/GapScanCursesDemo/readme.txt index 7a024dc..846a2ab 100644 --- a/examples/communication/bluetooth/GapScanCursesDemo/readme.txt +++ b/examples/communication/bluetooth/GapScanCursesDemo/readme.txt @@ -6,7 +6,7 @@ The application registers a callback to be notified when new GAP scan data coming from Bluetooth BLE is received and displays it on the screen. - NOTE: This demo is currently only supported on the XBee BLU device + NOTE: This demo is currently only supported on the XBee 3 BLU device which uses 'XBeeBLU' device class. @@ -14,7 +14,7 @@ ------------ To run this demo you will need: - * One XBee BLU module in API mode and its corresponding carrier board + * One XBee 3 BLU module in API mode and its corresponding carrier board (XBIB or equivalent). * The XCTU application (available at www.digi.com/xctu). * The Python 'curses' library installed. @@ -27,23 +27,23 @@ Demo setup ------------- - 1) Plug the XBee BLU radio into the XBee adapter and connect it to your + 1) Plug the XBee 3 BLU radio into the XBee adapter and connect it to your computer's USB or serial port. 2) Ensure that the module is in API mode. For further information on how to perform this task, read the 'Configuring Your XBee Modules' topic of the Getting Started guide. - 3) Enable the Bluetooth interface of the XBee BLU device using XCTU. + 3) Enable the Bluetooth interface of the XBee 3 BLU device using XCTU. For further information on how to perform this task, refer to the XCTU user manual. - 4) Set the port and baud rate of the XBee BLU radio in the demo file. + 4) Set the port and baud rate of the XBee 3 BLU radio in the demo file. If you configured the module in the previous step with XCTU, you will see the port number and baud rate in the 'Port' label of the device on the left view. - 5) Install the Python 'curses' library in your Python enviroment. + 5) Install the Python 'curses' library in your Python environment. - For Linux based installations, this typically has already been done for you by your Linux distribution. If not, use 'pip' to install the 'curses' package. @@ -51,7 +51,7 @@ - For Windows based installations, you will need to install the 'windows-curses' library using PIP. For example, the following steps should work, assuming the - Python enviroment has been properly set up: + Python environment has been properly set up: python.exe -m ensurepip --upgrade python.exe -m pip install --upgrade pip setuptools wheel diff --git a/examples/communication/bluetooth/GapScanExample/readme.txt b/examples/communication/bluetooth/GapScanExample/readme.txt index b8bd4a3..d65cbc4 100644 --- a/examples/communication/bluetooth/GapScanExample/readme.txt +++ b/examples/communication/bluetooth/GapScanExample/readme.txt @@ -9,7 +9,7 @@ and also registers a callback to be notified when the GAP scan status changes. - NOTE: This example is currently only supported on the XBee BLU device + NOTE: This example is currently only supported on the XBee 3 BLU device which uses 'XBeeBLU' device class. @@ -17,7 +17,7 @@ ------------ To run this example you will need: - * One XBee BLU module in API mode and its corresponding carrier board + * One XBee 3 BLU module in API mode and its corresponding carrier board (XBIB or equivalent). * The XCTU application (available at www.digi.com/xctu). @@ -29,18 +29,18 @@ Example setup ------------- - 1) Plug the XBee BLU radio into the XBee adapter and connect it to your + 1) Plug the XBee 3 BLU radio into the XBee adapter and connect it to your computer's USB or serial port. 2) Ensure that the module is in API mode. For further information on how to perform this task, read the 'Configuring Your XBee Modules' topic of the Getting Started guide. - 3) Enable the Bluetooth interface of the XBee BLU device using XCTU. + 3) Enable the Bluetooth interface of the XBee 3 BLU device using XCTU. For further information on how to perform this task, refer to the XCTU user manual. - 4) Set the port and baud rate of the XBee BLU radio in the example file. + 4) Set the port and baud rate of the XBee 3 BLU radio in the example file. If you configured the module in the previous step with XCTU, you will see the port number and baud rate in the 'Port' label of the device on the left view. diff --git a/functional_tests/Bluetooth/device_info.py b/functional_tests/Bluetooth/device_info.py index 901094f..878278d 100644 --- a/functional_tests/Bluetooth/device_info.py +++ b/functional_tests/Bluetooth/device_info.py @@ -20,9 +20,9 @@ def main(): - print(" +---------------------------+") - print(" | XBee BLU device info test |") - print(" +---------------------------+\n") + print(" +-----------------------------+") + print(" | XBee 3 BLU device info test |") + print(" +-----------------------------+\n") local = BluDevice(PORT_LOCAL, BAUD_RATE_LOCAL) From 5a1029adf40b8fd449ef1197521e5bcfc4f22b53 Mon Sep 17 00:00:00 2001 From: Tatiana Leon Date: Mon, 5 Aug 2024 14:30:11 +0200 Subject: [PATCH 22/36] command mode: XBee devices can enter command mode always Command mode methods threw an 'InvalidOperatingModeException' if current operation mode is not Transparent. XBee devices can enter in command mode always using '+++' (default sequence), independently of the current operating mode. Signed-off-by: Tatiana Leon --- digi/xbee/devices.py | 9 --------- 1 file changed, 9 deletions(-) diff --git a/digi/xbee/devices.py b/digi/xbee/devices.py index 27df7a7..6df2262 100644 --- a/digi/xbee/devices.py +++ b/digi/xbee/devices.py @@ -4128,14 +4128,10 @@ def _enter_at_command_mode(self): Raises: SerialTimeoutException: If there is any error trying to write to the serial port. - InvalidOperatingModeException: If the XBee is in API mode. """ if not self._serial_port: raise XBeeException( "Command mode is only supported for local XBee devices using a serial connection") - if self._operating_mode in (OperatingMode.API_MODE, OperatingMode.ESCAPED_API_MODE): - raise InvalidOperatingModeException( - message="Invalid mode. Command mode can be only accessed while in AT mode") from digi.xbee.recovery import enter_at_command_mode return enter_at_command_mode(self._serial_port) @@ -4147,16 +4143,11 @@ def _exit_at_command_mode(self): Raises: SerialTimeoutException: If there is any error trying to write to the serial port. - InvalidOperatingModeException: If the XBee is in API mode. """ if not self._serial_port: raise XBeeException( "Command mode is only supported for local XBee devices using a serial connection") - if self._operating_mode in (OperatingMode.API_MODE, OperatingMode.ESCAPED_API_MODE): - raise InvalidOperatingModeException( - message="Invalid mode. Command mode can be only be exited while in AT mode") - self._serial_port.write("ATCN\r".encode("utf8")) time.sleep(self.__DEFAULT_GUARD_TIME) From ab0ca35a61657de395464e1d7d449fe1c430eafd Mon Sep 17 00:00:00 2001 From: Tatiana Leon Date: Mon, 5 Aug 2024 14:41:04 +0200 Subject: [PATCH 23/36] at command: use '-' in '__str__' if parameter for ATCommand is not defined Signed-off-by: Tatiana Leon --- digi/xbee/models/atcomm.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/digi/xbee/models/atcomm.py b/digi/xbee/models/atcomm.py index 3f5d341..c9fa40f 100644 --- a/digi/xbee/models/atcomm.py +++ b/digi/xbee/models/atcomm.py @@ -1,4 +1,4 @@ -# Copyright 2017-2021, Digi International Inc. +# Copyright 2017-2024, Digi International Inc. # # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this @@ -291,7 +291,7 @@ def __str__(self): String: representation of this ATCommand. """ return "Command: %s - Parameter: %s" \ - % (self.__cmd, utils.hex_to_string(self.__param)) + % (self.__cmd, utils.hex_to_string(self.__param) if self.__param else "-") def __len__(self): """ From cdbfb8e396fbade314009ea39ad6a533f0fada52 Mon Sep 17 00:00:00 2001 From: Tatiana Leon Date: Mon, 5 Aug 2024 16:20:15 +0200 Subject: [PATCH 24/36] status: add missing status codes Signed-off-by: Tatiana Leon --- digi/xbee/models/status.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/digi/xbee/models/status.py b/digi/xbee/models/status.py index ed3c278..dee94fd 100644 --- a/digi/xbee/models/status.py +++ b/digi/xbee/models/status.py @@ -163,6 +163,7 @@ class TransmitStatus(Enum): 0x04, "Transceiver was unable to complete the transmission") INVALID_DESTINATION = (0x15, "Invalid destination endpoint") NO_BUFFERS = (0x18, "No buffers") + CONNECTION_NOT_FOUND = (0x20, "Connection not found") NETWORK_ACK_FAILURE = (0x21, "Network ACK Failure") NOT_JOINED_NETWORK = (0x22, "Not joined to network") SELF_ADDRESSED = (0x23, "Self-addressed") @@ -214,6 +215,9 @@ class TransmitStatus(Enum): "more certificates is invalid") SOCKET_NOT_CONNECTED = (0x87, "Socket not connected") SOCKET_NOT_BOUND = (0x88, "Socket not bound") + SOCKET_INACTIVITY_TIMEOUT = (0x89, "Socket inactivity timeout") + PDP_CONTEXT_DEACTIVATED = (0x8A, "PDP context deactivated by network") + TLS_AUTHENTICATION_ERROR = (0x8B, "TLS Socket Authentication Error") KEY_NOT_AUTHORIZED = (0xBB, "Key not authorized") UNKNOWN = (0xFF, "Unknown") @@ -308,6 +312,9 @@ class ModemStatus(Enum): ROUTER_PAN_ID_CHANGED = ( 0x40, "Router PAN ID was changed by coordinator due to a conflict") NET_WATCHDOG_EXPIRED = (0x42, "Network watchdog timeout expired") + OPEN_JOIN_WINDOW = (0x43, "Joining window open") + CLOSE_JOIN_WINDOW = (0x44, "Joining window closed") + NETWORK_KEY_CHANGE_INIT = (0x45, "Network security key change initiated") ERROR_STACK = (0x80, "Stack error") ERROR_AP_NOT_CONNECTED = ( 0x82, "Send/join command issued without connecting from AP") @@ -481,6 +488,12 @@ class AssociationIndicationStatus(Enum): COORDINATOR_START_FAILED = (0x2A, "Coordinator Start attempt failed") CHECKING_FOR_COORDINATOR = (0x2B, "Checking for an existing coordinator") NETWORK_LEAVE_FAILED = (0x2C, "Attempt to leave the network failed") + SEC_JOIN_ATTACHED_TO_NETWORK = ( + 0x40, "Secure Join - Successfully attached to network, waiting for new link key") + SEC_JOIN_SUCCESS_LINK_KEY = ( + 0x41, "Secure Join - Successfully received new link key from the trust center") + SEC_JOIN_FAILED_LINK_KEY = ( + 0x44, "Secure Join - Failed to receive new link key from the trust center") DEVICE_DIDNT_RESPOND = ( 0xAB, "Attempted to join a device that did not respond") UNSECURED_KEY_RECEIVED = ( @@ -554,7 +567,11 @@ class CellularAssociationIndicationStatus(Enum): USB_DIRECT = (0x2B, "USB Direct mode is active") PSM_LOW_POWER = ( 0x2C, "The cellular component is in the PSM low-power state") + MODEM_SHUT_DOWN = (0x2D, "Modem shut down") + LOW_VOLTAGE_SHUT_DOWN = (0x2E, "Low voltage shut down") BYPASS_MODE = (0x2F, "Bypass mode active") + UPGRADE_IN_PROCESS = (0x30, "An upgrade is in process") + REGULATORY_TESTING_ENABLED = (0x31, "Regulatory testing has been enabled") INITIALIZING = (0xFF, "Initializing") def __init__(self, code, description): @@ -854,10 +871,12 @@ class ZigbeeRegisterStatus(Enum): """ SUCCESS = (0x00, "Success") KEY_TOO_LONG = (0x01, "Key too long") + TRANSIENT_KEY_TABLE_FULL = (0x18, "Transient key table is full") ADDRESS_NOT_FOUND = (0xB1, "Address not found in the key table") INVALID_KEY = (0xB2, "Key is invalid (00 and FF are reserved)") INVALID_ADDRESS = (0xB3, "Invalid address") KEY_TABLE_FULL = (0xB4, "Key table is full") + INVALID_SECURITY_DATA = (0xBD, "Security data is invalid (Install code CRC fails)") KEY_NOT_FOUND = (0xFF, "Key not found") UNKNOWN = (0xEE, "Unknown") @@ -979,6 +998,9 @@ class SocketStatus(Enum): INTERNAL_ERROR = (0x31, "Internal error") RESOURCE_ERROR = (0x32, "Resource error: retry the operation later") INVALID_PROTOCOL = (0x7B, "Invalid protocol") + MODEM_UPDATE_IN_PROGRESS = (0x7E, "A modem update is in process. Try again after update completion") + UNKNOWN_ERROR_2 = (0x85, "Unknown error") + INVALID_TLS_CFG = (0x86, "Invalid TLS configuration") UNKNOWN = (0xFF, "Unknown") def __init__(self, code, description): @@ -1043,6 +1065,9 @@ class SocketState(Enum): UNKNOWN_SERVER = (0x09, "Unknown server") RESOURCE_ERROR = (0x0A, "Resource error") LISTENER_CLOSED = (0x0B, "Listener closed") + RST_CLOSE_BY_PEER = (0x0C, "RST Close by peer") + CLOSED_INACTIVITY_TIMEOUT = (0x0D, "Closed due to inactivity timeout") + PDP_CONTEXT_DEACTIVATED = (0x0E, "PDP context deactivated by network") UNKNOWN = (0xFF, "Unknown") def __init__(self, code, description): From 5a8f03ee5bbc787c16318fb0fdcf590508e04769 Mon Sep 17 00:00:00 2001 From: Tatiana Leon Date: Tue, 6 Aug 2024 12:34:33 +0200 Subject: [PATCH 25/36] filesystem: xbee blu: do not include FS API support While on it avoid a possible 'KeyError' if a protocol is not defined in the minimum and/or maximum versions dictionary. Related to commit 'Add support for the new XBee BLU device/product.' (187afbfa215541f7c99efce82c9c1fcb7385f95b) https://onedigi.atlassian.net/browse/XBPL-391 https://onedigi.atlassian.net/browse/XBPL-396 Signed-off-by: Tatiana Leon --- digi/xbee/filesystem.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/digi/xbee/filesystem.py b/digi/xbee/filesystem.py index a4c644d..78c5ff2 100644 --- a/digi/xbee/filesystem.py +++ b/digi/xbee/filesystem.py @@ -95,7 +95,10 @@ XB3_MIN_FW_VERSION_FS_API_SUPPORT = { XBeeProtocol.ZIGBEE: 0x10FF, XBeeProtocol.DIGI_MESH: 0x30FF, - XBeeProtocol.RAW_802_15_4: 0x20FF + XBeeProtocol.RAW_802_15_4: 0x20FF, + # XBeeProtocol.BLE, specify so: + # * FileSystemManager() is NOT supported until FS API frames are added + XBeeProtocol.BLE: 0x40FF } # Update this values when the File System OTA support is deprecated @@ -103,6 +106,9 @@ XBeeProtocol.ZIGBEE: 0x10FF, XBeeProtocol.DIGI_MESH: 0x30FF, XBeeProtocol.RAW_802_15_4: 0x20FF + # XBeeProtocol.BLE, do not specify so: + # * LocalXBeeFileSystemManager() is SUPPORTED + # * update_remote_filesystem_image() is NOT supported (remotes do not exist) } _DEFAULT_BLOCK_SIZE = 64 @@ -3385,8 +3391,8 @@ def check_fs_support(xbee, min_fw_vers=None, max_fw_vers=None): if not fw_version: return True - min_fw_version = min_fw_vers[xbee.get_protocol()] if min_fw_vers else None - max_fw_version = max_fw_vers[xbee.get_protocol()] if max_fw_vers else None + min_fw_version = min_fw_vers.get(xbee.get_protocol(), None) if min_fw_vers else None + max_fw_version = max_fw_vers.get(xbee.get_protocol(), None) if max_fw_vers else None version = utils.bytes_to_int(fw_version) if min_fw_version and version < min_fw_version: From d044441b4e4aa6148d6c9468da04602e395d07d2 Mon Sep 17 00:00:00 2001 From: Tatiana Leon Date: Thu, 8 Aug 2024 11:59:23 +0200 Subject: [PATCH 26/36] packets: xbee blu: fix some missing DictKey members Related to commit 'Add support for the new XBee BLU device/product.' (187afbfa215541f7c99efce82c9c1fcb7385f95b) https://onedigi.atlassian.net/browse/XBPL-391 https://onedigi.atlassian.net/browse/XBPL-396 Signed-off-by: Tatiana Leon --- digi/xbee/packets/base.py | 8 ++++++++ digi/xbee/packets/bluetooth.py | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/digi/xbee/packets/base.py b/digi/xbee/packets/base.py index c4db067..3ba0fa5 100644 --- a/digi/xbee/packets/base.py +++ b/digi/xbee/packets/base.py @@ -31,6 +31,7 @@ class DictKeys(Enum): ACK_TIMEOUT_COUNT = "ack_timeout_count" ADDITIONAL_DATA = "additional_data" + ADVERTISEMENT_SET_ID = "advertisement_set_id" ANALOG_MASK = "analog_mask" API_DATA = "api_spec_data" AT_CMD_STATUS = "at_command_status" @@ -38,6 +39,7 @@ class DictKeys(Enum): BLE_ADDRESS = "ble_address" BLE_ADDRESS_TYPE = "ble_address_type" BLE_ADVERTISEMENT_FLAGS = "advertisement_flags" + BLE_SCAN_DURATION = "scan_duration" BLE_SCAN_FILTER_TYPE = "scan_filter_type" BLE_SCAN_INTERVAL = "scan_interval" BLE_SCAN_START = "scan_start" @@ -54,6 +56,7 @@ class DictKeys(Enum): CONTENT_TYPE = "content_type" CONTENT_TYPE_LENGTH = "content_type_length" DATA = "data" + DATA_COMPLETENESS = "data_completeness" DC_STATUS = "device_cloud_status" DEST_ADDR = "dest_address" DEST_ADDR_TYPE = "dest_address_type" @@ -91,7 +94,9 @@ class DictKeys(Enum): PATH_ID = "path_id" PATH_LENGTH = "path_length" PAYLOAD = "payload" + PERIODIC_INTERVAL = "periodic_interval" PHONE_NUMBER = "phone_number" + PRIMARY_PHY = "primary_phy" PROFILE_ID = "profile_id" RECEIVE_OPTIONS = "receive_options" REQUEST_ID = "request_id" @@ -102,7 +107,9 @@ class DictKeys(Enum): RF_DATA = "rf_data" ROUTE_CMD_OPTIONS = "route_command_options" RSSI = "rssi" + SCAN_STATUS = "scan_status" SIZE = "size" + SECONDARY_PHY = "secondary_phy" SOCKET_ID = "socket_id" SOURCE_ENDPOINT = "source_endpoint" SOURCE_INTERFACE = "source_interface" @@ -121,6 +128,7 @@ class DictKeys(Enum): TIMESTAMP = "timestamp" TS_STATUS = "ts_status" TX_BLOCKED_COUNT = "tx_blocked_count" + TX_POWER = "tx_power" UPDATER_16BIT_ADDR = "updater_x16_addr" X16BIT_ADDR = "x16_addr" X64BIT_ADDR = "x64_addr" diff --git a/digi/xbee/packets/bluetooth.py b/digi/xbee/packets/bluetooth.py index 663dc67..a8b01d5 100644 --- a/digi/xbee/packets/bluetooth.py +++ b/digi/xbee/packets/bluetooth.py @@ -108,7 +108,7 @@ def __init__(self, start_command, scan_duration, scan_window, raise ValueError("The scan duration cannot be None") if not (self.INDEFINITE_SCAN_DURATION <= scan_duration <= self.__SCAN_DURATION_MAXIMUM): - raise ValueError(f"scan_duration must be between {self.__SCAN_DURATION_INDEFINITELY} and {self.__SCAN_DURATION_MAXIMUM}") + raise ValueError(f"scan_duration must be between {self.INDEFINITE_SCAN_DURATION} and {self.__SCAN_DURATION_MAXIMUM}") if scan_window is None: raise ValueError("The scan window cannot be None") From f951044eb3bd4c038863947048ce2be7e80d6daa Mon Sep 17 00:00:00 2001 From: Tatiana Leon Date: Thu, 8 Aug 2024 11:42:47 +0200 Subject: [PATCH 27/36] filesystem: add '*.fsota' extension as valid for filesystem ota images XCTU and XBee Studio creates filesystem images with extension '*.fsota' by default, so include this extension as a valid one. Signed-off-by: Tatiana Leon --- digi/xbee/firmware.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/digi/xbee/firmware.py b/digi/xbee/firmware.py index e406ae3..52608fe 100644 --- a/digi/xbee/firmware.py +++ b/digi/xbee/firmware.py @@ -218,6 +218,7 @@ EXTENSION_EHX2 = ".ehx2" EXTENSION_OTA = ".ota" EXTENSION_OTB = ".otb" +EXTENSION_FSOTA = ".fsota" EXTENSION_XML = ".xml" _IMAGE_BLOCK_REQUEST_PACKET_PAYLOAD_SIZE = 17 @@ -513,8 +514,9 @@ def parse_file(self): """ _log.debug("Parsing OTA firmware file %s:", self._file_path) if (not _file_exists(self._file_path) - or (not self._file_path.endswith(EXTENSION_OTA) - and not self._file_path.endswith(EXTENSION_OTB))): + or os.path.splitext(self._file_path)[1] not in (EXTENSION_OTA, + EXTENSION_OTB, + EXTENSION_FSOTA)): raise _ParsingOTAException(_ERROR_INVALID_OTA_FILE % self._file_path) try: From 64c3a66b4628265d8aa8aa76159ecf14e15ff124 Mon Sep 17 00:00:00 2001 From: Tatiana Leon Date: Thu, 8 Aug 2024 11:51:52 +0200 Subject: [PATCH 28/36] firmware: rework some error strings Signed-off-by: Tatiana Leon --- digi/xbee/firmware.py | 86 +++++++++++++++++++++++-------------------- 1 file changed, 46 insertions(+), 40 deletions(-) diff --git a/digi/xbee/firmware.py b/digi/xbee/firmware.py index 52608fe..a80faf9 100644 --- a/digi/xbee/firmware.py +++ b/digi/xbee/firmware.py @@ -139,13 +139,8 @@ _ERROR_DETERMINE_BOOTLOADER_TYPE = "Could not determine the bootloader type: %s" _ERROR_DEVICE_PROGRAMMING_MODE = "Could not put XBee device into programming mode" _ERROR_END_DEVICE_ORPHAN = "Could not find the parent node of the end device" -_ERROR_FILE_OTA_FS_NOT_FOUND = "OTA filesystem image file does not exist" -_ERROR_FILE_OTA_FS_NOT_SPECIFIED = "OTA filesystem image file must be specified" -_ERROR_FILE_XBEE_FW_NOT_FOUND = "Could not find XBee binary firmware file '%s'" -_ERROR_FILE_XML_FW_NOT_FOUND = "XML firmware file does not exist" -_ERROR_FILE_XML_FW_NOT_SPECIFIED = "XML firmware file must be specified" -_ERROR_FILE_BOOTLOADER_FW_NOT_FOUND = "Could not find bootloader binary " \ - "firmware file '%s'" +_ERROR_FILE_NOT_FOUND = "%s file '%s' not found" +_ERROR_FILE_NOT_SPECIFIED = "%s file must be specified" _ERROR_FINISH_PROCESS = "Could not finish firmware update process" _ERROR_FW_START = "Could not start the new firmware" _ERROR_FW_UPDATE_BOOTLOADER = "Bootloader update error: %s" @@ -2971,7 +2966,7 @@ def _check_fw_binary_file(self): path.stem + self._get_fw_binary_file_extension())) if not _file_exists(self._fw_file): - self._exit_with_error(_ERROR_FILE_XBEE_FW_NOT_FOUND % self._fw_file, + self._exit_with_error(_ERROR_FILE_NOT_FOUND % ("XBee firmware", self._fw_file), restore_updater=False) def _enter_bootloader_mode_with_break(self): @@ -3768,8 +3763,8 @@ def _check_bootloader_binary_file(self): + EXTENSION_GBL)) if not _file_exists(self._bootloader_fw_file): - self._exit_with_error(_ERROR_FILE_BOOTLOADER_FW_NOT_FOUND % - self._bootloader_fw_file, restore_updater=False) + self._exit_with_error(_ERROR_FILE_NOT_FOUND % ("booloader firmware", self._bootloader_fw_file), + restore_updater=False) def _transfer_firmware(self): """ @@ -4450,7 +4445,7 @@ def _check_fw_binary_file(self): self._ota_fw_file = str(Path(path.parent).joinpath(path.stem + EXTENSION_OTA)) if not _file_exists(self._ota_fw_file): - self._exit_with_error(_ERROR_FILE_XBEE_FW_NOT_FOUND % self._ota_fw_file, + self._exit_with_error(_ERROR_FILE_NOT_FOUND % ("XBee firmware", self._ota_fw_file), restore_updater=False) self._ota_file = _OTAFile(self._ota_fw_file) @@ -4471,7 +4466,7 @@ def _check_bootloader_binary_file(self): self._otb_fw_file = str(Path(path.parent).joinpath(path.stem + EXTENSION_OTB)) if not _file_exists(self._otb_fw_file): - self._exit_with_error(_ERROR_FILE_XBEE_FW_NOT_FOUND % self._otb_fw_file, + self._exit_with_error(_ERROR_FILE_NOT_FOUND % ("XBee firmware", self._otb_fw_file), restore_updater=False) # If asked to check the bootloader file, replace the OTA file with the @@ -5724,7 +5719,8 @@ def _check_fw_binary_file(self): """ # Verify the filesystem OTA image file. if not _file_exists(self._fs_ota_file): - self._exit_with_error(_ERROR_FILE_OTA_FS_NOT_FOUND, restore_updater=False) + self._exit_with_error(_ERROR_FILE_NOT_FOUND % ("OTA filesystem image", self._fs_ota_file), + restore_updater=False) self._ota_file = _OTAFile(self._fs_ota_file) try: @@ -5850,8 +5846,8 @@ def _check_fw_binary_file(self): ) if not _file_exists(self._fw_file): - self._exit_with_error(_ERROR_FILE_XBEE_FW_NOT_FOUND - % self._fw_file, restore_updater=False) + self._exit_with_error(_ERROR_FILE_NOT_FOUND % ("XBee firmware", self._fw_file), + restore_updater=False) def _check_bootloader_binary_file(self): """ @@ -6248,7 +6244,7 @@ def _check_fw_binary_file(self): self._fw_file = str(Path(path.parent).joinpath(path.stem + EXTENSION_EBL)) if not _file_exists(self._fw_file): - self._exit_with_error(_ERROR_FILE_XBEE_FW_NOT_FOUND % self._fw_file, + self._exit_with_error(_ERROR_FILE_NOT_FOUND % ("XBee firmware", self._fw_file), restore_updater=False) def _check_bootloader_binary_file(self): @@ -7020,13 +7016,13 @@ def __init__(self, xbee, xml_fw_path, fw_path=None, bl_fw_path=None, if not isinstance(xbee, (XBeeDevice, RemoteXBeeDevice)): raise ValueError("Invalid XBee") if xml_fw_path is None: - raise ValueError(_ERROR_FILE_XML_FW_NOT_SPECIFIED) + raise ValueError(_ERROR_FILE_NOT_SPECIFIED % "XML firmware") if not _file_exists(xml_fw_path): - raise ValueError(_ERROR_FILE_XML_FW_NOT_FOUND) + raise ValueError(_ERROR_FILE_NOT_FOUND % ("XML firmware", xml_fw_path)) if fw_path is not None and not _file_exists(fw_path): - raise ValueError(_ERROR_FILE_XBEE_FW_NOT_FOUND % fw_path) + raise ValueError(_ERROR_FILE_NOT_FOUND % ("XBee firmware", fw_path)) if bl_fw_path is not None and not _file_exists(bl_fw_path): - raise ValueError(_ERROR_FILE_BOOTLOADER_FW_NOT_FOUND % bl_fw_path) + raise ValueError(_ERROR_FILE_NOT_FOUND % ("booloader firmware", bl_fw_path)) self.__xbee = xbee self.__xml_path = xml_fw_path @@ -7126,17 +7122,21 @@ def update_local_firmware(target, xml_fw_file, xbee_firmware_file=None, _log.error("ERROR: %s", _ERROR_TARGET_INVALID) raise FirmwareUpdateException(_ERROR_TARGET_INVALID) if xml_fw_file is None: - _log.error("ERROR: %s", _ERROR_FILE_XML_FW_NOT_SPECIFIED) - raise FirmwareUpdateException(_ERROR_FILE_XML_FW_NOT_SPECIFIED) + error = _ERROR_FILE_NOT_SPECIFIED % "XML firmware" + _log.error("ERROR: %s", error) + raise FirmwareUpdateException(error) if not _file_exists(xml_fw_file): - _log.error("ERROR: %s", _ERROR_FILE_XML_FW_NOT_FOUND) - raise FirmwareUpdateException(_ERROR_FILE_XML_FW_NOT_FOUND) + error = _ERROR_FILE_NOT_FOUND % ("XML firmware", xml_fw_file) + _log.error("ERROR: %s", error) + raise FirmwareUpdateException(error) if xbee_firmware_file is not None and not _file_exists(xbee_firmware_file): - _log.error("ERROR: %s", _ERROR_FILE_XBEE_FW_NOT_FOUND % xbee_firmware_file) - raise FirmwareUpdateException(_ERROR_FILE_XBEE_FW_NOT_FOUND % xbee_firmware_file) + error = _ERROR_FILE_NOT_FOUND % ("XBee firmware", xbee_firmware_file) + _log.error("ERROR: %s", error) + raise FirmwareUpdateException(error) if bootloader_firmware_file is not None and not _file_exists(bootloader_firmware_file): - _log.error("ERROR: %s", _ERROR_FILE_BOOTLOADER_FW_NOT_FOUND % bootloader_firmware_file) - raise FirmwareUpdateException(_ERROR_FILE_BOOTLOADER_FW_NOT_FOUND % bootloader_firmware_file) + error = _ERROR_FILE_NOT_FOUND % ("bootloader firmware", bootloader_firmware_file) + _log.error("ERROR: %s", error) + raise FirmwareUpdateException(error) if isinstance(target, XBeeDevice): hw_version = target.get_hardware_version() @@ -7221,17 +7221,21 @@ def update_remote_firmware(remote, xml_fw_file, firmware_file=None, bootloader_f _log.error("ERROR: %s", _ERROR_REMOTE_DEVICE_INVALID) raise FirmwareUpdateException(_ERROR_TARGET_INVALID) if xml_fw_file is None: - _log.error("ERROR: %s", _ERROR_FILE_XML_FW_NOT_SPECIFIED) - raise FirmwareUpdateException(_ERROR_FILE_XML_FW_NOT_SPECIFIED) + error = _ERROR_FILE_NOT_SPECIFIED % "XML firmware" + _log.error("ERROR: %s", error) + raise FirmwareUpdateException(error) if not _file_exists(xml_fw_file): - _log.error("ERROR: %s", _ERROR_FILE_XML_FW_NOT_FOUND) - raise FirmwareUpdateException(_ERROR_FILE_XML_FW_NOT_FOUND) + error = _ERROR_FILE_NOT_FOUND % ("XML firmware", xml_fw_file) + _log.error("ERROR: %s", error) + raise FirmwareUpdateException(error) if firmware_file is not None and not _file_exists(firmware_file): - _log.error("ERROR: %s", _ERROR_FILE_XBEE_FW_NOT_FOUND % firmware_file) - raise FirmwareUpdateException(_ERROR_FILE_XBEE_FW_NOT_FOUND % firmware_file) + error = _ERROR_FILE_NOT_FOUND % ("XBee firmware", firmware_file) + _log.error("ERROR: %s", error) + raise FirmwareUpdateException(error) if bootloader_file is not None and not _file_exists(bootloader_file): - _log.error("ERROR: %s", _ERROR_FILE_XBEE_FW_NOT_FOUND % bootloader_file) - raise FirmwareUpdateException(_ERROR_FILE_XBEE_FW_NOT_FOUND % bootloader_file) + error = _ERROR_FILE_NOT_FOUND % ("XBee firmware", bootloader_file) + _log.error("ERROR: %s", error) + raise FirmwareUpdateException(error) if not isinstance(max_block_size, int): raise ValueError("Maximum block size must be an integer") if max_block_size < 0 or max_block_size > 255: @@ -7331,11 +7335,13 @@ def update_remote_filesystem(remote, ota_fs_file, max_block_size=0, timeout=None _log.error("ERROR: %s", _ERROR_REMOTE_DEVICE_INVALID) raise FirmwareUpdateException(_ERROR_REMOTE_DEVICE_INVALID) if ota_fs_file is None: - _log.error("ERROR: %s", _ERROR_FILE_OTA_FS_NOT_SPECIFIED) - raise FirmwareUpdateException(_ERROR_FILE_OTA_FS_NOT_SPECIFIED) + error = _ERROR_FILE_NOT_SPECIFIED % "OTA filesystem image" + _log.error("ERROR: %s", error) + raise FirmwareUpdateException(error) if not _file_exists(ota_fs_file): - _log.error("ERROR: %s", _ERROR_FILE_OTA_FS_NOT_FOUND) - raise FirmwareUpdateException(_ERROR_FILE_OTA_FS_NOT_FOUND) + error = _ERROR_FILE_NOT_FOUND % ("OTA filesystem image", ota_fs_file) + _log.error("ERROR: %s", error) + raise FirmwareUpdateException(error) if not isinstance(max_block_size, int): raise ValueError("Maximum block size must be an integer") if max_block_size < 0 or max_block_size > 255: From fc21f921f9dd867401fb0b879b82de146bae9ef9 Mon Sep 17 00:00:00 2001 From: Tatiana Leon Date: Thu, 8 Aug 2024 11:34:49 +0200 Subject: [PATCH 29/36] pylint: fix pylint issues Consider merging these comparisons with 'in' by *. Use a set instead if elements are hashable. (consider-using-in) Unnecessary parens after '=' keyword (superfluous-parens) Unused argument * (unused-argument) Signed-off-by: Tatiana Leon --- digi/xbee/devices.py | 31 +++++++++++++++---------------- digi/xbee/firmware.py | 8 ++++---- digi/xbee/packets/bluetooth.py | 6 +++--- 3 files changed, 22 insertions(+), 23 deletions(-) diff --git a/digi/xbee/devices.py b/digi/xbee/devices.py index 6df2262..9ea6275 100644 --- a/digi/xbee/devices.py +++ b/digi/xbee/devices.py @@ -2048,8 +2048,7 @@ def _before_send_method(func): def dec_function(self, *args, **kwargs): if not self._comm_iface.is_interface_open: raise XBeeException("XBee device's communication interface closed.") - if (self._operating_mode != OperatingMode.API_MODE - and self._operating_mode != OperatingMode.ESCAPED_API_MODE): + if self._operating_mode not in (OperatingMode.API_MODE, OperatingMode.ESCAPED_API_MODE): raise InvalidOperatingModeException(op_mode=self._operating_mode) return func(self, *args, **kwargs) return dec_function @@ -2063,8 +2062,8 @@ def _after_send_method(func): @wraps(func) def dec_function(*args, **kwargs): response = func(*args, **kwargs) - if (response.transmit_status != TransmitStatus.SUCCESS - and response.transmit_status != TransmitStatus.SELF_ADDRESSED): + if response.transmit_status not in (TransmitStatus.SUCCESS, + TransmitStatus.SELF_ADDRESSED): raise TransmitException(transmit_status=response.transmit_status) return response return dec_function @@ -7831,7 +7830,7 @@ def __get_signal_quality(wifi_version, signal_strength): elif signal_strength >= -50: quality = 100 else: - quality = (2 * (signal_strength + 100)) + quality = 2 * (signal_strength + 100) else: quality = 2 * signal_strength @@ -10419,14 +10418,14 @@ def __discover_next_node_neighbors(self, nodes_queue, active_processes, node_tim return code - def _check_not_discovered_nodes(self, devices_list, nodes_queue): + def _check_not_discovered_nodes(self, devices_list, _nodes_queue): """ Checks not discovered nodes in the current scan, and add them to the FIFO if necessary. Args: devices_list (List): List of nodes to check. - nodes_queue (:class:`queue.Queue`): FIFO where the nodes to + _nodes_queue (:class:`queue.Queue`): FIFO where the nodes to discover their neighbors are stored. """ # Check for nodes in the network not discovered in this scan and ensure @@ -10451,16 +10450,16 @@ def _check_not_discovered_nodes(self, devices_list, nodes_queue): self._log.debug(" - Reachable: %s (scan %d)", n_item._reachable, self.__scan_counter) - def _discover_neighbors(self, requester, nodes_queue, active_processes, node_timeout): + def _discover_neighbors(self, _requester, _nodes_queue, _active_processes, _node_timeout): """ Starts the process to discover the neighbors of the given node. Args: - requester(:class:`.AbstractXBeeDevice`): XBee to discover its neighbors. - nodes_queue (:class:`queue.Queue`): FIFO where the nodes to + _requester(:class:`.AbstractXBeeDevice`): XBee to discover its neighbors. + _nodes_queue (:class:`queue.Queue`): FIFO where the nodes to discover their neighbors are stored. - active_processes (List): List of active discovery processes. - node_timeout (Float): Timeout to discover neighbors (seconds). + _active_processes (List): List of active discovery processes. + _node_timeout (Float): Timeout to discover neighbors (seconds). Returns: :class:`.NetworkDiscoveryStatus`: Resulting status of the process. @@ -11126,8 +11125,8 @@ def __purge_network_connections(self, force=False): connections_to_remove = [] with self.__conn_lock: for conn in self.__connections: - if (conn.scan_counter_a2b != self.__scan_counter - and conn.scan_counter_b2a != self.__scan_counter): + if self.__scan_counter not in (conn.scan_counter_a2b, + conn.scan_counter_b2a): conn.lq_a2b = LinkQuality.UNKNOWN conn.lq_b2a = LinkQuality.UNKNOWN connections_to_remove.append(conn) @@ -11601,8 +11600,8 @@ def __process_discovered_neighbor_data(self, requester, routes, neighbor, nodes_ connection = Connection(node, requester, lq_a2b=neighbor.lq, lq_b2a=LinkQuality.UNKNOWN, status_a2b=RouteStatus.ACTIVE, status_b2a=RouteStatus.UNKNOWN) - elif (neighbor.relationship == NeighborRelationship.CHILD - or neighbor.relationship == NeighborRelationship.UNDETERMINED): + elif neighbor.relationship in (NeighborRelationship.CHILD, + NeighborRelationship.UNDETERMINED): connection = Connection(requester, node, lq_a2b=neighbor.lq, lq_b2a=LinkQuality.UNKNOWN, status_a2b=RouteStatus.ACTIVE, status_b2a=RouteStatus.UNKNOWN) diff --git a/digi/xbee/firmware.py b/digi/xbee/firmware.py index a80faf9..35920d2 100644 --- a/digi/xbee/firmware.py +++ b/digi/xbee/firmware.py @@ -5596,8 +5596,8 @@ def _finish_firmware_update(self): and self._remote.get_protocol() == XBeeProtocol.DIGI_MESH and self._target_fw_version <= 0x3004) raw_802_error = (st_frame.transmit_status == TransmitStatus.NO_ACK - and self._remote.get_protocol() == XBeeProtocol.RAW_802_15_4 - and self._target_fw_version <= 0x2002) + and self._remote.get_protocol() == XBeeProtocol.RAW_802_15_4 + and self._target_fw_version <= 0x2002) zb_addr_error = (st_frame.transmit_status == TransmitStatus.ADDRESS_NOT_FOUND and self._remote.get_protocol() == XBeeProtocol.ZIGBEE and self._target_fw_version <= 0x1009) @@ -7305,7 +7305,7 @@ def update_remote_firmware(remote, xml_fw_file, firmware_file=None, bootloader_f finally: configurer.restore_after_update( restore_settings=not update_process.check_protocol_changed_by_fw(orig_protocol)) - finished = (remote._active_update_type == NodeUpdateType.FIRMWARE) + finished = remote._active_update_type == NodeUpdateType.FIRMWARE if finished or msg != "Success": update_process._notify_progress(msg, 100, finished=finished) @@ -7370,7 +7370,7 @@ def update_remote_filesystem(remote, ota_fs_file, max_block_size=0, timeout=None raise exc finally: configurer.restore_after_update() - finished = (remote._active_update_type == NodeUpdateType.FILESYSTEM) + finished = remote._active_update_type == NodeUpdateType.FILESYSTEM if finished or msg != "Success": update_process._notify_progress(msg, 100, finished=finished) diff --git a/digi/xbee/packets/bluetooth.py b/digi/xbee/packets/bluetooth.py index a8b01d5..e8bbc37 100644 --- a/digi/xbee/packets/bluetooth.py +++ b/digi/xbee/packets/bluetooth.py @@ -107,19 +107,19 @@ def __init__(self, start_command, scan_duration, scan_window, if scan_duration is None: raise ValueError("The scan duration cannot be None") - if not (self.INDEFINITE_SCAN_DURATION <= scan_duration <= self.__SCAN_DURATION_MAXIMUM): + if not self.INDEFINITE_SCAN_DURATION <= scan_duration <= self.__SCAN_DURATION_MAXIMUM: raise ValueError(f"scan_duration must be between {self.INDEFINITE_SCAN_DURATION} and {self.__SCAN_DURATION_MAXIMUM}") if scan_window is None: raise ValueError("The scan window cannot be None") - if not (self.__SCAN_WINDOW_MINIMUM <= scan_window <= self.__SCAN_WINDOW_MAXIMUM): + if not self.__SCAN_WINDOW_MINIMUM <= scan_window <= self.__SCAN_WINDOW_MAXIMUM: raise ValueError(f"scan_window must be between {self.__SCAN_WINDOW_MINIMUM} and {self.__SCAN_WINDOW_MAXIMUM}") if scan_interval is None: raise ValueError("The scan interval cannot be None") - if not (self.__SCAN_INTERVAL_MINIMUM <= scan_interval <= self.__SCAN_INTERVAL_MAXIMUM): + if not self.__SCAN_INTERVAL_MINIMUM <= scan_interval <= self.__SCAN_INTERVAL_MAXIMUM: raise ValueError(f"scan_interval must be between {self.__SCAN_INTERVAL_MINIMUM} and {self.__SCAN_INTERVAL_MAXIMUM}") if scan_interval < scan_window: From 1212cce3b3d395eca0c6be06dcfb41bc15d95dce Mon Sep 17 00:00:00 2001 From: Tatiana Leon Date: Wed, 7 Aug 2024 09:54:45 +0200 Subject: [PATCH 30/36] doc: add read the docs configuration file ReadTheDocs documentation build was broken since October, 2023, since file '.readthedocs.yaml' file is required in the root of the project. https://onedigi.atlassian.net/browse/XBPL-425 Signed-off-by: Tatiana Leon --- .readthedocs.yaml | 34 ++++++++++++++++++++++++++++++++++ doc/requirements.txt | 1 + 2 files changed, 35 insertions(+) create mode 100644 .readthedocs.yaml create mode 100644 doc/requirements.txt diff --git a/.readthedocs.yaml b/.readthedocs.yaml new file mode 100644 index 0000000..38919f4 --- /dev/null +++ b/.readthedocs.yaml @@ -0,0 +1,34 @@ +# Read the Docs configuration file for Sphinx projects +# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details + +# Required +version: 2 + +# Set the OS, Python version and other tools you might need +build: + os: ubuntu-22.04 + tools: + python: "3.12" + # You can also specify other tool versions: + # nodejs: "20" + # rust: "1.70" + # golang: "1.20" + +# Build documentation in the "doc/" directory with Sphinx +sphinx: + configuration: doc/conf.py + # Configure Sphinx to use a different builder, for instance use the dirhtml builder for simpler URLs + # builder: "dirhtml" + # Fail on all warnings to avoid broken references + # fail_on_warning: true + +# Optionally build your docs in additional formats such as PDF and ePub +# formats: +# - pdf +# - epub + +# Declare the Python requirements required to build your documentation +# See https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html +python: + install: + - requirements: doc/requirements.txt diff --git a/doc/requirements.txt b/doc/requirements.txt new file mode 100644 index 0000000..52b04f2 --- /dev/null +++ b/doc/requirements.txt @@ -0,0 +1 @@ +sphinx_rtd_theme \ No newline at end of file From bdf72b1082ac6a98e206ad25e3554bec58a2898a Mon Sep 17 00:00:00 2001 From: Tatiana Leon Date: Tue, 6 Aug 2024 14:13:26 +0200 Subject: [PATCH 31/36] doc: add XBee RR and XBee 3 BLU firmware update feature Related to commits: * 'hardware: add new versions to the supported hardware list' (9c08721ccc53c9ba1d9330ca78b876b495958f4e) * 'xmodem: Handle first-chunk delay when sending firmware into XR bootloader' (f95e0fa5c0a220009654126454199d56ff1a0736) * 'Add support for the new XBee BLU device/product.' (187afbfa215541f7c99efce82c9c1fcb7385f95b) Signed-off-by: Tatiana Leon --- doc/user_doc/update_the_xbee.rst | 43 +++++++++++++++++++++----------- 1 file changed, 29 insertions(+), 14 deletions(-) diff --git a/doc/user_doc/update_the_xbee.rst b/doc/user_doc/update_the_xbee.rst index 3b8851c..8766e0b 100644 --- a/doc/user_doc/update_the_xbee.rst +++ b/doc/user_doc/update_the_xbee.rst @@ -16,15 +16,22 @@ profiles: * Local and remote firmware updates * Local and remote file system updates * Local and remote profile updates + * **XBee 3 BLU**: + * Local firmware updates + * Local file system updates + * Local profile updates + * **XBee RR** + * Local and remote firmware updates + * Local and remote profile updates + * **XBee XR 868/900** + * Local and remote firmware updates + * Local and remote profile updates * **XBee SX 868/900 MHz** * Local and remote firmware updates * Local and remote profile updates * **XBee S2C** * Remote firmware updates * Remote profile updates - * **XBee XR 868/900** - * Local and remote firmware updates - * Local and remote profile updates .. _updateFirmware: @@ -45,9 +52,11 @@ and remote devices: .. warning:: At the moment, firmware update is only supported in: * **XBee 3**: Local and remote firmware updates + * **XBee 3 BLU**: Local firmware updates + * **XBee RR**: Local and remote firmware updates + * **XBee XR 868/900**: 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: @@ -71,7 +80,8 @@ connection. For this operation, you need the following components: .. warning:: At the moment, local firmware update is only supported in **XBee 3**, - **XBee SX 868/900 MHz** and **XBee XR 868/900** devices. + **XBee 3 BLU**, **XBee RR**, **XBee XR 868/900**, and **XBee SX 868/900 MHz** + devices. +------------------------------------------------------------------------------------------------------------------------------------------------------+ @@ -225,7 +235,8 @@ components: .. warning:: At the moment, remote firmware update is only supported in **XBee 3**, - **XBee SX 868/900 MHz**, **XBee S2C**, and **XBee XR 868/900** devices. + **XBee RR**, **XBee XR 868/900**, **XBee SX 868/900 MHz**, + and **XBee S2C** devices. To perform the remote firmware update, call the ``update_firmware()`` method of the ``RemoteXBeeDevice`` class providing the required parameters: @@ -303,16 +314,16 @@ The ``update_firmware()`` method may fail for the following reasons: Update the XBee file system --------------------------- -XBee 3 devices feature file system capabilities, meaning that they are able to -persistently store files and folders in flash. The XBee Python Library provides -classes and methods to manage these files. +XBee 3 and XBee RR devices feature file system capabilities, meaning that they +are able to persistently store files and folders in flash. The XBee Python +Library provides classes and methods to manage these files. * :ref:`filesystemManager` * :ref:`filesystemOperations` .. warning:: - At the moment file system capabilities are only supported in **XBee 3** - devices. + At the moment file system capabilities are only supported in **XBee 3** and + **XBee RR** devices. .. _filesystemManager: @@ -491,9 +502,11 @@ To configure individual settings see :ref:`configureXBee`. .. warning:: At the moment, firmware update is only supported in: * **XBee 3**: Local and remote profile updates + * **XBee 3 BLU**: Local profile updates + * **XBee RR**: Local and remote profile updates + * **XBee XR 868/900**: 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: @@ -647,7 +660,8 @@ Applying a profile to a local XBee requires the following components: .. warning:: At the moment, local profile update is only supported in **XBee 3**, - **XBee SX 868/900 MHz**, and **XBee XR 868/900** devices. + **XBee 3 BLU**, **XBee RR**, **XBee XR 868/900**, and **XBee SX 868/900 MHz** + devices. To apply the XBee profile to a local XBee, call the ``apply_profile()`` method of the ``XBeeDevice`` class providing the required parameters: @@ -721,7 +735,8 @@ 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**, **XBee S2C**, and **XBee XR 868/900** devices. + **XBee RR**, **XBee XR 868/900**, **XBee SX 868/900 MHz** and + **XBee S2C** devices. To apply the XBee profile to a remote XBee, call the ``apply_profile()`` method of the ``RemoteXBeeDevice`` class providing the required parameters: From 0cbcf52352b2c0dcb29db901d96fabed63bf1b59 Mon Sep 17 00:00:00 2001 From: Tatiana Leon Date: Wed, 7 Aug 2024 10:21:33 +0200 Subject: [PATCH 32/36] doc: faq: clarify serial communication parameters can be forced from code Signed-off-by: Tatiana Leon --- doc/faq.rst | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/doc/faq.rst b/doc/faq.rst index 85daa9a..9b3a156 100644 --- a/doc/faq.rst +++ b/doc/faq.rst @@ -36,9 +36,16 @@ the **Port** label. Can I use the XBee Python Library with modules in AT operating mode? -------------------------------------------------------------------- -No, the XBee Python Library only supports **API** and **API Escaped** operating +The XBee Python Library only supports **API** and **API Escaped** operating modes. +If your devices are using a different operating mode, or even a different +serial communication configuration (baud rate, stop bits, etc.), use +`force_settings=True` in the `open()` method. This parameter tells the library +to establish the provided serial settings and change the operating mode to +**API** to be able to work with connected XBee devices. +See :ref:`openXBeeConnection`. + I get the Python error ``ImportError: No module named 'serial'`` ---------------------------------------------------------------- From 7ecfcf687e42d454badf104fde3d3addf75774f1 Mon Sep 17 00:00:00 2001 From: Tatiana Leon Date: Wed, 7 Aug 2024 12:04:09 +0200 Subject: [PATCH 33/36] doc: fix doc issues https://onedigi.atlassian.net/browse/XBPL-425 Signed-off-by: Tatiana Leon --- digi/xbee/devices.py | 4 +- digi/xbee/firmware.py | 14 +++---- digi/xbee/models/message.py | 2 +- digi/xbee/packets/cellular.py | 2 +- digi/xbee/packets/factory.py | 6 +-- digi/xbee/profile.py | 2 +- digi/xbee/reader.py | 38 +++++++++---------- digi/xbee/util/exportutils.py | 10 ++--- digi/xbee/util/srp.py | 28 +++++++------- doc/api/digi.xbee.models.rst | 1 + doc/conf.py | 2 +- .../communicating_with_xbee_devices.rst | 2 +- 12 files changed, 57 insertions(+), 54 deletions(-) diff --git a/digi/xbee/devices.py b/digi/xbee/devices.py index 9ea6275..e087ca8 100644 --- a/digi/xbee/devices.py +++ b/digi/xbee/devices.py @@ -9002,7 +9002,7 @@ def export(self, dir_path=None, name=None, desc=None): If the provided path already exists the file is removed. - Params: + Args: dir_path (String, optional, default=`None`): Absolute path of the directory to export the network. It should not include the file name. If not defined home directory is used. @@ -9052,7 +9052,7 @@ def update_nodes(self, task_list): """ Performs the provided update tasks. It blocks until all tasks finish. - Params: + Args: task_list (List or tuple): List of update tasks (:class:`.FwUpdateTask` or :class:`.ProfileUpdateTask`) diff --git a/digi/xbee/firmware.py b/digi/xbee/firmware.py index 35920d2..484258e 100644 --- a/digi/xbee/firmware.py +++ b/digi/xbee/firmware.py @@ -6440,7 +6440,7 @@ def _get_updater_candidates(self, net_discover=False): Returns a list of updater candidates extracted from the current network connections or from a neighbor discover. - Params: + Args: net_discover (Boolean, optional, default=False): `True` to perform a neighbor discover, `False` to use current network connections. @@ -6497,7 +6497,7 @@ def _is_valid_updater_candidate(self, node): Checks if the provided node is a valid candidate to be the updater node for the update process of the remote. - Params: + Args: node (:class: `.RemoteXBeeDevice`): The node to check if it is a possible updater. """ @@ -6522,7 +6522,7 @@ def _determine_best_updater_from_candidates_list_zigbee(self, candidates): Determines which is the best updater node of the given list for a Zigbee network. - Params: + Args: candidates (List): List of possible XBee updater devices. Returns: @@ -6546,7 +6546,7 @@ def _determine_best_updater_from_candidates_list_digimesh(self, candidates): Determines which is the best updater node of the given list for a DigiMesh network. - Params: + Args: candidates (List): List of possible XBee updater devices. Returns: @@ -6701,7 +6701,7 @@ def _ota_callback(self, frame): """ Callback used to receive OTA firmware update process status frames. - Params: + Args: frame (:class:`.XBeePacket`): Received XBee packet. """ # If frame was already received, ignore this frame, just notify. @@ -6737,7 +6737,7 @@ def _create_ota_explicit_packet(self, frame_id, payload): Creates and returns an OTA firmware update explicit packet using the given parameters. - Params: + Args: frame_id (Integer): Frame ID of the packet. payload (Bytearray): Packet payload. @@ -6832,7 +6832,7 @@ def _send_firmware_data(self, data, ebl_file): """ Sends the given firmware data to the updater device. - Params: + Args: Bytearray: Firmware data to send. ebl_file (:class:`._EBLFile`): Ebl file being transferred. diff --git a/digi/xbee/models/message.py b/digi/xbee/models/message.py index 21c5378..f6b63d9 100644 --- a/digi/xbee/models/message.py +++ b/digi/xbee/models/message.py @@ -320,7 +320,7 @@ class SMSMessage: This class is used within the library to read SMS sent to Cellular devices. """ - __PHONE_NUMBER_PATTERN = "^\+?\d+$" + __PHONE_NUMBER_PATTERN = r"^\+?\d+$" def __init__(self, phone_number, data): """ diff --git a/digi/xbee/packets/cellular.py b/digi/xbee/packets/cellular.py index 73a1226..7f2be6e 100644 --- a/digi/xbee/packets/cellular.py +++ b/digi/xbee/packets/cellular.py @@ -22,7 +22,7 @@ from digi.xbee.util import utils -PATTERN_PHONE_NUMBER = "^\+?\d+$" +PATTERN_PHONE_NUMBER = r"^\+?\d+$" """Pattern used to validate the phone number parameter of SMS packets.""" diff --git a/digi/xbee/packets/factory.py b/digi/xbee/packets/factory.py index 99be143..4e4c40b 100644 --- a/digi/xbee/packets/factory.py +++ b/digi/xbee/packets/factory.py @@ -44,8 +44,8 @@ +----------------+ +-------------------+ +--------------------------- + +----------------+ | 0x7E | | MSB | LSB | | API-specific Structure | | 1 Byte | +----------------+ +-------------------+ +----------------------------+ +----------------+ - \___________________________________ _________________________________/ - \/ + \\___________________________________ ________________________________/ + \\/ Characters Escaped If Needed MSB = Most Significant Byte, LSB = Least Significant Byte @@ -121,7 +121,7 @@ SocketReceiveFromPacket, SocketStatePacket from digi.xbee.packets.wifi import RemoteATCommandWifiPacket, \ RemoteATCommandResponseWifiPacket, IODataSampleRxIndicatorWifiPacket -from digi.xbee.packets.zigbee import RegisterJoiningDevicePacket,\ +from digi.xbee.packets.zigbee import RegisterJoiningDevicePacket, \ RegisterDeviceStatusPacket, RouteRecordIndicatorPacket, OTAFirmwareUpdateStatusPacket from digi.xbee.packets.bluetooth import BluetoothGAPScanLegacyAdvertisementResponsePacket, \ BluetoothGAPScanExtendedAdvertisementResponsePacket, BluetoothGAPScanStatusPacket diff --git a/digi/xbee/profile.py b/digi/xbee/profile.py index 0549f90..88febb4 100644 --- a/digi/xbee/profile.py +++ b/digi/xbee/profile.py @@ -1387,7 +1387,7 @@ def xbee(self): @property def profile_path(self): """ - Gets the *.xpro file path. + Gets the `*.xpro` file path. Returns: String: The profile path for the update task. diff --git a/digi/xbee/reader.py b/digi/xbee/reader.py index 2c344a9..29aa3f9 100644 --- a/digi/xbee/reader.py +++ b/digi/xbee/reader.py @@ -42,7 +42,6 @@ from digi.xbee.exception import TimeoutException, InvalidPacketException from digi.xbee.io import IOSample - # Maximum number of parallel callbacks. MAX_PARALLEL_CALLBACKS = 50 @@ -65,6 +64,7 @@ def callback_prototype(*args, **kwargs): .. seealso:: | list (Python standard class) """ + def __call__(self, *args, **kwargs): for func in self: future = EXECUTOR.submit(func, *args, **kwargs) @@ -510,7 +510,7 @@ class methods. This callbacks must have a certain header, see each event Here are the parameters which will be received by the event callbacks, depending on which event it is in each case: - The following parameters are passed via \*\*kwargs to event callbacks of: + The following parameters are passed via \\*\\*kwargs to event callbacks of: 1. PacketReceived: 1.1 received_packet (:class:`.XBeeAPIPacket`): Received packet. @@ -1659,11 +1659,11 @@ def __execute_user_callbacks(self, packet, remote=None): # Bluetooth BLE GAP Scan Legacy Advertisement Response elif f_type == ApiFrameType.BLUETOOTH_GAP_SCAN_LEGACY_ADVERTISEMENT_RESPONSE: self.__ble_gap_scan_received(BLEGAPScanLegacyAdvertisementMessage( - packet.address, - packet.address_type, - packet.advertisement_flags, - packet.rssi, - packet.payload)) + packet.address, + packet.address_type, + packet.advertisement_flags, + packet.rssi, + packet.payload)) self._log.debug(self._LOG_PATTERN.format( comm_iface=str(self.__xbee.comm_iface), event="RECEIVED", fr_type="BLE GAP SCAN LEGACY", sender=str(packet.address), @@ -1672,17 +1672,17 @@ def __execute_user_callbacks(self, packet, remote=None): # Bluetooth BLE GAP Scan Extended Advertisement Response elif f_type == ApiFrameType.BLUETOOTH_GAP_SCAN_EXTENDED_ADVERTISEMENT_RESPONSE: self.__ble_gap_scan_received(BLEGAPScanExtendedAdvertisementMessage( - packet.address, - packet.address_type, - packet.advertisement_flags, - packet.rssi, - packet.advertisement_set_id, - packet.primary_phy, - packet.secondary_phy, - packet.tx_power, - packet.periodic_interval, - packet.data_completeness, - packet.payload)) + packet.address, + packet.address_type, + packet.advertisement_flags, + packet.rssi, + packet.advertisement_set_id, + packet.primary_phy, + packet.secondary_phy, + packet.tx_power, + packet.periodic_interval, + packet.data_completeness, + packet.payload)) self._log.debug(self._LOG_PATTERN.format( comm_iface=str(self.__xbee.comm_iface), event="RECEIVED", fr_type="BLE GAP SCAN EXTENDED", sender=str(packet.address), @@ -1691,7 +1691,7 @@ def __execute_user_callbacks(self, packet, remote=None): # Bluetooth BLE GAP Scan Status Response elif f_type == ApiFrameType.BLUETOOTH_GAP_SCAN_STATUS: self.__ble_gap_scan_status_received(BLEGAPScanStatusMessage( - packet.scan_status)) + packet.scan_status)) self._log.debug(self._LOG_PATTERN.format( comm_iface=str(self.__xbee.comm_iface), event="RECEIVED", fr_type="BLE GAP SCAN STATUS", sender="None", diff --git a/digi/xbee/util/exportutils.py b/digi/xbee/util/exportutils.py index d045666..c524d63 100644 --- a/digi/xbee/util/exportutils.py +++ b/digi/xbee/util/exportutils.py @@ -30,7 +30,7 @@ def generate_network_xml(xbee, date_now=None, name=None, desc=None): """ Generates the XML hierarchy representing the network of the given XBee. - Params: + Args: xbee (:class:`.XBeeDevice`): Local XBee node. date_now (:class: `datetime.datetime`, optional, default=`None`): Date to set in the XML. @@ -69,7 +69,7 @@ def _generate_nodes_xml(xbee, level=0): """ Generates the XML element representing the network of the given XBee. - Params: + Args: xbee (:class:`.XBeeDevice`): Local XBee node. level (Integer, optional, default=0): Indentation level. @@ -96,7 +96,7 @@ def _generate_node_xml(node, level=0): """ Generates the XML element representing the given XBee node. - Params: + Args: xbee (:class:`.AbstractXBeeDevice`): XBee node. level (Integer, optional, default=0): Indentation level. @@ -146,7 +146,7 @@ def _generate_serial_config_xml(serial_port, level=0): """ Generates the XML element representing the given serial port. - Params: + Args: serial_port (:class:`serial.serialutil.SerialBase`): Serial port. level (Integer, optional, default=0): Indentation level. @@ -189,7 +189,7 @@ def _generate_connections_xml(node, connections, level=0): """ Generates the XML node representing the given connections. - Params: + Args: xbee (:class:`.AbstractXBeeDevice`): XBee node. connections (List): List of :class:`.Connection`. level (Integer, optional, default=0): Indentation level. diff --git a/digi/xbee/util/srp.py b/digi/xbee/util/srp.py index 499fb05..89c11de 100644 --- a/digi/xbee/util/srp.py +++ b/digi/xbee/util/srp.py @@ -20,6 +20,7 @@ from digi.xbee.util import utils + @unique class HAType(Enum): """ @@ -78,6 +79,7 @@ def get(cls, code): HAType.__doc__ += utils.doc_enum(HAType) + @unique class NgGroupParams(Enum): """ @@ -286,11 +288,11 @@ def get(cls, code): def create_salted_verification_key(user, password, hash_alg=HAType.SHA256, - ng_type=NgGroupParams.NG_1024, salt_len=4): + ng_type=NgGroupParams.NG_1024, salt_len=4): """ Generates a salted verification key for the provided username and password. - Params: + Args: user (String): Username string. password (String): Plain text password. hash_alg (:class:`.HAType`, optional, default=`HAType.SHA256`): Hash algorithm. @@ -301,24 +303,24 @@ def create_salted_verification_key(user, password, hash_alg=HAType.SHA256, Returns: Tuple (bytes, bytes): Tuple with salt and verifier. """ - s = generate_salt(l=salt_len) + s = generate_salt(length=salt_len) v = generate_verifier(user, password, hash_alg=hash_alg, ng_type=ng_type, salt=s, sep=":") return s, v -def generate_salt(l=4): +def generate_salt(length=4): """ Generates new salt. - Params: - l (Integer, optional, default=`4`): Number of bytes. + Args: + length (Integer, optional, default=`4`): Number of bytes. Returns: Bytes: The generated salt. """ - return os.urandom(l) + return os.urandom(length) def generate_verifier(user, password, salt, hash_alg=HAType.SHA256, @@ -326,14 +328,14 @@ def generate_verifier(user, password, salt, hash_alg=HAType.SHA256, """ Calculates a verifier for the provided salt and configured password. - Params: + Args: user (String): Username string. password (String): Plain text password. salt (bytes): Salt to generate a verifier. hash_alg (:class:`.HAType`, optional, default=`HAType.SHA256`): Hash algorithm. ng_type (:class:`.NgGroupParams`, optional, default=`NgGroupParams.NG_1024`): Prime generator type. - sep (String, optional, default= `:`): Separator string. + sep (String, optional): Separator string. Returns: Bytes: The generated verifier. @@ -352,12 +354,12 @@ def __calculate_x(user, password, salt, hash_alg=HAType.SHA256, sep=":"): """ Calculates the user secret parameter. - Params: + Args: user (String): Username string. password (String): Plain text password. salt (bytes): Salt byte array. hash_alg (:class:`.HAType`, optional, default=`HAType.SHA256`): Hash algorithm. - sep (String, optional, default= `:`): Separator string. + sep (String, optional): Separator string. Returns: Integer: The user secret value. @@ -376,7 +378,7 @@ def __hash(hash_alg, *args): """ Calculates the hash of the provided arguments. - Params: + Args: args: Variable argument list of object to use for hash. Returns: @@ -393,7 +395,7 @@ def __to_bytes(obj): """ Converts object to byte array, with optional context. - Params: + Args: obj: Object to convert. Returns: diff --git a/doc/api/digi.xbee.models.rst b/doc/api/digi.xbee.models.rst index 72ca4c2..624f0d1 100644 --- a/doc/api/digi.xbee.models.rst +++ b/doc/api/digi.xbee.models.rst @@ -21,5 +21,6 @@ Submodules digi.xbee.models.message digi.xbee.models.options digi.xbee.models.protocol + digi.xbee.models.statistics digi.xbee.models.status digi.xbee.models.zdo diff --git a/doc/conf.py b/doc/conf.py index 8ca3056..453c90b 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -68,7 +68,7 @@ # # This is also used if you do content translation via gettext catalogs. # Usually you set "language" from the command line for these cases. -language = None +language = 'en' # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: diff --git a/doc/user_doc/communicating_with_xbee_devices.rst b/doc/user_doc/communicating_with_xbee_devices.rst index 8f69e6c..314f19c 100644 --- a/doc/user_doc/communicating_with_xbee_devices.rst +++ b/doc/user_doc/communicating_with_xbee_devices.rst @@ -577,7 +577,7 @@ of the network: +--------------------------------------------------------------------------------------------------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | Method | Description | +==============================================================================================================+======================================================================================================================================================================================================+ -| **send_expl_data_async(RemoteXBeeDevice, String or Bytearray, Integer, Integer, Integer, Integer, Integer)** | Specifies remote XBee destination object, the data to send, four application layer fields (source endpoint, destination endpoint, cluster ID, and profile ID), and, optionally, the transmit options. | +| **send_expl_data_async(RemoteXBeeDevice, String or Bytearray, Integer, Integer, Integer, Integer, Integer)** | Specifies remote XBee destination object, the data to send, four application layer fields (source endpoint, destination endpoint, cluster ID, and profile ID), and, optionally, the transmit options.| +--------------------------------------------------------------------------------------------------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ **Send unicast explicit data asynchronously** From c37ac199702a7159537c7c3f3615f5daa828b2f4 Mon Sep 17 00:00:00 2001 From: Tatiana Leon Date: Tue, 6 Aug 2024 14:21:40 +0200 Subject: [PATCH 34/36] changelog: fix bad format and typos https://onedigi.atlassian.net/browse/XBPL-425 Signed-off-by: Tatiana Leon --- CHANGELOG.rst | 43 ++++++++++++++++++++++++------------------- 1 file changed, 24 insertions(+), 19 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 8524ad0..8220d3b 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -40,21 +40,21 @@ v1.4.1 - 12/22/2021 * Support for new hardware variants: * XBee 3 Cellular LTE-M/NB-IoT (Telit) - * XBee 3 Reduced RAM + * XBee RR MMT/SMT * S2C P5 - * XBee XR 900 - * XBee XR 868 + * XBee XR 900 MMT/SMT + * XBee XR 868 MMT/SMT * OTA firmware update: - * Implementation of considerations for versions 1009, 300A, 200A or prior + * Implementation of considerations for versions 1009, 300A, 200A, or prior (XBPL-375) See: * `Zigbee (1009 an prior) considerations `_ * `DigiMesh (older than 300A) considerations `_ * `802.15.4 (older than 200A) considerations `_ - * When updating a remote profile, let the library calculate the `*.otb` - file path based on the `*.xml` firmware file, as it does for the `*.ota`. + * When updating a remote profile, let the library calculate the ``*.otb`` + file path based on the ``*.xml`` firmware file, as it does for the ``*.ota``. * XBee Cellular: * Do not work with network if the XBee does not support it (XBPL-374) @@ -65,14 +65,17 @@ v1.4.1 - 12/22/2021 * Add info about the ``force_settings`` parameter of ``open`` method (#241) * Add missing ``exportutils`` module to documentation. * Set exclusive access mode to the XBee serial port (#222, #252) -* Do not stop frames reader if a serial buffer empty exception occurs (#222, #252) -* Do not use 'os.path.join()' for relative paths of zip entries (#247) +* Do not stop frames reader if a serial buffer empty exception occurs + (#222, #252) +* Do not use ``os.path.join()`` for relative paths of zip entries (#247) * Fix bad conditions when checking for a received packet (#242) * Fix attribute name in find neighbors debug message (#122) * Fix remote firmware update issue with binary file on SX devices. * Fix protocol change issues during firmware update operation on SX devices. -* Do not reconfigure SP and SN values after a firmware update operation in P2MP protocol. -* Add new method to update salt and verifier values of Bluetooth password SRP authentication. +* Do not reconfigure SP and SN values after a firmware update operation in P2MP + protocol. +* Add new method to update salt and verifier values of Bluetooth password SRP + authentication. * Several minor bug fixes. v1.4.0 - 03/18/2021 @@ -111,14 +114,16 @@ v1.4.0 - 03/18/2021 ``set_many_to_one_broadcasting_time()``. * Support for source route creation: ``create_source_route()``. * New frames: - * 'Route Record Indicator' (0xA1) - * 'Create Source Route Packet' (0x21) + + * Route Record Indicator (0xA1) + * Create Source Route Packet (0x21) * DigiMesh: * Method to get node neighbors: ``get_neighbors()``. * Method to build aggregate route: ``build_aggregate_routes()``. * New frames: - * 'Route Information Packet' (0x8D) + + * Route Information Packet (0x8D) * Documentation update * Bug fixing: @@ -136,7 +141,7 @@ v1.4.0 - 03/18/2021 * Use requested file offset and size instead of fixed chunks (XBPL-344) * Mechanism to calculate the proper block size based on the maximum size received by the client and the maximum payload size (XBPL-346) - * For asyncronous sleeping nodes (Zigbee, DigiMesh, 802.15.4) and + * For asynchronous sleeping nodes (Zigbee, DigiMesh, 802.15.4) and synchronous sleeping networks (DigiMesh), configure a minimum sleep time before update and restore settings at the end. For DigiMesh synchronous sleeping network, the local XBee must be a @@ -147,7 +152,7 @@ v1.4.0 - 03/18/2021 extra processing time and required space when retrieving profile info. * Remove profile extracted files. A profile is opened to access to its contents, and must be closed when done with it. - * Fixed the application of XBee profiles with 'AP' setting changes + * Fixed the application of XBee profiles with ``AP`` setting changes (XBPL-340) * Fixed bootloader update from profile due to bootloader image path mismatch (XBPL-338) @@ -193,8 +198,8 @@ v1.2.0 - 04/05/2019 * Callbacks are now executed in parallel. * Internal callbacks are now defined when needed to avoid issues when more than one callback of the same type is defined. -* Add missing 'Transmit Status', 'Modem Status' and 'Cellular Association - Indication Status' values to cover all XBee Cellular/XBee3 Cellular features. +* Add missing ``Transmit Status``, ``Modem Status``, and ``Cellular Association + Indication Status`` values to cover all XBee Cellular/XBee3 Cellular features. * Bug Fixing: * Fix some bugs related to package spec data. @@ -225,12 +230,12 @@ v1.1.0 - 01/19/2018 * Add support for new hardware variants: * XB8X -* Add missing 'Modem Status' values for Remote Manager connect and disconnect +* Add missing ``Modem Status`` values for Remote Manager connect and disconnect events. * Bug Fixing: * Fix timeouts on Unix platforms. - * Fix the return source endpoint method from the 'ExplicitRXIndicatorPacket' + * Fix the return source endpoint method from the ``ExplicitRXIndicatorPacket`` class. * Perform general bug fixing when working in API escaped mode. From 22f2253ab3657946eb93db6881f59c567ae2d2ca Mon Sep 17 00:00:00 2001 From: Tatiana Leon Date: Wed, 7 Aug 2024 09:33:29 +0200 Subject: [PATCH 35/36] setup: added url to changelog and latest python versions https://onedigi.atlassian.net/browse/XBPL-425 Signed-off-by: Tatiana Leon --- setup.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/setup.py b/setup.py index d0ced24..1dac2a2 100644 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. # -# Copyright 2017-2020, Digi International Inc. All Rights Reserved. +# Copyright 2017-2024, Digi International Inc. All Rights Reserved. import os import sys @@ -59,9 +59,11 @@ classifiers=[ 'Development Status :: 5 - Production/Stable', 'Intended Audience :: Developers', - 'Intended Audience :: Telecommunications Industry', + 'Intended Audience :: Education', 'Intended Audience :: End Users/Desktop', + 'Intended Audience :: Telecommunications Industry', 'Topic :: Software Development :: Libraries', + 'Topic :: Software Development :: Libraries :: Python Modules', 'Topic :: Home Automation', 'Topic :: Games/Entertainment', 'License :: OSI Approved :: Mozilla Public License 2.0 (MPL 2.0)', @@ -72,11 +74,14 @@ 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.9', 'Programming Language :: Python :: 3.10', + 'Programming Language :: Python :: 3.11', + 'Programming Language :: Python :: 3.12', 'Operating System :: OS Independent', ], project_urls={ 'Documentation': 'https://xbplib.readthedocs.io', - 'Source': 'https://github.com/digidotcom/xbee-python', - 'Tracker': 'https://github.com/digidotcom/xbee-python/issues', + 'Source': about['__url__'], + 'Tracker': '%s/issues' % about['__url__'], + 'Changelog': '%s/blob/%s/CHANGELOG.rst' % (about['__url__'], about['__version__']) }, ) From cbd2cfe5ed18ff217cb97c565463be7879055be6 Mon Sep 17 00:00:00 2001 From: Tatiana Leon Date: Wed, 7 Aug 2024 09:38:37 +0200 Subject: [PATCH 36/36] version: update version to 1.5.0 This commit updates the library version, adds a new entry for 1.5.0 in 'CHANGELOG.rst' file, and updates copyright years. https://onedigi.atlassian.net/browse/XBPL-425 Signed-off-by: Tatiana Leon --- CHANGELOG.rst | 35 +++++++++++++++++++++++++---------- README.rst | 2 +- digi/xbee/__init__.py | 6 +++--- doc/index.rst | 2 +- 4 files changed, 30 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 8220d3b..5417d57 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,7 +1,7 @@ Changelog ========= -v1.4.2 - XX/XX/202X +v1.5.0 - 08/08/2024 ------------------- * Support for new hardware variants: @@ -12,27 +12,42 @@ v1.4.2 - XX/XX/202X * XBee RR TH Pro/Non-Pro * XBee 3 Cellular Global Cat 4 * XBee 3 Cellular North America Cat 4 - * XBee XR 900 TH * XBee XR 868 TH + * XBee XR 900 TH * XBee 3 BLU * 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. -* Remove 'pysrp' dependency: +* Firmware/profile update support for: - * The library includes support to generate salt and verifier required to - configure '$S', '$V', '$W', '$X', and '$Y' XBee parameters to establish - Bluetooth password. + * XBee RR MMT/SMT/TH Pro/Non-Pro (XBPL-384) + * XBee XR 868 MMT/SMT/TH, local and remote (LCG-398) + * XBee XR 900 MMT/SMT/TH, local and remote (LCG-398) + * XBee 3 BLU, local (XBPL-391) * Support for sending BLE Generic Access Profile (GAP) scans. - (Only XBee 3 BLU modules support this feature) + (Only XBee 3 BLU modules support this feature) (XBPL-391) +* Remove ``pysrp`` dependency. + + The library includes support to generate salt and verifier required to + configure ``$S``, ``$V``, ``$W``, ``$X``, and ``$Y`` XBee parameters to + establish Bluetooth password. * Bug fixing: + * Fix ``TransmitOptions.ENABLE_UNICAST_TRACE_ROUTE`` value. + * Python 3.10 compatibility: Fix removed deprecated ``collections`` module. + See `collections.abc `_, + `Removed `_, and + `Changes in the Python API `_ + sections at `What’s New In Python 3.10 `_ + (DAL-5918) * Fix order of nodes when creating a Zigbee source route (#278) - * Salt/verifier generation using 'pysrp' was not working with certain + * Firmware update: Do not check XBee region for 'skip' code in XML firmware + file (XBPL-394) + * Salt/verifier generation using ``pysrp`` was not working with certain passwords (see https://github.com/cocagne/pysrp/issues/55) - Solved by removing 'pysrp' dependency and implementing the code to + Solved by removing ``pysrp`` dependency and implementing the code to generate them. + * Several minor bug fixes. v1.4.1 - 12/22/2021 ------------------- diff --git a/README.rst b/README.rst index dfef5bf..d477593 100644 --- a/README.rst +++ b/README.rst @@ -47,7 +47,7 @@ The contributing guidelines are in the `CONTRIBUTING.rst document License ------- -Copyright 2017-2021, Digi International Inc. +Copyright 2017-2024, Digi International Inc. The `MPL 2.0 license `_ covers the majority of this project with the following exceptions: diff --git a/digi/xbee/__init__.py b/digi/xbee/__init__.py index 7cfe313..cd6218c 100644 --- a/digi/xbee/__init__.py +++ b/digi/xbee/__init__.py @@ -1,4 +1,4 @@ -# Copyright 2017-2021, Digi International Inc. +# Copyright 2017-2024, Digi International Inc. # # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this @@ -12,11 +12,11 @@ # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -__version__ = '1.4.1' +__version__ = '1.5.0' __title__ = 'digi-xbee' __description__ = 'Digi XBee Python library' __url__ = 'https://github.com/digidotcom/xbee-python' __author__ = 'Digi International Inc.' __author_email__ = 'tech.support@digi.com' __license__ = 'Mozilla Public License 2.0 (MPL 2.0)' -__copyright__ = '2017-2021, Digi International Inc.' +__copyright__ = '2017-2024, Digi International Inc.' diff --git a/doc/index.rst b/doc/index.rst index 82a2b99..096329b 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -206,7 +206,7 @@ Indices and tables License ======= -Copyright 2017-2021, Digi International Inc. +Copyright 2017-2024, Digi International Inc. This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this