diff --git a/README.rst b/README.rst index 7dfc624..3e11af0 100644 --- a/README.rst +++ b/README.rst @@ -1,22 +1,12 @@ -Digi XBee Python library |pypiversion| |pythonversion| +Digi XBee Python 2 library ====================================================== This project contains the source code of the XBee Python library, an easy-to-use API developed in Python that allows you to interact with Digi International's `XBee `_ radio frequency (RF) -modules. - -This source has been contributed by Digi International. - - -Installation ------------- - -You can install XBee Python library using `pip -`_:: - - $ pip install digi-xbee +modules. Modifications were made to satisfy use in python 2 with Digi XTend Modules. +The code is still under development, but it is a working prototype for Python 2 usage of the modules. Install from Source ------------------- @@ -37,12 +27,7 @@ Read the Docs. You can find the latest, most up to date, documentation at features which have been released, check out the `stable docs `_. - -How to contribute ------------------ - -The contributing guidelines are in the `CONTRIBUTING.rst document -`_. +In addition to the official documentation from Digi, the python 2 library "copy" is required. License diff --git a/digi/xbee/comm_interface.py b/digi/xbee/comm_interface.py deleted file mode 100644 index 3f2dc85..0000000 --- a/digi/xbee/comm_interface.py +++ /dev/null @@ -1,124 +0,0 @@ -# Copyright 2019, 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 abc -from abc import abstractmethod - - -class XBeeCommunicationInterface(metaclass=abc.ABCMeta): - """ - This class represents the way the communication with the local XBee is established. - """ - - @abstractmethod - def open(self): - """ - Establishes the underlying hardware communication interface. - - Subclasses may throw specific exceptions to signal implementation specific - errors. - """ - pass - - @abstractmethod - def close(self): - """ - Terminates the underlying hardware communication interface. - - Subclasses may throw specific exceptions to signal implementation specific - hardware errors. - """ - pass - - @property - @abstractmethod - def is_interface_open(self): - """ - Returns whether the underlying hardware communication interface is active or not. - - Returns: - Boolean. ``True`` if the interface is active, ``False`` otherwise. - """ - pass - - @abstractmethod - def wait_for_frame(self, operating_mode): - """ - Reads the next API frame packet. - - This method blocks until: - * A complete frame is read, in which case returns it. - * The configured timeout goes by, in which case returns None. - * Another thread calls quit_reading, in which case returns None. - - This method is not thread-safe, so no more than one thread should invoke it at the same time. - - Subclasses may throw specific exceptions to signal implementation specific - hardware errors. - - Args: - operating_mode (:class:`.OperatingMode`): the operating mode of the XBee connected to this hardware - interface. - Note: if this parameter does not match the connected XBee configuration, the behavior is undefined. - - Returns: - Bytearray: the read packet as bytearray if a packet is read, ``None`` otherwise. - """ - pass - - @abstractmethod - def quit_reading(self): - """ - Makes the thread (if any) blocking on wait_for_frame return. - - If a thread was blocked on wait_for_frame, this method blocks (for a maximum of 'timeout' seconds) until - the blocked thread is resumed. - """ - pass - - @abstractmethod - def write_frame(self, frame): - """ - Writes an XBee frame to the underlying hardware interface. - - Subclasses may throw specific exceptions to signal implementation specific - hardware errors. - - Args: - frame (:class:`.Bytearray`): The XBee API frame packet to write. If the bytearray does not - correctly represent an XBee frame, the behaviour is undefined. - """ - pass - - @property - @abstractmethod - def timeout(self): - """ - Returns the read timeout. - - Returns: - Integer: read timeout in seconds. - """ - pass - - @timeout.setter - @abstractmethod - def timeout(self, timeout): - """ - Sets the read timeout in seconds. - - Args: - timeout (Integer): the new read timeout in seconds. - """ - pass \ No newline at end of file diff --git a/digi/xbee/devices.py b/digi/xbee/devices.py old mode 100755 new mode 100644 index 8043de8..94f727c --- a/digi/xbee/devices.py +++ b/digi/xbee/devices.py @@ -14,24 +14,24 @@ from abc import ABCMeta, abstractmethod import logging -from enum import Enum, unique from ipaddress import IPv4Address import threading import time -from queue import Queue, Empty -from digi.xbee import serial +import serial +from serial.serialutil import SerialTimeoutException + +import srp + from digi.xbee.packets.cellular import TXSMSPacket from digi.xbee.models.accesspoint import AccessPoint, WiFiEncryptionType -from digi.xbee.models.atcomm import ATCommandResponse, ATCommand, ATStringCommand +from digi.xbee.models.atcomm import ATCommandResponse, ATCommand from digi.xbee.models.hw import HardwareVersion -from digi.xbee.models.mode import OperatingMode, APIOutputMode, IPAddressingMode, NeighborDiscoveryMode, APIOutputModeBit +from digi.xbee.models.mode import OperatingMode, APIOutputMode, IPAddressingMode from digi.xbee.models.address import XBee64BitAddress, XBee16BitAddress, XBeeIMEIAddress -from digi.xbee.models.info import SocketInfo from digi.xbee.models.message import XBeeMessage, ExplicitXBeeMessage, IPMessage -from digi.xbee.models.options import TransmitOptions, RemoteATCmdOptions, DiscoveryOptions, XBeeLocalInterface, \ - RegisterKeyOptions -from digi.xbee.models.protocol import XBeeProtocol, IPProtocol, Role +from digi.xbee.models.options import TransmitOptions, RemoteATCmdOptions, DiscoveryOptions, XBeeLocalInterface +from digi.xbee.models.protocol import XBeeProtocol, IPProtocol from digi.xbee.models.status import ATCommandStatus, TransmitStatus, PowerLevel, \ ModemStatus, CellularAssociationIndicationStatus, WiFiAssociationIndicationStatus, AssociationIndicationStatus,\ NetworkDiscoveryStatus @@ -42,15 +42,13 @@ from digi.xbee.packets.network import TXIPv4Packet from digi.xbee.packets.raw import TX64Packet, TX16Packet from digi.xbee.packets.relay import UserDataRelayPacket -from digi.xbee.packets.zigbee import RegisterJoiningDevicePacket, RegisterDeviceStatusPacket from digi.xbee.util import utils from digi.xbee.exception import XBeeException, TimeoutException, InvalidOperatingModeException, \ - ATCommandException, OperationNotSupportedException, TransmitException + ATCommandException, OperationNotSupportedException from digi.xbee.io import IOSample, IOMode -from digi.xbee.reader import PacketListener, PacketReceived, DeviceDiscovered, \ - DiscoveryProcessFinished, NetworkModified -from digi.xbee.serial import FlowControl -from digi.xbee.serial import XBeeSerialPort +from digi.xbee.reader import PacketListener, PacketReceived, DeviceDiscovered, DiscoveryProcessFinished +from digi.xbee.xbeeserial import FlowControl +from digi.xbee.xbeeserial import XBeeSerialPort from functools import wraps @@ -70,7 +68,7 @@ class AbstractXBeeDevice(object): The Bluetooth Low Energy API username. """ - LOG_PATTERN = "{comm_iface:s} - {event:s} - {opmode:s}: {content:s}" + LOG_PATTERN = "{port:<6s}{event:<12s}{opmode:<20s}{content:<50s}" """ Pattern used to log packet events. """ @@ -80,8 +78,7 @@ class AbstractXBeeDevice(object): Logger. """ - def __init__(self, local_xbee_device=None, serial_port=None, sync_ops_timeout=_DEFAULT_TIMEOUT_SYNC_OPERATIONS, - comm_iface=None): + def __init__(self, local_xbee_device=None, serial_port=None, sync_ops_timeout=_DEFAULT_TIMEOUT_SYNC_OPERATIONS): """ Class constructor. Instantiates a new :class:`.AbstractXBeeDevice` object with the provided parameters. @@ -92,16 +89,11 @@ def __init__(self, local_xbee_device=None, serial_port=None, sync_ops_timeout=_D port that will be used to communicate with this XBee. sync_ops_timeout (Integer, default: :attr:`AbstractXBeeDevice._DEFAULT_TIMEOUT_SYNC_OPERATIONS`): the timeout (in seconds) that will be applied for all synchronous operations. - comm_iface (:class:`.XBeeCommunicationInterface`, optional): only necessary if the XBee device is local. The - hardware interface that will be used to communicate with this XBee. .. seealso:: | :class:`.XBeeDevice` | :class:`.XBeeSerialPort` """ - if (serial_port, comm_iface).count(None) != 1: - raise XBeeException("Either ``serial_port`` or ``comm_iface`` must be ``None`` (and only one of them)") - self.__current_frame_id = 0x00 self._16bit_addr = None @@ -112,9 +104,7 @@ def __init__(self, local_xbee_device=None, serial_port=None, sync_ops_timeout=_D self._operating_mode = None self._local_xbee_device = local_xbee_device - self._comm_iface = serial_port if serial_port is not None else comm_iface - self._serial_port = self._comm_iface if isinstance(self._comm_iface, XBeeSerialPort) else None - + self._serial_port = serial_port self._timeout = sync_ops_timeout self.__io_packet_received = False @@ -124,15 +114,17 @@ def __init__(self, local_xbee_device=None, serial_port=None, sync_ops_timeout=_D self._firmware_version = None self._protocol = None self._node_id = None - self._role = Role.UNKNOWN self._packet_listener = None - self._scan_counter = 0 - self._reachable = True + self._log_handler = logging.StreamHandler() + self._log.addHandler(self._log_handler) self.__generic_lock = threading.Lock() + def __del__(self): + self._log.removeHandler(self._log_handler) + def __eq__(self, other): """ Operator '=='. Compares two :class:`.AbstractXBeeDevice` instances. @@ -155,15 +147,10 @@ def __eq__(self, other): return False if self.get_64bit_addr() is not None and other.get_64bit_addr() is not None: return self.get_64bit_addr() == other.get_64bit_addr() + if self.get_16bit_addr() is not None and other.get_16bit_addr() is not None: + return self.get_16bit_addr() == other.get_16bit_addr() return False - def __hash__(self): - return hash((23, self.get_64bit_addr())) - - def __str__(self): - node_id = "" if self.get_node_id() is None else self.get_node_id() - return "%s - %s" % (self.get_64bit_addr(), node_id) - def update_device_data_from(self, device): """ Updates the current device reference with the data provided for the given device. @@ -172,49 +159,27 @@ def update_device_data_from(self, device): Args: device (:class:`.AbstractXBeeDevice`): the XBee device to get the data from. + """ + if device.get_node_id() is not None: + self._node_id = device.get_node_id() - Return: - Boolean: ``True`` if the device data has been updated, ``False`` otherwise. - """ - updated = False - - new_ni = device.get_node_id() - if new_ni is not None and new_ni != self._node_id: - self._node_id = new_ni - updated = True - - new_addr64 = device.get_64bit_addr() - if (new_addr64 is not None - and new_addr64 != XBee64BitAddress.UNKNOWN_ADDRESS - and new_addr64 != self._64bit_addr - and (self._64bit_addr is None - or self._64bit_addr == XBee64BitAddress.UNKNOWN_ADDRESS)): - self._64bit_addr = new_addr64 - updated = True - - new_addr16 = device.get_16bit_addr() - if (new_addr16 is not None - and new_addr16 != XBee16BitAddress.UNKNOWN_ADDRESS - and new_addr16 != self._16bit_addr): - self._16bit_addr = new_addr16 - updated = True - - new_role = device.get_role() - if (new_role is not None - and new_role != Role.UNKNOWN - and new_role != self._role): - self._role = new_role - updated = True - - return updated - - def get_parameter(self, parameter, parameter_value=None): + addr64 = device.get_64bit_addr() + if (addr64 is not None and + addr64 != XBee64BitAddress.UNKNOWN_ADDRESS and + addr64 != self._64bit_addr and + (self._64bit_addr is None or self._64bit_addr == XBee64BitAddress.UNKNOWN_ADDRESS)): + self._64bit_addr = addr64 + + addr16 = device.get_16bit_addr() + if addr16 is not None and addr16 != self._16bit_addr: + self._16bit_addr = addr16 + + def get_parameter(self, parameter): """ Returns the value of the provided parameter via an AT Command. Args: parameter (String): parameter to get. - parameter_value (Bytearray, optional): The value of the parameter to execute (if any). Returns: Bytearray: the parameter value. @@ -226,11 +191,11 @@ def get_parameter(self, parameter, parameter_value=None): method only checks the cached value of the operating mode. ATCommandException: if the response is not as expected. """ - value = self.__send_parameter(parameter, parameter_value=parameter_value) + value = self.__send_parameter(parameter) - # Check if the response is None, if so throw an exception (maybe it was a write-only parameter). + # Check if the response is null, if so throw an exception (maybe it was a write-only parameter). if value is None: - raise OperationNotSupportedException(message="Could not get the %s value." % parameter) + raise OperationNotSupportedException("Could not get the %s value." % parameter) return value @@ -269,7 +234,7 @@ def set_parameter(self, parameter, value): if value is None: raise ValueError("Value of the parameter cannot be None.") - self.__send_parameter(parameter, parameter_value=value) + self.__send_parameter(parameter, value) # Refresh cached parameters if this method modifies some of them. self._refresh_if_cached(parameter, value) @@ -278,9 +243,6 @@ def execute_command(self, parameter): """ Executes the provided command. - Args: - parameter (String): The name of the AT command to be executed. - Raises: TimeoutException: if the response is not received before the read timeout expires. XBeeException: if the XBee device's serial port is closed. @@ -288,7 +250,7 @@ def execute_command(self, parameter): method only checks the cached value of the operating mode. ATCommandException: if the response is not as expected. """ - self.__send_parameter(parameter, parameter_value=None) + self.__send_parameter(parameter, None) def __send_parameter(self, parameter, parameter_value=None): """ @@ -311,7 +273,7 @@ def __send_parameter(self, parameter, parameter_value=None): if len(parameter) != 2: raise ValueError("Parameter must contain exactly 2 characters.") - at_command = ATCommand(parameter, parameter=parameter_value) + at_command = ATCommand(parameter, parameter_value) # Send the AT command. response = self._send_at_command(at_command) @@ -333,9 +295,9 @@ def _check_at_cmd_response_is_valid(self, response): if ``response.response != OK``. """ if response is None or not isinstance(response, ATCommandResponse) or response.status is None: - raise ATCommandException() + raise ATCommandException(None) elif response.status != ATCommandStatus.OK: - raise ATCommandException(cmd_status=response.status) + raise ATCommandException(response.status) def _send_at_command(self, command): """ @@ -359,7 +321,7 @@ def _send_at_command(self, command): operating_mode = self._get_operating_mode() if operating_mode != OperatingMode.API_MODE and operating_mode != OperatingMode.ESCAPED_API_MODE: - raise InvalidOperatingModeException(op_mode=operating_mode) + raise InvalidOperatingModeException.from_operating_mode(operating_mode) if self.is_remote(): remote_at_cmd_opts = RemoteATCmdOptions.NONE.value @@ -371,13 +333,12 @@ def _send_at_command(self, command): remote_16bit_addr = XBee16BitAddress.UNKNOWN_ADDRESS packet = RemoteATCommandPacket(self._get_next_frame_id(), self.get_64bit_addr(), remote_16bit_addr, - remote_at_cmd_opts, command.command, parameter=command.parameter) + remote_at_cmd_opts, command.command, command.parameter) else: if self.is_apply_changes_enabled(): - packet = ATCommPacket(self._get_next_frame_id(), command.command, - parameter=command.parameter) + packet = ATCommPacket(self._get_next_frame_id(), command.command, command.parameter) else: - packet = ATCommQueuePacket(self._get_next_frame_id(), command.command, parameter=command.parameter) + packet = ATCommQueuePacket(self._get_next_frame_id(), command.command, command.parameter) if self.is_remote(): answer_packet = self._local_xbee_device.send_packet_sync_and_get_response(packet) @@ -387,8 +348,7 @@ def _send_at_command(self, command): response = None if isinstance(answer_packet, ATCommResponsePacket) or isinstance(answer_packet, RemoteATCommandResponsePacket): - response = ATCommandResponse(command, response=answer_packet.command_value, - status=answer_packet.status) + response = ATCommandResponse(command, answer_packet.command_value, answer_packet.status) return response @@ -403,7 +363,7 @@ def apply_changes(self): method only checks the cached value of the operating mode. ATCommandException: if the response is not as expected. """ - self.execute_command(ATStringCommand.AC.command) + self.execute_command("AC") def write_changes(self): """ @@ -433,7 +393,7 @@ def write_changes(self): method only checks the cached value of the operating mode. ATCommandException: if the response is not as expected. """ - self.execute_command(ATStringCommand.WR.command) + self.execute_command("WR") @abstractmethod def reset(self): @@ -449,13 +409,10 @@ def reset(self): """ pass - def read_device_info(self, init=True): + def read_device_info(self): """ Updates all instance parameters reading them from the XBee device. - Args: - init (Boolean, optional, default=`True`): If ``False`` only not initialized parameters - are read, all if ``True``. Raises: TimeoutException: if the response is not received before the read timeout expires. XBeeException: if the XBee device's serial port is closed. @@ -464,127 +421,44 @@ def read_device_info(self, init=True): ATCommandException: if the response is not as expected. """ if self.is_remote(): - if not self._local_xbee_device.comm_iface.is_interface_open: + if not self._local_xbee_device.serial_port.is_open: raise XBeeException("Local XBee device's serial port closed") else: if (self._operating_mode != OperatingMode.API_MODE and self._operating_mode != OperatingMode.ESCAPED_API_MODE): - raise InvalidOperatingModeException(op_mode=self._operating_mode) + raise InvalidOperatingModeException("Not supported operating mode: " + str(self._operating_mode)) - if not self._comm_iface.is_interface_open: + if not self._serial_port.is_open: raise XBeeException("XBee device's serial port closed") # Hardware version: - if init or self._hardware_version is None: - self._hardware_version = HardwareVersion.get( - self.get_parameter(ATStringCommand.HV.command)[0]) + self._hardware_version = HardwareVersion.get(self.get_parameter("HV")[0]) # Firmware version: - if init or self._firmware_version is None: - self._firmware_version = self.get_parameter(ATStringCommand.VR.command) - + self._firmware_version = self.get_parameter("VR") # Original value of the protocol: orig_protocol = self.get_protocol() # Protocol: self._protocol = XBeeProtocol.determine_protocol(self._hardware_version.code, self._firmware_version) - + if orig_protocol is not None and orig_protocol != XBeeProtocol.UNKNOWN and orig_protocol != self._protocol: raise XBeeException("Error reading device information: " "Your module seems to be %s and NOT %s. " % (self._protocol, orig_protocol) + "Check if you are using the appropriate device class.") - + # 64-bit address: - if init or self._64bit_addr is None or self._64bit_addr == XBee64BitAddress.UNKNOWN_ADDRESS: - sh = self.get_parameter(ATStringCommand.SH.command) - sl = self.get_parameter(ATStringCommand.SL.command) - self._64bit_addr = XBee64BitAddress(sh + sl) + sh = self.get_parameter("SH") + sl = self.get_parameter("SL") + self._64bit_addr = XBee64BitAddress(sh + sl) # Node ID: - if init or self._node_id is None: - self._node_id = self.get_parameter(ATStringCommand.NI.command).decode() + self._node_id = self.get_parameter("NI").decode() # 16-bit address: - if (self._protocol in [XBeeProtocol.ZIGBEE, XBeeProtocol.RAW_802_15_4, XBeeProtocol.XTEND, - XBeeProtocol.SMART_ENERGY, XBeeProtocol.ZNET] - and (init or self._16bit_addr is None - or self._16bit_addr == XBee16BitAddress.UNKNOWN_ADDRESS)): - r = self.get_parameter(ATStringCommand.MY.command) + if self._protocol in [XBeeProtocol.ZIGBEE, + XBeeProtocol.RAW_802_15_4, + XBeeProtocol.XTEND, + XBeeProtocol.SMART_ENERGY, + XBeeProtocol.ZNET]: + r = self.get_parameter("MY") self._16bit_addr = XBee16BitAddress(r) - else: - # For protocols that do not support a 16 bit address, set it to unknown - self._16bit_addr = XBee16BitAddress.UNKNOWN_ADDRESS - - # Role: - if init or self._role is None or self._role == Role.UNKNOWN: - self._role = self._determine_role() - - def _determine_role(self): - """ - Determines the role of the device depending on the device protocol. - - Returns: - :class:`digi.xbee.models.protocol.Role`: The XBee role. - - Raises: - TimeoutException: if the response is not received before the read timeout expires. - XBeeException: if the XBee device's serial port is closed. - 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. - ATCommandException: if the response is not as expected. - """ - if self._protocol in [XBeeProtocol.DIGI_MESH, XBeeProtocol.SX, XBeeProtocol.XTEND_DM]: - ce = utils.bytes_to_int(self.get_parameter(ATStringCommand.CE.command)) - if ce == 0: - try: - # Capture the possible exception because DigiMesh S2C does not have - # SS command, so the read will throw an ATCommandException - ss = self.get_parameter(ATStringCommand.SS.command) - except ATCommandException: - ss = None - - if not ss: - return Role.ROUTER - - ss = utils.bytes_to_int(ss) - if utils.is_bit_enabled(ss, 1): - return Role.COORDINATOR - else: - return Role.ROUTER - elif ce == 1: - return Role.COORDINATOR - else: - return Role.END_DEVICE - elif self._protocol in [XBeeProtocol.RAW_802_15_4, XBeeProtocol.DIGI_POINT, - XBeeProtocol.XLR, XBeeProtocol.XLR_DM]: - ce = utils.bytes_to_int(self.get_parameter(ATStringCommand.CE.command)) - if self._protocol == XBeeProtocol.RAW_802_15_4: - if ce == 0: - return Role.END_DEVICE - elif ce == 1: - return Role.COORDINATOR - else: - if ce == 0: - return Role.ROUTER - elif ce in (1, 3): - return Role.COORDINATOR - elif ce in (2, 4, 6): - return Role.END_DEVICE - elif self._protocol in [XBeeProtocol.ZIGBEE, XBeeProtocol.SMART_ENERGY]: - try: - ce = utils.bytes_to_int(self.get_parameter(ATStringCommand.CE.command)) - if ce == 1: - return Role.COORDINATOR - - sm = utils.bytes_to_int(self.get_parameter(ATStringCommand.SM.command)) - - return Role.ROUTER if sm == 0 else Role.END_DEVICE - except ATCommandException: - from digi.xbee.models.zdo import NodeDescriptorReader - nd = NodeDescriptorReader( - self, configure_ao=True, - timeout=3*self._timeout if self.is_remote() else 2*self._timeout) \ - .get_node_descriptor() - if nd: - return nd.role - - return Role.UNKNOWN def get_node_id(self): """ @@ -611,7 +485,7 @@ def set_node_id(self, node_id): if len(node_id) > 20: raise ValueError("Node ID length must be less than 21") - self.set_parameter(ATStringCommand.NI.command, bytearray(node_id, 'utf8')) + self.set_parameter("NI", bytearray(node_id, 'utf8')) self._node_id = node_id def get_hardware_version(self): @@ -675,9 +549,9 @@ def set_16bit_addr(self, value): OperationNotSupportedException: if the current protocol is not 802.15.4. """ if self.get_protocol() != XBeeProtocol.RAW_802_15_4: - raise OperationNotSupportedException(message="16-bit address can only be set in 802.15.4 protocol") + raise OperationNotSupportedException("16-bit address can only be set in 802.15.4 protocol") - self.set_parameter(ATStringCommand.MY.command, value.address) + self.set_parameter("MY", value.address) self._16bit_addr = value def get_64bit_addr(self): @@ -692,18 +566,6 @@ def get_64bit_addr(self): """ return self._64bit_addr - def get_role(self): - """ - Gets the XBee role. - - Returns: - :class:`digi.xbee.models.protocol.Role`: the role of the XBee. - - .. seealso:: - | :class:`digi.xbee.models.protocol.Role` - """ - return self._role - def get_current_frame_id(self): """ Returns the last used frame ID. @@ -750,9 +612,9 @@ def set_sync_ops_timeout(self, sync_ops_timeout): """ self._timeout = sync_ops_timeout if self.is_remote(): - self._local_xbee_device.comm_iface.timeout = self._timeout + self._local_xbee_device.serial_port.timeout = self._timeout else: - self._comm_iface.timeout = self._timeout + self._serial_port.timeout = self._timeout def get_sync_ops_timeout(self): """ @@ -776,8 +638,8 @@ def get_dest_address(self): .. seealso:: | :class:`.XBee64BitAddress` """ - dh = self.get_parameter(ATStringCommand.DH.command) - dl = self.get_parameter(ATStringCommand.DL.command) + dh = self.get_parameter("DH") + dl = self.get_parameter("DL") return XBee64BitAddress(dh + dl) def set_dest_address(self, addr): @@ -785,16 +647,12 @@ def set_dest_address(self, addr): Sets the 64-bit address of the XBee device that data will be reported to. Args: - addr (:class:`.XBee64BitAddress` or :class:`.RemoteXBeeDevice`): the address itself or the remote XBee - device that you want to set up its address as destination address. + addr(:class:`.XBee64BitAddress` or :class:`.RemoteXBeeDevice`): the address itself or the remote XBee device + that you want to set up its address as destination address. Raises: - TimeoutException: If the response is not received before the read timeout expires. - XBeeException: If the XBee device's serial port is closed. - 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. - ATCommandException: If the response is not as expected. - ValueError: If ``addr`` is ``None``. + TimeoutException: if the response is not received before the read timeout expires. + All exceptions raised by :meth:`.XBeeDevice.set_parameter`. """ if isinstance(addr, RemoteXBeeDevice): addr = addr.get_64bit_addr() @@ -804,8 +662,8 @@ def set_dest_address(self, addr): try: apply_changes = self.is_apply_changes_enabled() self.enable_apply_changes(False) - self.set_parameter(ATStringCommand.DH.command, addr.address[:4]) - self.set_parameter(ATStringCommand.DL.command, addr.address[4:]) + self.set_parameter("DH", addr.address[:4]) + self.set_parameter("DL", addr.address[4:]) except (TimeoutException, XBeeException, InvalidOperatingModeException, ATCommandException) as e: # Raise the exception. raise e @@ -825,8 +683,8 @@ def get_pan_id(self): TimeoutException: if the response is not received before the read timeout expires. """ if self.get_protocol() == XBeeProtocol.ZIGBEE: - return self.get_parameter(ATStringCommand.OP.command) - return self.get_parameter(ATStringCommand.ID.command) + return self.get_parameter("OP") + return self.get_parameter("ID") def set_pan_id(self, value): """ @@ -838,7 +696,7 @@ def set_pan_id(self, value): Raises: TimeoutException: if the response is not received before the read timeout expires. """ - self.set_parameter(ATStringCommand.ID.command, value) + self.set_parameter("ID", value) def get_power_level(self): """ @@ -853,7 +711,7 @@ def get_power_level(self): .. seealso:: | :class:`.PowerLevel` """ - return PowerLevel.get(self.get_parameter(ATStringCommand.PL.command)[0]) + return PowerLevel.get(self.get_parameter("PL")[0]) def set_power_level(self, power_level): """ @@ -868,7 +726,7 @@ def set_power_level(self, power_level): .. seealso:: | :class:`.PowerLevel` """ - self.set_parameter(ATStringCommand.PL.command, bytearray([power_level.code])) + self.set_parameter("PL", bytearray([power_level.code])) def set_io_configuration(self, io_line, io_mode): """ @@ -909,12 +767,10 @@ def get_io_configuration(self, io_line): ATCommandException: if the response is not as expected. OperationNotSupportedException: if the received data is not an IO mode. """ - value = self.get_parameter(io_line.at_command) try: - mode = IOMode(value[0]) + mode = IOMode.get(self.get_parameter(io_line.at_command)[0]) except ValueError: - raise OperationNotSupportedException( - "Received configuration IO mode '%s' is invalid." % utils.hex_to_string(value)) + raise OperationNotSupportedException("The received value is not an IO mode.") return mode def get_io_sampling_rate(self): @@ -931,7 +787,7 @@ def get_io_sampling_rate(self): method only checks the cached value of the operating mode. ATCommandException: if the response is not as expected. """ - resp = self.get_parameter(ATStringCommand.IR.command) + resp = self.get_parameter("IR") return utils.bytes_to_int(resp) / 1000.00 def set_io_sampling_rate(self, rate): @@ -949,7 +805,7 @@ def set_io_sampling_rate(self, rate): method only checks the cached value of the operating mode. ATCommandException: if the response is not as expected. """ - self.set_parameter(ATStringCommand.IR.command, utils.int_to_bytes(int(rate * 1000))) + self.set_parameter("IR", utils.int_to_bytes(int(rate * 1000))) def read_io_sample(self): """ @@ -1002,19 +858,19 @@ def io_sample_callback(received_packet): try: # Execute command. - self.execute_command(ATStringCommand.IS.command) + self.execute_command("IS") lock.acquire() lock.wait(self.get_sync_ops_timeout()) lock.release() if self.__io_packet_payload is None: - raise TimeoutException(message="Timeout waiting for the IO response packet.") + raise TimeoutException("Timeout waiting for the IO response packet.") sample_payload = self.__io_packet_payload finally: self._del_packet_received_callback(io_sample_callback) else: - sample_payload = self.get_parameter(ATStringCommand.IS.command) + sample_payload = self.get_parameter("IS") try: return IOSample(sample_payload) @@ -1047,9 +903,7 @@ def get_adc_value(self, io_line): """ io_sample = self.read_io_sample() if not io_sample.has_analog_values() or io_line not in io_sample.analog_values.keys(): - raise OperationNotSupportedException( - "Answer does not contain analog data for %s." % io_line.description) - + raise OperationNotSupportedException("Answer does not contain analog values for the given IO line.") return io_sample.analog_values[io_line] def set_pwm_duty_cycle(self, io_line, cycle): @@ -1137,8 +991,7 @@ def get_dio_value(self, io_line): """ sample = self.read_io_sample() if not sample.has_digital_values() or io_line not in sample.digital_values.keys(): - raise OperationNotSupportedException( - "Answer does not contain digital data for %s." % io_line.description) + raise OperationNotSupportedException("Answer does not contain digital values for the given IO_LINE") return sample.digital_values[io_line] def set_dio_value(self, io_line, io_value): @@ -1189,9 +1042,8 @@ def set_dio_change_detection(self, io_lines_set): flags[1] = flags[1] | (1 << i) else: flags[0] = flags[0] | ((1 << i) - 8) - self.set_parameter(ATStringCommand.IC.command, flags) + self.set_parameter("IC", flags) - @utils.deprecated("1.3", details="Use :meth:`get_api_output_mode_value`") def get_api_output_mode(self): """ Returns the API output mode of the XBee device. @@ -1212,39 +1064,8 @@ def get_api_output_mode(self): .. seealso:: | :class:`.APIOutputMode` """ - return APIOutputMode.get(self.get_parameter(ATStringCommand.AO.command)[0]) - - def get_api_output_mode_value(self): - """ - Returns the API output mode of the XBee. - - The API output mode determines the format that the received data is - output through the serial interface of the XBee. - - Returns: - Bytearray: the parameter value. - - Raises: - TimeoutException: if the response is not received before the read timeout expires. - XBeeException: if the XBee device's serial port is closed. - 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. - ATCommandException: if the response is not as expected. - OperationNotSupportedException: if it is not supported by the current protocol. - - .. seealso:: - | :class:`digi.xbee.models.mode.APIOutputModeBit` - """ - if self.get_protocol() not in (XBeeProtocol.ZIGBEE, XBeeProtocol.DIGI_MESH, - XBeeProtocol.DIGI_POINT, XBeeProtocol.XLR, - XBeeProtocol.XLR_DM): - raise OperationNotSupportedException( - message="Operation not supported for the current protocol (%s)" - % self.get_protocol().description) - - return self.get_parameter(ATStringCommand.AO.command) + return APIOutputMode.get(self.get_parameter("AO")[0]) - @utils.deprecated("1.3", details="Use :meth:`set_api_output_mode_value`") def set_api_output_mode(self, api_output_mode): """ Sets the API output mode of the XBee device. @@ -1263,40 +1084,7 @@ def set_api_output_mode(self, api_output_mode): .. seealso:: | :class:`.APIOutputMode` """ - self.set_parameter(ATStringCommand.AO.command, bytearray([api_output_mode.code])) - - def set_api_output_mode_value(self, api_output_mode): - """ - Sets the API output mode of the XBee. - - Args: - api_output_mode (Integer): new API output mode options. Calculate this value using - the method - :meth:`digi.xbee.models.mode.APIOutputModeBit.calculate_api_output_mode_value` - with a set of :class:`digi.xbee.models.mode.APIOutputModeBit`. - - Raises: - TimeoutException: if the response is not received before the read timeout expires. - XBeeException: if the XBee device's serial port is closed. - 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. - ATCommandException: if the response is not as expected. - OperationNotSupportedException: if it is not supported by the current protocol. - - .. seealso:: - | :class:`digi.xbee.models.mode.APIOutputModeBit` - """ - if api_output_mode is None: - raise ValueError("API output mode cannot be None") - - if self.get_protocol() not in (XBeeProtocol.ZIGBEE, XBeeProtocol.DIGI_MESH, - XBeeProtocol.DIGI_POINT, XBeeProtocol.XLR, - XBeeProtocol.XLR_DM): - raise OperationNotSupportedException( - message="Operation not supported for the current protocol (%s)" - % self.get_protocol().description) - - self.set_parameter(ATStringCommand.AO.command, bytearray([api_output_mode])) + self.set_parameter("AO", bytearray([api_output_mode.code])) def enable_bluetooth(self): """ @@ -1342,7 +1130,7 @@ def _enable_bluetooth(self, enable): 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. """ - self.set_parameter(ATStringCommand.BT.command, b'\x01' if enable else b'\x00') + self.set_parameter("BT", b'\x01' if enable else b'\x00') self.write_changes() self.apply_changes() @@ -1361,7 +1149,7 @@ def get_bluetooth_mac_addr(self): 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. """ - return utils.hex_to_string(self.get_parameter(ATStringCommand.BL.command), pretty=False) + return utils.hex_to_string(self.get_parameter("BL"), False) def update_bluetooth_password(self, new_password): """ @@ -1378,8 +1166,6 @@ def update_bluetooth_password(self, new_password): 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. """ - 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) @@ -1388,115 +1174,24 @@ def update_bluetooth_password(self, new_password): verifier = (128 - len(verifier)) * b'\x00' + verifier # Set the salt. - self.set_parameter(ATStringCommand.DOLLAR_S.command, salt) + self.set_parameter("$S", salt) # Set the verifier (split in 4 settings) index = 0 at_length = int(len(verifier) / 4) - self.set_parameter(ATStringCommand.DOLLAR_V.command, verifier[index:(index + at_length)]) + self.set_parameter("$V", verifier[index:(index + at_length)]) index += at_length - self.set_parameter(ATStringCommand.DOLLAR_W.command, verifier[index:(index + at_length)]) + self.set_parameter("$W", verifier[index:(index + at_length)]) index += at_length - self.set_parameter(ATStringCommand.DOLLAR_X.command, verifier[index:(index + at_length)]) + self.set_parameter("$X", verifier[index:(index + at_length)]) index += at_length - self.set_parameter(ATStringCommand.DOLLAR_Y.command, verifier[index:(index + at_length)]) + self.set_parameter("$Y", verifier[index:(index + at_length)]) # Write and apply changes. self.write_changes() self.apply_changes() - def update_firmware(self, xml_firmware_file, xbee_firmware_file=None, bootloader_firmware_file=None, - timeout=None, progress_callback=None): - """ - Performs a firmware update operation of the device. - - Args: - xml_firmware_file (String): path of the XML file that describes the firmware to upload. - xbee_firmware_file (String, optional): location of the XBee binary firmware file. - bootloader_firmware_file (String, optional): location of the bootloader binary firmware file. - timeout (Integer, optional): the maximum time to wait for target read operations during the update process. - progress_callback (Function, optional): function to execute to receive progress information. Receives two - arguments: - - * The current update task as a String - * The current update task percentage as an Integer - - Raises: - XBeeException: if the device is not open. - InvalidOperatingModeException: if the device operating mode is invalid. - OperationNotSupportedException: if the firmware update is not supported in the XBee device. - FirmwareUpdateException: if there is any error performing the firmware update. - """ - from digi.xbee import firmware - - if not self._comm_iface.is_open: - raise XBeeException("XBee device's communication interface closed.") - if self.get_hardware_version() and self.get_hardware_version().code not in firmware.SUPPORTED_HARDWARE_VERSIONS: - raise OperationNotSupportedException("Firmware update is only supported in XBee3 devices") - if self.is_remote(): - firmware.update_remote_firmware(self, xml_firmware_file, - ota_firmware_file=xbee_firmware_file, - otb_firmware_file=bootloader_firmware_file, - timeout=timeout, - progress_callback=progress_callback) - else: - if self._operating_mode != OperatingMode.API_MODE and \ - self._operating_mode != OperatingMode.ESCAPED_API_MODE: - raise InvalidOperatingModeException(op_mode=self._operating_mode) - if not self._serial_port: - raise OperationNotSupportedException("Firmware update is only supported in local XBee connected by " - "serial.") - firmware.update_local_firmware(self, xml_firmware_file, - xbee_firmware_file=xbee_firmware_file, - bootloader_firmware_file=bootloader_firmware_file, - timeout=timeout, - progress_callback=progress_callback) - - def _autodetect_device(self): - """ - Performs an autodetection of the device. - - Raises: - RecoveryException: if there is any error performing the recovery. - OperationNotSupportedException: if the firmware autodetection is not supported in the XBee device. - """ - from digi.xbee import recovery - - if self.get_hardware_version() and self.get_hardware_version().code not in recovery.SUPPORTED_HARDWARE_VERSIONS: - raise OperationNotSupportedException("Autodetection is only supported in XBee3 devices") - recovery.recover_device(self) - - def apply_profile(self, profile_path, progress_callback=None): - """ - Applies the given XBee profile to the XBee device. - - Args: - profile_path (String): path of the XBee profile file to apply. - progress_callback (Function, optional): function to execute to receive progress information. Receives two - arguments: - - * The current apply profile task as a String - * The current apply profile task percentage as an Integer - - Raises: - XBeeException: if the device is not open. - InvalidOperatingModeException: if the device operating mode is invalid. - UpdateProfileException: if there is any error applying the XBee profile. - OperationNotSupportedException: if XBee profiles are not supported in the XBee device. - """ - from digi.xbee import profile - - if not self._comm_iface.is_open: - raise XBeeException("XBee device's communication interface closed.") - if not self.is_remote() and self._operating_mode != OperatingMode.API_MODE and \ - self._operating_mode != OperatingMode.ESCAPED_API_MODE: - raise InvalidOperatingModeException(op_mode=self._operating_mode) - if self.get_hardware_version() and self.get_hardware_version().code not in profile.SUPPORTED_HARDWARE_VERSIONS: - raise OperationNotSupportedException("XBee profiles are only supported in XBee3 devices") - - profile.apply_xbee_profile(self, profile_path, progress_callback=progress_callback) - def _get_ai_status(self): """ Returns the current association status of this XBee device. @@ -1514,7 +1209,7 @@ def _get_ai_status(self): method only checks the cached value of the operating mode. ATCommandException: if the response is not as expected. """ - value = self.get_parameter(ATStringCommand.AI.command) + value = self.get_parameter("AI") return AssociationIndicationStatus.get(utils.bytes_to_int(value)) def _force_disassociate(self): @@ -1531,7 +1226,7 @@ def _force_disassociate(self): method only checks the cached value of the operating mode. ATCommandException: if the response is not as expected. """ - self.execute_command(ATStringCommand.DA.command) + self.execute_command("DA") def _refresh_if_cached(self, parameter, value): """ @@ -1543,11 +1238,11 @@ def _refresh_if_cached(self, parameter, value): parameter (String): the parameter to refresh its value. value (Bytearray): the new value of the parameter. """ - if parameter == ATStringCommand.NI.command: + if parameter == "NI": self._node_id = value.decode() - elif parameter == ATStringCommand.MY.command: + elif parameter == "MY": self._16bit_addr = XBee16BitAddress(value) - elif parameter == ATStringCommand.AP.command: + elif parameter == "AP": self._operating_mode = OperatingMode.get(utils.bytes_to_int(value)) def _get_next_frame_id(self): @@ -1557,17 +1252,11 @@ def _get_next_frame_id(self): Returns: Integer: The next frame ID of the XBee device. """ - if self.is_remote(): - fid = self._local_xbee_device._get_next_frame_id() - + if self.__current_frame_id == 0xFF: + self.__current_frame_id = 1 else: - if self.__current_frame_id == 0xFF: - self.__current_frame_id = 1 - else: - self.__current_frame_id += 1 - fid = self.__current_frame_id - - return fid + self.__current_frame_id += 1 + return self.__current_frame_id def _get_operating_mode(self): """ @@ -1589,11 +1278,12 @@ def _before_send_method(func): """ @wraps(func) def dec_function(self, *args, **kwargs): - if not self._comm_iface.is_interface_open: + if not self._serial_port.is_open: raise XBeeException("XBee device's serial port closed.") if (self._operating_mode != OperatingMode.API_MODE and self._operating_mode != OperatingMode.ESCAPED_API_MODE): - raise InvalidOperatingModeException(op_mode=args[0].operating_mode) + raise InvalidOperatingModeException("Not supported operating mode: " + + str(args[0].operating_mode.description)) return func(self, *args, **kwargs) return dec_function @@ -1605,9 +1295,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): - raise TransmitException(transmit_status=response.transmit_status) + if response.transmit_status != TransmitStatus.SUCCESS: + raise XBeeException("Transmit status: %s" % response.transmit_status.description) return response return dec_function @@ -1630,7 +1319,7 @@ def _get_packet_by_id(self, frame_id): queue = self._packet_listener.get_queue() - packet = queue.get_by_id(frame_id, timeout=XBeeDevice.TIMEOUT_READ_PACKET) + packet = queue.get_by_id(frame_id, XBeeDevice.TIMEOUT_READ_PACKET) return packet @@ -1652,29 +1341,192 @@ def __is_api_packet(xbee_packet): def _add_packet_received_callback(self, callback): """ - Adds a callback for the event :class:`digi.xbee.reader.PacketReceived`. + Adds a callback for the event :class:`.PacketReceived`. Args: callback (Function): the callback. Receives two arguments. - * The received packet as a :class:`digi.xbee.packets.base.XBeeAPIPacket` + * The received packet as a :class:`.XBeeAPIPacket` + * The sender as a :class:`.RemoteXBeeDevice` """ self._packet_listener.add_packet_received_callback(callback) + def _add_data_received_callback(self, callback): + """ + Adds a callback for the event :class:`.DataReceived`. + + Args: + callback (Function): the callback. Receives one argument. + + * The data received as an :class:`.XBeeMessage` + """ + self._packet_listener.add_data_received_callback(callback) + + def _add_modem_status_received_callback(self, callback): + """ + Adds a callback for the event :class:`.ModemStatusReceived`. + + Args: + callback (Function): the callback. Receives one argument. + + * The modem status as a :class:`.ModemStatus` + """ + self._packet_listener.add_modem_status_received_callback(callback) + + def _add_io_sample_received_callback(self, callback): + """ + Adds a callback for the event :class:`.IOSampleReceived`. + + Args: + callback (Function): the callback. Receives three arguments. + + * The received IO sample as an :class:`.IOSample` + * The remote XBee device who has sent the packet as a :class:`.RemoteXBeeDevice` + * The time in which the packet was received as an Integer + """ + self._packet_listener.add_io_sample_received_callback(callback) + + def _add_expl_data_received_callback(self, callback): + """ + Adds a callback for the event :class:`.ExplicitDataReceived`. + + Args: + callback (Function): the callback. Receives one argument. + + * The explicit data received as an :class:`.ExplicitXBeeMessage` + """ + self._packet_listener.add_explicit_data_received_callback(callback) + + def _add_user_data_relay_received_callback(self, callback): + """ + Adds a callback for the event :class:`.RelayDataReceived`. + + Args: + callback (Function): the callback. Receives one argument. + + * The relay data as a :class:`.UserDataRelayMessage` + """ + self._packet_listener.add_user_data_relay_received_callback(callback) + + def _add_bluetooth_data_received_callback(self, callback): + """ + Adds a callback for the event :class:`.BluetoothDataReceived`. + + Args: + callback (Function): the callback. Receives one argument. + + * The Bluetooth data as a Bytearray + """ + self._packet_listener.add_bluetooth_data_received_callback(callback) + + def _add_micropython_data_received_callback(self, callback): + """ + Adds a callback for the event :class:`.MicroPythonDataReceived`. + + Args: + callback (Function): the callback. Receives one argument. + + * The MicroPython data as a Bytearray + """ + self._packet_listener.add_micropython_data_received_callback(callback) + def _del_packet_received_callback(self, callback): """ - Deletes a callback for the callback list of :class:`digi.xbee.reader.PacketReceived` event. + Deletes a callback for the callback list of :class:`.PacketReceived` event. Args: callback (Function): the callback to delete. Raises: - ValueError: if ``callback`` is not in the callback list of - :class:`digi.xbee.reader.PacketReceived` event. + ValueError: if ``callback`` is not in the callback list of :class:`.PacketReceived` event. """ self._packet_listener.del_packet_received_callback(callback) - def _send_packet_sync_and_get_response(self, packet_to_send, timeout=None): + def _del_data_received_callback(self, callback): + """ + Deletes a callback for the callback list of :class:`.DataReceived` event. + + Args: + callback (Function): the callback to delete. + + Raises: + ValueError: if ``callback`` is not in the callback list of :class:`.DataReceived` event. + """ + self._packet_listener.del_data_received_callback(callback) + + def _del_modem_status_received_callback(self, callback): + """ + Deletes a callback for the callback list of :class:`.ModemStatusReceived` event. + + Args: + callback (Function): the callback to delete. + + Raises: + ValueError: if ``callback`` is not in the callback list of :class:`.ModemStatusReceived` event. + """ + self._packet_listener.del_modem_status_received_callback(callback) + + def _del_io_sample_received_callback(self, callback): + """ + Deletes a callback for the callback list of :class:`.IOSampleReceived` event. + + Args: + callback (Function): the callback to delete. + + Raises: + ValueError: if ``callback`` is not in the callback list of :class:`.IOSampleReceived` event. + """ + self._packet_listener.del_io_sample_received_callback(callback) + + def _del_expl_data_received_callback(self, callback): + """ + Deletes a callback for the callback list of :class:`.ExplicitDataReceived` event. + + Args: + callback (Function): the callback to delete. + + Raises: + ValueError: if ``callback`` is not in the callback list of :class:`.ExplicitDataReceived` event. + """ + self._packet_listener.del_explicit_data_received_callback(callback) + + def _del_user_data_relay_received_callback(self, callback): + """ + Deletes a callback for the callback list of :class:`.RelayDataReceived` event. + + Args: + callback (Function): the callback to delete. + + Raises: + ValueError: if ``callback`` is not in the callback list of :class:`.RelayDataReceived` event. + """ + self._packet_listener.del_user_data_relay_received_callback(callback) + + def _del_bluetooth_data_received_callback(self, callback): + """ + Deletes a callback for the callback list of :class:`.BluetoothDataReceived` event. + + Args: + callback (Function): the callback to delete. + + Raises: + ValueError: if ``callback`` is not in the callback list of :class:`.BluetoothDataReceived` event. + """ + self._packet_listener.del_bluetooth_data_received_callback(callback) + + def _del_micropython_data_received_callback(self, callback): + """ + Deletes a callback for the callback list of :class:`.MicroPythonDataReceived` event. + + Args: + callback (Function): the callback to delete. + + Raises: + ValueError: if ``callback`` is not in the callback list of :class:`.MicroPythonDataReceived` event. + """ + self._packet_listener.del_micropython_data_received_callback(callback) + + def _send_packet_sync_and_get_response(self, packet_to_send): """ Perform all operations needed for a synchronous operation when the packet listener is online. This operations are: @@ -1699,8 +1551,6 @@ def _send_packet_sync_and_get_response(self, packet_to_send, timeout=None): Args: packet_to_send (:class:`.XBeePacket`): the packet to send. - timeout (Integer, optional): timeout to wait. If no timeout is provided, the default one is used. To wait - indefinitely, set to ``-1``. Returns: :class:`.XBeePacket`: the response packet obtained after sending the provided one. @@ -1722,60 +1572,25 @@ def packet_received_callback(received_packet): return # If the packet sent is an AT command, verify that the received one is an AT command response and # the command matches in both packets. - if packet_to_send.get_frame_type() == ApiFrameType.AT_COMMAND \ - and (received_packet.get_frame_type() != ApiFrameType.AT_COMMAND_RESPONSE - or packet_to_send.command != received_packet.command): - return + if packet_to_send.get_frame_type() == ApiFrameType.AT_COMMAND: + if received_packet.get_frame_type() != ApiFrameType.AT_COMMAND_RESPONSE: + return + if packet_to_send.command != received_packet.command: + return # If the packet sent is a remote AT command, verify that the received one is a remote AT command # response and the command matches in both packets. - if packet_to_send.get_frame_type() == ApiFrameType.REMOTE_AT_COMMAND_REQUEST \ - and (received_packet.get_frame_type() != ApiFrameType.REMOTE_AT_COMMAND_RESPONSE - or packet_to_send.command != received_packet.command - or (packet_to_send.x64bit_dest_addr != XBee64BitAddress.BROADCAST_ADDRESS - and packet_to_send.x64bit_dest_addr != XBee64BitAddress.UNKNOWN_ADDRESS - and packet_to_send.x64bit_dest_addr != received_packet.x64bit_source_addr) - or (packet_to_send.x16bit_dest_addr != XBee16BitAddress.BROADCAST_ADDRESS - and packet_to_send.x16bit_dest_addr != XBee16BitAddress.UNKNOWN_ADDRESS - and packet_to_send.x16bit_dest_addr != received_packet.x16bit_source_addr)): - return - # If the packet sent is a Socket Create, verify that the received one is a Socket Create Response. - if packet_to_send.get_frame_type() == ApiFrameType.SOCKET_CREATE \ - and received_packet.get_frame_type() != ApiFrameType.SOCKET_CREATE_RESPONSE: - return - # If the packet sent is a Socket Option Request, verify that the received one is a Socket Option - # Response and the socket ID matches in both packets. - if packet_to_send.get_frame_type() == ApiFrameType.SOCKET_OPTION_REQUEST \ - and (received_packet.get_frame_type() != ApiFrameType.SOCKET_OPTION_RESPONSE - or packet_to_send.socket_id != received_packet.socket_id): - return - # If the packet sent is a Socket Connect, verify that the received one is a Socket Connect Response - # and the socket ID matches in both packets. - if packet_to_send.get_frame_type() == ApiFrameType.SOCKET_CONNECT \ - and (received_packet.get_frame_type() != ApiFrameType.SOCKET_CONNECT_RESPONSE - or packet_to_send.socket_id != received_packet.socket_id): - return - # If the packet sent is a Socket Close, verify that the received one is a Socket Close Response - # and the socket ID matches in both packets. - if packet_to_send.get_frame_type() == ApiFrameType.SOCKET_CLOSE \ - and (received_packet.get_frame_type() != ApiFrameType.SOCKET_CLOSE_RESPONSE - or packet_to_send.socket_id != received_packet.socket_id): - return - # If the packet sent is a Socket Bind, verify that the received one is a Socket Listen Response - # and the socket ID matches in both packets. - if packet_to_send.get_frame_type() == ApiFrameType.SOCKET_BIND \ - and (received_packet.get_frame_type() != ApiFrameType.SOCKET_LISTEN_RESPONSE - or packet_to_send.socket_id != received_packet.socket_id): - return + if packet_to_send.get_frame_type() == ApiFrameType.REMOTE_AT_COMMAND_REQUEST: + if received_packet.get_frame_type() != ApiFrameType.REMOTE_AT_COMMAND_RESPONSE: + return + if packet_to_send.command != received_packet.command: + return # Verify that the sent packet is not the received one! This can happen when the echo mode is enabled # in the serial port. - if packet_to_send == received_packet: - return - - # Add the received packet to the list and notify the lock. - response_list.append(received_packet) - lock.acquire() - lock.notify() - lock.release() + if not packet_to_send == received_packet: + response_list.append(received_packet) + lock.acquire() + lock.notify() + lock.release() # Add the packet received callback. self._add_packet_received_callback(packet_received_callback) @@ -1785,14 +1600,11 @@ def packet_received_callback(received_packet): self._send_packet(packet_to_send) # Wait for response or timeout. lock.acquire() - if timeout == -1: - lock.wait() - else: - lock.wait(self._timeout if timeout is None else timeout) + lock.wait(self._timeout) lock.release() # After the wait check if we received any response, if not throw timeout exception. if not response_list: - raise TimeoutException(message="Response not received in the configured timeout.") + raise TimeoutException("Response not received in the configured timeout.") # Return the received packet. return response_list[0] finally: @@ -1834,125 +1646,15 @@ def _send_packet(self, packet, sync=False): raise XBeeException("Packet listener is not running.") escape = self._operating_mode == OperatingMode.ESCAPED_API_MODE - out = packet.output(escaped=escape) - self._comm_iface.write_frame(out) - self._log.debug(self.LOG_PATTERN.format(comm_iface=str(self._comm_iface), + out = packet.output(escape) + self._serial_port.write(out) + self._log.debug(self.LOG_PATTERN.format(port=self._serial_port.port, event="SENT", opmode=self._operating_mode, content=utils.hex_to_string(out))) return self._get_packet_by_id(packet.frame_id) if sync else None - def _get_routes(self, route_callback=None, process_finished_callback=None, timeout=None): - """ - Returns the routes of this XBee. If ``route_callback`` is not defined, the process blocks - until the complete routing table is read. - - Args: - route_callback (Function, optional, default=``None``): method called when a new route - is received. Receives two arguments: - - * The XBee that owns this new route. - * The new route. - - process_finished_callback (Function, optional, default=``None``): method to execute when - the process finishes. Receives two arguments: - - * The XBee device that executed the ZDO command. - * A list with the discovered routes. - * An error message if something went wrong. - - timeout (Float, optional, default=``RouteTableReader.DEFAULT_TIMEOUT``): The ZDO command - timeout in seconds. - Returns: - List: List of :class:`com.digi.models.zdo.Route` when ``route_callback`` is defined, - ``None`` otherwise (in this case routes are received in the callback). - - Raises: - OperationNotSupportedException: If XBee protocol is not Zigbee or Smart Energy. - - .. seealso:: - | :class:`com.digi.models.zdo.Route` - """ - from digi.xbee.models.zdo import RouteTableReader - reader = RouteTableReader(self, configure_ao=True, - timeout=timeout if timeout else RouteTableReader.DEFAULT_TIMEOUT) - - return reader.get_route_table(route_callback=route_callback, - process_finished_callback=process_finished_callback) - - def _get_neighbors(self, neighbor_callback=None, process_finished_callback=None, timeout=None): - """ - Returns the neighbors of this XBee. If ``neighbor_callback`` is not defined: - * In Zigbee and SmartEnergy the process blocks until the complete neighbor table is read. - * In DigiMesh the process blocks the provided timeout. - - Args: - neighbor_callback (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. - - process_finished_callback (Function, optional, default=``None``): method to execute when - the process finishes. Receives two arguments: - - * The XBee device that is searching for its neighbors. - * A list with the discovered neighbors. - * An error message if something went wrong. - - timeout (Float, optional, default=``None``): The timeout in seconds. - Returns: - List: List of :class:`com.digi.models.zdo.Neighbor` when ``neighbor_callback`` is - defined, ``None`` otherwise (in this case neighbors are received in the callback). - - Raises: - OperationNotSupportedException: If XBee protocol is not Zigbee, Smart Energy - or DigiMesh. - - .. seealso:: - | :class:`com.digi.models.zdo.Neighbor` - """ - if self.get_protocol() in (XBeeProtocol.ZIGBEE, XBeeProtocol.SMART_ENERGY): - from digi.xbee.models.zdo import NeighborTableReader - reader = NeighborTableReader( - self, configure_ao=True, - timeout=timeout if timeout else NeighborTableReader.DEFAULT_TIMEOUT) - - return reader.get_neighbor_table(neighbor_callback=neighbor_callback, - process_finished_callback=process_finished_callback) - elif self.get_protocol() in (XBeeProtocol.DIGI_MESH, XBeeProtocol.XLR_DM, - XBeeProtocol.XTEND_DM, XBeeProtocol.SX): - from digi.xbee.models.zdo import NeighborFinder - finder = NeighborFinder( - self, timeout=timeout if timeout else NeighborFinder.DEFAULT_TIMEOUT) - - return finder.get_neighbors(neighbor_callback=neighbor_callback, - process_finished_callback=process_finished_callback) - else: - raise OperationNotSupportedException("Get neighbors is not supported in %s" - % self.get_protocol().description) - - @property - def reachable(self): - """ - Returns whether the XBee is reachable. - - Returns: - Boolean: ``True`` if the device is reachable, ``False`` otherwise. - """ - return self._reachable - - @property - def scan_counter(self): - """ - Returns the scan counter for this node. - - Returns: - Integer: The scan counter for this node. - """ - return self._scan_counter - def __get_log(self): """ Returns the XBee device log. @@ -1975,12 +1677,12 @@ class XBeeDevice(AbstractXBeeDevice): If you do something more with them, it's for your own risk. """ - __DEFAULT_GUARD_TIME = 1.2 # seconds + __TIMEOUT_BEFORE_COMMAND_MODE = 1.2 # seconds """ - Timeout to wait after entering and exiting command mode in seconds. + Timeout to wait after entering in command mode in seconds. It is used to determine the operating mode of the module (this - library only supports API modes, not AT (transparent) mode). + library only supports API modes, not transparent mode). """ __TIMEOUT_ENTER_COMMAND_MODE = 1.5 # seconds @@ -2011,9 +1713,9 @@ class XBeeDevice(AbstractXBeeDevice): Response that will be receive if the attempt to enter in at command mode goes well. """ - def __init__(self, port=None, baud_rate=None, data_bits=serial.EIGHTBITS, stop_bits=serial.STOPBITS_ONE, + def __init__(self, port, baud_rate, 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): + _sync_ops_timeout=AbstractXBeeDevice._DEFAULT_TIMEOUT_SYNC_OPERATIONS): """ Class constructor. Instantiates a new :class:`.XBeeDevice` with the provided parameters. @@ -2026,8 +1728,7 @@ def __init__(self, port=None, baud_rate=None, data_bits=serial.EIGHTBITS, stop_b stop_bits (Integer, default: :attr:`.serial.STOPBITS_ONE`): comm port stop bits. parity (Character, default: :attr:`.serial.PARITY_NONE`): comm port parity. flow_control (Integer, default: :attr:`.FlowControl.NONE`): comm port flow control. - _sync_ops_timeout (Integer, default: 3): the read timeout (in seconds). - comm_iface (:class:`.XBeeCommunicationInterface`): the communication interface. + _sync_ops_timeout (Integer, default: 3): comm port read timeout. Raises: All exceptions raised by PySerial's Serial class constructor. @@ -2035,17 +1736,23 @@ def __init__(self, port=None, baud_rate=None, data_bits=serial.EIGHTBITS, stop_b .. seealso:: | PySerial documentation: http://pyserial.sourceforge.net """ - super().__init__(serial_port=XBeeSerialPort(baud_rate=baud_rate, - port=port, + super(XBeeDevice, self).__init__(serial_port=XBeeSerialPort(baud_rate=baud_rate, + port=None, # to keep port closed until init(). data_bits=data_bits, stop_bits=stop_bits, parity=parity, flow_control=flow_control, - timeout=_sync_ops_timeout) if comm_iface is None else None, - sync_ops_timeout=_sync_ops_timeout, - comm_iface=comm_iface + timeout=_sync_ops_timeout), + sync_ops_timeout=_sync_ops_timeout ) - self._network = self._init_network() + self.__port = port + self.__baud_rate = baud_rate + self.__data_bits = data_bits + self.__stop_bits = stop_bits + self.__parity = parity + self.__flow_control = flow_control + + self._network = XBeeNetwork(self) self.__packet_queue = None self.__data_queue = None @@ -2080,109 +1787,45 @@ def create_xbee_device(cls, comm_port_data): """ return XBeeDevice(comm_port_data["port"], comm_port_data["baudRate"], - data_bits=comm_port_data["bitSize"], - stop_bits=comm_port_data["stopBits"], - parity=comm_port_data["parity"], - flow_control=comm_port_data["flowControl"], - _sync_ops_timeout=comm_port_data["timeout"]) + comm_port_data["bitSize"], + comm_port_data["stopBits"], + comm_port_data["parity"], + comm_port_data["flowControl"], + comm_port_data["timeout"]) - def open(self, force_settings=False): + def open(self): """ Opens the communication with the XBee device and loads some information about it. - Args: - force_settings (Boolean, optional): ``True`` to open the device ensuring/forcing that the specified - serial settings are applied even if the current configuration is different, - ``False`` to open the device with the current configuration. Default to False. - Raises: TimeoutException: if there is any problem with the communication. 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 is already open. """ + if self._is_open: raise XBeeException("XBee device already open.") - # Store already registered callbacks - packet_cbs = self._packet_listener.get_packet_received_callbacks() \ - if self._packet_listener else None - data_cbs = self._packet_listener.get_data_received_callbacks() \ - if self._packet_listener else None - modem_status_cbs = self._packet_listener.get_modem_status_received_callbacks() \ - if self._packet_listener else None - io_cbs = self._packet_listener.get_io_sample_received_callbacks() \ - if self._packet_listener else None - expl_data_cbs = self._packet_listener.get_explicit_data_received_callbacks() \ - if self._packet_listener else None - ip_data_cbs = self._packet_listener.get_ip_data_received_callbacks() \ - if self._packet_listener else None - sms_cbs = self._packet_listener.get_sms_received_callbacks() \ - if self._packet_listener else None - user_data_relay_cbs = self._packet_listener.get_user_data_relay_received_callbacks() \ - if self._packet_listener else None - bt_data_cbs = self._packet_listener.get_bluetooth_data_received_callbacks() \ - if self._packet_listener else None - mp_data_cbs = self._packet_listener.get_micropython_data_received_callbacks() \ - if self._packet_listener else None - - self._comm_iface.open() - self._log.info("%s port opened" % self._comm_iface) + self._serial_port.port = self.__port + self._serial_port.open() + self._log.info("%s port opened" % self.__port) # Initialize the packet listener. - self._packet_listener = None - self._packet_listener = PacketListener(self._comm_iface, self) + self._packet_listener = PacketListener(self._serial_port, self) self.__packet_queue = self._packet_listener.get_queue() self.__data_queue = self._packet_listener.get_data_queue() self.__explicit_queue = self._packet_listener.get_explicit_queue() - - # Restore callbacks if any - self._packet_listener.add_packet_received_callback(packet_cbs) - self._packet_listener.add_data_received_callback(data_cbs) - self._packet_listener.add_modem_status_received_callback(modem_status_cbs) - self._packet_listener.add_io_sample_received_callback(io_cbs) - self._packet_listener.add_explicit_data_received_callback(expl_data_cbs) - self._packet_listener.add_ip_data_received_callback(ip_data_cbs) - self._packet_listener.add_sms_received_callback(sms_cbs) - self._packet_listener.add_user_data_relay_received_callback(user_data_relay_cbs) - self._packet_listener.add_bluetooth_data_received_callback(bt_data_cbs) - self._packet_listener.add_micropython_data_received_callback(mp_data_cbs) - self._packet_listener.start() - self._packet_listener.wait_until_started() - - if force_settings: - try: - self._do_open() - except XBeeException as e: - self.log.debug("Could not open the port with default setting, " - "forcing settings using recovery: %s" % str(e)) - if self._serial_port is None: - raise XBeeException("Can not open the port by forcing the settings, " - "it is only supported for Serial") - self._autodetect_device() - self.open(force_settings=False) - else: - self._do_open() - - def _do_open(self): - """ - Opens the communication with the XBee device and loads some information about it. - Raises: - TimeoutException: if there is any problem with the communication. - 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 is already open. - """ # Determine the operating mode of the XBee device. self._operating_mode = self._determine_operating_mode() if self._operating_mode == OperatingMode.UNKNOWN: self.close() - raise InvalidOperatingModeException(message="Could not determine operating mode") - if self._operating_mode not in [OperatingMode.API_MODE, OperatingMode.ESCAPED_API_MODE]: + raise InvalidOperatingModeException("Could not determine operating mode") + if self._operating_mode == OperatingMode.AT_MODE: self.close() - raise InvalidOperatingModeException(op_mode=self._operating_mode) + raise InvalidOperatingModeException.from_operating_mode(self._operating_mode) # Read the device info (obtain its parameters and protocol). self.read_device_info() @@ -2202,46 +1845,33 @@ def close(self): if self._packet_listener is not None: self._packet_listener.stop() - if self._comm_iface is not None and self._comm_iface.is_interface_open: - self._comm_iface.close() - self._log.info("%s closed" % self._comm_iface) + if self._serial_port is not None and self._serial_port.isOpen(): + self._serial_port.close() + self._log.info("%s port closed" % self.__port) self._is_open = False def __get_serial_port(self): """ - Returns the serial port associated to the XBee device, if any. + Returns the serial port associated to the XBee device. Returns: - :class:`.XBeeSerialPort`: the serial port associated to the XBee device. Returns ``None`` if the local XBee - does not use serial communication. + :class:`.XBeeSerialPort`: the serial port associated to the XBee device. .. seealso:: | :class:`.XBeeSerialPort` """ return self._serial_port - def __get_comm_iface(self): - """ - Returns the hardware interface associated to the XBee device. - - Returns: - :class:`.XBeeCommunicationInterface`: the hardware interface associated to the XBee device. - - .. seealso:: - | :class:`.XBeeSerialPort` - """ - return self._comm_iface - @AbstractXBeeDevice._before_send_method - def get_parameter(self, param, parameter_value=None): + def get_parameter(self, param): """ Override. .. seealso:: | :meth:`.AbstractXBeeDevice.get_parameter` """ - return super().get_parameter(param, parameter_value=parameter_value) + return super(XBeeDevice, self).get_parameter(param) @AbstractXBeeDevice._before_send_method def set_parameter(self, param, value): @@ -2251,7 +1881,7 @@ def set_parameter(self, param, value): See: :meth:`.AbstractXBeeDevice.set_parameter` """ - super().set_parameter(param, value) + super(XBeeDevice, self).set_parameter(param, value) @AbstractXBeeDevice._before_send_method @AbstractXBeeDevice._after_send_method @@ -2283,8 +1913,8 @@ def _send_data_64_16(self, x64addr, x16addr, data, transmit_options=TransmitOpti :attr:`.XBeeDevice._DEFAULT_TIMEOUT_SYNC_OPERATIONS` seconds. 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. - TransmitException: if the status of the response received is not OK. XBeeException: if the XBee device's serial port is closed. + XBeeException: if the status of the response received is not OK. .. seealso:: | :class:`.XBee64BitAddress` @@ -2299,7 +1929,7 @@ def _send_data_64_16(self, x64addr, x16addr, data, transmit_options=TransmitOpti raise ValueError("Data cannot be None") if self.is_remote(): - raise OperationNotSupportedException(message="Cannot send data to a remote device from a remote device") + raise OperationNotSupportedException("Cannot send data to a remote device from a remote device") if isinstance(data, str): data = data.encode("utf8") @@ -2309,7 +1939,7 @@ def _send_data_64_16(self, x64addr, x16addr, data, transmit_options=TransmitOpti x16addr, 0, transmit_options, - rf_data=data) + data) return self.send_packet_sync_and_get_response(packet) @AbstractXBeeDevice._before_send_method @@ -2339,8 +1969,8 @@ def _send_data_64(self, x64addr, data, transmit_options=TransmitOptions.NONE.val :attr:`.XBeeDevice._DEFAULT_TIMEOUT_SYNC_OPERATIONS` seconds. 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. - TransmitException: if the status of the response received is not OK. XBeeException: if the XBee device's serial port is closed. + XBeeException: if the status of the response received is not OK. .. seealso:: | :class:`.XBee64BitAddress` @@ -2352,7 +1982,7 @@ def _send_data_64(self, x64addr, data, transmit_options=TransmitOptions.NONE.val raise ValueError("Data cannot be None") if self.is_remote(): - raise OperationNotSupportedException(message="Cannot send data to a remote device from a remote device") + raise OperationNotSupportedException("Cannot send data to a remote device from a remote device") if isinstance(data, str): data = data.encode("utf8") @@ -2361,14 +1991,14 @@ def _send_data_64(self, x64addr, data, transmit_options=TransmitOptions.NONE.val packet = TX64Packet(self.get_next_frame_id(), x64addr, transmit_options, - rf_data=data) + data) else: packet = TransmitPacket(self.get_next_frame_id(), x64addr, XBee16BitAddress.UNKNOWN_ADDRESS, 0, transmit_options, - rf_data=data) + data) return self.send_packet_sync_and_get_response(packet) @AbstractXBeeDevice._before_send_method @@ -2398,8 +2028,8 @@ def _send_data_16(self, x16addr, data, transmit_options=TransmitOptions.NONE.val :attr:`.XBeeDevice._DEFAULT_TIMEOUT_SYNC_OPERATIONS` seconds. 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. - TransmitException: if the status of the response received is not OK. XBeeException: if the XBee device's serial port is closed. + XBeeException: if the status of the response received is not OK. .. seealso:: | :class:`.XBee16BitAddress` @@ -2411,7 +2041,7 @@ def _send_data_16(self, x16addr, data, transmit_options=TransmitOptions.NONE.val raise ValueError("Data cannot be None") if self.is_remote(): - raise OperationNotSupportedException(message="Cannot send data to a remote device from a remote device") + raise OperationNotSupportedException("Cannot send data to a remote device from a remote device") if isinstance(data, str): data = data.encode("utf8") @@ -2419,7 +2049,7 @@ def _send_data_16(self, x16addr, data, transmit_options=TransmitOptions.NONE.val packet = TX16Packet(self.get_next_frame_id(), x16addr, transmit_options, - rf_data=data) + data) return self.send_packet_sync_and_get_response(packet) def send_data(self, remote_xbee_device, data, transmit_options=TransmitOptions.NONE.value): @@ -2445,8 +2075,8 @@ def send_data(self, remote_xbee_device, data, transmit_options=TransmitOptions.N :attr:`.XBeeDevice._DEFAULT_TIMEOUT_SYNC_OPERATIONS` seconds. 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. - TransmitException: if the status of the response received is not OK. XBeeException: if the XBee device's serial port is closed. + XBeeException: if the status of the response received is not OK. .. seealso:: | :class:`.RemoteXBeeDevice` @@ -2459,23 +2089,19 @@ def send_data(self, remote_xbee_device, data, transmit_options=TransmitOptions.N if protocol in [XBeeProtocol.ZIGBEE, XBeeProtocol.DIGI_POINT]: if remote_xbee_device.get_64bit_addr() is not None and remote_xbee_device.get_16bit_addr() is not None: return self._send_data_64_16(remote_xbee_device.get_64bit_addr(), remote_xbee_device.get_16bit_addr(), - data, transmit_options=transmit_options) + data, transmit_options) elif remote_xbee_device.get_64bit_addr() is not None: - return self._send_data_64(remote_xbee_device.get_64bit_addr(), data, - transmit_options=transmit_options) + return self._send_data_64(remote_xbee_device.get_64bit_addr(), data, transmit_options) else: return self._send_data_64_16(XBee64BitAddress.UNKNOWN_ADDRESS, remote_xbee_device.get_16bit_addr(), - data, transmit_options=transmit_options) + data, transmit_options) elif protocol == XBeeProtocol.RAW_802_15_4: if remote_xbee_device.get_64bit_addr() is not None: - return self._send_data_64(remote_xbee_device.get_64bit_addr(), data, - transmit_options=transmit_options) + return self._send_data_64(remote_xbee_device.get_64bit_addr(), data, transmit_options) else: - return self._send_data_16(remote_xbee_device.get_16bit_addr(), data, - transmit_options=transmit_options) + return self._send_data_16(remote_xbee_device.get_16bit_addr(), data, transmit_options) else: - return self._send_data_64(remote_xbee_device.get_64bit_addr(), data, - transmit_options=transmit_options) + return self._send_data_64(remote_xbee_device.get_64bit_addr(), data, transmit_options) @AbstractXBeeDevice._before_send_method def _send_data_async_64_16(self, x64addr, x16addr, data, transmit_options=TransmitOptions.NONE.value): @@ -2517,7 +2143,7 @@ def _send_data_async_64_16(self, x64addr, x16addr, data, transmit_options=Transm raise ValueError("Data cannot be None") if self.is_remote(): - raise OperationNotSupportedException(message="Cannot send data to a remote device from a remote device") + raise OperationNotSupportedException("Cannot send data to a remote device from a remote device") if isinstance(data, str): data = data.encode("utf8") @@ -2527,7 +2153,7 @@ def _send_data_async_64_16(self, x64addr, x16addr, data, transmit_options=Transm x16addr, 0, transmit_options, - rf_data=data) + data) self.send_packet(packet) @AbstractXBeeDevice._before_send_method @@ -2564,7 +2190,7 @@ def _send_data_async_64(self, x64addr, data, transmit_options=TransmitOptions.NO raise ValueError("Data cannot be None") if self.is_remote(): - raise OperationNotSupportedException(message="Cannot send data to a remote device from a remote device") + raise OperationNotSupportedException("Cannot send data to a remote device from a remote device") if isinstance(data, str): data = data.encode("utf8") @@ -2573,14 +2199,14 @@ def _send_data_async_64(self, x64addr, data, transmit_options=TransmitOptions.NO packet = TX64Packet(self.get_next_frame_id(), x64addr, transmit_options, - rf_data=data) + data) else: packet = TransmitPacket(self.get_next_frame_id(), x64addr, XBee16BitAddress.UNKNOWN_ADDRESS, 0, transmit_options, - rf_data=data) + data) self.send_packet(packet) @AbstractXBeeDevice._before_send_method @@ -2617,7 +2243,7 @@ def _send_data_async_16(self, x16addr, data, transmit_options=TransmitOptions.NO raise ValueError("Data cannot be None") if self.is_remote(): - raise OperationNotSupportedException(message="Cannot send data to a remote device from a remote device") + raise OperationNotSupportedException("Cannot send data to a remote device from a remote device") if isinstance(data, str): data = data.encode("utf8") @@ -2625,7 +2251,7 @@ def _send_data_async_16(self, x16addr, data, transmit_options=TransmitOptions.NO packet = TX16Packet(self.get_next_frame_id(), x16addr, transmit_options, - rf_data=data) + data) self.send_packet(packet) def send_data_async(self, remote_xbee_device, data, transmit_options=TransmitOptions.NONE.value): @@ -2655,23 +2281,19 @@ def send_data_async(self, remote_xbee_device, data, transmit_options=TransmitOpt if protocol in [XBeeProtocol.ZIGBEE, XBeeProtocol.DIGI_POINT]: if remote_xbee_device.get_64bit_addr() is not None and remote_xbee_device.get_16bit_addr() is not None: self._send_data_async_64_16(remote_xbee_device.get_64bit_addr(), remote_xbee_device.get_16bit_addr(), - data, transmit_options=transmit_options) + data, transmit_options) elif remote_xbee_device.get_64bit_addr() is not None: - self._send_data_async_64(remote_xbee_device.get_64bit_addr(), data, - transmit_options=transmit_options) + self._send_data_async_64(remote_xbee_device.get_64bit_addr(), data, transmit_options) else: self._send_data_async_64_16(XBee64BitAddress.UNKNOWN_ADDRESS, remote_xbee_device.get_16bit_addr(), - data, transmit_options=transmit_options) + data, transmit_options) elif protocol == XBeeProtocol.RAW_802_15_4: if remote_xbee_device.get_64bit_addr() is not None: - self._send_data_async_64(remote_xbee_device.get_64bit_addr(), data, - transmit_options=transmit_options) + self._send_data_async_64(remote_xbee_device.get_64bit_addr(), data, transmit_options) else: - self._send_data_async_16(remote_xbee_device.get_16bit_addr(), data, - transmit_options=transmit_options) + self._send_data_async_16(remote_xbee_device.get_16bit_addr(), data, transmit_options) else: - self._send_data_async_64(remote_xbee_device.get_64bit_addr(), data, - transmit_options=transmit_options) + self._send_data_async_64(remote_xbee_device.get_64bit_addr(), data, transmit_options) def send_data_broadcast(self, data, transmit_options=TransmitOptions.NONE.value): """ @@ -2693,11 +2315,10 @@ def send_data_broadcast(self, data, transmit_options=TransmitOptions.NONE.value) :attr:`.XBeeDevice._DEFAULT_TIMEOUT_SYNC_OPERATIONS` seconds. 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. - TransmitException: if the status of the response received is not OK. XBeeException: if the XBee device's serial port is closed. + XBeeException: if the status of the response received is not OK. """ - return self._send_data_64(XBee64BitAddress.BROADCAST_ADDRESS, data, - transmit_options=transmit_options) + return self._send_data_64(XBee64BitAddress.BROADCAST_ADDRESS, data, transmit_options) @AbstractXBeeDevice._before_send_method def send_user_data_relay(self, local_interface, data): @@ -2721,7 +2342,7 @@ def send_user_data_relay(self, local_interface, data): raise ValueError("Destination interface cannot be None") # Send the packet asynchronously since User Data Relay frames do not receive any transmit status. - self.send_packet(UserDataRelayPacket(self.get_next_frame_id(), local_interface, data=data)) + self.send_packet(UserDataRelayPacket(self.get_next_frame_id(), local_interface, data)) def send_bluetooth_data(self, data): """ @@ -2856,7 +2477,7 @@ def reset(self): | :meth:`.AbstractXBeeDevice.reset` """ # Send reset command. - response = self._send_at_command(ATCommand(ATStringCommand.FR.command)) + response = self._send_at_command(ATCommand("FR")) # Check if AT Command response is valid. self._check_at_cmd_response_is_valid(response) @@ -2878,305 +2499,116 @@ def ms_callback(modem_status): self.del_modem_status_received_callback(ms_callback) if self.__modem_status_received is False: - raise TimeoutException(message="Timeout waiting for the modem status packet.") + raise XBeeException("Invalid modem status.") def add_packet_received_callback(self, callback): """ - Adds a callback for the event :class:`digi.xbee.reader.PacketReceived`. - - Args: - callback (Function): the callback. Receives two arguments. - - * The received packet as a :class:`digi.xbee.packets.base.XBeeAPIPacket` + Override. """ - super()._add_packet_received_callback(callback) + super(XBeeDevice, self)._add_packet_received_callback(callback) def add_data_received_callback(self, callback): """ - Adds a callback for the event :class:`digi.xbee.reader.DataReceived`. - - Args: - callback (Function): the callback. Receives one argument. - - * The data received as an :class:`digi.xbee.models.message.XBeeMessage` + Override. """ - self._packet_listener.add_data_received_callback(callback) + super(XBeeDevice, self)._add_data_received_callback(callback) def add_modem_status_received_callback(self, callback): """ - Adds a callback for the event :class:`digi.xbee.reader.ModemStatusReceived`. - - Args: - callback (Function): the callback. Receives one argument. - - * The modem status as a :class:`digi.xbee.models.status.ModemStatus` + Override. """ - self._packet_listener.add_modem_status_received_callback(callback) + super(XBeeDevice, self)._add_modem_status_received_callback(callback) def add_io_sample_received_callback(self, callback): """ - Adds a callback for the event :class:`digi.xbee.reader.IOSampleReceived`. - - Args: - callback (Function): the callback. Receives three arguments. - - * The received IO sample as an :class:`digi.xbee.io.IOSample` - * The remote XBee device who has sent the packet as a :class:`.RemoteXBeeDevice` - * The time in which the packet was received as an Integer + Override. """ - self._packet_listener.add_io_sample_received_callback(callback) + super(XBeeDevice, self)._add_io_sample_received_callback(callback) def add_expl_data_received_callback(self, callback): """ - Adds a callback for the event :class:`digi.xbee.reader.ExplicitDataReceived`. - - Args: - callback (Function): the callback. Receives one argument. - - * The explicit data received as a - :class:`digi.xbee.models.message.ExplicitXBeeMessage`. + Override. """ - self._packet_listener.add_explicit_data_received_callback(callback) + super(XBeeDevice, self)._add_expl_data_received_callback(callback) def add_user_data_relay_received_callback(self, callback): """ - Adds a callback for the event :class:`digi.xbee.reader.RelayDataReceived`. - - Args: - callback (Function): the callback. Receives one argument. - - * The relay data as a :class:`digi.xbee.models.message.UserDataRelayMessage` + Override. """ - self._packet_listener.add_user_data_relay_received_callback(callback) + super(XBeeDevice, self)._add_user_data_relay_received_callback(callback) def add_bluetooth_data_received_callback(self, callback): """ - Adds a callback for the event :class:`digi.xbee.reader.BluetoothDataReceived`. - - Args: - callback (Function): the callback. Receives one argument. - - * The Bluetooth data as a Bytearray + Override. """ - self._packet_listener.add_bluetooth_data_received_callback(callback) + super(XBeeDevice, self)._add_bluetooth_data_received_callback(callback) def add_micropython_data_received_callback(self, callback): """ - Adds a callback for the event :class:`digi.xbee.reader.MicroPythonDataReceived`. - - Args: - callback (Function): the callback. Receives one argument. - - * The MicroPython data as a Bytearray - """ - self._packet_listener.add_micropython_data_received_callback(callback) - - def add_socket_state_received_callback(self, callback): - """ - Adds a callback for the event :class:`digi.xbee.reader.SocketStateReceived`. - - Args: - callback (Function): the callback. Receives two arguments. - - * The socket ID as an Integer. - * The state received as a :class:`.SocketState` - """ - self._packet_listener.add_socket_state_received_callback(callback) - - def add_socket_data_received_callback(self, callback): - """ - Adds a callback for the event :class:`digi.xbee.reader.SocketDataReceived`. - - Args: - callback (Function): the callback. Receives two arguments. - - * The socket ID as an Integer. - * The data received as Bytearray - """ - self._packet_listener.add_socket_data_received_callback(callback) - - def add_socket_data_received_from_callback(self, callback): - """ - Adds a callback for the event :class:`digi.xbee.reader.SocketDataReceivedFrom`. - - Args: - callback (Function): the callback. Receives three arguments. - - * The socket ID as an Integer. - * A pair (host, port) of the source address where host is a string - representing an IPv4 address like '100.50.200.5', and port is an - integer. - * The data received as Bytearray + Override. """ - self._packet_listener.add_socket_data_received_from_callback(callback) + super(XBeeDevice, self)._add_micropython_data_received_callback(callback) def del_packet_received_callback(self, callback): """ - Deletes a callback for the callback list of :class:`digi.xbee.reader.PacketReceived` event. - - Args: - callback (Function): the callback to delete. - - Raises: - ValueError: if ``callback`` is not in the callback list of - :class:`digi.xbee.reader.PacketReceived` event. + Override. """ - super()._del_packet_received_callback(callback) + super(XBeeDevice, self)._del_packet_received_callback(callback) def del_data_received_callback(self, callback): """ - Deletes a callback for the callback list of :class:`digi.xbee.reader.DataReceived` event. - - Args: - callback (Function): the callback to delete. - - Raises: - ValueError: if ``callback`` is not in the callback list of - :class:`digi.xbee.reader.DataReceived` event. + Override. """ - self._packet_listener.del_data_received_callback(callback) + super(XBeeDevice, self)._del_data_received_callback(callback) def del_modem_status_received_callback(self, callback): """ - Deletes a callback for the callback list of :class:`digi.xbee.reader.ModemStatusReceived` - event. - - Args: - callback (Function): the callback to delete. - - Raises: - ValueError: if ``callback`` is not in the callback list of - :class:`digi.xbee.reader.ModemStatusReceived` event. + Override. """ - self._packet_listener.del_modem_status_received_callback(callback) + super(XBeeDevice, self)._del_modem_status_received_callback(callback) def del_io_sample_received_callback(self, callback): """ - Deletes a callback for the callback list of :class:`digi.xbee.reader.IOSampleReceived` - event. - - Args: - callback (Function): the callback to delete. - - Raises: - ValueError: if ``callback`` is not in the callback list of - :class:`digi.xbee.reader.IOSampleReceived` event. + Override. """ - self._packet_listener.del_io_sample_received_callback(callback) + super(XBeeDevice, self)._del_io_sample_received_callback(callback) def del_expl_data_received_callback(self, callback): """ - Deletes a callback for the callback list of :class:`digi.xbee.reader.ExplicitDataReceived` - event. - - Args: - callback (Function): the callback to delete. - - Raises: - ValueError: if ``callback`` is not in the callback list of - :class:`digi.xbee.reader.ExplicitDataReceived` event. + Override. """ - self._packet_listener.del_explicit_data_received_callback(callback) + super(XBeeDevice, self)._del_expl_data_received_callback(callback) def del_user_data_relay_received_callback(self, callback): """ - Deletes a callback for the callback list of :class:`digi.xbee.reader.RelayDataReceived` - event. - - Args: - callback (Function): the callback to delete. - - Raises: - ValueError: if ``callback`` is not in the callback list of - :class:`digi.xbee.reader.RelayDataReceived` event. + Override. """ - self._packet_listener.del_user_data_relay_received_callback(callback) + super(XBeeDevice, self)._del_user_data_relay_received_callback(callback) def del_bluetooth_data_received_callback(self, callback): """ - Deletes a callback for the callback list of :class:`digi.xbee.reader.BluetoothDataReceived` - event. - - Args: - callback (Function): the callback to delete. - - Raises: - ValueError: if ``callback`` is not in the callback list of - :class:`digi.xbee.reader.BluetoothDataReceived` event. + Override. """ - self._packet_listener.del_bluetooth_data_received_callback(callback) + super(XBeeDevice, self)._del_bluetooth_data_received_callback(callback) def del_micropython_data_received_callback(self, callback): """ - Deletes a callback for the callback list of - :class:`digi.xbee.reader.MicroPythonDataReceived` event. - - Args: - callback (Function): the callback to delete. - - Raises: - ValueError: if ``callback`` is not in the callback list of - :class:`digi.xbee.reader.MicroPythonDataReceived` event. - """ - self._packet_listener.del_micropython_data_received_callback(callback) - - def del_socket_state_received_callback(self, callback): - """ - Deletes a callback for the callback list of - :class:`digi.xbee.reader.SocketStateReceived` event. - - Args: - callback (Function): the callback to delete. - - Raises: - ValueError: if ``callback`` is not in the callback list of - :class:`digi.xbee.reader.SocketStateReceived` event. - """ - self._packet_listener.del_socket_state_received_callback(callback) - - def del_socket_data_received_callback(self, callback): - """ - Deletes a callback for the callback list of - :class:`digi.xbee.reader.SocketDataReceived` event. - - Args: - callback (Function): the callback to delete. - - Raises: - ValueError: if ``callback`` is not in the callback list of - :class:`digi.xbee.reader.SocketDataReceived` event. - """ - self._packet_listener.del_socket_data_received_callback(callback) - - def del_socket_data_received_from_callback(self, callback): - """ - Deletes a callback for the callback list of - :class:`digi.xbee.reader.SocketDataReceivedFrom` event. - - Args: - callback (Function): the callback to delete. - - Raises: - ValueError: if ``callback`` is not in the callback list of - :class:`digi.xbee.reader.SocketDataReceivedFrom` event. + Override. """ - self._packet_listener.del_socket_data_received_from_callback(callback) + super(XBeeDevice, self)._del_micropython_data_received_callback(callback) def get_xbee_device_callbacks(self): """ Returns this XBee internal callbacks for process received packets. - + This method is called by the PacketListener associated with this XBee to get its callbacks. These callbacks will be executed before user callbacks. - + Returns: :class:`.PacketReceived` """ api_callbacks = PacketReceived() - if not self._network: - return api_callbacks - for i in self._network.get_discovery_callbacks(): api_callbacks.append(i) return api_callbacks @@ -3188,7 +2620,7 @@ def __get_operating_mode(self): Returns: :class:`.OperatingMode`. This XBee device's operating mode. """ - return super()._get_operating_mode() + return super(XBeeDevice, self)._get_operating_mode() def is_open(self): """ @@ -3215,20 +2647,8 @@ def get_network(self): Returns: :class:`.XBeeDevice.XBeeNetwork` """ - if self._network is None: - self._network = self._init_network() - return self._network - def _init_network(self): - """ - Initializes a new network. - - Returns: - :class:`.XBeeDevice.XBeeNetwork` - """ - return XBeeNetwork(self) - @AbstractXBeeDevice._before_send_method @AbstractXBeeDevice._after_send_method def _send_expl_data(self, remote_xbee_device, data, src_endpoint, dest_endpoint, @@ -3261,8 +2681,8 @@ def _send_expl_data(self, remote_xbee_device, data, src_endpoint, dest_endpoint, :attr:`.XBeeDevice._DEFAULT_TIMEOUT_SYNC_OPERATIONS` seconds. 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. - TransmitException: if the status of the response received is not OK. XBeeException: if the XBee device's serial port is closed. + XBeeException: if the status of the response received is not OK. ValueError: if ``cluster_id`` is less than 0x0 or greater than 0xFFFF. ValueError: if ``profile_id`` is less than 0x0 or greater than 0xFFFF. @@ -3273,8 +2693,7 @@ def _send_expl_data(self, remote_xbee_device, data, src_endpoint, dest_endpoint, return self.send_packet_sync_and_get_response(self.__build_expldata_packet(remote_xbee_device, data, src_endpoint, dest_endpoint, cluster_id, profile_id, - broadcast=False, - transmit_options=transmit_options)) + False, transmit_options)) @AbstractXBeeDevice._before_send_method def _send_expl_data_async(self, remote_xbee_device, data, src_endpoint, dest_endpoint, @@ -3306,8 +2725,7 @@ def _send_expl_data_async(self, remote_xbee_device, data, src_endpoint, dest_end """ self.send_packet(self.__build_expldata_packet(remote_xbee_device, data, src_endpoint, dest_endpoint, cluster_id, - profile_id, broadcast=False, - transmit_options=transmit_options)) + profile_id, False, transmit_options)) def _send_expl_data_broadcast(self, data, src_endpoint, dest_endpoint, cluster_id, profile_id, transmit_options=TransmitOptions.NONE.value): @@ -3336,9 +2754,7 @@ def _send_expl_data_broadcast(self, data, src_endpoint, dest_endpoint, cluster_i """ return self.send_packet_sync_and_get_response(self.__build_expldata_packet(None, data, src_endpoint, dest_endpoint, cluster_id, - profile_id, - broadcast=True, - transmit_options=transmit_options)) + profile_id, True, transmit_options)) def _read_expl_data(self, timeout=None): """ @@ -3431,21 +2847,21 @@ def __read_data_packet(self, remote, timeout, explicit): if remote is None: packet = self.__data_queue.get(timeout=timeout) else: - packet = self.__data_queue.get_by_remote(remote, timeout=timeout) + packet = self.__data_queue.get_by_remote(remote, timeout) else: if remote is None: packet = self.__explicit_queue.get(timeout=timeout) else: - packet = self.__explicit_queue.get_by_remote(remote, timeout=timeout) + packet = self.__explicit_queue.get_by_remote(remote, timeout) if packet is None: return None frame_type = packet.get_frame_type() if frame_type in [ApiFrameType.RECEIVE_PACKET, ApiFrameType.RX_16, ApiFrameType.RX_64]: - return self.__build_xbee_message(packet, explicit=False) + return self.__build_xbee_message(packet, False) elif frame_type == ApiFrameType.EXPLICIT_RX_INDICATOR: - return self.__build_xbee_message(packet, explicit=True) + return self.__build_xbee_message(packet, True) else: return None @@ -3459,18 +2875,18 @@ def _enter_at_command_mode(self): Raises: SerialTimeoutException: if there is any error trying to write within the serial port. - InvalidOperatingModeException: if the XBee device 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") + if self._operating_mode != OperatingMode.AT_MODE: + raise InvalidOperatingModeException("Invalid mode. Command mode can be only accessed while in AT mode") + listening = self._packet_listener is not None and self._packet_listener.is_running() + if listening: + self._packet_listener.stop() + self._packet_listener.join() self._serial_port.flushInput() # It is necessary to wait at least 1 second to enter in command mode after sending any data to the device. - time.sleep(self.__DEFAULT_GUARD_TIME) + time.sleep(self.__TIMEOUT_BEFORE_COMMAND_MODE) # Send the command mode sequence. b = bytearray(self.__COMMAND_MODE_CHAR, "utf8") self._serial_port.write(b) @@ -3483,25 +2899,7 @@ def _enter_at_command_mode(self): return data and data in self.__COMMAND_MODE_OK - def _exit_at_command_mode(self): - """ - Exits AT command mode. The XBee device has to be in command mode. - - Raises: - SerialTimeoutException: if there is any error trying to write within the serial port. - InvalidOperatingModeException: if the XBee device 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("utf-8")) - time.sleep(self.__DEFAULT_GUARD_TIME) - - def _determine_operating_mode(self): + def _determine_operating_mode(self): """ Determines and returns the operating mode of the XBee device. @@ -3516,42 +2914,27 @@ def _determine_operating_mode(self): """ try: self._operating_mode = OperatingMode.API_MODE - response = self.get_parameter(ATStringCommand.AP.command) + response = self.get_parameter("AP") return OperatingMode.get(response[0]) except TimeoutException: self._operating_mode = OperatingMode.AT_MODE - listening = self._packet_listener is not None and self._packet_listener.is_running() try: - # Stop listening for packets. - if listening: - self._packet_listener.stop() - self._packet_listener.join() # If there is timeout exception and is possible to enter - # in AT command mode, get the actual mode. + # in AT command mode, the current operating mode is AT. if self._enter_at_command_mode(): - return self.__get_actual_mode() - except XBeeException as ste: + return OperatingMode.AT_MODE + except SerialTimeoutException as ste: self._log.exception(ste) - except UnicodeDecodeError: - # This error is thrown when trying to decode bytes without utf-8 representation, just ignore. - pass - finally: - # Exit AT command mode. - self._exit_at_command_mode() - # Restore the packets listening. - if listening: - self._packet_listener = PacketListener(self._comm_iface, self) - self._packet_listener.start() return OperatingMode.UNKNOWN - def send_packet_sync_and_get_response(self, packet_to_send, timeout=None): + def send_packet_sync_and_get_response(self, packet_to_send): """ Override method. .. seealso:: | :meth:`.AbstractXBeeDevice._send_packet_sync_and_get_response` """ - return super()._send_packet_sync_and_get_response(packet_to_send, timeout=timeout) + return super(XBeeDevice, self)._send_packet_sync_and_get_response(packet_to_send) def send_packet(self, packet, sync=False): """ @@ -3560,7 +2943,7 @@ def send_packet(self, packet, sync=False): .. seealso:: | :meth:`.AbstractXBeeDevice._send_packet` """ - return super()._send_packet(packet, sync=sync) + return super(XBeeDevice, self)._send_packet(packet, sync) def __build_xbee_message(self, packet, explicit=False): """ @@ -3588,14 +2971,14 @@ def __build_xbee_message(self, packet, explicit=False): if hasattr(packet, "x64bit_source_addr"): x64addr = packet.x64bit_source_addr if x64addr is not None or x16addr is not None: - remote = RemoteXBeeDevice(self, x64bit_addr=x64addr, x16bit_addr=x16addr) + remote = RemoteXBeeDevice(self, x64addr, x16addr) if explicit: msg = ExplicitXBeeMessage(packet.rf_data, remote, time.time(), packet.source_endpoint, packet.dest_endpoint, packet.cluster_id, - packet.profile_id, broadcast=packet.is_broadcast()) + packet.profile_id, packet.is_broadcast()) else: - msg = XBeeMessage(packet.rf_data, remote, time.time(), broadcast=packet.is_broadcast()) + msg = XBeeMessage(packet.rf_data, remote, time.time(), packet.is_broadcast()) return msg @@ -3643,33 +3026,7 @@ def __build_expldata_packet(self, remote_xbee_device, data, src_endpoint, dest_e return ExplicitAddressingPacket(self._get_next_frame_id(), x64addr, x16addr, src_endpoint, dest_endpoint, - cluster_id, profile_id, 0, transmit_options, rf_data=data) - - def __get_actual_mode(self): - """ - Gets and returns the actual operating mode of the XBee device reading the ``AP`` parameter in AT command mode. - - Returns: - :class:`.OperatingMode`. The actual operating mode of the XBee device or ``OperatingMode.UNKNOWN`` if the - mode could not be read. - - Raises: - SerialTimeoutException: if there is any error trying to write within the serial port. - """ - if not self._serial_port: - raise XBeeException("Command mode is only supported for local XBee devices using a serial connection") - - # Clear the serial input stream. - self._serial_port.flushInput() - # Send the 'AP' command. - self._serial_port.write("ATAP\r".encode("utf-8")) - time.sleep(0.1) - # Read the 'AP' answer. - ap_answer = self._serial_port.read_existing().decode("utf-8").rstrip() - if len(ap_answer) == 0: - return OperatingMode.UNKNOWN - # Return the corresponding operating mode for the AP answer. - return OperatingMode.get(int(ap_answer, 16)) + cluster_id, profile_id, 0, transmit_options, data) def get_next_frame_id(self): """ @@ -3680,9 +3037,6 @@ def get_next_frame_id(self): """ return self._get_next_frame_id() - comm_iface = property(__get_comm_iface) - """:class:`.XBeeCommunicationInterface`. The hardware interface associated to the XBee device.""" - serial_port = property(__get_serial_port) """:class:`.XBeeSerialPort`. The serial port associated to the XBee device.""" @@ -3695,60 +3049,50 @@ class Raw802Device(XBeeDevice): This class represents a local 802.15.4 XBee device. """ - 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): + def __init__(self, port, baud_rate): """ - Class constructor. Instantiates a new :class:`.Raw802Device` with the provided parameters. + Class constructor. Instantiates a new :class:`Raw802Device` with the provided parameters. Args: port (Integer or String): serial port identifier. Integer: number of XBee device, numbering starts at zero. - Device name: depending on operating system. e.g. '/dev/ttyUSB0' on 'GNU/Linux' or - 'COM3' on Windows. + Device name: depending on operating system. e.g. '/dev/ttyUSB0' on GNU/Linux or 'COM3' on Windows. baud_rate (Integer): the serial port baud rate. - data_bits (Integer, default: :attr:`.serial.EIGHTBITS`): comm port bitsize. - stop_bits (Integer, default: :attr:`.serial.STOPBITS_ONE`): comm port stop bits. - parity (Character, default: :attr:`.serial.PARITY_NONE`): comm port parity. - flow_control (Integer, default: :attr:`.FlowControl.NONE`): comm port flow control. - _sync_ops_timeout (Integer, default: 3): the read timeout (in seconds). - comm_iface (:class:`.XBeeCommunicationInterface`): the communication interface. Raises: All exceptions raised by :meth:`.XBeeDevice.__init__` constructor. - + .. seealso:: | :class:`.XBeeDevice` | :meth:`.XBeeDevice.__init__` """ - super().__init__(port, baud_rate, data_bits=data_bits, stop_bits=stop_bits, parity=parity, - flow_control=flow_control, _sync_ops_timeout=_sync_ops_timeout, comm_iface=comm_iface) + super(Raw802Device, self).__init__(port, baud_rate) - def open(self, force_settings=False): + def open(self): """ Override. Raises: - TimeoutException: If there is any problem with the communication. - 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 protocol is invalid or if the XBee device is already open. + XBeeException: if the protocol is invalid. + All exceptions raised by :meth:`.XBeeDevice.open`. .. seealso:: | :meth:`.XBeeDevice.open` """ - super().open(force_settings=force_settings) + super(Raw802Device, self).open() if not self.is_remote() and self.get_protocol() != XBeeProtocol.RAW_802_15_4: raise XBeeException("Invalid protocol.") - def _init_network(self): + def get_network(self): """ Override. .. seealso:: | :meth:`.XBeeDevice.get_network` """ - return Raw802Network(self) + if self._network is None: + self._network = Raw802Network(self) + return self._network def get_protocol(self): """ @@ -3766,7 +3110,7 @@ def get_ai_status(self): .. seealso:: | :meth:`.AbstractXBeeDevice._get_ai_status` """ - return super()._get_ai_status() + return super(Raw802Device, self)._get_ai_status() def send_data_64(self, x64addr, data, transmit_options=TransmitOptions.NONE.value): """ @@ -3775,7 +3119,7 @@ def send_data_64(self, x64addr, data, transmit_options=TransmitOptions.NONE.valu .. seealso:: | :meth:`.XBeeDevice.send_data_64` """ - return super()._send_data_64(x64addr, data, transmit_options=transmit_options) + return super(Raw802Device, self)._send_data_64(x64addr, data, transmit_options) def send_data_async_64(self, x64addr, data, transmit_options=TransmitOptions.NONE.value): """ @@ -3784,7 +3128,7 @@ def send_data_async_64(self, x64addr, data, transmit_options=TransmitOptions.NON .. seealso:: | :meth:`.XBeeDevice.send_data_async_64` """ - super()._send_data_async_64(x64addr, data, transmit_options=transmit_options) + super(Raw802Device, self)._send_data_async_64(x64addr, data, transmit_options) def send_data_16(self, x16addr, data, transmit_options=TransmitOptions.NONE.value): """ @@ -3793,7 +3137,7 @@ def send_data_16(self, x16addr, data, transmit_options=TransmitOptions.NONE.valu .. seealso:: | :meth:`.XBeeDevice._send_data_16` """ - return super()._send_data_16(x16addr, data, transmit_options=transmit_options) + return super(Raw802Device, self)._send_data_16(x16addr, data, transmit_options) def send_data_async_16(self, x16addr, data, transmit_options=TransmitOptions.NONE.value): """ @@ -3802,7 +3146,7 @@ def send_data_async_16(self, x16addr, data, transmit_options=TransmitOptions.NON .. seealso:: | :meth:`.XBeeDevice._send_data_async_16` """ - super()._send_data_async_16(x16addr, data, transmit_options=transmit_options) + super(Raw802Device, self)._send_data_async_16(x16addr, data, transmit_options) class DigiMeshDevice(XBeeDevice): @@ -3810,24 +3154,15 @@ class DigiMeshDevice(XBeeDevice): This class represents a local DigiMesh XBee device. """ - 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): + def __init__(self, port, baud_rate): """ - Class constructor. Instantiates a new :class:`.DigiMeshDevice` with the provided parameters. + Class constructor. Instantiates a new :class:`DigiMeshDevice` with the provided parameters. Args: port (Integer or String): serial port identifier. Integer: number of XBee device, numbering starts at zero. - Device name: depending on operating system. e.g. '/dev/ttyUSB0' on 'GNU/Linux' or - 'COM3' on Windows. + Device name: depending on operating system. e.g. '/dev/ttyUSB0' on GNU/Linux or 'COM3' on Windows. baud_rate (Integer): the serial port baud rate. - data_bits (Integer, default: :attr:`.serial.EIGHTBITS`): comm port bitsize. - stop_bits (Integer, default: :attr:`.serial.STOPBITS_ONE`): comm port stop bits. - parity (Character, default: :attr:`.serial.PARITY_NONE`): comm port parity. - flow_control (Integer, default: :attr:`.FlowControl.NONE`): comm port flow control. - _sync_ops_timeout (Integer, default: 3): the read timeout (in seconds). - comm_iface (:class:`.XBeeCommunicationInterface`): the communication interface. Raises: All exceptions raised by :meth:`.XBeeDevice.__init__` constructor. @@ -3836,34 +3171,33 @@ def __init__(self, port=None, baud_rate=None, data_bits=serial.EIGHTBITS, stop_b | :class:`.XBeeDevice` | :meth:`.XBeeDevice.__init__` """ - super().__init__(port, baud_rate, data_bits=data_bits, stop_bits=stop_bits, parity=parity, - flow_control=flow_control, _sync_ops_timeout=_sync_ops_timeout, comm_iface=comm_iface) + super(DigiMeshDevice, self).__init__(port, baud_rate) - def open(self, force_settings=False): + def open(self): """ Override. Raises: - TimeoutException: If there is any problem with the communication. - 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 protocol is invalid or if the XBee device is already open. + XBeeException: if the protocol is invalid. + All exceptions raised by :meth:`.XBeeDevice.open`. .. seealso:: | :meth:`.XBeeDevice.open` """ - super().open(force_settings=force_settings) + super(DigiMeshDevice, self).open() if self.get_protocol() != XBeeProtocol.DIGI_MESH: raise XBeeException("Invalid protocol.") - def _init_network(self): + def get_network(self): """ Override. .. seealso:: | :meth:`.XBeeDevice.get_network` """ - return DigiMeshNetwork(self) + if self._network is None: + self._network = DigiMeshNetwork(self) + return self._network def get_protocol(self): """ @@ -3881,7 +3215,7 @@ def send_data_64(self, x64addr, data, transmit_options=TransmitOptions.NONE.valu .. seealso:: | :meth:`.XBeeDevice.send_data_64` """ - return super()._send_data_64(x64addr, data, transmit_options=transmit_options) + return super(DigiMeshDevice, self)._send_data_64(x64addr, data, transmit_options) def send_data_async_64(self, x64addr, data, transmit_options=TransmitOptions.NONE.value): """ @@ -3890,7 +3224,7 @@ def send_data_async_64(self, x64addr, data, transmit_options=TransmitOptions.NON .. seealso:: | :meth:`.XBeeDevice.send_data_async_64` """ - super()._send_data_async_64(x64addr, data, transmit_options=transmit_options) + super(DigiMeshDevice, self)._send_data_async_64(x64addr, data, transmit_options) def read_expl_data(self, timeout=None): """ @@ -3899,7 +3233,7 @@ def read_expl_data(self, timeout=None): .. seealso:: | :meth:`.XBeeDevice.read_expl_data` """ - return super()._read_expl_data(timeout=timeout) + return super(DigiMeshDevice, self)._read_expl_data(timeout=timeout) def read_expl_data_from(self, remote_xbee_device, timeout=None): """ @@ -3908,7 +3242,7 @@ def read_expl_data_from(self, remote_xbee_device, timeout=None): .. seealso:: | :meth:`.XBeeDevice.read_expl_data_from` """ - return super()._read_expl_data_from(remote_xbee_device, timeout=timeout) + return super(DigiMeshDevice, self)._read_expl_data_from(remote_xbee_device, timeout=timeout) def send_expl_data(self, remote_xbee_device, data, src_endpoint, dest_endpoint, cluster_id, profile_id, transmit_options=TransmitOptions.NONE.value): @@ -3918,8 +3252,8 @@ def send_expl_data(self, remote_xbee_device, data, src_endpoint, dest_endpoint, .. seealso:: | :meth:`.XBeeDevice.send_expl_data` """ - return super()._send_expl_data(remote_xbee_device, data, src_endpoint, dest_endpoint, cluster_id, - profile_id, transmit_options=transmit_options) + return super(DigiMeshDevice, self)._send_expl_data(remote_xbee_device, data, src_endpoint, dest_endpoint, cluster_id, + profile_id, transmit_options) def send_expl_data_broadcast(self, data, src_endpoint, dest_endpoint, cluster_id, profile_id, transmit_options=TransmitOptions.NONE.value): @@ -3929,8 +3263,8 @@ def send_expl_data_broadcast(self, data, src_endpoint, dest_endpoint, cluster_id .. seealso:: | :meth:`.XBeeDevice._send_expl_data_broadcast` """ - return super()._send_expl_data_broadcast(data, src_endpoint, dest_endpoint, cluster_id, profile_id, - transmit_options=transmit_options) + return super(DigiMeshDevice, self)._send_expl_data_broadcast(data, src_endpoint, dest_endpoint, cluster_id, profile_id, + transmit_options) def send_expl_data_async(self, remote_xbee_device, data, src_endpoint, dest_endpoint, cluster_id, profile_id, transmit_options=TransmitOptions.NONE.value): @@ -3940,46 +3274,8 @@ def send_expl_data_async(self, remote_xbee_device, data, src_endpoint, dest_endp .. seealso:: | :meth:`.XBeeDevice.send_expl_data_async` """ - super()._send_expl_data_async(remote_xbee_device, data, src_endpoint, - dest_endpoint, cluster_id, profile_id, - transmit_options=transmit_options) - - def get_neighbors(self, neighbor_callback=None, process_finished_callback=None, timeout=None): - """ - Returns the neighbors of this XBee. If ``neighbor_callback`` is not defined, the process - blocks during the specified timeout. - - Args: - neighbor_callback (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. - - process_finished_callback (Function, optional, default=``None``): method to execute when - the process finishes. Receives two arguments: - - * The XBee device 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:`com.digi.models.zdo.Neighbor` when ``neighbor_callback`` is - 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_callback=neighbor_callback, - process_finished_callback=process_finished_callback, - timeout=timeout if timeout else NeighborFinder.DEFAULT_TIMEOUT) + super(DigiMeshDevice, self)._send_expl_data_async(remote_xbee_device, data, src_endpoint, + dest_endpoint, cluster_id, profile_id, transmit_options) class DigiPointDevice(XBeeDevice): @@ -3987,61 +3283,50 @@ class DigiPointDevice(XBeeDevice): This class represents a local DigiPoint XBee device. """ - 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): + def __init__(self, port, baud_rate): """ - Class constructor. Instantiates a new :class:`.DigiPointDevice` with the provided - parameters. + Class constructor. Instantiates a new :class:`DigiPointDevice` with the provided parameters. Args: port (Integer or String): serial port identifier. Integer: number of XBee device, numbering starts at zero. - Device name: depending on operating system. e.g. '/dev/ttyUSB0' on 'GNU/Linux' or - 'COM3' on Windows. + Device name: depending on operating system. e.g. '/dev/ttyUSB0' on GNU/Linux or 'COM3' on Windows. baud_rate (Integer): the serial port baud rate. - data_bits (Integer, default: :attr:`.serial.EIGHTBITS`): comm port bitsize. - stop_bits (Integer, default: :attr:`.serial.STOPBITS_ONE`): comm port stop bits. - parity (Character, default: :attr:`.serial.PARITY_NONE`): comm port parity. - flow_control (Integer, default: :attr:`.FlowControl.NONE`): comm port flow control. - _sync_ops_timeout (Integer, default: 3): the read timeout (in seconds). - comm_iface (:class:`.XBeeCommunicationInterface`): the communication interface. Raises: - All exceptions raised by :meth:`.XBeeDevice.__init__` constructor. + All exceptions raised by :meth:`XBeeDevice.__init__` constructor. .. seealso:: | :class:`.XBeeDevice` | :meth:`.XBeeDevice.__init__` """ - super().__init__(port, baud_rate, data_bits=data_bits, stop_bits=stop_bits, parity=parity, - flow_control=flow_control, _sync_ops_timeout=_sync_ops_timeout, comm_iface=comm_iface) + super(DigiPointDevice, self).__init__(port, baud_rate) - def open(self, force_settings=False): + def open(self): """ Override. Raises: - TimeoutException: If there is any problem with the communication. - 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 protocol is invalid or if the XBee device is already open. + XBeeException: if the protocol is invalid. + All exceptions raised by :meth:`.XBeeDevice.open`. .. seealso:: | :meth:`.XBeeDevice.open` """ - super().open(force_settings=force_settings) + super(DigiPointDevice, self).open() if self.get_protocol() != XBeeProtocol.DIGI_POINT: raise XBeeException("Invalid protocol.") - def _init_network(self): + def get_network(self): """ Override. .. seealso:: | :meth:`.XBeeDevice.get_network` """ - return DigiPointNetwork(self) + if self._network is None: + self._network = DigiPointNetwork(self) + return self._network def get_protocol(self): """ @@ -4059,7 +3344,7 @@ def send_data_64_16(self, x64addr, x16addr, data, transmit_options=TransmitOptio .. seealso:: | :meth:`.XBeeDevice.send_data_64_16` """ - return super()._send_data_64_16(x64addr, x16addr, data, transmit_options=transmit_options) + return super(DigiPointDevice, self)._send_data_64_16(x64addr, x16addr, data, transmit_options) def send_data_async_64_16(self, x64addr, x16addr, data, transmit_options=TransmitOptions.NONE.value): """ @@ -4068,7 +3353,7 @@ def send_data_async_64_16(self, x64addr, x16addr, data, transmit_options=Transmi .. seealso:: | :meth:`.XBeeDevice.send_data_async_64_16` """ - super()._send_data_async_64_16(x64addr, x16addr, data, transmit_options=transmit_options) + super(DigiPointDevice, self)._send_data_async_64_16(x64addr, x16addr, data, transmit_options) def read_expl_data(self, timeout=None): """ @@ -4077,7 +3362,7 @@ def read_expl_data(self, timeout=None): .. seealso:: | :meth:`.XBeeDevice.read_expl_data` """ - return super()._read_expl_data(timeout=timeout) + return super(DigiPointDevice, self)._read_expl_data(timeout=timeout) def read_expl_data_from(self, remote_xbee_device, timeout=None): """ @@ -4086,7 +3371,7 @@ def read_expl_data_from(self, remote_xbee_device, timeout=None): .. seealso:: | :meth:`.XBeeDevice.read_expl_data_from` """ - return super()._read_expl_data_from(remote_xbee_device, timeout=timeout) + return super(DigiPointDevice, self)._read_expl_data_from(remote_xbee_device, timeout=timeout) def send_expl_data(self, remote_xbee_device, data, src_endpoint, dest_endpoint, cluster_id, profile_id, transmit_options=TransmitOptions.NONE.value): @@ -4096,8 +3381,8 @@ def send_expl_data(self, remote_xbee_device, data, src_endpoint, dest_endpoint, .. seealso:: | :meth:`.XBeeDevice.send_expl_data` """ - return super()._send_expl_data(remote_xbee_device, data, src_endpoint, dest_endpoint, cluster_id, - profile_id, transmit_options=transmit_options) + return super(DigiPointDevice, self)._send_expl_data(remote_xbee_device, data, src_endpoint, dest_endpoint, cluster_id, + profile_id, transmit_options) def send_expl_data_broadcast(self, data, src_endpoint, dest_endpoint, cluster_id, profile_id, transmit_options=TransmitOptions.NONE.value): @@ -4107,8 +3392,8 @@ def send_expl_data_broadcast(self, data, src_endpoint, dest_endpoint, cluster_id .. seealso:: | :meth:`.XBeeDevice._send_expl_data_broadcast` """ - return super()._send_expl_data_broadcast(data, src_endpoint, dest_endpoint, cluster_id, profile_id, - transmit_options=transmit_options) + return super(DigiPointDevice, self)._send_expl_data_broadcast(data, src_endpoint, dest_endpoint, cluster_id, profile_id, + transmit_options) def send_expl_data_async(self, remote_xbee_device, data, src_endpoint, dest_endpoint, cluster_id, profile_id, transmit_options=TransmitOptions.NONE.value): @@ -4118,9 +3403,8 @@ def send_expl_data_async(self, remote_xbee_device, data, src_endpoint, dest_endp .. seealso:: | :meth:`.XBeeDevice.send_expl_data_async` """ - super()._send_expl_data_async(remote_xbee_device, data, src_endpoint, - dest_endpoint, cluster_id, profile_id, - transmit_options=transmit_options) + super(DigiPointDevice, self)._send_expl_data_async(remote_xbee_device, data, src_endpoint, + dest_endpoint, cluster_id, profile_id, transmit_options) class ZigBeeDevice(XBeeDevice): @@ -4128,60 +3412,50 @@ class ZigBeeDevice(XBeeDevice): This class represents a local ZigBee XBee device. """ - 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): + def __init__(self, port, baud_rate): """ - Class constructor. Instantiates a new :class:`.ZigBeeDevice` with the provided parameters. + Class constructor. Instantiates a new :class:`ZigBeeDevice` with the provided parameters. Args: port (Integer or String): serial port identifier. Integer: number of XBee device, numbering starts at zero. - Device name: depending on operating system. e.g. '/dev/ttyUSB0' on 'GNU/Linux' or - 'COM3' on Windows. + Device name: depending on operating system. e.g. '/dev/ttyUSB0' on GNU/Linux or 'COM3' on Windows. baud_rate (Integer): the serial port baud rate. - data_bits (Integer, default: :attr:`.serial.EIGHTBITS`): comm port bitsize. - stop_bits (Integer, default: :attr:`.serial.STOPBITS_ONE`): comm port stop bits. - parity (Character, default: :attr:`.serial.PARITY_NONE`): comm port parity. - flow_control (Integer, default: :attr:`.FlowControl.NONE`): comm port flow control. - _sync_ops_timeout (Integer, default: 3): the read timeout (in seconds). - comm_iface (:class:`.XBeeCommunicationInterface`): the communication interface. Raises: - All exceptions raised by :meth:`.XBeeDevice.__init__` constructor. + All exceptions raised by :func:`.XBeeDevice.__init__` constructor. .. seealso:: | :class:`.XBeeDevice` - | :meth:`.XBeeDevice.__init__` + | :meth:`XBeeDevice.__init__` """ - super().__init__(port, baud_rate, data_bits=data_bits, stop_bits=stop_bits, parity=parity, - flow_control=flow_control, _sync_ops_timeout=_sync_ops_timeout, comm_iface=comm_iface) + super(ZigBeeDevice, self).__init__(port, baud_rate) - def open(self, force_settings=False): + def open(self): """ Override. Raises: - TimeoutException: If there is any problem with the communication. - 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 protocol is invalid or if the XBee device is already open. + XBeeException: if the protocol is invalid. + All exceptions raised by :meth:`.XBeeDevice.open`. .. seealso:: | :meth:`.XBeeDevice.open` """ - super().open(force_settings=force_settings) + super(ZigBeeDevice, self).open() if self.get_protocol() != XBeeProtocol.ZIGBEE: raise XBeeException("Invalid protocol.") - def _init_network(self): + def get_network(self): """ Override. .. seealso:: | :meth:`.XBeeDevice.get_network` """ - return ZigBeeNetwork(self) + if self._network is None: + self._network = ZigBeeNetwork(self) + return self._network def get_protocol(self): """ @@ -4199,7 +3473,7 @@ def get_ai_status(self): .. seealso:: | :meth:`.AbstractXBeeDevice._get_ai_status` """ - return super()._get_ai_status() + return super(ZigBeeDevice, self)._get_ai_status() def force_disassociate(self): """ @@ -4208,7 +3482,7 @@ def force_disassociate(self): .. seealso:: | :meth:`.AbstractXBeeDevice._force_disassociate` """ - super()._force_disassociate() + super(ZigBeeDevice, self)._force_disassociate() def send_data_64_16(self, x64addr, x16addr, data, transmit_options=TransmitOptions.NONE.value): """ @@ -4217,7 +3491,7 @@ def send_data_64_16(self, x64addr, x16addr, data, transmit_options=TransmitOptio .. seealso:: | :meth:`.XBeeDevice.send_data_64_16` """ - return super()._send_data_64_16(x64addr, x16addr, data, transmit_options=transmit_options) + return super(ZigBeeDevice, self)._send_data_64_16(x64addr, x16addr, data, transmit_options) def send_data_async_64_16(self, x64addr, x16addr, data, transmit_options=TransmitOptions.NONE.value): """ @@ -4226,7 +3500,7 @@ def send_data_async_64_16(self, x64addr, x16addr, data, transmit_options=Transmi .. seealso:: | :meth:`.XBeeDevice.send_data_async_64_16` """ - super()._send_data_async_64_16(x64addr, x16addr, data, transmit_options=transmit_options) + super(ZigBeeDevice, self)._send_data_async_64_16(x64addr, x16addr, data, transmit_options) def read_expl_data(self, timeout=None): """ @@ -4235,7 +3509,7 @@ def read_expl_data(self, timeout=None): .. seealso:: | :meth:`.XBeeDevice._read_expl_data` """ - return super()._read_expl_data(timeout=timeout) + return super(ZigBeeDevice, self)._read_expl_data(timeout=timeout) def read_expl_data_from(self, remote_xbee_device, timeout=None): """ @@ -4244,7 +3518,7 @@ def read_expl_data_from(self, remote_xbee_device, timeout=None): .. seealso:: | :meth:`.XBeeDevice._read_expl_data_from` """ - return super()._read_expl_data_from(remote_xbee_device, timeout=timeout) + return super(ZigBeeDevice, self)._read_expl_data_from(remote_xbee_device, timeout=timeout) def send_expl_data(self, remote_xbee_device, data, src_endpoint, dest_endpoint, cluster_id, profile_id, transmit_options=TransmitOptions.NONE.value): @@ -4254,8 +3528,8 @@ def send_expl_data(self, remote_xbee_device, data, src_endpoint, dest_endpoint, .. seealso:: | :meth:`.XBeeDevice._send_expl_data` """ - return super()._send_expl_data(remote_xbee_device, data, src_endpoint, dest_endpoint, cluster_id, - profile_id, transmit_options=transmit_options) + return super(ZigBeeDevice, self)._send_expl_data(remote_xbee_device, data, src_endpoint, dest_endpoint, cluster_id, + profile_id, transmit_options) def send_expl_data_broadcast(self, data, src_endpoint, dest_endpoint, cluster_id, profile_id, transmit_options=TransmitOptions.NONE.value): @@ -4265,8 +3539,8 @@ def send_expl_data_broadcast(self, data, src_endpoint, dest_endpoint, cluster_id .. seealso:: | :meth:`.XBeeDevice._send_expl_data_broadcast` """ - return super()._send_expl_data_broadcast(data, src_endpoint, dest_endpoint, cluster_id, profile_id, - transmit_options=transmit_options) + return super(ZigBeeDevice, self)._send_expl_data_broadcast(data, src_endpoint, dest_endpoint, cluster_id, profile_id, + transmit_options) def send_expl_data_async(self, remote_xbee_device, data, src_endpoint, dest_endpoint, cluster_id, profile_id, transmit_options=TransmitOptions.NONE.value): @@ -4276,9 +3550,8 @@ def send_expl_data_async(self, remote_xbee_device, data, src_endpoint, dest_endp .. seealso:: | :meth:`.XBeeDevice.send_expl_data_async` """ - super()._send_expl_data_async(remote_xbee_device, data, src_endpoint, - dest_endpoint, cluster_id, profile_id, - transmit_options=transmit_options) + super(ZigBeeDevice, self)._send_expl_data_async(remote_xbee_device, data, src_endpoint, + dest_endpoint, cluster_id, profile_id, transmit_options) @AbstractXBeeDevice._before_send_method @AbstractXBeeDevice._after_send_method @@ -4319,8 +3592,8 @@ def send_multicast_data(self, group_id, data, src_endpoint, dest_endpoint, XBee64BitAddress.UNKNOWN_ADDRESS, group_id, src_endpoint, dest_endpoint, cluster_id, profile_id, 0, - TransmitOptions.ENABLE_MULTICAST.value, rf_data=data) - + TransmitOptions.ENABLE_MULTICAST.value, data) + return self.send_packet_sync_and_get_response(packet_to_send) @AbstractXBeeDevice._before_send_method @@ -4348,221 +3621,14 @@ def send_multicast_data_async(self, group_id, data, src_endpoint, dest_endpoint, .. seealso:: | :class:`XBee16BitAddress` """ - packet_to_send = ExplicitAddressingPacket(self._get_next_frame_id(), + packet_to_send = ExplicitAddressingPacket(self._get_next_frame_id(), XBee64BitAddress.UNKNOWN_ADDRESS, group_id, src_endpoint, dest_endpoint, cluster_id, profile_id, 0, - TransmitOptions.ENABLE_MULTICAST.value, rf_data=data) - + TransmitOptions.ENABLE_MULTICAST.value, data) + self.send_packet(packet_to_send) - @AbstractXBeeDevice._before_send_method - def register_joining_device(self, registrant_address, options, key): - """ - Securely registers a joining device to a trust center. Registration is the process by which a node is - authorized to join the network using a preconfigured link key or installation code that is conveyed to - the trust center out-of-band (using a physical interface and not over-the-air). - - This method is synchronous, it sends the register joining device packet and waits for the answer of the - operation. Then, returns the corresponding status. - - Args: - registrant_address (:class:`XBee64BitAddress`): the 64-bit address of the device to register. - options (RegisterKeyOptions): the register options indicating the key source. - key (Bytearray): key of the device to register. - - Returns: - :class:`.ZigbeeRegisterStatus`: the register device operation status or ``None`` if the answer - received is not a ``RegisterDeviceStatusPacket``. - - Raises: - TimeoutException: if the answer is not received in the configured timeout. - 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 serial port is closed. - ValueError: if ``registrant_address`` is ``None`` or if ``options`` is ``None``. - - .. seealso:: - | :class:`RegisterKeyOptions` - | :class:`XBee64BitAddress` - | :class:`ZigbeeRegisterStatus` - """ - if registrant_address is None: - raise ValueError("Registrant address cannot be ``None``.") - if options is None: - raise ValueError("Options cannot be ``None``.") - - packet_to_send = RegisterJoiningDevicePacket(self.get_next_frame_id(), - registrant_address, - options, - key) - response_packet = self.send_packet_sync_and_get_response(packet_to_send) - if isinstance(response_packet, RegisterDeviceStatusPacket): - return response_packet.status - return None - - @AbstractXBeeDevice._before_send_method - def register_joining_device_async(self, registrant_address, options, key): - """ - Securely registers a joining device to a trust center. Registration is the process by which a node is - authorized to join the network using a preconfigured link key or installation code that is conveyed to - the trust center out-of-band (using a physical interface and not over-the-air). - - This method is asynchronous, which means that it will not wait for an answer after sending the - register frame. - - Args: - registrant_address (:class:`XBee64BitAddress`): the 64-bit address of the device to register. - options (RegisterKeyOptions): the register options indicating the key source. - key (Bytearray): key of the device to register. - - Raises: - 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 serial port is closed. - ValueError: if ``registrant_address`` is ``None`` or if ``options`` is ``None``. - - .. seealso:: - | :class:`RegisterKeyOptions` - | :class:`XBee64BitAddress` - """ - if registrant_address is None: - raise ValueError("Registrant address cannot be ``None``.") - if options is None: - raise ValueError("Options cannot be ``None``.") - - packet_to_send = RegisterJoiningDevicePacket(self.get_next_frame_id(), - registrant_address, - options, - key) - self.send_packet(packet_to_send, sync=True) - - @AbstractXBeeDevice._before_send_method - def unregister_joining_device(self, unregistrant_address): - """ - Unregisters a joining device from a trust center. - - This method is synchronous, it sends the unregister joining device packet and waits for the answer of the - operation. Then, returns the corresponding status. - - Args: - unregistrant_address (:class:`XBee64BitAddress`): the 64-bit address of the device to unregister. - - Returns: - :class:`.ZigbeeRegisterStatus`: the unregister device operation status or ``None`` if the answer - received is not a ``RegisterDeviceStatusPacket``. - - Raises: - TimeoutException: if the answer is not received in the configured timeout. - 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 serial port is closed. - ValueError: if ``registrant_address`` is ``None``. - - .. seealso:: - | :class:`XBee64BitAddress` - | :class:`ZigbeeRegisterStatus` - """ - return self.register_joining_device(unregistrant_address, RegisterKeyOptions.LINK_KEY, None) - - @AbstractXBeeDevice._before_send_method - def unregister_joining_device_async(self, unregistrant_address): - """ - Unregisters a joining device from a trust center. - - This method is asynchronous, which means that it will not wait for an answer after sending the - uregister frame. - - Args: - unregistrant_address (:class:`XBee64BitAddress`): the 64-bit address of the device to unregister. - - Raises: - 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 serial port is closed. - ValueError: if ``registrant_address`` is ``None``. - - .. seealso:: - | :class:`XBee64BitAddress` - """ - self.register_joining_device_async(unregistrant_address, RegisterKeyOptions.LINK_KEY, None) - - def get_routes(self, route_callback=None, process_finished_callback=None, timeout=None): - """ - Returns the routes of this XBee. If ``route_callback`` is not defined, the process blocks - until the complete routing table is read. - - Args: - route_callback (Function, optional, default=``None``): method called when a new route - is received. Receives two arguments: - - * The XBee that owns this new route. - * The new route. - - process_finished_callback (Function, optional, default=``None``): method to execute when - the process finishes. Receives two arguments: - - * The XBee device that executed the ZDO command. - * A list with the discovered routes. - * An error message if something went wrong. - - timeout (Float, optional, default=``RouteTableReader.DEFAULT_TIMEOUT``): The ZDO command - timeout in seconds. - Returns: - List: List of :class:`com.digi.models.zdo.Route` when ``route_callback`` is defined, - ``None`` otherwise (in this case routes are received in the callback). - - Raises: - 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. - OperationNotSupportedException: If XBee protocol is not Zigbee or Smart Energy. - XBeeException: If the XBee device's serial port is closed. - - .. seealso:: - | :class:`com.digi.models.zdo.Route` - """ - from digi.xbee.models.zdo import RouteTableReader - return super()._get_routes(route_callback=route_callback, - process_finished_callback=process_finished_callback, - timeout=timeout if timeout else RouteTableReader.DEFAULT_TIMEOUT) - - def get_neighbors(self, neighbor_callback=None, process_finished_callback=None, timeout=None): - """ - Returns the neighbors of this XBee. If ``neighbor_callback`` is not defined, the process - blocks until the complete neighbor table is read. - - Args: - neighbor_callback (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. - - process_finished_callback (Function, optional, default=``None``): method to execute when - the process finishes. Receives two arguments: - - * The XBee device that executed the ZDO command. - * A list with the discovered neighbors. - * An error message if something went wrong. - - timeout (Float, optional, default=``NeighborTableReader.DEFAULT_TIMEOUT``): The ZDO - command timeout in seconds. - Returns: - List: List of :class:`com.digi.models.zdo.Neighbor` when ``neighbor_callback`` is - defined, ``None`` otherwise (in this case neighbors are received in the callback). - - Raises: - OperationNotSupportedException: If XBee protocol is not Zigbee or Smart Energy. - - .. seealso:: - | :class:`com.digi.models.zdo.Neighbor` - """ - from digi.xbee.models.zdo import NeighborTableReader - return super()._get_neighbors( - neighbor_callback=neighbor_callback, - process_finished_callback=process_finished_callback, - timeout=timeout if timeout else NeighborTableReader.DEFAULT_TIMEOUT) - class IPDevice(XBeeDevice): """ @@ -4577,60 +3643,50 @@ class IPDevice(XBeeDevice): __OPERATION_EXCEPTION = "Operation not supported in this module." - 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): + def __init__(self, port, baud_rate): """ - Class constructor. Instantiates a new :class:`.IPDevice` with the provided parameters. + Class constructor. Instantiates a new :class:`.IPDevice` with the + provided parameters. Args: port (Integer or String): serial port identifier. Integer: number of XBee device, numbering starts at zero. - Device name: depending on operating system. e.g. '/dev/ttyUSB0' on 'GNU/Linux' or - 'COM3' on Windows. + Device name: depending on operating system. e.g. '/dev/ttyUSB0' on GNU/Linux or 'COM3' on Windows. baud_rate (Integer): the serial port baud rate. - data_bits (Integer, default: :attr:`.serial.EIGHTBITS`): comm port bitsize. - stop_bits (Integer, default: :attr:`.serial.STOPBITS_ONE`): comm port stop bits. - parity (Character, default: :attr:`.serial.PARITY_NONE`): comm port parity. - flow_control (Integer, default: :attr:`.FlowControl.NONE`): comm port flow control. - _sync_ops_timeout (Integer, default: 3): the read timeout (in seconds). - comm_iface (:class:`.XBeeCommunicationInterface`): the communication interface. Raises: - All exceptions raised by :meth:`.XBeeDevice.__init__` constructor. + All exceptions raised by :func:`.XBeeDevice.__init__` constructor. .. seealso:: | :class:`.XBeeDevice` - | :meth:`.XBeeDevice.__init__` + | :meth:`XBeeDevice.__init__` """ - super().__init__(port, baud_rate, data_bits=data_bits, stop_bits=stop_bits, parity=parity, - flow_control=flow_control, _sync_ops_timeout=_sync_ops_timeout, comm_iface=comm_iface) + super(IPDevice, self).__init__(port, baud_rate) + self._ip_addr = None self._source_port = self.__DEFAULT_SOURCE_PORT - def read_device_info(self, init=True): + def read_device_info(self): """ Override. .. seealso:: | :meth:`.AbstractXBeeDevice.read_device_info` """ - super().read_device_info(init=init) + super(IPDevice, self).read_device_info() # Read the module's IP address. - if init or self._ip_addr is None: - resp = self.get_parameter(ATStringCommand.MY.command) - self._ip_addr = IPv4Address(utils.bytes_to_int(resp)) + resp = self.get_parameter("MY") + self._ip_addr = IPv4Address(utils.bytes_to_int(resp)) # Read the source port. - if init or self._source_port is None: - try: - resp = self.get_parameter(ATStringCommand.C0.command) - self._source_port = utils.bytes_to_int(resp) - except XBeeException: - # Do not refresh the source port value if there is an error reading - # it from the module. - pass + try: + resp = self.get_parameter("C0") + self._source_port = utils.bytes_to_int(resp) + except XBeeException: + # Do not refresh the source port value if there is an error reading + # it from the module. + pass def get_ip_addr(self): """ @@ -4664,7 +3720,7 @@ def set_dest_ip_addr(self, address): if address is None: raise ValueError("Destination IP address cannot be None") - self.set_parameter(ATStringCommand.DL.command, bytearray(address.exploded, "utf8")) + self.set_parameter("DL", bytearray(address.exploded, "utf8")) def get_dest_ip_addr(self): """ @@ -4680,31 +3736,30 @@ def get_dest_ip_addr(self): .. seealso:: | :class:`ipaddress.IPv4Address` """ - resp = self.get_parameter(ATStringCommand.DL.command) + resp = self.get_parameter("DL") return IPv4Address(resp.decode("utf8")) def add_ip_data_received_callback(self, callback): """ - Adds a callback for the event :class:`digi.xbee.reader.IPDataReceived`. + Adds a callback for the event :class:`.IPDataReceived`. Args: callback (Function): the callback. Receives one argument. - * The data received as an :class:`digi.xbee.models.message.IPMessage` + * The data received as an :class:`.IPMessage` """ self._packet_listener.add_ip_data_received_callback(callback) def del_ip_data_received_callback(self, callback): """ - Deletes a callback for the callback list of :class:`digi.xbee.reader.IPDataReceived` + Deletes a callback for the callback list of :class:`.IPDataReceived` event. Args: callback (Function): the callback to delete. Raises: - ValueError: if ``callback`` is not in the callback list of - :class:`digi.xbee.reader.IPDataReceived` event. + ValueError: if ``callback`` is not in the callback list of :class:`.IPDataReceived` event. """ self._packet_listener.del_ip_data_received_callback(callback) @@ -4723,7 +3778,7 @@ def start_listening(self, source_port): if not 0 <= source_port <= 65535: raise ValueError("Source port must be between 0 and 65535") - self.set_parameter(ATStringCommand.C0.command, utils.int_to_bytes(source_port)) + self.set_parameter("C0", utils.int_to_bytes(source_port)) self._source_port = source_port def stop_listening(self): @@ -4734,7 +3789,7 @@ def stop_listening(self): TimeoutException: if there is a timeout processing the operation. XBeeException: if there is any other XBee related exception. """ - self.set_parameter(ATStringCommand.C0.command, utils.int_to_bytes(0)) + self.set_parameter("C0", utils.int_to_bytes(0)) self._source_port = 0 @AbstractXBeeDevice._before_send_method @@ -4777,7 +3832,7 @@ def send_ip_data(self, ip_addr, dest_port, protocol, data, close_socket=False): # Check if device is remote. if self.is_remote(): - raise OperationNotSupportedException(message="Cannot send IP data from a remote device") + raise OperationNotSupportedException("Cannot send IP data from a remote device") # The source port value depends on the protocol used in the transmission. # For UDP, source port value must be the same as 'C0' one. For TCP it must be 0. @@ -4791,7 +3846,7 @@ def send_ip_data(self, ip_addr, dest_port, protocol, data, close_socket=False): options = TXIPv4Packet.OPTIONS_CLOSE_SOCKET if close_socket else TXIPv4Packet.OPTIONS_LEAVE_SOCKET_OPEN packet = TXIPv4Packet(self.get_next_frame_id(), ip_addr, dest_port, source_port, protocol, - options, data=data) + options, data) return self.send_packet_sync_and_get_response(packet) @@ -4834,7 +3889,7 @@ def send_ip_data_async(self, ip_addr, dest_port, protocol, data, close_socket=Fa # Check if device is remote. if self.is_remote(): - raise OperationNotSupportedException(message="Cannot send IP data from a remote device") + raise OperationNotSupportedException("Cannot send IP data from a remote device") # The source port value depends on the protocol used in the transmission. # For UDP, source port value must be the same as 'C0' one. For TCP it must be 0. @@ -4848,7 +3903,7 @@ def send_ip_data_async(self, ip_addr, dest_port, protocol, data, close_socket=Fa options = TXIPv4Packet.OPTIONS_CLOSE_SOCKET if close_socket else TXIPv4Packet.OPTIONS_LEAVE_SOCKET_OPEN packet = TXIPv4Packet(self.get_next_frame_id(), ip_addr, dest_port, source_port, protocol, - options, data=data) + options, data) self.send_packet(packet) @@ -4933,7 +3988,7 @@ def read_ip_data_from(self, ip_addr, timeout=XBeeDevice.TIMEOUT_READ_PACKET): if timeout < 0: raise ValueError("Read timeout must be 0 or greater.") - return self.__read_ip_data_packet(timeout, ip_addr=ip_addr) + return self.__read_ip_data_packet(timeout, ip_addr) def __read_ip_data_packet(self, timeout, ip_addr=None): """ @@ -4962,7 +4017,7 @@ def __read_ip_data_packet(self, timeout, ip_addr=None): if ip_addr is None: packet = queue.get(timeout=timeout) else: - packet = queue.get_by_ip(ip_addr, timeout=timeout) + packet = queue.get_by_ip(ip_addr, timeout) if packet is None: return None @@ -4982,15 +4037,6 @@ def get_network(self): """ return None - def _init_network(self): - """ - Override. - - .. seealso:: - | :meth:`.XBeeDevice.get_network` - """ - return None - def get_16bit_addr(self): """ Deprecated. @@ -5122,50 +4168,40 @@ class CellularDevice(IPDevice): This class represents a local Cellular device. """ - 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): + def __init__(self, port, baud_rate): """ - Class constructor. Instantiates a new :class:`.CellularDevice` with the provided parameters. + Class constructor. Instantiates a new :class:`.CellularDevice` with the + provided parameters. Args: port (Integer or String): serial port identifier. Integer: number of XBee device, numbering starts at zero. - Device name: depending on operating system. e.g. '/dev/ttyUSB0' on 'GNU/Linux' or - 'COM3' on Windows. + Device name: depending on operating system. e.g. '/dev/ttyUSB0' on GNU/Linux or 'COM3' on Windows. baud_rate (Integer): the serial port baud rate. - data_bits (Integer, default: :attr:`.serial.EIGHTBITS`): comm port bitsize. - stop_bits (Integer, default: :attr:`.serial.STOPBITS_ONE`): comm port stop bits. - parity (Character, default: :attr:`.serial.PARITY_NONE`): comm port parity. - flow_control (Integer, default: :attr:`.FlowControl.NONE`): comm port flow control. - _sync_ops_timeout (Integer, default: 3): the read timeout (in seconds). - comm_iface (:class:`.XBeeCommunicationInterface`): the communication interface. Raises: - All exceptions raised by :meth:`.XBeeDevice.__init__` constructor. + All exceptions raised by :func:`.XBeeDevice.__init__` constructor. .. seealso:: | :class:`.XBeeDevice` - | :meth:`.XBeeDevice.__init__` + | :meth:`XBeeDevice.__init__` """ - super().__init__(port, baud_rate, data_bits=data_bits, stop_bits=stop_bits, parity=parity, - flow_control=flow_control, _sync_ops_timeout=_sync_ops_timeout, comm_iface=comm_iface) + super(CellularDevice, self).__init__(port, baud_rate) + self._imei_addr = None - def open(self, force_settings=False): + def open(self): """ Override. Raises: - TimeoutException: If there is any problem with the communication. - 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 protocol is invalid or if the XBee device is already open. + XBeeException: if the protocol is invalid. + All exceptions raised by :meth:`.XBeeDevice.open`. .. seealso:: | :meth:`.XBeeDevice.open` """ - super().open(force_settings=force_settings) + super(CellularDevice, self).open() if self.get_protocol() not in [XBeeProtocol.CELLULAR, XBeeProtocol.CELLULAR_NBIOT]: raise XBeeException("Invalid protocol.") @@ -5178,18 +4214,17 @@ def get_protocol(self): """ return XBeeProtocol.CELLULAR - def read_device_info(self, init=True): + def read_device_info(self): """ Override. .. seealso:: | :meth:`.XBeeDevice.read_device _info` """ - super().read_device_info(init=init) + super(CellularDevice, self).read_device_info() # Generate the IMEI address. - if init or self._imei_addr is None: - self._imei_addr = XBeeIMEIAddress(self._64bit_addr.address) + self._imei_addr = XBeeIMEIAddress(self._64bit_addr.address) def is_connected(self): """ @@ -5219,31 +4254,30 @@ def get_cellular_ai_status(self): TimeoutException: if there is a timeout getting the association indication status. XBeeException: if there is any other XBee related exception. """ - value = self.get_parameter(ATStringCommand.AI.command) + value = self.get_parameter("AI") return CellularAssociationIndicationStatus.get(utils.bytes_to_int(value)) def add_sms_callback(self, callback): """ - Adds a callback for the event :class:`digi.xbee.reader.SMSReceived`. + Adds a callback for the event :class:`.SMSReceived`. Args: callback (Function): the callback. Receives one argument. - * The data received as an :class:`digi.xbee.models.message.SMSMessage` + * The data received as an :class:`.SMSMessage` """ self._packet_listener.add_sms_received_callback(callback) def del_sms_callback(self, callback): """ - Deletes a callback for the callback list of :class:`digi.xbee.reader.SMSReceived` + Deletes a callback for the callback list of :class:`.SMSReceived` event. Args: callback (Function): the callback to delete. Raises: - ValueError: if ``callback`` is not in the callback list of - :class:`digi.xbee.reader.SMSReceived` event. + ValueError: if ``callback`` is not in the callback list of :class:`.SMSReceived` event. """ self._packet_listener.del_sms_received_callback(callback) @@ -5287,7 +4321,7 @@ def send_sms(self, phone_number, data): # Check if device is remote. if self.is_remote(): - raise OperationNotSupportedException(message="Cannot send SMS from a remote device") + raise OperationNotSupportedException("Cannot send SMS from a remote device") xbee_packet = TXSMSPacket(self.get_next_frame_id(), phone_number, data) @@ -5318,54 +4352,12 @@ def send_sms_async(self, phone_number, data): # Check if device is remote. if self.is_remote(): - raise OperationNotSupportedException(message="Cannot send SMS from a remote device") + raise OperationNotSupportedException("Cannot send SMS from a remote device") xbee_packet = TXSMSPacket(self.get_next_frame_id(), phone_number, data) self.send_packet(xbee_packet) - def get_sockets_list(self): - """ - Returns a list with the IDs of all active (open) sockets. - - Returns: - List: list with the IDs of all active (open) sockets, or empty list if there is not any active socket. - - Raises: - 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. - TimeoutException: if the response is not received before the read timeout expires. - XBeeException: if the XBee device's serial port is closed. - """ - response = self.get_parameter(ATStringCommand.SI.command) - return SocketInfo.parse_socket_list(response) - - def get_socket_info(self, socket_id): - """ - Returns the information of the socket with the given socket ID. - - Args: - socket_id (Integer): ID of the socket. - - Returns: - :class:`.SocketInfo`: The socket information, or ``None`` if the socket with that ID does not exist. - - Raises: - 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. - TimeoutException: if the response is not received before the read timeout expires. - XBeeException: if the XBee device's serial port is closed. - - .. seealso:: - | :class:`.SocketInfo` - """ - try: - response = self.get_parameter(ATStringCommand.SI.command, - parameter_value=utils.int_to_bytes(socket_id, 1)) - return SocketInfo.create_socket_info(response) - except ATCommandException: - return None - def get_64bit_addr(self): """ Deprecated. @@ -5462,34 +4454,25 @@ class LPWANDevice(CellularDevice): devices. """ - 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): + def __init__(self, port, baud_rate): """ - Class constructor. Instantiates a new :class:`.LPWANDevice` with the provided parameters. + Class constructor. Instantiates a new :class:`.LPWANDevice` with the + provided parameters. Args: port (Integer or String): serial port identifier. Integer: number of XBee device, numbering starts at zero. - Device name: depending on operating system. e.g. '/dev/ttyUSB0' on 'GNU/Linux' or - 'COM3' on Windows. + Device name: depending on operating system. e.g. '/dev/ttyUSB0' on GNU/Linux or 'COM3' on Windows. baud_rate (Integer): the serial port baud rate. - data_bits (Integer, default: :attr:`.serial.EIGHTBITS`): comm port bitsize. - stop_bits (Integer, default: :attr:`.serial.STOPBITS_ONE`): comm port stop bits. - parity (Character, default: :attr:`.serial.PARITY_NONE`): comm port parity. - flow_control (Integer, default: :attr:`.FlowControl.NONE`): comm port flow control. - _sync_ops_timeout (Integer, default: 3): the read timeout (in seconds). - comm_iface (:class:`.XBeeCommunicationInterface`): the communication interface. Raises: - All exceptions raised by :meth:`.XBeeDevice.__init__` constructor. + All exceptions raised by :func:`.XBeeDevice.__init__` constructor. .. seealso:: - | :class:`.CellularDevice` - | :meth:`.CellularDevice.__init__` + | :class:`.XBeeDevice` + | :meth:`XBeeDevice.__init__` """ - super().__init__(port, baud_rate, data_bits=data_bits, stop_bits=stop_bits, parity=parity, - flow_control=flow_control, _sync_ops_timeout=_sync_ops_timeout, comm_iface=comm_iface) + super(LPWANDevice, self).__init__(port, baud_rate) def send_ip_data(self, ip_addr, dest_port, protocol, data, close_socket=False): """ @@ -5512,7 +4495,7 @@ def send_ip_data(self, ip_addr, dest_port, protocol, data, close_socket=False): if protocol != IPProtocol.UDP: raise ValueError("This protocol only supports UDP transmissions") - super().send_ip_data(ip_addr, dest_port, protocol, data, close_socket=close_socket) + super(LPWANDevice, self).send_ip_data(ip_addr, dest_port, protocol, data) def send_ip_data_async(self, ip_addr, dest_port, protocol, data, close_socket=False): """ @@ -5535,7 +4518,7 @@ def send_ip_data_async(self, ip_addr, dest_port, protocol, data, close_socket=Fa if protocol != IPProtocol.UDP: raise ValueError("This protocol only supports UDP transmissions") - super().send_ip_data_async(ip_addr, dest_port, protocol, data, close_socket=close_socket) + super(LPWANDevice, self).send_ip_data_async(ip_addr, dest_port, protocol, data) def add_sms_callback(self, callback): """ @@ -5579,50 +4562,40 @@ class NBIoTDevice(LPWANDevice): This class represents a local NB-IoT device. """ - 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): + def __init__(self, port, baud_rate): """ - Class constructor. Instantiates a new :class:`.NBIoTDevice` with the provided parameters. + Class constructor. Instantiates a new :class:`.CellularDevice` with the + provided parameters. Args: port (Integer or String): serial port identifier. Integer: number of XBee device, numbering starts at zero. - Device name: depending on operating system. e.g. '/dev/ttyUSB0' on 'GNU/Linux' or - 'COM3' on Windows. + Device name: depending on operating system. e.g. '/dev/ttyUSB0' on GNU/Linux or 'COM3' on Windows. baud_rate (Integer): the serial port baud rate. - data_bits (Integer, default: :attr:`.serial.EIGHTBITS`): comm port bitsize. - stop_bits (Integer, default: :attr:`.serial.STOPBITS_ONE`): comm port stop bits. - parity (Character, default: :attr:`.serial.PARITY_NONE`): comm port parity. - flow_control (Integer, default: :attr:`.FlowControl.NONE`): comm port flow control. - _sync_ops_timeout (Integer, default: 3): the read timeout (in seconds). - comm_iface (:class:`.XBeeCommunicationInterface`): the communication interface. Raises: - All exceptions raised by :meth:`.XBeeDevice.__init__` constructor. + All exceptions raised by :func:`.XBeeDevice.__init__` constructor. .. seealso:: - | :class:`.LPWANDevice` - | :meth:`.LPWANDevice.__init__` + | :class:`.XBeeDevice` + | :meth:`XBeeDevice.__init__` """ - super().__init__(port, baud_rate, data_bits=data_bits, stop_bits=stop_bits, parity=parity, - flow_control=flow_control, _sync_ops_timeout=_sync_ops_timeout, comm_iface=comm_iface) + super(NBIoTDevice, self).__init__(port, baud_rate) + self._imei_addr = None - def open(self, force_settings=False): + def open(self): """ Override. Raises: - TimeoutException: If there is any problem with the communication. - 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 protocol is invalid or if the XBee device is already open. + XBeeException: if the protocol is invalid. + All exceptions raised by :meth:`.XBeeDevice.open`. .. seealso:: | :meth:`.XBeeDevice.open` """ - super().open(force_settings=force_settings) + super(NBIoTDevice, self).open() if self.get_protocol() != XBeeProtocol.CELLULAR_NBIOT: raise XBeeException("Invalid protocol.") @@ -5644,52 +4617,40 @@ class WiFiDevice(IPDevice): __DEFAULT_ACCESS_POINT_TIMEOUT = 15 # 15 seconds of timeout to connect, disconnect and scan access points. __DISCOVER_TIMEOUT = 30 # 30 seconds of access points discovery timeout. - 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): + def __init__(self, port, baud_rate): """ - Class constructor. Instantiates a new :class:`.WiFiDevice` with the provided parameters. + Class constructor. Instantiates a new :class:`WiFiDevice` with the provided parameters. Args: port (Integer or String): serial port identifier. Integer: number of XBee device, numbering starts at zero. - Device name: depending on operating system. e.g. '/dev/ttyUSB0' on 'GNU/Linux' or - 'COM3' on Windows. + Device name: depending on operating system. e.g. '/dev/ttyUSB0' on GNU/Linux or 'COM3' on Windows. baud_rate (Integer): the serial port baud rate. - data_bits (Integer, default: :attr:`.serial.EIGHTBITS`): comm port bitsize. - stop_bits (Integer, default: :attr:`.serial.STOPBITS_ONE`): comm port stop bits. - parity (Character, default: :attr:`.serial.PARITY_NONE`): comm port parity. - flow_control (Integer, default: :attr:`.FlowControl.NONE`): comm port flow control. - _sync_ops_timeout (Integer, default: 3): the read timeout (in seconds). - comm_iface (:class:`.XBeeCommunicationInterface`): the communication interface. Raises: - All exceptions raised by :meth:`.XBeeDevice.__init__` constructor. + All exceptions raised by :func:`.XBeeDevice.__init__` constructor. .. seealso:: - | :class:`.IPDevice` - | :meth:`.v.__init__` + | :class:`.XBeeDevice` + | :meth:`XBeeDevice.__init__` """ - super().__init__(port, baud_rate, data_bits=data_bits, stop_bits=stop_bits, parity=parity, - flow_control=flow_control, _sync_ops_timeout=_sync_ops_timeout, comm_iface=comm_iface) + super(WiFiDevice, self).__init__(port, baud_rate) self.__ap_timeout = self.__DEFAULT_ACCESS_POINT_TIMEOUT self.__scanning_aps = False self.__scanning_aps_error = False - def open(self, force_settings=False): + def open(self): """ Override. Raises: - TimeoutException: If there is any problem with the communication. - 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 protocol is invalid or if the XBee device is already open. + XBeeException: if the protocol is invalid. + All exceptions raised by :meth:`.XBeeDevice.open`. .. seealso:: | :meth:`.XBeeDevice.open` """ - super().open(force_settings=force_settings) + super(WiFiDevice, self).open() if self.get_protocol() != XBeeProtocol.XBEE_WIFI: raise XBeeException("Invalid protocol.") @@ -5716,8 +4677,7 @@ def get_wifi_ai_status(self): .. seealso:: | :class:`.WiFiAssociationIndicationStatus` """ - return WiFiAssociationIndicationStatus.get(utils.bytes_to_int( - self.get_parameter(ATStringCommand.AI.command))) + return WiFiAssociationIndicationStatus.get(utils.bytes_to_int(self.get_parameter("AI"))) def get_access_point(self, ssid): """ @@ -5768,15 +4728,15 @@ def scan_access_points(self): """ access_points_list = [] - if self.operating_mode not in [OperatingMode.API_MODE, OperatingMode.ESCAPED_API_MODE]: - raise InvalidOperatingModeException(message="Only can scan for access points in API mode.") + if self.operating_mode == OperatingMode.AT_MODE or self.operating_mode == OperatingMode.UNKNOWN: + raise InvalidOperatingModeException("Cannot scan for access points in AT mode.") def packet_receive_callback(xbee_packet): if not self.__scanning_aps: return if xbee_packet.get_frame_type() != ApiFrameType.AT_COMMAND_RESPONSE: return - if xbee_packet.command != ATStringCommand.AS.command: + if xbee_packet.command != "AS": return # Check for error. @@ -5796,8 +4756,7 @@ def packet_receive_callback(xbee_packet): self.__scanning_aps = True try: - self.send_packet(ATCommPacket(self.get_next_frame_id(), ATStringCommand.AS.command), - sync=False) + self.send_packet(ATCommPacket(self.get_next_frame_id(), "AS"), False) dead_line = time.time() + self.__DISCOVER_TIMEOUT while self.__scanning_aps and time.time() < dead_line: @@ -5857,17 +4816,17 @@ def connect_by_ap(self, access_point, password=None): raise ValueError("The access point to connect to cannot be None.") # Set connection parameters. - self.set_parameter(ATStringCommand.ID.command, bytearray(access_point.ssid, "utf8")) - self.set_parameter(ATStringCommand.EE.command, utils.int_to_bytes(access_point.encryption_type.code, num_bytes=1)) + self.set_parameter("ID", bytearray(access_point.ssid, "utf8")) + self.set_parameter("EE", utils.int_to_bytes(access_point.encryption_type.code, num_bytes=1)) if password is not None and access_point.encryption_type != WiFiEncryptionType.NONE: - self.set_parameter(ATStringCommand.PK.command, bytearray(password, "utf8")) + self.set_parameter("PK", bytearray(password, "utf8")) # Wait for the module to connect to the access point. dead_line = time.time() + self.__ap_timeout while time.time() < dead_line: time.sleep(0.1) # Get the association indication value of the module. - status = self.get_parameter(ATStringCommand.AI.command) + status = self.get_parameter("AI") if status is None or len(status) < 1: continue if status[0] == 0: @@ -5919,7 +4878,7 @@ def connect_by_ssid(self, ssid, password=None): if access_point is None: raise XBeeException("Couldn't find any access point with SSID '%s'." % ssid) - return self.connect_by_ap(access_point, password=password) + return self.connect_by_ap(access_point, password) def disconnect(self): """ @@ -5945,12 +4904,12 @@ def disconnect(self): | :meth:`.WiFiDevice.get_access_point_timeout` | :meth:`.WiFiDevice.set_access_point_timeout` """ - self.execute_command(ATStringCommand.NR.command) + self.execute_command("NR") dead_line = time.time() + self.__ap_timeout while time.time() < dead_line: time.sleep(0.1) # Get the association indication value of the module. - status = self.get_parameter(ATStringCommand.AI.command) + status = self.get_parameter("AI") if status is None or len(status) < 1: continue if status[0] == 0x23: @@ -6018,8 +4977,7 @@ def __parse_access_point(self, ap_data): signal_quality = self.__get_signal_quality(version, signal_strength) ssid = (ap_data[index:]).decode("utf8") - return AccessPoint(ssid, WiFiEncryptionType.get(encryption_type), channel=channel, - signal_quality=signal_quality) + return AccessPoint(ssid, WiFiEncryptionType.get(encryption_type), channel, signal_quality) @staticmethod def __get_signal_quality(wifi_version, signal_strength): @@ -6097,7 +5055,7 @@ def get_ip_addressing_mode(self): | :meth:`.WiFiDevice.set_ip_addressing_mode` | :class:`.IPAddressingMode` """ - return IPAddressingMode.get(utils.bytes_to_int(self.get_parameter(ATStringCommand.MA.command))) + return IPAddressingMode.get(utils.bytes_to_int(self.get_parameter("MA"))) def set_ip_addressing_mode(self, mode): """ @@ -6113,7 +5071,7 @@ def set_ip_addressing_mode(self, mode): | :meth:`.WiFiDevice.get_ip_addressing_mode` | :class:`.IPAddressingMode` """ - self.set_parameter(ATStringCommand.MA.command, utils.int_to_bytes(mode.code, num_bytes=1)) + self.set_parameter("MA", utils.int_to_bytes(mode.code, num_bytes=1)) def set_ip_address(self, ip_address): """ @@ -6133,7 +5091,7 @@ def set_ip_address(self, ip_address): | :meth:`.WiFiDevice.get_mask_address` | :class:`ipaddress.IPv4Address` """ - self.set_parameter(ATStringCommand.MY.command, ip_address.packed) + self.set_parameter("MY", ip_address.packed) def get_mask_address(self): """ @@ -6149,7 +5107,7 @@ def get_mask_address(self): | :meth:`.WiFiDevice.set_mask_address` | :class:`ipaddress.IPv4Address` """ - return IPv4Address(bytes(self.get_parameter(ATStringCommand.MK.command))) + return IPv4Address(bytes(self.get_parameter("MK"))) def set_mask_address(self, mask_address): """ @@ -6169,7 +5127,7 @@ def set_mask_address(self, mask_address): | :meth:`.WiFiDevice.get_mask_address` | :class:`ipaddress.IPv4Address` """ - self.set_parameter(ATStringCommand.MK.command, mask_address.packed) + self.set_parameter("MK", mask_address.packed) def get_gateway_address(self): """ @@ -6185,7 +5143,7 @@ def get_gateway_address(self): | :meth:`.WiFiDevice.set_dns_address` | :class:`ipaddress.IPv4Address` """ - return IPv4Address(bytes(self.get_parameter(ATStringCommand.GW.command))) + return IPv4Address(bytes(self.get_parameter("GW"))) def set_gateway_address(self, gateway_address): """ @@ -6205,7 +5163,7 @@ def set_gateway_address(self, gateway_address): | :meth:`.WiFiDevice.get_gateway_address` | :class:`ipaddress.IPv4Address` """ - self.set_parameter(ATStringCommand.GW.command, gateway_address.packed) + self.set_parameter("GW", gateway_address.packed) def get_dns_address(self): """ @@ -6221,7 +5179,7 @@ def get_dns_address(self): | :meth:`.WiFiDevice.set_dns_address` | :class:`ipaddress.IPv4Address` """ - return IPv4Address(bytes(self.get_parameter(ATStringCommand.NS.command))) + return IPv4Address(bytes(self.get_parameter("NS"))) def set_dns_address(self, dns_address): """ @@ -6237,7 +5195,7 @@ def set_dns_address(self, dns_address): | :meth:`.WiFiDevice.get_dns_address` | :class:`ipaddress.IPv4Address` """ - self.set_parameter(ATStringCommand.NS.command, dns_address.packed) + self.set_parameter("NS", dns_address.packed) class RemoteXBeeDevice(AbstractXBeeDevice): @@ -6261,26 +5219,22 @@ def __init__(self, local_xbee_device, x64bit_addr=XBee64BitAddress.UNKNOWN_ADDRE | :class:`XBee64BitAddress` | :class:`XBeeDevice` """ - super().__init__(local_xbee_device=local_xbee_device, - comm_iface=local_xbee_device.comm_iface) + super(RemoteXBeeDevice, self).__init__(local_xbee_device=local_xbee_device, + serial_port=local_xbee_device.serial_port) self._local_xbee_device = local_xbee_device self._64bit_addr = x64bit_addr - if not x64bit_addr: - self._64bit_addr = XBee64BitAddress.UNKNOWN_ADDRESS self._16bit_addr = x16bit_addr - if not x16bit_addr: - self._16bit_addr = XBee16BitAddress.UNKNOWN_ADDRESS self._node_id = node_id - def get_parameter(self, parameter, parameter_value=None): + def get_parameter(self, parameter): """ Override. .. seealso:: | :meth:`.AbstractXBeeDevice.get_parameter` """ - return super().get_parameter(parameter, parameter_value=parameter_value) + return super(RemoteXBeeDevice, self).get_parameter(parameter) def set_parameter(self, parameter, value): """ @@ -6289,7 +5243,7 @@ def set_parameter(self, parameter, value): .. seealso:: | :meth:`.AbstractXBeeDevice.set_parameter` """ - super().set_parameter(parameter, value) + super(RemoteXBeeDevice, self).set_parameter(parameter, value) def is_remote(self): """ @@ -6309,7 +5263,7 @@ def reset(self): """ # Send reset command. try: - response = self._send_at_command(ATCommand(ATStringCommand.FR.command)) + response = self._send_at_command(ATCommand("FR")) except TimeoutException as te: # Remote 802.15.4 devices do not respond to the AT command. if self._local_xbee_device.get_protocol() == XBeeProtocol.RAW_802_15_4: @@ -6354,18 +5308,9 @@ def get_serial_port(self): """ return self._local_xbee_device.serial_port - def get_comm_iface(self): - """ - Returns the communication interface of the local XBee device associated to the remote one. - - Returns: - :class:`XBeeCommunicationInterface`: the communication interface of the local XBee device associated to - the remote one. - - .. seealso:: - | :class:`XBeeCommunicationInterface` - """ - return self._local_xbee_device.comm_iface + def __str__(self): + node_id = "" if self.get_node_id() is None else self.get_node_id() + return "%s - %s" % (self.get_64bit_addr(), node_id) class RemoteRaw802Device(RemoteXBeeDevice): @@ -6385,6 +5330,7 @@ def __init__(self, local_xbee_device, x64bit_addr=None, x16bit_addr=None, node_i Raises: XBeeException: if the protocol of ``local_xbee_device`` is invalid. + All exceptions raised by :class:`.RemoteXBeeDevice` constructor. .. seealso:: | :class:`RemoteXBeeDevice` @@ -6395,8 +5341,7 @@ def __init__(self, local_xbee_device, x64bit_addr=None, x16bit_addr=None, node_i if local_xbee_device.get_protocol() != XBeeProtocol.RAW_802_15_4: raise XBeeException("Invalid protocol.") - super().__init__(local_xbee_device, x64bit_addr=x64bit_addr, x16bit_addr=x16bit_addr, - node_id=node_id) + super(RemoteRaw802Device, self).__init__(local_xbee_device, x64bit_addr, x16bit_addr, node_id=node_id) def get_protocol(self): """ @@ -6429,7 +5374,7 @@ def get_ai_status(self): .. seealso:: | :meth:`.AbstractXBeeDevice._get_ai_status` """ - return super()._get_ai_status() + return super(RemoteRaw802Device, self)._get_ai_status() class RemoteDigiMeshDevice(RemoteXBeeDevice): @@ -6448,6 +5393,7 @@ def __init__(self, local_xbee_device, x64bit_addr=None, node_id=None): Raises: XBeeException: if the protocol of ``local_xbee_device`` is invalid. + All exceptions raised by :class:`.RemoteXBeeDevice` constructor. .. seealso:: | :class:`RemoteXBeeDevice` @@ -6457,8 +5403,7 @@ def __init__(self, local_xbee_device, x64bit_addr=None, node_id=None): if local_xbee_device.get_protocol() != XBeeProtocol.DIGI_MESH: raise XBeeException("Invalid protocol.") - super().__init__(local_xbee_device, x64bit_addr=x64bit_addr, - x16bit_addr=XBee16BitAddress.UNKNOWN_ADDRESS, node_id=node_id) + super(RemoteDigiMeshDevice, self).__init__(local_xbee_device, x64bit_addr, None, node_id) def get_protocol(self): """ @@ -6469,43 +5414,6 @@ def get_protocol(self): """ return XBeeProtocol.DIGI_MESH - def get_neighbors(self, neighbor_callback=None, process_finished_callback=None, timeout=None): - """ - Returns the neighbors of this XBee. If ``neighbor_callback`` is not defined, the process - blocks during the specified timeout. - - Args: - neighbor_callback (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. - - process_finished_callback (Function, optional, default=``None``): method to execute when - the process finishes. Receives two arguments: - - * The XBee device 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:`com.digi.models.zdo.Neighbor` when ``neighbor_callback`` is - 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_callback=neighbor_callback, - process_finished_callback=process_finished_callback, - timeout=timeout if timeout else NeighborFinder.DEFAULT_TIMEOUT) - class RemoteDigiPointDevice(RemoteXBeeDevice): """ @@ -6523,6 +5431,7 @@ def __init__(self, local_xbee_device, x64bit_addr=None, node_id=None): Raises: XBeeException: if the protocol of ``local_xbee_device`` is invalid. + All exceptions raised by :class:`.RemoteXBeeDevice` constructor. .. seealso:: | :class:`RemoteXBeeDevice` @@ -6532,8 +5441,7 @@ def __init__(self, local_xbee_device, x64bit_addr=None, node_id=None): if local_xbee_device.get_protocol() != XBeeProtocol.DIGI_POINT: raise XBeeException("Invalid protocol.") - super().__init__(local_xbee_device, x64bit_addr=x64bit_addr, - x16bit_addr=XBee16BitAddress.UNKNOWN_ADDRESS, node_id=node_id) + super(RemoteDigiPointDevice, self).__init__(local_xbee_device, x64bit_addr, None, node_id) def get_protocol(self): """ @@ -6562,6 +5470,7 @@ def __init__(self, local_xbee_device, x64bit_addr=None, x16bit_addr=None, node_i Raises: XBeeException: if the protocol of ``local_xbee_device`` is invalid. + All exceptions raised by :class:`.RemoteXBeeDevice` constructor. .. seealso:: | :class:`RemoteXBeeDevice` @@ -6572,8 +5481,7 @@ def __init__(self, local_xbee_device, x64bit_addr=None, x16bit_addr=None, node_i if local_xbee_device.get_protocol() != XBeeProtocol.ZIGBEE: raise XBeeException("Invalid protocol.") - super().__init__(local_xbee_device, x64bit_addr=x64bit_addr, x16bit_addr=x16bit_addr, - node_id=node_id) + super(RemoteZigBeeDevice, self).__init__(local_xbee_device, x64bit_addr, x16bit_addr, node_id) def get_protocol(self): """ @@ -6591,7 +5499,7 @@ def get_ai_status(self): .. seealso:: | :meth:`.AbstractXBeeDevice._get_ai_status` """ - return super()._get_ai_status() + return super(RemoteZigBeeDevice, self)._get_ai_status() def force_disassociate(self): """ @@ -6600,80 +5508,7 @@ def force_disassociate(self): .. seealso:: | :meth:`.AbstractXBeeDevice._force_disassociate` """ - super()._force_disassociate() - - def get_routes(self, route_callback=None, process_finished_callback=None, timeout=None): - """ - Returns the routes of this XBee. If ``route_callback`` is not defined, the process blocks - until the complete routing table is read. - - Args: - route_callback (Function, optional, default=``None``): method called when a new route - is received. Receives two arguments: - - * The XBee that owns this new route. - * The new route. - - process_finished_callback (Function, optional, default=``None``): method to execute when - the process finishes. Receives two arguments: - - * The XBee device that executed the ZDO command. - * A list with the discovered routes. - * An error message if something went wrong. - - timeout (Float, optional, default=``RouteTableReader.DEFAULT_TIMEOUT``): The ZDO command - timeout in seconds. - Returns: - List: List of :class:`com.digi.models.zdo.Route` when ``route_callback`` is defined, - ``None`` otherwise (in this case routes are received in the callback). - - Raises: - OperationNotSupportedException: If XBee protocol is not Zigbee or Smart Energy. - - .. seealso:: - | :class:`com.digi.models.zdo.Route` - """ - from digi.xbee.models.zdo import RouteTableReader - return super()._get_routes(route_callback=route_callback, - process_finished_callback=process_finished_callback, - timeout=timeout if timeout else RouteTableReader.DEFAULT_TIMEOUT) - - def get_neighbors(self, neighbor_callback=None, process_finished_callback=None, timeout=None): - """ - Returns the neighbors of this XBee. If ``neighbor_callback`` is not defined, the process - blocks until the complete neighbor table is read. - - Args: - neighbor_callback (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. - - process_finished_callback (Function, optional, default=``None``): method to execute when - the process finishes. Receives two arguments: - - * The XBee device that executed the ZDO command. - * A list with the discovered neighbors. - * An error message if something went wrong. - - timeout (Float, optional, default=``NeighborTableReader.DEFAULT_TIMEOUT``): The ZDO - command timeout in seconds. - Returns: - List: List of :class:`com.digi.models.zdo.Neighbor` when ``neighbor_callback`` is - defined, ``None`` otherwise (in this case neighbors are received in the callback). - - Raises: - OperationNotSupportedException: If XBee protocol is not Zigbee or Smart Energy. - - .. seealso:: - | :class:`com.digi.models.zdo.Neighbor` - """ - from digi.xbee.models.zdo import NeighborTableReader - return super()._get_neighbors( - neighbor_callback=neighbor_callback, - process_finished_callback=process_finished_callback, - timeout=timeout if timeout else NeighborTableReader.DEFAULT_TIMEOUT) + super(RemoteZigBeeDevice, self)._force_disassociate() class XBeeNetwork(object): @@ -6704,61 +5539,7 @@ class XBeeNetwork(object): __DIGI_MESH_SLEEP_TIMEOUT_CORRECTION = 0.1 # DigiMesh with sleep support. __DIGI_POINT_TIMEOUT_CORRECTION = 8 - __TIME_FOR_NEW_NODES_IN_FIFO = 1 # seconds - __TIME_WHILE_FINISH_PREVIOUS_PROCESS = 1 # seconds, for 'Cascade' mode - - __DEFAULT_QUEUE_MAX_SIZE = 300 - """ - Default max. size that the queue has. - """ - - __MAX_SCAN_COUNTER = 10000 - - DEFAULT_TIME_BETWEEN_SCANS = 10 # seconds - """ - Default time (in seconds) to wait before starting a new scan. - """ - - MIN_TIME_BETWEEN_SCANS = 0 # seconds - """ - Low limit for the time (in seconds) to wait before starting a new scan. - """ - - MAX_TIME_BETWEEN_SCANS = 300 # seconds - """ - High limit for the time (in seconds) to wait before starting a new scan. - """ - - DEFAULT_TIME_BETWEEN_REQUESTS = 5 # seconds - """ - Default time (in seconds) to wait between node neighbors requests. - """ - - MIN_TIME_BETWEEN_REQUESTS = 0 # seconds - """ - Low limit for the time (in seconds) to wait between node neighbors requests. - """ - - MAX_TIME_BETWEEN_REQUESTS = 300 # seconds - """ - High limit for the time (in seconds) to wait between node neighbors requests. - """ - - SCAN_TIL_CANCEL = 0 # 0 for not stopping - """ - The neighbor discovery process continues until is manually stopped. - """ - - __NT_LIMITS = { - XBeeProtocol.RAW_802_15_4: (0x1 / 10, 0xFC / 10), # 0.1, 25.2 seconds - XBeeProtocol.ZIGBEE: (0x20 / 10, 0xFF / 10), # 3.2, 25.5 seconds - XBeeProtocol.DIGI_MESH: (0x20 / 10, 0x2EE0 / 10) # 3.2, 5788.8 seconds - } - - _log = logging.getLogger("XBeeNetwork") - """ - Logger. - """ + __NODE_DISCOVERY_COMMAND = "ND" def __init__(self, xbee_device): """ @@ -6773,124 +5554,41 @@ def __init__(self, xbee_device): if xbee_device is None: raise ValueError("Local XBee device cannot be None") - self._local_xbee = xbee_device + self.__xbee_device = xbee_device self.__devices_list = [] self.__last_search_dev_list = [] self.__lock = threading.Lock() self.__discovering = False - self._stop_event = threading.Event() - self.__discover_result = None - self.__network_modified = NetworkModified() self.__device_discovered = DeviceDiscovered() self.__device_discovery_finished = DiscoveryProcessFinished() self.__discovery_thread = None self.__sought_device_id = None self.__discovered_device = None - # FIFO to store the nodes to ask for their neighbors - self.__nodes_queue = Queue(self.__class__.__DEFAULT_QUEUE_MAX_SIZE) - - # List with the MAC address (string format) of the still active request processes - self.__active_processes = [] - - # Last date of a sent request. Used to wait certain time between requests: - # * In 'Flood' mode to satisfy the minimum time to wait between node requests - # * For 'Cascade', the time to wait is applied after finishing the previous request - # process - self.__last_request_date = 0 - - self.__scan_counter = 0 - - self.__connections = [] - self.__conn_lock = threading.Lock() - - # Dictionary to store the route and node discovery processes per node, so they can be - # stop when required. - # The dictionary uses as key the 64-bit address string representation (to be thread-safe) - self.__nd_processes = {} - - self.__mode = NeighborDiscoveryMode.CASCADE - self.__stop_scan = 1 - self.__rm_not_discovered_in_last_scan = False - self.__time_bw_scans = self.__class__.DEFAULT_TIME_BETWEEN_SCANS - self.__time_bw_nodes = self.__class__.DEFAULT_TIME_BETWEEN_REQUESTS - self._node_timeout = None - - self.__saved_nt = None - - def __increment_scan_counter(self): - """ - Increments (by one) the scan counter. - """ - self.__scan_counter += 1 - if self.__scan_counter > self.__class__.__MAX_SCAN_COUNTER: - self.__scan_counter = 0 - - @property - def scan_counter(self): - """ - Returns the scan counter. - - Returns: - Integer: The scan counter. - """ - return self.__scan_counter - - def start_discovery_process(self, deep=False, n_deep_scans=1): + def start_discovery_process(self): """ Starts the discovery process. This method is not blocking. - - This process can discover node neighbors and connections, or only nodes: - - * Deep discovery: Network nodes and connections between them (including quality) - are discovered. - - The discovery process will be running the number of scans configured in - ``n_deep_scans``. A scan is considered the process of discovering the full network. - If there are more than one number of scans configured, after finishing one another - is started, until ``n_deep_scans`` is satisfied. - - See :meth:`~.XBeeNetwork.set_deep_discovery_options` to establish the way the - network discovery process is performed. - - * No deep discovery: Only network nodes are discovered. - - The discovery process will be running until the configured timeout expires or, in - case of 802.15.4, until the 'end' packet is read. - - It may be that, after the timeout expires, there are devices that continue sending - discovery packets to this XBee device. In this case, these devices will not be - added to the network. - - In 802.15.4, both (deep and no deep discovery) are the same and none discover the node - connections or their quality. The difference is the possibility of running more than - one scan using a deep discovery. - - Args: - deep (Boolean, optional, default=``False``): ``True`` for a deep network scan, - looking for neighbors and their connections, ``False`` otherwise. - n_deep_scans (Integer, optional, default=1): Number of scans to perform before - automatically stopping the discovery process. - :const:`SCAN_TIL_CANCEL` means the process will not be automatically - stopped. Only applicable if ``deep=True``. + + The discovery process will be running until the configured + timeout expires or, in case of 802.15.4, until the 'end' packet + is read. + + It may be that, after the timeout expires, there are devices + that continue sending discovery packets to this XBee device. In this + case, these devices will not be added to the network. .. seealso:: | :meth:`.XBeeNetwork.add_device_discovered_callback` | :meth:`.XBeeNetwork.add_discovery_process_finished_callback` | :meth:`.XBeeNetwork.del_device_discovered_callback` | :meth:`.XBeeNetwork.del_discovery_process_finished_callback` - | :meth:`.XBeeNetwork.get_deep_discovery_options` - | :meth:`.XBeeNetwork.set_deep_discovery_options` """ with self.__lock: if self.__discovering: return - if deep: - self.__stop_scan = n_deep_scans - - self.__discovery_thread = threading.Thread(target=self.__discover_devices_and_notify_callbacks, - kwargs={'discover_network': deep}, daemon=True) + self.__discovery_thread = threading.Thread(target=self.__discover_devices_and_notify_callbacks) + self.__discovering = True self.__discovery_thread.start() def stop_discovery_process(self): @@ -6902,11 +5600,9 @@ def stop_discovery_process(self): any parameter during the discovery process you will receive a timeout exception. """ - self._stop_event.set() - - if self.__discovery_thread and self.__discovering: - self.__discovery_thread.join() - self.__discovery_thread = None + if self.__discovering: + with self.__lock: + self.__discovering = False def discover_device(self, node_id): """ @@ -6920,19 +5616,17 @@ def discover_device(self, node_id): :class:`.RemoteXBeeDevice`: the discovered remote XBee device with the given identifier, ``None`` if the timeout expires and the device was not found. """ - self._stop_event.clear() - try: with self.__lock: self.__sought_device_id = node_id - self.__discover_devices(node_id=node_id) + self.__discover_devices(node_id) finally: with self.__lock: self.__sought_device_id = None remote = self.__discovered_device self.__discovered_device = None if remote is not None: - self.__add_remote(remote, NetworkEventReason.DISCOVERED) + self.add_remote(remote) return remote def discover_devices(self, device_id_list): @@ -6998,23 +5692,6 @@ def get_number_devices(self): """ return len(self.__devices_list) - def add_network_modified_callback(self, callback): - """ - Adds a callback for the event :class:`digi.xbee.reader.NetworkModified`. - - Args: - callback (Function): the callback. Receives one argument. - - * The event type as a :class:`.NetworkEventType` - * The reason of the event as a :class:`.NetworkEventReason` - * The node added, updated or removed from the network as a :class:`.XBeeDevice` or - :class:`.RemoteXBeeDevice`. - - .. seealso:: - | :meth:`.XBeeNetwork.del_network_modified_callback` - """ - self.__network_modified += callback - def add_device_discovered_callback(self, callback): """ Adds a callback for the event :class:`.DeviceDiscovered`. @@ -7047,18 +5724,6 @@ def add_discovery_process_finished_callback(self, callback): """ self.__device_discovery_finished += callback - def del_network_modified_callback(self, callback): - """ - Deletes a callback for the callback list of :class:`digi.xbee.reader.NetworkModified`. - - Args: - callback (Function): the callback to delete. - - .. seealso:: - | :meth:`.XBeeNetwork.add_network_modified_callback` - """ - self.__network_modified -= callback - def del_device_discovered_callback(self, callback): """ Deletes a callback for the callback list of :class:`.DeviceDiscovered` event. @@ -7098,12 +5763,7 @@ def clear(self): Removes all the remote XBee devices from the network. """ with self.__lock: - self.__devices_list.clear() - - with self.__conn_lock: - self.__connections.clear() - - self.__network_modified(NetworkEventType.CLEAR, NetworkEventReason.MANUAL, None) + self.__devices_list = [] def get_discovery_options(self): """ @@ -7119,7 +5779,7 @@ def get_discovery_options(self): method only checks the cached value of the operating mode. ATCommandException: if the response is not as expected. """ - return self._local_xbee.get_parameter(ATStringCommand.NO.command) + return self.__xbee_device.get_parameter("NO") def set_discovery_options(self, options): """ @@ -7142,52 +5802,8 @@ def set_discovery_options(self, options): if options is None: raise ValueError("Options cannot be None") - value = DiscoveryOptions.calculate_discovery_value(self._local_xbee.get_protocol(), options) - self._local_xbee.set_parameter(ATStringCommand.NO.command, utils.int_to_bytes(value)) - - def get_deep_discovery_options(self): - """ - Returns the deep discovery process options. - - Returns: - Tuple: (:class:`digi.xbee.models.mode.NeighborDiscoveryMode`, Boolean): Tuple containing: - - mode (:class:`digi.xbee.models.mode.NeighborDiscoveryMode`): Neighbor discovery - mode, the way to perform the network discovery process. - - remove_nodes (Boolean): ``True`` to remove nodes from the network if they were - not discovered in the last scan, ``False`` otherwise. - - .. seealso:: - | :class:`digi.xbee.models.mode.NeighborDiscoveryMode` - | :meth:`.XBeeNetwork.set_deep_discovery_timeouts` - | :meth:`.XBeeNetwork.start_discovery_process` - """ - return self.__mode, self.__rm_not_discovered_in_last_scan - - def set_deep_discovery_options(self, deep_mode=NeighborDiscoveryMode.CASCADE, - del_not_discovered_nodes_in_last_scan=False): - """ - Configures the deep discovery options with the given values. - These options are only applicable for "deep" discovery - (see :meth:`~.XBeeNetwork.start_discovery_process`) - - Args: - deep_mode (:class:`.NeighborDiscoveryMode`, optional, default=`NeighborDiscoveryMode.CASCADE`): Neighbor - discovery mode, the way to perform the network discovery process. - del_not_discovered_nodes_in_last_scan (Boolean, optional, default=``False``): ``True`` to - remove nodes from the network if they were not discovered in the last scan, - - .. seealso:: - | :class:`digi.xbee.models.mode.NeighborDiscoveryMode` - | :meth:`.XBeeNetwork.get_deep_discovery_timeouts` - | :meth:`.XBeeNetwork.start_discovery_process` - """ - if deep_mode is not None and not isinstance(deep_mode, NeighborDiscoveryMode): - raise TypeError("Deep mode must be NeighborDiscoveryMode not {!r}".format( - deep_mode.__class__.__name__)) - - self.__mode = deep_mode if deep_mode is not None else NeighborDiscoveryMode.CASCADE - - self.__rm_not_discovered_in_last_scan = del_not_discovered_nodes_in_last_scan + value = DiscoveryOptions.calculate_discovery_value(self.__xbee_device.get_protocol(), options) + self.__xbee_device.set_parameter("NO", utils.int_to_bytes(value)) def get_discovery_timeout(self): """ @@ -7203,170 +5819,41 @@ def get_discovery_timeout(self): method only checks the cached value of the operating mode. ATCommandException: if the response is not as expected. """ - tout = self._local_xbee.get_parameter(ATStringCommand.NT.command) + tout = self.__xbee_device.get_parameter("NT") return utils.bytes_to_int(tout) / 10.0 def set_discovery_timeout(self, discovery_timeout): """ Sets the discovery network timeout. - + Args: discovery_timeout (Float): timeout in seconds. - + Raises: - TimeoutException: if the response is not received before the read - timeout expires. + TimeoutException: if the response is not received before the read timeout expires. XBeeException: if the XBee device's serial port is closed. - 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. + 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. ATCommandException: if the response is not as expected. - ValueError: if ``discovery_timeout`` is not between the allowed - minimum and maximum values. + ValueError: if ``discovery_timeout`` is not between 0x20 and 0xFF """ - min_nt, max_nt = self.__get_nt_limits() - if discovery_timeout < min_nt or discovery_timeout > max_nt: - raise ValueError("Value must be between %f and %f seconds" - % (min_nt, max_nt)) - discovery_timeout *= 10 # seconds to 100ms + if discovery_timeout < 0x20 or discovery_timeout > 0xFF: + raise ValueError("Value must be between 3.2 and 25.5") timeout = bytearray([int(discovery_timeout)]) - self._local_xbee.set_parameter(ATStringCommand.NT.command, timeout) - - def get_deep_discovery_timeouts(self): - """ - Gets deep discovery network timeouts. - These timeouts are only applicable for "deep" discovery - (see :meth:`~.XBeeNetwork.start_discovery_process`) - - Returns: - Tuple (Float, Float, Float): Tuple containing: - - node_timeout (Float): Maximum duration in seconds of the discovery process per node. - This used to find a node neighbors. This timeout is highly dependent on the - nature of the network: - - .. hlist:: - :columns: 1 - - * It should be greater than the highest NT (Node Discovery Timeout) of your - network - * and include enough time to let the message propagate depending on the - sleep cycle of your devices. - - - time_bw_nodes (Float): Time to wait between node neighbors requests. - Use this setting not to saturate your network: - - .. hlist:: - :columns: 1 - - * For 'Cascade' the number of seconds to wait after completion of the - neighbor discovery process of the previous node. - * For 'Flood' the minimum time to wait between each node's neighbor - requests. - - - time_bw_scans (Float): Time to wait before starting a new network scan. - - .. seealso:: - | :meth:`.XBeeNetwork.set_deep_discovery_timeouts` - | :meth:`.XBeeNetwork.start_discovery_process` - """ - return self._node_timeout, self.__time_bw_nodes, self.__time_bw_scans - - def set_deep_discovery_timeouts(self, node_timeout=None, time_bw_requests=None, time_bw_scans=None): - """ - Sets deep discovery network timeouts. - These timeouts are only applicable for "deep" discovery - (see :meth:`~.XBeeNetwork.start_discovery_process`) - - node_timeout (Float, optional, default=`None`): - Maximum duration in seconds of the discovery process used to find neighbors of a node. - If ``None`` already configured timeouts are used. - - time_bw_requests (Float, optional, default=`DEFAULT_TIME_BETWEEN_REQUESTS`): Time to wait - between node neighbors requests. - It must be between :const:`MIN_TIME_BETWEEN_REQUESTS` and - :const:`MAX_TIME_BETWEEN_REQUESTS` seconds inclusive. Use this setting not to saturate - your network: - - .. hlist:: - :columns: 1 - - * For 'Cascade' the number of seconds to wait after completion of the - neighbor discovery process of the previous node. - * For 'Flood' the minimum time to wait between each node's neighbor requests. - - time_bw_scans (Float, optional, default=`DEFAULT_TIME_BETWEEN_SCANS`): Time to wait - before starting a new network scan. - It must be between :const:`MIN_TIME_BETWEEN_SCANS` and :const:`MAX_TIME_BETWEEN_SCANS` - seconds inclusive. - - Raises: - ValueError: if ``node_timeout``, ``time_bw_requests`` or ``time_bw_scans`` are not - between their corresponding limits. - - .. seealso:: - | :meth:`.XBeeNetwork.get_deep_discovery_timeouts` - | :meth:`.XBeeNetwork.start_discovery_process` - """ - min_nt, max_nt = self.__get_nt_limits() - - if node_timeout and (node_timeout < min_nt or node_timeout > max_nt): - raise ValueError("Node timeout must be between %f and %f seconds" - % (min_nt, max_nt)) - - if time_bw_requests \ - and (time_bw_requests < self.__class__.MIN_TIME_BETWEEN_REQUESTS - or time_bw_requests > self.__class__.MAX_TIME_BETWEEN_REQUESTS): - raise ValueError("Time between neighbor requests must be between %d and %d" % - (self.__class__.MIN_TIME_BETWEEN_REQUESTS, - self.__class__.MAX_TIME_BETWEEN_REQUESTS)) - - if time_bw_scans \ - and (time_bw_scans < self.__class__.MIN_TIME_BETWEEN_SCANS - or time_bw_scans > self.__class__.MAX_TIME_BETWEEN_SCANS): - raise ValueError("Time between scans must be between %d and %d" % - (self.__class__.MIN_TIME_BETWEEN_SCANS, - self.__class__.MAX_TIME_BETWEEN_SCANS)) - - self._node_timeout = node_timeout - self.__time_bw_nodes = time_bw_requests if time_bw_requests is not None \ - else self.__class__.DEFAULT_TIME_BETWEEN_REQUESTS - self.__time_bw_scans = time_bw_scans if time_bw_scans is not None \ - else self.__class__.DEFAULT_TIME_BETWEEN_SCANS - - def __get_nt_limits(self): - """ - Returns a tuple with the minimum and maximum values for the 'NT' - value depending on the protocol. - - Returns: - Tuple (Float, Float): Minimum value in seconds, maximum value in - seconds. - """ - protocol = self._local_xbee.get_protocol() - if protocol in [XBeeProtocol.RAW_802_15_4, XBeeProtocol.ZIGBEE, - XBeeProtocol.DIGI_MESH]: - return self.__class__.__NT_LIMITS[protocol] - - # Calculate the minimum of the min values and the maximum of max values - min_nt = self.__class__.__NT_LIMITS[XBeeProtocol.RAW_802_15_4][0] - max_nt = self.__class__.__NT_LIMITS[XBeeProtocol.RAW_802_15_4][1] - for protocol in self.__class__.__NT_LIMITS: - min_nt = min(min_nt, self.__class__.__NT_LIMITS[protocol][0]) - max_nt = max(max_nt, self.__class__.__NT_LIMITS[protocol][1]) - - return min_nt, max_nt + self.__xbee_device.set_parameter("NT", timeout) def get_device_by_64(self, x64bit_addr): """ - Returns the XBee in the network whose 64-bit address matches the given one. + Returns the remote device already contained in the network whose 64-bit + address matches the given one. Args: x64bit_addr (:class:`XBee64BitAddress`): The 64-bit address of the device to be retrieved. Returns: - :class:`.AbstractXBeeDevice`: the XBee device in the network or ``None`` if it is not found. + :class:`.RemoteXBeeDevice`: the remote XBee device in the network or ``None`` if it is not found. Raises: ValueError: if ``x64bit_addr`` is ``None`` or unknown. @@ -7376,9 +5863,6 @@ def get_device_by_64(self, x64bit_addr): if x64bit_addr == XBee64BitAddress.UNKNOWN_ADDRESS: raise ValueError("64-bit address cannot be unknown") - if self._local_xbee.get_64bit_addr() == x64bit_addr: - return self._local_xbee - with self.__lock: for device in self.__devices_list: if device.get_64bit_addr() is not None and device.get_64bit_addr() == x64bit_addr: @@ -7388,29 +5872,27 @@ def get_device_by_64(self, x64bit_addr): def get_device_by_16(self, x16bit_addr): """ - Returns the XBee in the network whose 16-bit address matches the given one. + Returns the remote device already contained in the network whose 16-bit + address matches the given one. Args: x16bit_addr (:class:`XBee16BitAddress`): The 16-bit address of the device to be retrieved. Returns: - :class:`.AbstractXBeeDevice`: the XBee device in the network or ``None`` if it is not found. + :class:`.RemoteXBeeDevice`: the remote XBee device in the network or ``None`` if it is not found. Raises: ValueError: if ``x16bit_addr`` is ``None`` or unknown. """ - if self._local_xbee.get_protocol() == XBeeProtocol.DIGI_MESH: + if self.__xbee_device.get_protocol() == XBeeProtocol.DIGI_MESH: raise ValueError("DigiMesh protocol does not support 16-bit addressing") - if self._local_xbee.get_protocol() == XBeeProtocol.DIGI_POINT: + if self.__xbee_device.get_protocol() == XBeeProtocol.DIGI_POINT: raise ValueError("Point-to-Multipoint protocol does not support 16-bit addressing") if x16bit_addr is None: raise ValueError("16-bit address cannot be None") if x16bit_addr == XBee16BitAddress.UNKNOWN_ADDRESS: raise ValueError("16-bit address cannot be unknown") - if self._local_xbee.get_16bit_addr() == x16bit_addr: - return self._local_xbee - with self.__lock: for device in self.__devices_list: if device.get_16bit_addr() is not None and device.get_16bit_addr() == x16bit_addr: @@ -7420,13 +5902,14 @@ def get_device_by_16(self, x16bit_addr): def get_device_by_node_id(self, node_id): """ - Returns the XBee in the network whose node identifier matches the given one. + Returns the remote device already contained in the network whose node identifier + matches the given one. Args: node_id (String): The node identifier of the device to be retrieved. Returns: - :class:`.AbstractXBeeDevice`: the XBee device in the network or ``None`` if it is not found. + :class:`.RemoteXBeeDevice`: the remote XBee device in the network or ``None`` if it is not found. Raises: ValueError: if ``node_id`` is ``None``. @@ -7434,9 +5917,6 @@ def get_device_by_node_id(self, node_id): if node_id is None: raise ValueError("Node ID cannot be None") - if self._local_xbee.get_node_id() == node_id: - return self._local_xbee - with self.__lock: for device in self.__devices_list: if device.get_node_id() is not None and device.get_node_id() == node_id: @@ -7456,14 +5936,11 @@ def add_if_not_exist(self, x64bit_addr=None, x16bit_addr=None, node_id=None): node_id (String, optional): the node identifier of the XBee device. Optional. Returns: - :class:`.AbstractXBeeDevice`: the remote XBee device with the updated parameters. If the XBee device + :class:`.RemoteXBeeDevice`: the remote XBee device with the updated parameters. If the XBee device was not in the list yet, this method returns the given XBee device without changes. """ - if x64bit_addr == self._local_xbee.get_64bit_addr(): - return self._local_xbee - - return self.__add_remote_from_attr(NetworkEventReason.MANUAL, x64bit_addr=x64bit_addr, - x16bit_addr=x16bit_addr, node_id=node_id) + remote = RemoteXBeeDevice(self.__xbee_device, x64bit_addr, x16bit_addr, node_id) + return self.add_remote(remote) def add_remote(self, remote_xbee_device): """ @@ -7479,102 +5956,13 @@ def add_remote(self, remote_xbee_device): :class:`.RemoteXBeeDevice`: the provided XBee device with the updated parameters. If the XBee device was not in the list yet, this method returns it without changes. """ - return self.__add_remote(remote_xbee_device, NetworkEventReason.MANUAL) - - def __add_remote(self, remote_xbee, reason): - """ - Adds the provided remote XBee device to the network if it is not contained yet. - - If the XBee device is already contained in the network, its data will be updated with the - parameters of the XBee device that are not ``None``. - - Args: - remote_xbee (:class:`.RemoteXBeeDevice`): The remote XBee device to add to the network. - reason (:class:`.NetworkEventReason`): The reason of the addition to the network. - - Returns: - :class:`.AbstractXBeeDevice`: the provided XBee with the updated parameters. If the - XBee was not in the list yet, this method returns it without changes. - """ - found = None - - # Check if it is the local device - if not remote_xbee.is_remote() or remote_xbee == remote_xbee.get_local_xbee_device(): - found = remote_xbee if not remote_xbee.is_remote() else remote_xbee.get_local_xbee_device() - # Look for the remote in the network list - else: - x64 = remote_xbee.get_64bit_addr() - if not x64 or x64 == XBee64BitAddress.UNKNOWN_ADDRESS: - # Ask for the 64-bit address - try: - sh = remote_xbee.get_parameter(ATStringCommand.SH.command) - sl = remote_xbee.get_parameter(ATStringCommand.SL.command) - remote_xbee._64bit_addr = XBee64BitAddress(sh + sl) - except XBeeException as e: - self._log.debug("Error while trying to get 64-bit address of XBee (%s): %s" - % (remote_xbee.get_16bit_addr(), str(e))) - - with self.__lock: - if remote_xbee in self.__devices_list: - found = self.__devices_list[self.__devices_list.index(remote_xbee)] - - if found: - already_in_scan = False - if reason in (NetworkEventReason.NEIGHBOR, NetworkEventReason.DISCOVERED): - already_in_scan = found.scan_counter == self.__scan_counter - if not already_in_scan: - found._scan_counter = self.__scan_counter - - if found.update_device_data_from(remote_xbee): - self.__network_modified(NetworkEventType.UPDATE, reason, node=found) - found._reachable = True - - return None if already_in_scan else found - - if reason in (NetworkEventReason.NEIGHBOR, NetworkEventReason.DISCOVERED): - remote_xbee._scan_counter = self.__scan_counter - - self.__devices_list.append(remote_xbee) - self.__network_modified(NetworkEventType.ADD, reason, node=remote_xbee) - - return remote_xbee - - def __add_remote_from_attr(self, reason, x64bit_addr=None, x16bit_addr=None, node_id=None, - role=Role.UNKNOWN): - """ - Creates a new XBee using the provided data and adds it to the network if it is not - included yet. - - If the XBee is already in the network, its data will be updated with the parameters of the - XBee that are not ``None``. - - Args: - reason (:class:`.NetworkEventReason`): The reason of the addition to the network. - x64bit_addr (:class:`digi.xbee.models.address.XBee64BitAddress`, optional, - default=``None``): The 64-bit address of the remote XBee. - x16bit_addr (:class:`digi.xbee.models.address.XBee16BitAddress`, optional, - default=``None``): The 16-bit address of the remote XBee. - node_id (String, optional, default=``None``): The node identifier of the remote XBee. - role (:class:`digi.xbee.models.protocol.Role`, optional, default=``Role.UNKNOWN``): - The role of the remote XBee - - Returns: - :class:`.RemoteXBeeDevice`: the remote XBee device generated from the provided data if - the data provided is correct and the XBee device's protocol is valid, ``None`` - otherwise. - - .. seealso:: - | :class:`.NetworkEventReason` - | :class:`digi.xbee.models.address.XBee16BitAddress` - | :class:`digi.xbee.models.address.XBee64BitAddress` - | :class:`digi.xbee.models.protocol.Role` - - Returns: - :class:`.AbstractXBeeDevice`: The created XBee with the updated parameters. - """ - return self.__add_remote( - self.__create_remote(x64bit_addr=x64bit_addr, x16bit_addr=x16bit_addr, - node_id=node_id, role=role), reason) + with self.__lock: + for local_xbee in self.__devices_list: + if local_xbee == remote_xbee_device: + local_xbee.update_device_data_from(remote_xbee_device) + return local_xbee + self.__devices_list.append(remote_xbee_device) + return remote_xbee_device def add_remotes(self, remote_xbee_devices): """ @@ -7589,60 +5977,17 @@ def add_remotes(self, remote_xbee_devices): for rem in remote_xbee_devices: self.add_remote(rem) - def _remove_device(self, remote_xbee_device, reason, force=True): - """ - Removes the provided remote XBee device from the network. - - Args: - remote_xbee_device (:class:`.RemoteXBeeDevice`): the remote XBee device to be removed - from the list. - reason (:class:`.NetworkEventReason`): The reason of the removal from the network. - force (Boolean, optional, default=``True``): ``True`` to force the deletion of the node, - ``False`` otherwise. - """ - if not remote_xbee_device: - return - - with self.__lock: - if remote_xbee_device not in self.__devices_list: - return - - i = self.__devices_list.index(remote_xbee_device) - found_node = self.__devices_list[i] - if force: - self.__devices_list.remove(found_node) - if found_node.reachable: - self.__network_modified(NetworkEventType.DEL, reason, node=remote_xbee_device) - - node_b_connections = self.__get_connections_for_node_a_b(found_node, node_a=False) - - # Remove connections with this node as one of its ends - self.__remove_node_connections(found_node, only_as_node_a=True, force=force) - - if not force: - # Only for Zigbee, mark non-reachable end devices - if remote_xbee_device.get_protocol() \ - in (XBeeProtocol.ZIGBEE, XBeeProtocol.SMART_ENERGY) \ - and remote_xbee_device.get_role() == Role.END_DEVICE: - for c in node_b_connections: - # End devices do not have connections from them (not asking for their route - # and neighbor tables), but if their parent is not reachable they are not either - if not c.node_a.reachable: - self._set_node_reachable(remote_xbee_device, False) - break - def remove_device(self, remote_xbee_device): """ Removes the provided remote XBee device from the network. - + Args: - remote_xbee_device (:class:`.RemoteXBeeDevice`): the remote XBee device to be removed - from the list. - + remote_xbee_device (:class:`.RemoteXBeeDevice`): the remote XBee device to be removed from the list. + Raises: ValueError: if the provided :class:`.RemoteXBeeDevice` is not in the network. """ - self._remove_device(remote_xbee_device, NetworkEventReason.MANUAL, force=True) + self.__devices_list.remove(remote_xbee_device) def get_discovery_callbacks(self): """ @@ -7665,50 +6010,21 @@ def discovery_gen_callback(xbee_packet): nd_id = self.__check_nd_packet(xbee_packet) if nd_id == XBeeNetwork.ND_PACKET_FINISH: # if it's a ND finish signal, stop wait for packets - self.__discover_result = xbee_packet.status - self._stop_event.set() + with self.__lock: + self.__discovering = xbee_packet.status != ATCommandStatus.OK elif nd_id == XBeeNetwork.ND_PACKET_REMOTE: - x16, x64, n_id, role = self.__get_data_for_remote(xbee_packet.command_value) - remote = self.__create_remote(x64bit_addr=x64, x16bit_addr=x16, node_id=n_id, - role=role) + remote = self.__create_remote(xbee_packet.command_value) + # if remote was created successfully and it is not int the + # XBee device list, add it and notify callbacks. if remote is not None: - # If remote was successfully created and it is not in the XBee list, add it - # and notify callbacks. - - # Do not add a connection to the same node (the local one) - if remote == self._local_xbee: - return - - self._log.debug(" o Discovered neighbor of %s: %s" - % (self._local_xbee, remote)) - - node = self.__add_remote(remote, NetworkEventReason.DISCOVERED) - if not node: - # Node already in network for this scan - node = self.get_device_by_64(remote.get_64bit_addr()) - self._log.debug( - " - NODE already in network in this scan (scan: %d) %s" - % (self.__scan_counter, node)) - else: - # Do not add the neighbors to the FIFO, because - # only the local device performs an 'ND' - self._log.debug(" - Added to network (scan: %d)" % node.scan_counter) - - # Add connection (there is not RSSI info for a 'ND') - from digi.xbee.models.zdo import RouteStatus - if self.__add_connection(Connection( - self._local_xbee, node, LinkQuality.UNKNOWN, LinkQuality.UNKNOWN, - RouteStatus.ACTIVE, RouteStatus.ACTIVE)): - self._log.debug(" - Added connection: %s >>> %s" - % (self._local_xbee, node)) - else: - self._log.debug( - " - CONNECTION already in network in this scan (scan: %d) %s >>> %s" - % (self.__scan_counter, self._local_xbee, node)) - - # Always add the XBee device to the last discovered devices list: - self.__last_search_dev_list.append(node) - self.__device_discovered(node) + # if remote was created successfully and it is not int the + # XBee device list, add it and notify callbacks. + if remote not in self.__devices_list: + with self.__lock: + self.__devices_list.append(remote) + # always add the XBee device to the last discovered devices list: + self.__last_search_dev_list.append(remote) + self.__device_discovered(remote) def discovery_spec_callback(xbee_packet): """ @@ -7721,22 +6037,17 @@ def discovery_spec_callback(xbee_packet): nd_id = self.__check_nd_packet(xbee_packet) if nd_id == XBeeNetwork.ND_PACKET_FINISH: # if it's a ND finish signal, stop wait for packets - self.__discover_result = xbee_packet.status if xbee_packet.status == ATCommandStatus.OK: with self.__lock: self.__sought_device_id = None - self.stop_discovery_process() elif nd_id == XBeeNetwork.ND_PACKET_REMOTE: # if it is not a finish signal, it contains info about a remote XBee device. - x16, x64, n_id, role = self.__get_data_for_remote(xbee_packet.command_value) - remote = self.__create_remote(x64bit_addr=x64, x16bit_addr=x16, node_id=n_id, - role=role) + remote = self.__create_remote(xbee_packet.command_value) # if it's the sought XBee device, put it in the proper variable. if self.__sought_device_id == remote.get_node_id(): with self.__lock: self.__discovered_device = remote self.__sought_device_id = None - self.stop_discovery_process() return discovery_gen_callback, discovery_spec_callback @@ -7765,7 +6076,7 @@ def __check_nd_packet(xbee_packet): * :attr:`.XBeeNetwork.ND_PACKET_REMOTE`: if ``xbee_packet`` has info about a remote XBee device. """ if (xbee_packet.get_frame_type() == ApiFrameType.AT_COMMAND_RESPONSE and - xbee_packet.command == ATStringCommand.ND.command): + xbee_packet.command == XBeeNetwork.__NODE_DISCOVERY_COMMAND): if xbee_packet.command_value is None or len(xbee_packet.command_value) == 0: return XBeeNetwork.ND_PACKET_FINISH else: @@ -7773,476 +6084,60 @@ def __check_nd_packet(xbee_packet): else: return None - def __discover_devices_and_notify_callbacks(self, discover_network=False): + def __discover_devices_and_notify_callbacks(self): """ Blocking method. Performs a discovery operation, waits until it finish (timeout or 'end' packet for 802.15.4), and notifies callbacks. - - Args: - discover_network (Boolean, optional, default=``False``): ``True`` to discovery the - full network with connections between nodes, ``False`` to only discover nodes - with a single 'ND'. """ - self._stop_event.clear() - self.__discovering = True - self.__discover_result = None - - if not discover_network: - status = self.__discover_devices() - self._discovery_done(self.__active_processes) - else: - status = self._discover_full_network() - - self.__device_discovery_finished(status if status else NetworkDiscoveryStatus.SUCCESS) + self.__discover_devices() + self.__device_discovery_finished(NetworkDiscoveryStatus.SUCCESS) - def _discover_full_network(self): + def __discover_devices(self, node_id=None): """ - Discovers the network of the local node. + Blocking method. Performs a device discovery in the network and waits until it finish (timeout or 'end' + packet for 802.15.4) - Returns: - :class:`digi.xbee.models.status.NetworkDiscoveryStatus`: Resulting status of - the discovery process. + Args: + node_id (String, optional): node identifier of the remote XBee device to discover. Optional. """ try: - code = self.__init_discovery(self.__nodes_queue) - if code != NetworkDiscoveryStatus.SUCCESS: - return code - - while self.__stop_scan == self.__class__.SCAN_TIL_CANCEL \ - or self.__scan_counter < self.__stop_scan: - - if self.__scan_counter > 0: - self._log.debug("") - self._log.debug(" [*] Waiting %f seconds to start next scan" - % self.__time_bw_scans) - code = self.__wait_checking(self.__time_bw_scans) - if code != NetworkDiscoveryStatus.SUCCESS: - return code - - self.__init_scan() - - # Check for cancel - if self._stop_event.is_set(): - return NetworkDiscoveryStatus.CANCEL - - code = self.__discover_network(self.__nodes_queue, self.__active_processes, - self._node_timeout) - if code != NetworkDiscoveryStatus.SUCCESS: - return code - - # Purge network - self.__purge(force=self.__rm_not_discovered_in_last_scan) - - return code - finally: - self._discovery_done(self.__active_processes) - - def __init_discovery(self, nodes_queue): - """ - Initializes the discovery process before starting any network scan: - * Initializes the scan counter - * Removes all the nodes from the FIFO - * Prepares the local XBee to start the process - - Args: - nodes_queue (:class:`queue.Queue`): FIFO where the nodes to discover their - neighbors are stored. - - Returns: - :class:`digi.xbee.models.status.NetworkDiscoveryStatus`: Resulting status of - the discovery process. - """ - # Initialize the scan number - self.__scan_counter = 0 - - # Initialize all nodes/connections scan counter - with self.__lock: - for xb in self.__devices_list: - xb._scan_counter = self.__scan_counter - - with self.__conn_lock: - for c in self.__connections: - c.scan_counter_a2b = self.__scan_counter - c.scan_counter_b2a = self.__scan_counter - - # Clear the nodes FIFO - while not nodes_queue.empty(): - try: - nodes_queue.get(block=False) - except Empty: - continue - nodes_queue.task_done() - - self.__purge(force=self.__rm_not_discovered_in_last_scan) - - try: - self._prepare_network_discovery() - except XBeeException as e: - self._log.debug(str(e)) - return NetworkDiscoveryStatus.ERROR_GENERAL - - return NetworkDiscoveryStatus.SUCCESS - - def _prepare_network_discovery(self): - """ - Performs XBee configuration before starting the full network discovery. This saves the - current NT value and sets it to the ``self._node_timeout``. - """ - self._log.debug("[*] Preconfiguring %s" % ATStringCommand.NT.command) - - try: - - self.__saved_nt = self.get_discovery_timeout() - - if self._node_timeout is None: - self._node_timeout = self.__saved_nt - - # Do not configure NT if it is already - if self.__saved_nt == self._node_timeout: - self.__saved_nt = None - return - - self.set_discovery_timeout(self._node_timeout) - except XBeeException as e: - raise XBeeException("Could not prepare XBee for network discovery: " + str(e)) - - def __init_scan(self): - """ - Prepares a network to start a new scan. - """ - self.__increment_scan_counter() - self._local_xbee._scan_counter = self.__scan_counter - - self.__last_request_date = 0 - - self._log.debug("\n") - self._log.debug("================================") - self._log.debug(" %d network scan" % self.__scan_counter) - self._log.debug(" Mode: %s (%d)" % (self.__mode.description, self.__mode.code)) - self._log.debug(" Stop after scan: %d" % self.__stop_scan) - self._log.debug(" Timeout/node: %s" % self._node_timeout - if self._node_timeout is not None else "-") - self._log.debug("================================") - - def __discover_network(self, nodes_queue, active_processes, node_timeout): - """ - Discovers the network of the local node. - - Args: - nodes_queue (:class:`queue.Queue`): FIFO where the nodes to discover their - neighbors are stored. - active_processes (List): The list of active discovery processes. - node_timeout (Float): Timeout to discover neighbors for each node (seconds). - - Returns: - :class:`digi.xbee.models.status.NetworkDiscoveryStatus`: Resulting status of - the discovery process. - """ - code = NetworkDiscoveryStatus.SUCCESS - - # Add local node to the FIFO - nodes_queue.put(self._local_xbee) - - while True: - # Wait to have items in the nodes FIFO while some nodes are being discovered, - # because them can fill the FIFO with new nodes to ask - while nodes_queue.empty() and active_processes: - self._log.debug("") - self._log.debug( - " [*] Waiting for more nodes to request or finishing active processes (%d)\n" - % (len(active_processes))) - [self._log.debug(" Waiting for %s" % p) for p in active_processes] - - code = self.__wait_checking(self.__class__.__TIME_FOR_NEW_NODES_IN_FIFO) - if code == NetworkDiscoveryStatus.CANCEL: - return code - - # Check if there are more nodes in the FIFO - while not nodes_queue.empty(): - # Process the next node - code = self.__discover_next_node_neighbors(nodes_queue, active_processes, - node_timeout) - # Only stop if the process has been cancelled, otherwise continue with the - # next node - if code == NetworkDiscoveryStatus.CANCEL: - return code - - # For cascade, wait until previous processes finish - if self.__mode == NeighborDiscoveryMode.CASCADE: - while active_processes: - code = self.__wait_checking( - self.__class__.__TIME_WHILE_FINISH_PREVIOUS_PROCESS) - if code == NetworkDiscoveryStatus.CANCEL: - return code - - # Check if all processes finish - if not active_processes: - self._check_not_discovered_nodes(self.__devices_list, nodes_queue) - if not nodes_queue.empty(): - continue - break - - return code - - def __discover_next_node_neighbors(self, nodes_queue, active_processes, node_timeout): - """ - Discovers the neighbors of the next node in the given FIFO. - - Args: - nodes_queue (:class:`queue.Queue`): FIFO where the nodes to discover their - neighbors are stored. - active_processes (List): The list of active discovery processes. - node_timeout (Float): Timeout to discover neighbors for each node (seconds). - - Returns: - :class:`digi.xbee.models.status.NetworkDiscoveryStatus`: Resulting status of the - neighbor discovery process. - """ - code = NetworkDiscoveryStatus.SUCCESS - - # Check for cancel - if self._stop_event.is_set(): - return NetworkDiscoveryStatus.CANCEL - - requester = nodes_queue.get() - - # Wait between nodes but not for the local one - if requester != self._local_xbee: - time_to_wait = self.__time_bw_nodes - if self.__mode != NeighborDiscoveryMode.CASCADE: - time_to_wait = self.__time_bw_nodes + (time.time() - self.__last_request_date) - self._log.debug("") - self._log.debug(" [*] Waiting %f before sending next request to %s" - % (time_to_wait if time_to_wait > 0 else 0.0, requester)) - code = self.__wait_checking(time_to_wait) - if code != NetworkDiscoveryStatus.SUCCESS: - return code - - # If the previous request finished, discover node neighbors - if not requester.get_64bit_addr() in active_processes: - self._log.debug("") - self._log.debug(" [*] Discovering neighbors of %s" % requester) - self.__last_request_date = time.time() - return self._discover_neighbors(requester, nodes_queue, active_processes, node_timeout) - - self._log.debug("") - self._log.debug(" [*] Previous request for %s did not finish..." % requester) - nodes_queue.put(requester) - - return code - - 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 discover their - neighbors are stored. - """ - # Check for nodes in the network not discovered in this scan and ensure - # they are reachable by directly asking them for its NI - for n in devices_list: - if n.scan_counter != self.__scan_counter: - self._log.debug(" [*] Checking not discovered node %s... (scan %d)" - % (n, self.__scan_counter)) - n._scan_counter = self.__scan_counter - try: - n.get_parameter(ATStringCommand.NI.command) - n._reachable = True - # Update also the connection - from digi.xbee.models.zdo import RouteStatus - if self.__add_connection(Connection( - self._local_xbee, n, LinkQuality.UNKNOWN, LinkQuality.UNKNOWN, - RouteStatus.ACTIVE, RouteStatus.ACTIVE)): - self._log.debug(" - Added connection: %s >>> %s" - % (self._local_xbee, n)) - except XBeeException: - n._reachable = False - self._log.debug(" - Reachable: %s (scan %d)" - % (n._reachable, self.__scan_counter)) - - 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`): The XBee to discover its neighbors. - nodes_queue (:class:`queue.Queue`): FIFO where the nodes to discover their - neighbors are stored. - active_processes (List): The list of active discovery processes. - node_timeout (Float): Timeout to discover neighbors (seconds). - - Returns: - :class:`digi.xbee.models.status.NetworkDiscoveryStatus`: Resulting status of - the neighbor discovery process. - """ - code = self.__discover_devices() - if not code: - return NetworkDiscoveryStatus.SUCCESS - - # Do not stop scans unless the process is cancel, not because of an error. - if code is NetworkDiscoveryStatus.ERROR_NET_DISCOVER: - self._stop_event.clear() - return NetworkDiscoveryStatus.SUCCESS - - return code - - 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) - - Args: - node_id (String, optional): node identifier of the remote XBee device to discover. - - Returns: - :class:`digi.xbee.models.status.NetworkDiscoveryStatus`: The error code, ``None`` - if finished successfully. - """ - self.__active_processes.append(str(self._local_xbee.get_64bit_addr())) - - try: - timeout = self.__calculate_timeout() + init_time = time.time() + + # In 802.15.4 devices, the discovery finishes when the 'end' command + # is received, so it's not necessary to calculate the timeout. + # This also applies to S1B devices working in compatibility mode. + is_802_compatible = self.__is_802_compatible() + timeout = 0 + if not is_802_compatible: + timeout = self.__calculate_timeout() # send "ND" async - self._local_xbee.send_packet(ATCommPacket(self._local_xbee.get_next_frame_id(), - ATStringCommand.ND.command, - parameter=None if node_id is None else bytearray(node_id, 'utf8')), - sync=False) - - self.__nd_processes.update({str(self._local_xbee.get_64bit_addr()): self}) - - op_times_out = not self._stop_event.wait(timeout) - - self.__nd_processes.pop(str(self._local_xbee), None) - - if op_times_out or not self.__discover_result or self.__discover_result == ATCommandStatus.OK: - err_code = None - elif self.__discover_result and self.__discover_result != ATCommandStatus.OK: - err_code = NetworkDiscoveryStatus.ERROR_NET_DISCOVER - else: - err_code = NetworkDiscoveryStatus.CANCEL - - self._node_discovery_process_finished(self._local_xbee, code=err_code, - error=err_code.description if err_code else None) - - return err_code - except Exception as e: - self._local_xbee.log.exception(e) - - def _node_discovery_process_finished(self, requester, code=None, error=None): - """ - Notifies the discovery process has finished successfully for ``requester`` node. - - Args: - requester (:class:`.AbstractXBeeDevice`): The XBee that requests the discovery process. - code (:class:`digi.xbee.models.status.NetworkDiscoveryStatus`): The error code for the process. - error (String): The error message if there was one, ``None`` if successfully finished. - """ - # Purge the connections of the node - self._log.debug("") - self._log.debug(" [*] Purging node connections of %s" % requester) - purged = self.__purge_node_connections(requester, force=self.__rm_not_discovered_in_last_scan) - if self.__rm_not_discovered_in_last_scan: - for c in purged: - self._log.debug(" o Removed connection: %s" % c) - - # Remove the discovery process from the active processes list - self.__active_processes.remove(str(requester.get_64bit_addr())) + self.__xbee_device.send_packet(ATCommPacket(self.__xbee_device.get_next_frame_id(), + "ND", + None if node_id is None else bytearray(node_id, 'utf8')), + False) + + if not is_802_compatible: + # If XBee device is not 802.15.4, wait until timeout expires. + while self.__discovering or self.__sought_device_id is not None: + if (time.time() - init_time) > timeout: + with self.__lock: + self.__discovering = False + break + time.sleep(0.1) - if code and code not in (NetworkDiscoveryStatus.SUCCESS, NetworkDiscoveryStatus.CANCEL) or error: - self._log.debug("[***** ERROR] During neighbors scan of %s" % requester) - if error: - self._log.debug(" %s" % error) else: - self._log.debug(" %s" % code.description) - - self._handle_special_errors(requester, error) - else: - self._log.debug("[!!!] Process finishes for %s - Remaining: %d" - % (requester, len(self.__active_processes))) - - def _handle_special_errors(self, requester, error): - """ - Process some special errors. - - Args: - requester (:class:`.AbstractXBeeDevice`): The XBee that requests the discovery process. - error (String): The error message. - """ - if not error.endswith(TransmitStatus.NOT_JOINED_NETWORK.description) \ - and not error.endswith(TransmitStatus.ADDRESS_NOT_FOUND.description) \ - and not error.endswith("FN command answer not received"): - return - - # The node is not found so it is not reachable - self._log.debug(" o [***] Non-reachable: %s -> ERROR %s" % (requester, error)) - - # Do not remove any node here, although the preference is configured to so - # Do it at the end of the scan... - no_reachables = [requester] - - requester._scan_counter = self.__scan_counter - - # Get the children nodes to mark them as non-reachable - conn_list = self.__get_connections_for_node_a_b(requester, node_a=True) - for c in conn_list: - child = c.node_b - # Child node already discovered in this scan - if not child or child.scan_counter == self.__scan_counter: - continue - # Only the connection with the requester node joins the child to the network - # so it is not reachable - if len(self.get_node_connections(child)) <= 1: - no_reachables.append(child) - - # If the node has more than one connection, we cannot be sure if it will - # be discovered by other devices later since the scan did not end - - # Mark as non-reachable - [self._set_node_reachable(n, False) for n in no_reachables] - - def _discovery_done(self, active_processes): - """ - Discovery process has finished either due to cancellation, successful completion, or failure. - - Args: - active_processes (List): The list of active discovery processes. - """ - self._restore_network() - - if self.__nd_processes: - copy = active_processes[:] - for p in copy: - nd = self.__nd_processes.get(p) - if not nd: - continue - nd.stop_discovery_process() - while p in self.__nd_processes: + # If XBee device is 802.15.4, wait until the 'end' xbee_message arrive. + # "__discovering" will be assigned as False by the callback + # when this receive that 'end' xbee_message. If this xbee_message never arrives, + # stop when timeout expires. + while self.__discovering or self.__sought_device_id is not None: time.sleep(0.1) - - self.__nd_processes.clear() - self.__active_processes.clear() - - with self.__lock: - self.__discovering = False - - def _restore_network(self): - """ - Performs XBee configuration after the full network discovery. - This restores the previous NT value. - """ - if self.__saved_nt is None: - return - - self._log.debug("[*] Postconfiguring %s" % ATStringCommand.NT.command) - try: - self.set_discovery_timeout(self.__saved_nt) - except XBeeException as e: - self._error = "Could not restore XBee after network discovery: " + str(e) - - self.__saved_nt = None + except Exception as e: + self.__xbee_device.log.exception(e) + finally: + with self.__lock: + self.__discovering = False def __is_802_compatible(self): """ @@ -8254,11 +6149,11 @@ def __is_802_compatible(self): 802.15.4 device or S1B in compatibility mode, ``False`` otherwise. """ - if self._local_xbee.get_protocol() != XBeeProtocol.RAW_802_15_4: + if self.__xbee_device.get_protocol() != XBeeProtocol.RAW_802_15_4: return False param = None try: - param = self._local_xbee.get_parameter(ATStringCommand.C8.command) + param = self.__xbee_device.get_parameter("C8") except ATCommandException: pass if param is None or param[0] & 0x2 == 2: @@ -8280,7 +6175,7 @@ def __calculate_timeout(self): """ # Read the maximum discovery timeout (N?) try: - discovery_timeout = utils.bytes_to_int(self._local_xbee.get_parameter(ATStringCommand.N_QUESTION.command)) / 1000 + discovery_timeout = utils.bytes_to_int(self.__xbee_device.get_parameter("N?")) / 1000 except XBeeException: discovery_timeout = None @@ -8288,81 +6183,59 @@ def __calculate_timeout(self): if discovery_timeout is None: # Read the XBee device timeout (NT). try: - discovery_timeout = utils.bytes_to_int(self._local_xbee.get_parameter(ATStringCommand.NT.command)) / 10 + discovery_timeout = utils.bytes_to_int(self.__xbee_device.get_parameter("NT")) / 10 except XBeeException as xe: discovery_timeout = XBeeNetwork.__DEFAULT_DISCOVERY_TIMEOUT - self._local_xbee.log.exception(xe) + self.__xbee_device.log.exception(xe) self.__device_discovery_finished(NetworkDiscoveryStatus.ERROR_READ_TIMEOUT) # In DigiMesh/DigiPoint the network discovery timeout is NT + the # network propagation time. It means that if the user sends an AT # command just after NT ms, s/he will receive a timeout exception. - if self._local_xbee.get_protocol() == XBeeProtocol.DIGI_MESH: + if self.__xbee_device.get_protocol() == XBeeProtocol.DIGI_MESH: discovery_timeout += XBeeNetwork.__DIGI_MESH_TIMEOUT_CORRECTION - elif self._local_xbee.get_protocol() == XBeeProtocol.DIGI_POINT: + elif self.__xbee_device.get_protocol() == XBeeProtocol.DIGI_POINT: discovery_timeout += XBeeNetwork.__DIGI_POINT_TIMEOUT_CORRECTION - if self._local_xbee.get_protocol() == XBeeProtocol.DIGI_MESH: + if self.__xbee_device.get_protocol() == XBeeProtocol.DIGI_MESH: # If the module is 'Sleep support', wait another discovery cycle. try: - if utils.bytes_to_int(self._local_xbee.get_parameter( - ATStringCommand.SM.command)) == 7: + if utils.bytes_to_int(self.__xbee_device.get_parameter("SM")) == 7: discovery_timeout += discovery_timeout + \ (discovery_timeout * XBeeNetwork.__DIGI_MESH_SLEEP_TIMEOUT_CORRECTION) except XBeeException as xe: - self._local_xbee.log.exception(xe) - elif self.__is_802_compatible(): - discovery_timeout += 2 # Give some time to receive the ND finish packet + self.__xbee_device.log.exception(xe) return discovery_timeout - def __create_remote(self, x64bit_addr=XBee64BitAddress.UNKNOWN_ADDRESS, - x16bit_addr=XBee16BitAddress.UNKNOWN_ADDRESS, node_id=None, role=Role.UNKNOWN): + def __create_remote(self, discovery_data): """ Creates and returns a :class:`.RemoteXBeeDevice` from the provided data, if the data contains the required information and in the required format. - - Args: - x64bit_addr (:class:`digi.xbee.models.address.XBee64BitAddress`, optional, - default=``XBee64BitAddress.UNKNOWN_ADDRESS``): The 64-bit address of the remote XBee. - x16bit_addr (:class:`digi.xbee.models.address.XBee16BitAddress`, optional, - default=``XBee16BitAddress.UNKNOWN_ADDRESS``): The 16-bit address of the remote XBee. - node_id (String, optional, default=``None``): The node identifier of the remote XBee. - role (:class:`digi.xbee.models.protocol.Role`, optional, default=``Role.UNKNOWN``): - The role of the remote XBee - + Returns: - :class:`.RemoteXBeeDevice`: the remote XBee device generated from the provided data if - the data provided is correct and the XBee device's protocol is valid, ``None`` - otherwise. + :class:`.RemoteXBeeDevice`: the remote XBee device generated from the provided data if the data + provided is correct and the XBee device's protocol is valid, ``None`` otherwise. .. seealso:: - | :class:`digi.xbee.models.address.XBee16BitAddress` - | :class:`digi.xbee.models.address.XBee64BitAddress` - | :class:`digi.xbee.models.protocol.Role` + | :meth:`.XBeeNetwork.__get_data_for_remote` """ - if not x64bit_addr and not x16bit_addr: + if discovery_data is None: return None - - p = self._local_xbee.get_protocol() + p = self.__xbee_device.get_protocol() + x16bit_addr, x64bit_addr, node_id = self.__get_data_for_remote(discovery_data) if p == XBeeProtocol.ZIGBEE: - xb = RemoteZigBeeDevice(self._local_xbee, x64bit_addr=x64bit_addr, - x16bit_addr=x16bit_addr, node_id=node_id) + return RemoteZigBeeDevice(self.__xbee_device, x64bit_addr, x16bit_addr, node_id) elif p == XBeeProtocol.DIGI_MESH: - xb = RemoteDigiMeshDevice(self._local_xbee, x64bit_addr=x64bit_addr, node_id=node_id) + return RemoteDigiMeshDevice(self.__xbee_device, x64bit_addr, node_id) elif p == XBeeProtocol.DIGI_POINT: - xb = RemoteDigiPointDevice(self._local_xbee, x64bit_addr=x64bit_addr, node_id=node_id) + return RemoteDigiPointDevice(self.__xbee_device, x64bit_addr, node_id) elif p == XBeeProtocol.RAW_802_15_4: - xb = RemoteRaw802Device(self._local_xbee, x64bit_addr=x64bit_addr, - x16bit_addr=x16bit_addr, node_id=node_id) + return RemoteRaw802Device(self.__xbee_device, x64bit_addr, x16bit_addr, node_id) else: - xb = RemoteXBeeDevice(self._local_xbee, x64bit_addr=x64bit_addr, - x16bit_addr=x16bit_addr, node_id=node_id) - - xb._role = role - return xb + return RemoteXBeeDevice(self.__xbee_device, x64bit_addr, x16bit_addr, node_id) def __get_data_for_remote(self, data): """ @@ -8376,8 +6249,7 @@ def __get_data_for_remote(self, data): Returns: Tuple (:class:`.XBee16BitAddress`, :class:`.XBee64BitAddress`, Bytearray): remote device information """ - role = Role.UNKNOWN - if self._local_xbee.get_protocol() == XBeeProtocol.RAW_802_15_4: + if self.__xbee_device.get_protocol() == XBeeProtocol.RAW_802_15_4: # node ID starts at 11 if protocol is not 802.15.4: # 802.15.4 adds a byte of info between 64bit address and XBee device ID, avoid it: i = 11 @@ -8392,977 +6264,83 @@ def __get_data_for_remote(self, data): while data[i] != 0x00: i += 1 node_id = data[10:i] - i += 1 - # parent address: next 2 bytes from i - parent_addr = data[i:i+2] - i += 2 - # role is the next byte - role = Role.get(utils.bytes_to_int(data[i:i+1])) - return XBee16BitAddress(data[0:2]), XBee64BitAddress(data[2:10]), node_id.decode(), role - - def _set_node_reachable(self, node, reachable): - """ - Configures a node as reachable or non-reachable. It throws an network event if this - attribute changes. - If the value of the attribute was already ``reachable`` value, this method does nothing. - - Args: - node (:class:`.AbstractXBeeDevice`): The node to configure. - reachable (Boolean): ``True`` to configure as reachable, ``False`` otherwise. - """ - if node._reachable != reachable: - node._reachable = reachable - self.__network_modified(NetworkEventType.UPDATE, NetworkEventReason.NEIGHBOR, node=node) - - def get_connections(self): - """ - Returns a copy of the XBee connections. - - If a new connection is added to the list after the execution of this method, - this connection is not added to the list returned by this method. - - Returns: - List: A copy of the list of :class:`.Connection` for the network. - """ - with self.__conn_lock: - return self.__connections.copy() - - def get_node_connections(self, node): - """ - Returns the network connections with one of their ends ``node``. - - If a new connection is added to the list after the execution of this method, - this connection is not added to the list returned by this method. - - Returns: - List: List of :class:`.Connection` with ``node`` end. - """ - connections = [] - with self.__conn_lock: - for c in self.__connections: - if c.node_a == node or c.node_b == node: - connections.append(c) - - return connections - - def __get_connections_for_node_a_b(self, node, node_a=True): - """ - Returns the network connections with the given node as "node_a" or "node_b". + return XBee16BitAddress(data[0:2]), XBee64BitAddress(data[2:10]), node_id.decode() - Args: - node (:class:`.AbstractXBeeDevice`): The node to get the connections. - node_a (Boolean, optional, default=``True``): ``True`` to get connections where - the given node is "node_a", ``False`` to get those where the node is "node_b". - Returns: - List: List of :class:`.Connection` with ``node`` as "node_a" end. - """ - connections = [] - with self.__conn_lock: - for c in self.__connections: - if (node_a and c.node_a == node) \ - or (not node_a and c.node_b == node): - connections.append(c) +class ZigBeeNetwork(XBeeNetwork): + """ + This class represents a ZigBee network. - return connections + The network allows the discovery of remote devices in the same network + as the local one and stores them. + """ - def __get_connection(self, node_a, node_b): + def __init__(self, device): """ - Returns the connection with ends node_a and node_b. + Class constructor. Instantiates a new ``ZigBeeNetwork``. Args: - node_a (:class:`.AbstractXBeeDevice`): "node_a" end of the connection. - node_b (:class:`.AbstractXBeeDevice`): "node_b" end of the connection. - - Returns: - :class:`.Connection`: The connection with ends ``node_a`` and ``node_b``, - ``None`` if not found. + device (:class:`.ZigBeeDevice`): the local ZigBee device to get the network from. Raises: - ValueError: If ``node_a`` or ``node_b`` are ``None`` - """ - if not node_a: - raise ValueError("Node A cannot be None") - if not node_b: - raise ValueError("Node B cannot be None") - - c = Connection(node_a, node_b) - - with self.__conn_lock: - if c not in self.__connections: - return None - - index = self.__connections.index(c) - - return self.__connections[index] - - def __append_connection(self, connection): - """ - Adds a new connection to the network. - - Args: - connection (:class:`.Connection`): The connection to be added. - - Raise: - ValueError: If ``connection`` is ``None``. - """ - if not connection: - raise ValueError("Connection cannot be None") - - with self.__conn_lock: - self.__connections.append(connection) - - def __del_connection(self, connection): - """ - Removes a connection from the network. - - Args: - connection (:class:`.Connection`): The connection to be removed. - - Raise: - ValueError: If ``connection`` is ``None``. - """ - if not connection: - raise ValueError("Connection cannot be None") - - with self.__conn_lock: - if connection in self.__connections: - self.__connections.remove(connection) - - def __add_connection(self, connection): - """ - Adds a new connection to the network. The end nodes of this connection are added - to the network if they do not exist. - - Args: - connection (class:`.Connection`): The connection to add. - - Returns: - Boolean: ``True`` if the connection was successfully added, ``False`` - if the connection was already added. - """ - if not connection: - return False - - node_a = self.get_device_by_64(connection.node_a.get_64bit_addr()) - node_b = self.get_device_by_64(connection.node_b.get_64bit_addr()) - - # Add the source node - if not node_a: - node_a = self.__add_remote(connection.node_a, NetworkEventReason.NEIGHBOR) - - if not node_b: - node_b = self.__add_remote(connection.node_b, NetworkEventReason.NEIGHBOR) - - if not node_a or not node_b: - return False - - # Check if the connection already exists a -> b or b -> a - c_ab = self.__get_connection(node_a, node_b) - c_ba = self.__get_connection(node_b, node_a) - - # If none of them exist, add it - if not c_ab and not c_ba: - connection.scan_counter_a2b = self.__scan_counter - self.__append_connection(connection) - return True - - # If the connection exists, update its data - if c_ab: - if c_ab.scan_counter_a2b != self.__scan_counter: - c_ab.lq_a2b = connection.lq_a2b - c_ab.status_a2b = connection.status_a2b - c_ab.scan_counter_a2b = self.__scan_counter - return True - - elif c_ba: - if c_ba.scan_counter_b2a != self.__scan_counter: - c_ba.lq_b2a = connection.lq_a2b - c_ba.status_b2a = connection.status_a2b - c_ba.scan_counter_b2a = self.__scan_counter - return True - - return False - - def __remove_node_connections(self, node, only_as_node_a=False, force=False): - """ - Remove the connections that has node as one of its ends. - - Args: - node (:class:`.AbstractXBeeDevice`): The node whose connections are being removed. - only_as_node_a (Boolean, optional, default=``False``): Only remove those connections - with the provided node as "node_a". - force (Boolean, optional, default=``True``): ``True`` to force the - deletion of the connections, ``False`` otherwise. - - Returns: - List: List of removed connections. - """ - if only_as_node_a: - node_conn = self.__get_connections_for_node_a_b(node, node_a=True) - else: - node_conn = self.get_node_connections(node) - - with self.__conn_lock: - c_removed = [len(node_conn)] - c_removed[:] = node_conn[:] - for c in node_conn: - if force: - self.__del_connection(c) - else: - c.lq_a2b = LinkQuality.UNKNOWN - - return c_removed - - def __purge(self, force=False): - """ - Removes the nodes and connections that has not been discovered during the last scan. - - Args: - force (Boolean, optional, default=``False``): ``True`` to force the deletion of nodes - and connections, ``False`` otherwise. - """ - # Purge nodes and connections from network - removed_nodes = self.__purge_network_nodes(force=force) - removed_connections = self.__purge_network_connections(force=force) - - self._log.debug("") - self._log.debug(" [*] Purging network...") - [self._log.debug(" o Removed node: %s" % n) for n in removed_nodes] - [self._log.debug(" o Removed connections: %s" % n) for n in removed_connections] - - def __purge_network_nodes(self, force=False): - """ - Removes the nodes and connections that has not been discovered during the last scan. - - Args: - force (Boolean, optional, default=``False``): ``True`` to force the deletion of nodes, - ``False`` otherwise. - - Returns: - List: The list of purged nodes. - """ - nodes_to_remove = [] - with self.__lock: - for n in self.__devices_list: - if not n.scan_counter or n.scan_counter != self.__scan_counter or not n.reachable: - nodes_to_remove.append(n) - - [self._remove_device(n, NetworkEventReason.NEIGHBOR, force=force) for n in nodes_to_remove] - - return nodes_to_remove - - def __purge_network_connections(self, force=False): + ValueError: if ``device`` is ``None``. """ - Removes the connections that has not been discovered during the last scan. - - Args: - force (Boolean, optional, default=``False``): ``True`` to force the deletion of - connections, ``False`` otherwise. + super(ZigBeeNetwork, self).__init__(device) - Returns: - List: The list of purged connections. - """ - connections_to_remove = [] - with self.__conn_lock: - for c in self.__connections: - if c.scan_counter_a2b != self.__scan_counter \ - and c.scan_counter_b2a != self.__scan_counter: - c.lq_a2b = LinkQuality.UNKNOWN - c.lq_b2a = LinkQuality.UNKNOWN - connections_to_remove.append(c) - elif c.scan_counter_a2b != self.__scan_counter: - c.lq_a2b = LinkQuality.UNKNOWN - elif c.scan_counter_b2a != self.__scan_counter: - c.lq_b2a = LinkQuality.UNKNOWN - elif c.lq_a2b == LinkQuality.UNKNOWN \ - and c.lq_b2a == LinkQuality.UNKNOWN: - connections_to_remove.append(c) - if force: - [self.__del_connection(c) for c in connections_to_remove] +class Raw802Network(XBeeNetwork): + """ + This class represents an 802.15.4 network. - return connections_to_remove + The network allows the discovery of remote devices in the same network + as the local one and stores them. + """ - def __purge_node_connections(self, node_a, force=False): + def __init__(self, device): """ - Purges given node connections. Removes the connections that has not been discovered during - the last scan. + Class constructor. Instantiates a new ``Raw802Network``. Args: - node_a (:class:`.AbstractXBeeDevice`): The node_a of the connections to purge. - force (Boolean, optional, default=``False``): ``True`` to force the deletion of the - connections, ``False`` otherwise. + device (:class:`.Raw802Device`): the local 802.15.4 device to get the network from. - Returns: - List: List of purged connections. + Raises: + ValueError: if ``device`` is ``None``. """ - c_purged = [] + super(Raw802Network, self).__init__(device) - # Get node connections, but only those whose "node_a" is "node" (we are only purging - # connections that are discovered with "node", and they are those with "node" as "node_a") - node_conn = self.__get_connections_for_node_a_b(node_a, node_a=True) - with self.__conn_lock: - for c in node_conn: - if c.scan_counter_a2b != self.__scan_counter: - c.lq_a2b = LinkQuality.UNKNOWN - if c.scan_counter_b2a == self.__scan_counter \ - and c.lq_b2a == LinkQuality.UNKNOWN: - c_purged.append(c) - - if force: - [self.__del_connection(c) for c in c_purged] +class DigiMeshNetwork(XBeeNetwork): + """ + This class represents a DigiMesh network. - return c_purged + The network allows the discovery of remote devices in the same network + as the local one and stores them. + """ - def __wait_checking(self, seconds): + def __init__(self, device): """ - Waits some time, verifying if the process has been canceled. + Class constructor. Instantiates a new ``DigiMeshNetwork``. Args: - seconds (Float): The amount of seconds to wait. + device (:class:`.DigiMeshDevice`): the local DigiMesh device to get the network from. - Returns: - :class:`digi.xbee.models.status.NetworkDiscoveryStatus`: Resulting status - of the discovery process. + Raises: + ValueError: if ``device`` is ``None``. """ - if seconds <= 0: - return NetworkDiscoveryStatus.SUCCESS - - def current_ms_time(): - return int(round(time.time() * 1000)) + super(DigiMeshNetwork, self).__init__(device) - dead_line = current_ms_time() + seconds*1000 - while current_ms_time() < dead_line: - time.sleep(0.25) - # Check for cancel - if self._stop_event.is_set(): - return NetworkDiscoveryStatus.CANCEL - return NetworkDiscoveryStatus.SUCCESS - - -class ZigBeeNetwork(XBeeNetwork): +class DigiPointNetwork(XBeeNetwork): """ - This class represents a ZigBee network. + This class represents a DigiPoint network. The network allows the discovery of remote devices in the same network as the local one and stores them. """ - __ROUTE_TABLE_TYPE = "route_table" - __NEIGHBOR_TABLE_TYPE = "neighbor_table" def __init__(self, device): """ - Class constructor. Instantiates a new ``ZigBeeNetwork``. - - Args: - device (:class:`.ZigBeeDevice`): the local ZigBee device to get the network from. - - Raises: - ValueError: if ``device`` is ``None``. - """ - super().__init__(device) - - self.__saved_ao = None - - # Dictionary to store the route and neighbor discovery processes per node, so they can be - # stop when required. - # The dictionary uses as key the 64-bit address string representation (to be thread-safe) - self.__zdo_processes = {} - - # Dictionary to store discovered routes for each Zigbee device - # The dictionary uses as key the 64-bit address string representation (to be thread-safe) - self.__discovered_routes = {} - - def _prepare_network_discovery(self): - """ - Override. - - .. seealso:: - | :meth:`.XBeeNetwork._prepare_network_discovery` - """ - self._log.debug("[*] Preconfiguring %s" % ATStringCommand.AO.command) - try: - self.__saved_ao = self._local_xbee.get_api_output_mode_value() - - # Do not configure AO if it is already - if utils.is_bit_enabled(self.__saved_ao[0], 0): - self.__saved_ao = None - - return - - value = APIOutputModeBit.calculate_api_output_mode_value( - self._local_xbee.get_protocol(), {APIOutputModeBit.EXPLICIT}) - - self._local_xbee.set_api_output_mode_value(value) - - except XBeeException as e: - raise XBeeException("Could not prepare XBee for network discovery: " + str(e)) - - def _discover_neighbors(self, requester, nodes_queue, active_processes, node_timeout): - """ - Override. - - .. seealso:: - | :meth:`.XBeeNetwork._discover_neighbors` - """ - active_processes.append(str(requester.get_64bit_addr())) - - if node_timeout is None: - node_timeout = 30 - - code = self.__get_route_table(requester, nodes_queue, node_timeout) - - return code - - def _check_not_discovered_nodes(self, devices_list, nodes_queue): - """ - Override. - - .. seealso:: - | :meth:`.XBeeNetwork._check_not_discovered_nodes` - """ - for n in devices_list: - if not n.scan_counter or n.scan_counter != self.scan_counter: - self._log.debug(" [*] Adding to FIFO not discovered node %s... (scan %d)" - % (n, self.scan_counter)) - nodes_queue.put(n) - - def _discovery_done(self, active_processes): - """ - Override. - - .. seealso:: - | :meth:`.XBeeNetwork._discovery_done` - """ - copy = active_processes[:] - for p in copy: - zdos = self.__zdo_processes.get(p) - if not zdos: - continue - - self.__stop_zdo_command(zdos, self.__class__.__ROUTE_TABLE_TYPE) - self.__stop_zdo_command(zdos, self.__class__.__NEIGHBOR_TABLE_TYPE) - - zdos.clear() - - self.__zdo_processes.clear() - self.__discovered_routes.clear() - - super()._discovery_done(active_processes) - - def _restore_network(self): - """ - Override. - - .. seealso:: - | :meth:`.XBeeNetwork._restore_network` - """ - if self.__saved_ao is None: - return - - self._log.debug("[*] Postconfiguring %s" % ATStringCommand.AO.command) - try: - self._local_xbee.set_api_output_mode_value(self.__saved_ao[0]) - except XBeeException as e: - self._error = "Could not restore XBee after network discovery: " + str(e) - - self.__saved_ao = None - - def _handle_special_errors(self, requester, error): - """ - Override. - - .. seealso:: - | :meth:`.XBeeNetwork._handle_special_errors` - """ - super()._handle_special_errors(requester, error) - - if error == "ZDO command answer not received": - # 'AO' value is misconfigured, restore it - self._log.debug(" [***] Local XBee misconfigured: restoring 'AO' value") - value = APIOutputModeBit.calculate_api_output_mode_value( - self._local_xbee.get_protocol(), {APIOutputModeBit.EXPLICIT}) - - self._local_xbee.set_api_output_mode_value(value) - - # Add the node to the FIFO to try again - self._XBeeNetwork__nodes_queue.put(requester) - - def __get_route_table(self, requester, nodes_queue, node_timeout): - """ - Launch the process to get the route table of the XBee. - - Args: - requester (:class:`.AbstractXBeeDevice`): The XBee to discover its route table. - nodes_queue (:class:`queue.Queue`): FIFO where the nodes to discover their - neighbors are stored. - node_timeout (Float): Timeout to get the route table (seconds). - - Returns: - :class:`digi.xbee.models.status.NetworkDiscoveryStatus`: Resulting status of - the route table process. - """ - def __new_route_callback(xbee, route): - self._log.debug(" o Discovered route of %s: %s - %s -> %s" - % (xbee, route.destination, route.next_hop, route.status)) - - # Requester node is clearly reachable - self._set_node_reachable(xbee, True) - - # Get the discovered routes of the node - routes_list = self.__discovered_routes.get(str(xbee.get_64bit_addr())) - if not routes_list: - routes_list = {} - self.__discovered_routes.update({str(xbee.get_64bit_addr()): routes_list}) - - # Add the new route - if str(route.next_hop) not in routes_list: - routes_list.update({str(route.next_hop): route}) - - # Check for cancel - if self._stop_event.is_set(): - cmd = self.__get_zdo_command(xbee, self.__class__.__ROUTE_TABLE_TYPE) - if cmd: - cmd.stop() - - def __route_discover_finished_callback(xbee, routes, error): - zdo_processes = self.__zdo_processes.get(str(requester.get_64bit_addr())) - if zdo_processes: - zdo_processes.pop(self.__class__.__ROUTE_TABLE_TYPE) - - if error: - self.__zdo_processes.pop(str(requester.get_64bit_addr()), None) - # Remove the discovered routes - self.__discovered_routes.pop(str(xbee.get_64bit_addr()), None) - # Process the error and do not continue - self._node_discovery_process_finished( - xbee, code=NetworkDiscoveryStatus.ERROR_GENERAL, error=error) - else: - # Check for cancel - if self._stop_event.is_set(): - # Remove the discovered routes - self.__discovered_routes.pop(str(xbee.get_64bit_addr()), None) - self._node_discovery_process_finished(xbee, code=NetworkDiscoveryStatus.CANCEL) - # return - - # Get neighbor table - code = self.__get_neighbor_table(xbee, nodes_queue, node_timeout) - if code != NetworkDiscoveryStatus.SUCCESS: - self._node_discovery_process_finished( - xbee, code=NetworkDiscoveryStatus.ERROR_GENERAL, error=error) - - self._log.debug(" [o] Getting ROUTE TABLE of node %s" % requester) - - from digi.xbee.models.zdo import RouteTableReader - reader = RouteTableReader(requester, configure_ao=False, timeout=node_timeout) - reader.get_route_table(route_callback=__new_route_callback, - process_finished_callback=__route_discover_finished_callback) - - processes = self.__zdo_processes.get(str(requester.get_64bit_addr())) - if not processes: - processes = {} - self.__zdo_processes.update({str(requester.get_64bit_addr()): processes}) - processes.update({self.__class__.__ROUTE_TABLE_TYPE: reader}) - - return NetworkDiscoveryStatus.SUCCESS - - def __get_neighbor_table(self, requester, nodes_queue, node_timeout): - """ - Launch the process to get the neighbor table of the XBee. - - Args: - requester (:class:`.AbstractXBeeDevice`): The XBee to discover its neighbor table. - nodes_queue (:class:`queue.Queue`): FIFO where the nodes to discover their - neighbors are stored. - node_timeout (Float): Timeout to get the route neighbor (seconds). - - Returns: - :class:`digi.xbee.models.status.NetworkDiscoveryStatus`: Resulting status of the - neighbor table process. - """ - def __new_neighbor_callback(xbee, neighbor): - # Do not add a connection to the same node - if neighbor == xbee: - return - - # Get the discovered routes of the node - routes_list = self.__discovered_routes.get(str(xbee.get_64bit_addr())) - - # Add the new neighbor - self.__process_discovered_neighbor_data(xbee, routes_list, neighbor, nodes_queue) - - # Check for cancel - if self._stop_event.is_set(): - cmd = self.__get_zdo_command(xbee, self.__class__.__NEIGHBOR_TABLE_TYPE) - if cmd: - cmd.stop() - - def __neighbor_discover_finished_callback(xbee, _, error): - zdo_processes = self.__zdo_processes.get(str(requester.get_64bit_addr())) - if zdo_processes: - zdo_processes.pop(self.__class__.__NEIGHBOR_TABLE_TYPE, None) - self.__zdo_processes.pop(str(requester.get_64bit_addr()), None) - - # Remove the discovered routes - self.__discovered_routes.pop(str(xbee.get_64bit_addr()), None) - - # Process the error if exists - code = NetworkDiscoveryStatus.SUCCESS if not error \ - else NetworkDiscoveryStatus.ERROR_GENERAL - self._node_discovery_process_finished(xbee, code=code, error=error) - - self._log.debug(" [o] Getting NEIGHBOR TABLE of node %s" % requester) - - from digi.xbee.models.zdo import NeighborTableReader - reader = NeighborTableReader(requester, configure_ao=False, timeout=node_timeout) - reader.get_neighbor_table(neighbor_callback=__new_neighbor_callback, - process_finished_callback=__neighbor_discover_finished_callback) - - processes = self.__zdo_processes.get(str(requester.get_64bit_addr())) - if not processes: - processes = {} - self.__zdo_processes.update({str(requester.get_64bit_addr()): processes}) - processes.update({self.__class__.__NEIGHBOR_TABLE_TYPE: reader}) - - return NetworkDiscoveryStatus.SUCCESS - - def __process_discovered_neighbor_data(self, requester, routes, neighbor, nodes_queue): - """ - Notifies a neighbor has been discovered. - - Args: - requester (:class:`.AbstractXBeeDevice`): The Zigbee Device whose neighbor table was requested. - routes (Dictionary): A dictionary with the next hop 16-bit address string as key, and - the route (``digi.xbee.models.zdo.Route``) as value. - neighbor (:class:`digi.xbee.models.zdo.Neighbor`): The discovered neighbor. - nodes_queue (:class:`queue.Queue`): FIFO where the nodes to discover their - neighbors are stored. - """ - self._log.debug(" o Discovered neighbor of %s: %s (%s)" - % (requester, neighbor.node, neighbor.relationship.name)) - - # Requester node is clearly reachable - self._set_node_reachable(requester, True) - - # Add the neighbor node to the network - node = self._XBeeNetwork__add_remote(neighbor.node, NetworkEventReason.NEIGHBOR) - if not node: - # Node already in network for this scan - node = self.get_device_by_64(neighbor.node.get_64bit_addr()) - self._log.debug(" - NODE already in network in this scan (scan: %d) %s" - % (node.scan_counter, node)) - else: - if neighbor.node.get_role() != Role.END_DEVICE: - # Add to the FIFO to ask for its neighbors - nodes_queue.put(node) - self._log.debug(" - Added to network (scan: %d)" % node.scan_counter) - else: - # Not asking to End Devices when found, consider them as reachable - self._set_node_reachable(node, True) - self._XBeeNetwork__device_discovered(node) - - # Add connections - route = None - if routes: - route = routes.get(str(neighbor.node.get_16bit_addr())) - - if not route and not neighbor.relationship: - return - - from digi.xbee.models.zdo import RouteStatus, NeighborRelationship - connection = None - - if route: - connection = Connection(requester, node, lq_a2b=neighbor.lq, - lq_b2a=LinkQuality.UNKNOWN, status_a2b=route.status, - status_b2a=RouteStatus.UNKNOWN) - self._log.debug(" - Using route for the connection: %d" % route.status.id) - elif neighbor.node.get_role() != Role.UNKNOWN \ - and neighbor.relationship != NeighborRelationship.PREVIOUS_CHILD \ - and neighbor.relationship != NeighborRelationship.SIBLING: - self._log.debug( - " - No route for this node, using relationship for the connection: %s" - % neighbor.relationship.name) - if neighbor.relationship == NeighborRelationship.PARENT: - 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: - connection = Connection(requester, node, lq_a2b=neighbor.lq, - lq_b2a=LinkQuality.UNKNOWN, status_a2b=RouteStatus.ACTIVE, - status_b2a=RouteStatus.UNKNOWN) - if not connection: - self._log.debug(" - Connection NULL for this neighbor") - return - - if self._XBeeNetwork__add_connection(connection): - self._log.debug(" - Added connection (LQI: %d) %s >>> %s" - % (neighbor.lq, requester, node)) - else: - self._log.debug( - " - CONNECTION (LQI: %d) already in network in this" - " scan (scan: %d) %s >>> %s" - % (neighbor.lq, node.scan_counter, requester, node)) - - def __get_zdo_command(self, xbee, cmd_type): - """ - Returns the ZDO command in process (route/neighbor table) for the provided device. - - Args: - xbee (:class:`.AbstractXBeeDevice`): The device to get a ZDO command in process. - cmd_type (String): The ZDO command type (route/neighbor table) - """ - cmds = self.__zdo_processes.get(str(xbee.get_64bit_addr())) - if cmds: - return cmds.get(cmd_type) - - return None - - def __stop_zdo_command(self, commands, cmd_type): - """ - Stops the execution of the ZDO command contained in the given dictionary. - This method blocks until the ZDO command is completely stopped. - - Args: - commands (Dictionary): The dictionary with the ZDO command to stop. - cmd_type (String): The ZDO command type (route/neighbor table) - """ - if not commands or not cmd_type: - return - - cmd = commands.get(cmd_type) - if not cmd or not cmd.running: - return - - cmd.stop() - - -class Raw802Network(XBeeNetwork): - """ - This class represents an 802.15.4 network. - - The network allows the discovery of remote devices in the same network - as the local one and stores them. - """ - - def __init__(self, device): - """ - Class constructor. Instantiates a new ``Raw802Network``. - - Args: - device (:class:`.Raw802Device`): the local 802.15.4 device to get the network from. - - Raises: - ValueError: if ``device`` is ``None``. - """ - super().__init__(device) - - -class DigiMeshNetwork(XBeeNetwork): - """ - This class represents a DigiMesh network. - - The network allows the discovery of remote devices in the same network - as the local one and stores them. - """ - - def __init__(self, device): - """ - Class constructor. Instantiates a new ``DigiMeshNetwork``. - - Args: - device (:class:`.DigiMeshDevice`): the local DigiMesh device to get the network from. - - Raises: - ValueError: if ``device`` is ``None``. - """ - super().__init__(device) - - self.__saved_no = None - - # Dictionary to store the neighbor find processes per node, so they can be - # stop when required. - # The dictionary uses as key the 64-bit address string representation (to be thread-safe) - self.__neighbor_finders = {} - - def _prepare_network_discovery(self): - """ - Override. - - .. seealso:: - | :meth:`.XBeeNetwork._prepare_network_discovery` - """ - super()._prepare_network_discovery() - - self._log.debug("[*] Preconfiguring %s" % ATStringCommand.NO.command) - try: - self.__saved_no = self.get_discovery_options() - - # Do not configure NO if it is already - if utils.is_bit_enabled(self.__saved_no[0], 2): - self.__saved_no = None - - return - - self.set_discovery_options({DiscoveryOptions.APPEND_RSSI}) - - except XBeeException as e: - raise XBeeException("Could not prepare XBee for network discovery: " + str(e)) - - def _discover_neighbors(self, requester, nodes_queue, active_processes, node_timeout): - """ - Override. - - .. seealso:: - | :meth:`.XBeeNetwork._discover_neighbors` - """ - def __new_neighbor_callback(xbee, neighbor): - # Do not add a connection to the same node - if neighbor == xbee: - return - - # Add the new neighbor - self.__process_discovered_neighbor_data(xbee, neighbor, nodes_queue) - - def __neighbor_discover_finished_callback(xbee, _, error): - self.__neighbor_finders.pop(str(requester.get_64bit_addr()), None) - - # Process the error if exists - code = NetworkDiscoveryStatus.SUCCESS if not error \ - else NetworkDiscoveryStatus.ERROR_GENERAL - self._node_discovery_process_finished(xbee, code=code, error=error) - - self._log.debug(" [o] Calling NEIGHBOR FINDER for node %s" % requester) - - from digi.xbee.models.zdo import NeighborFinder - finder = NeighborFinder(requester, timeout=node_timeout) - finder.get_neighbors(neighbor_callback=__new_neighbor_callback, - process_finished_callback=__neighbor_discover_finished_callback) - - active_processes.append(str(requester.get_64bit_addr())) - self.__neighbor_finders.update({str(requester.get_64bit_addr()): finder}) - - return NetworkDiscoveryStatus.SUCCESS - - def _check_not_discovered_nodes(self, devices_list, nodes_queue): - """ - Override. - - .. seealso:: - | :meth:`.XBeeNetwork._check_not_discovered_nodes` - """ - for n in devices_list: - if not n.scan_counter or n.scan_counter != self.scan_counter: - self._log.debug(" [*] Adding to FIFO not discovered node %s... (scan %d)" - % (n, self.scan_counter)) - nodes_queue.put(n) - - def _discovery_done(self, active_processes): - """ - Override. - - .. seealso:: - | :meth:`.XBeeNetwork._discovery_done` - """ - copy = active_processes[:] - for p in copy: - finder = self.__neighbor_finders.get(p) - if not finder: - continue - - finder.stop() - - self.__neighbor_finders.clear() - - super()._discovery_done(active_processes) - - def _restore_network(self): - """ - Override. - - .. seealso:: - | :meth:`.XBeeNetwork._restore_network` - """ - super()._restore_network() - - if self.__saved_no is None: - return - - self._log.debug("[*] Postconfiguring %s" % ATStringCommand.NO.command) - try: - self._local_xbee.set_parameter(ATStringCommand.NO.command, self.__saved_no) - except XBeeException as e: - self._error = "Could not restore XBee after network discovery: " + str(e) - - self.__saved_no = None - - def __process_discovered_neighbor_data(self, requester, neighbor, nodes_queue): - """ - Notifies a neighbor has been discovered. - - Args: - requester (:class:`.AbstractXBeeDevice`): The DigiMesh device whose neighbors was - requested. - neighbor (:class:`digi.xbee.models.zdo.Neighbor`): The discovered neighbor. - nodes_queue (:class:`queue.Queue`): FIFO where the nodes to discover their - neighbors are stored. - """ - self._log.debug(" o Discovered neighbor of %s: %s (%s)" - % (requester, neighbor.node, neighbor.relationship.name)) - - # Requester node is clearly reachable - self._set_node_reachable(requester, True) - - # Add the neighbor node to the network - node = self._XBeeNetwork__add_remote(neighbor.node, NetworkEventReason.NEIGHBOR) - if not node: - # Node already in network for this scan - node = self.get_device_by_64(neighbor.node.get_64bit_addr()) - self._log.debug(" - NODE already in network in this scan (scan: %d) %s" - % (node.scan_counter, node)) - # Do not add the connection if the discovered device is itself - if node.get_64bit_addr() == requester.get_64bit_addr(): - return - else: - # Add to the FIFO to ask for its neighbors - nodes_queue.put(node) - self._log.debug(" - Added to network (scan: %d)" % node.scan_counter) - - self._XBeeNetwork__device_discovered(node) - - # Add connections - from digi.xbee.models.zdo import RouteStatus - connection = Connection(requester, node, lq_a2b=neighbor.lq, lq_b2a=LinkQuality.UNKNOWN, - status_a2b=RouteStatus.ACTIVE, status_b2a=RouteStatus.ACTIVE) - - if self._XBeeNetwork__add_connection(connection): - self._log.debug(" - Added connection (RSSI: %s) %s >>> %s" - % (connection.lq_a2b, requester, node)) - else: - self._log.debug( - " - CONNECTION (RSSI: %d) already in network in this " - "scan (scan: %d) %s >>> %s" - % (connection.lq_a2b, node.scan_counter, requester, node)) - - # Found node is clearly reachable, it answered to a FN - self._set_node_reachable(node, True) - - -class DigiPointNetwork(XBeeNetwork): - """ - This class represents a DigiPoint network. - - The network allows the discovery of remote devices in the same network - as the local one and stores them. - """ - - def __init__(self, device): - """ - Class constructor. Instantiates a new ``DigiPointNetwork``. + Class constructor. Instantiates a new ``DigiPointNetwork``. Args: device (:class:`.DigiPointDevice`): the local DigiPoint device to get the network from. @@ -9370,454 +6348,4 @@ def __init__(self, device): Raises: ValueError: if ``device`` is ``None``. """ - super().__init__(device) - - -@unique -class NetworkEventType(Enum): - """ - Enumerates the different network event types. - """ - - ADD = (0x00, "XBee added to the network") - DEL = (0x01, "XBee removed from the network") - UPDATE = (0x02, "XBee in the network updated") - CLEAR = (0x03, "Network cleared") - - def __init__(self, code, description): - self.__code = code - self.__description = description - - @property - def code(self): - """ - Returns the code of the ``NetworkEventType`` element. - - Returns: - Integer: the code of the ``NetworkEventType`` element. - """ - return self.__code - - @property - def description(self): - """ - Returns the description of the ``NetworkEventType`` element. - - Returns: - String: the description of the ``NetworkEventType`` element. - """ - return self.__description - - @classmethod - def get(cls, code): - """ - Returns the network event for the given code. - - Args: - code (Integer): the code of the network event to get. - - Returns: - :class:`.NetworkEventType`: the ``NetworkEventType`` with the given code, ``None`` if - there is not any event with the provided code. - """ - for ev_type in cls: - if ev_type.code == code: - return ev_type - - return None - - -NetworkEventType.__doc__ += utils.doc_enum(NetworkEventType) - - -@unique -class NetworkEventReason(Enum): - """ - Enumerates the different network event reasons. - """ - - DISCOVERED = (0x00, "Discovered XBee") - NEIGHBOR = (0x01, "Discovered as XBee neighbor") - RECEIVED_MSG = (0x02, "Received message from XBee") - MANUAL = (0x03, "Manual modification") - - def __init__(self, code, description): - self.__code = code - self.__description = description - - @property - def code(self): - """ - Returns the code of the ``NetworkEventReason`` element. - - Returns: - Integer: the code of the ``NetworkEventReason`` element. - """ - return self.__code - - @property - def description(self): - """ - Returns the description of the ``NetworkEventReason`` element. - - Returns: - String: the description of the ``NetworkEventReason`` element. - """ - return self.__description - - @classmethod - def get(cls, code): - """ - Returns the network event reason for the given code. - - Args: - code (Integer): the code of the network event reason to get. - - Returns: - :class:`.NetworkEventReason`: the ``NetworkEventReason`` with the given code, ``None`` - if there is not any reason with the provided code. - """ - for reason in cls: - if reason.code == code: - return reason - - return None - - -NetworkEventReason.__doc__ += utils.doc_enum(NetworkEventReason) - - -class LinkQuality(object): - """ - This class represents the link qualitity of a connection. - It can be a LQI (Link Quality Index) for ZigBee devices, or RSSI - (Received Signal Strength Indicator) for the rest. - """ - - UNKNOWN = None - """ - Unknown link quality. - """ - - UNKNOWN_VALUE = -9999 - """ - Unknown link quality value. - """ - - __UNKNOWN_STR = '?' - - def __init__(self, lq=UNKNOWN, is_rssi=False): - """ - Class constructor. Instanciates a new ``LinkQuality``. - - Args: - lq (Integer, optional, default=``L_UNKNOWN``): The link quality or ``None`` if unknown. - is_rssi (Boolean, optional, default=``False``): ``True`` to specify the value is a RSSI, - ``False`` otherwise. - """ - self.__lq = lq - self.__is_rssi = is_rssi - - def __str__(self): - if self.__lq == 0: - return str(self.__lq) - - if self.__lq == self.__class__.UNKNOWN_VALUE: - return self.__class__.__UNKNOWN_STR - - if self.__is_rssi: - return "-" + str(self.__lq) - - return str(self.__lq) - - @property - def lq(self): - """ - Returns the link quality value. - - Returns: - Integer: The link quality value. - """ - return self.__lq - - @property - def is_rssi(self): - """ - Returns whether this is a RSSI value. - - Returns: - Boolean: ``True`` if this is an RSSI value, ``False`` for LQI. - """ - return self.__lq - - -LinkQuality.UNKNOWN = LinkQuality(lq=LinkQuality.UNKNOWN_VALUE) - - -class Connection(object): - """ - This class represents a generic connection between two nodes in a XBee network. - It contains the source and destination nodes, the LQI value for the connection between them and - its status. - """ - - def __init__(self, node_a, node_b, lq_a2b=None, lq_b2a=None, status_a2b=None, status_b2a=None): - """ - Class constructor. Instantiates a new ``Connection``. - - Args: - node_a (:class:`.AbstractXBeeDevice`): One of the connection ends. - node_b (:class:`.AbstractXBeeDevice`): The other connection end. - lq_a2b (:class:`.LinkQuality` or Integer, optional, default=``None``): The link - quality for the connection node_a -> node_b. If not specified - ``LinkQuality.UNKNOWN`` is used. - lq_b2a (:class:`.LinkQuality` or Integer, optional, default=``None``): The link - quality for the connection node_b -> node_a. If not specified - ``LinkQuality.UNKNOWN`` is used. - status_a2b (:class:`digi.xbee.models.zdo.RouteStatus`, optional, default=``None``): The - status for the connection node_a -> node_b. If not specified - ``RouteStatus.UNKNOWN`` is used. - status_b2a (:class:`digi.xbee.models.zdo.RouteStatus`, optional, default=``None``): The - status for the connection node_b -> node_a. If not specified - ``RouteStatus.UNKNOWN`` is used. - - Raises: - ValueError: if ``node_a`` or ``node_b`` is ``None``. - - .. seealso:: - | :class:`.AbstractXBeeDevice` - | :class:`.LinkQuality` - | :class:`digi.xbee.models.zdo.RouteStatus` - """ - if not node_a: - raise ValueError("Node A must be defined") - if not node_b: - raise ValueError("Node B must be defined") - - self.__node_a = node_a - self.__node_b = node_b - - self.__lq_a2b = Connection.__get_lq(lq_a2b, node_a) - self.__lq_b2a = Connection.__get_lq(lq_b2a, node_a) - - from digi.xbee.models.zdo import RouteStatus - self.__st_a2b = status_a2b if status_a2b else RouteStatus.UNKNOWN - self.__st_b2a = status_b2a if status_b2a else RouteStatus.UNKNOWN - - self.__scan_counter_a2b = 0 - self.__scan_counter_b2a = 0 - - def __str__(self): - return "{{{!s} >>> {!s} [{!s} / {!s}]: {!s} / {!s}}}".format( - self.__node_a, self.__node_b, self.__st_a2b, self.__st_b2a, self.__lq_a2b, - self.__lq_b2a) - - def __eq__(self, other): - if not isinstance(other, Connection): - return False - - return self.__node_a.get_64bit_addr() == other.node_a.get_64bit_addr() \ - and self.__node_b.get_64bit_addr() == other.node_b.get_64bit_addr() - - def __hash__(self): - return hash((self.__node_a.get_64bit_addr(), self.__node_b.get_64bit_addr())) - - @property - def node_a(self): - """ - Returns the node A of this connection. - - Returns: - :class:`.AbstractXBeeDevice`: The node A. - - .. seealso:: - | :class:`.AbstractXBeeDevice` - """ - return self.__node_a - - @property - def node_b(self): - """ - Returns the node B of this connection. - - Returns: - :class:`.AbstractXBeeDevice`: The node . - - .. seealso:: - | :class:`.AbstractXBeeDevice` - """ - return self.__node_b - - @property - def lq_a2b(self): - """ - Returns the link quality of the connection from node A to node B. - - Returns: - :class:`.LinkQuality`: The link quality for the connection A -> B. - - .. seealso:: - | :class:`.LinkQuality` - """ - return self.__lq_a2b - - @lq_a2b.setter - def lq_a2b(self, new_lq_a2b): - """ - Sets the link quality of the connection from node A to node B. - - Args: - new_lq_a2b (:class:`.LinkQuality`): The new A -> B link quality value. - - .. seealso:: - | :class:`.LinkQuality` - """ - self.__lq_a2b = new_lq_a2b - - @property - def lq_b2a(self): - """ - Returns the link quality of the connection from node B to node A. - - Returns: - :class:`.LinkQuality`: The link quality for the connection B -> A. - - .. seealso:: - | :class:`.LinkQuality` - """ - return self.__lq_b2a - - @lq_b2a.setter - def lq_b2a(self, new_lq_b2a): - """ - Sets the link quality of the connection from node B to node A. - - Args: - new_lq_b2a (:class:`.LinkQuality`): The new B -> A link quality value. - - .. seealso:: - | :class:`.LinkQuality` - """ - self.__lq_b2a = new_lq_b2a - - @property - def status_a2b(self): - """ - Returns the status of this connection from node A to node B. - - Returns: - :class:`digi.xbee.models.zdo.RouteStatus`: The status for A -> B connection. - - .. seealso:: - | :class:`digi.xbee.models.zdo.RouteStatus` - """ - return self.__st_a2b - - @status_a2b.setter - def status_a2b(self, new_status_a2b): - """ - Sets the status of this connection from node A to node B. - - Args: - new_status_a2b (:class:`digi.xbee.models.zdo.RouteStatus`): The new - A -> B connection status. - - .. seealso:: - | :class:`digi.xbee.models.zdo.RouteStatus` - """ - self.__st_a2b = new_status_a2b - - @property - def status_b2a(self): - """ - Returns the status of this connection from node B to node A. - - Returns: - :class:`digi.xbee.models.zdo.RouteStatus`: The status for B -> A connection. - - .. seealso:: - | :class:`digi.xbee.models.zdo.RouteStatus` - """ - return self.__st_b2a - - @status_b2a.setter - def status_b2a(self, new_status_b2a): - """ - Sets the status of this connection from node B to node A. - - Args: - new_status_b2a (:class:`digi.xbee.models.zdo.RouteStatus`): The new - B -> A connection status. - - .. seealso:: - | :class:`digi.xbee.models.zdo.RouteStatus` - """ - self.__st_b2a = new_status_b2a - - @staticmethod - def __get_lq(lq, src): - """ - Retrieves the `LinkQuality` object that corresponds to the integer provided. - - Args: - lq (Integer): The link quality value. - src (:class:`.AbstractXBeeDevice`): The node from where the connection starts. - - Returns: - :class:`.LinkQuality`: The corresponding `LinkQuality`. - - .. seealso:: - | :class:`.AbstractXBeeDevice` - | :class:`.LinkQuality` - """ - if isinstance(lq, LinkQuality): - return lq - elif isinstance(lq, int): - return LinkQuality(lq=lq, - is_rssi=src.get_protocol() in [XBeeProtocol.DIGI_MESH, - XBeeProtocol.XTEND_DM, - XBeeProtocol.XLR_DM, XBeeProtocol.SX]) - else: - return LinkQuality.UNKNOWN - - @property - def scan_counter_a2b(self): - """ - Returns the scan counter for this connection, discovered by its A node. - - Returns: - Integer: The scan counter for this connection, discovered by its A node. - """ - return self.__scan_counter_a2b - - @scan_counter_a2b.setter - def scan_counter_a2b(self, new_scan_counter_a2b): - """ - Configures the scan counter for this connection, discovered by its A node. - - Args: - new_scan_counter_a2b (Integer): The scan counter for this connection, discovered by its - A node. - """ - self.__scan_counter_a2b = new_scan_counter_a2b - - @property - def scan_counter_b2a(self): - """ - Returns the scan counter for this connection, discovered by its B node. - - Returns: - Integer: The scan counter for this connection, discovered by its B node. - """ - return self.__scan_counter_b2a - - @scan_counter_b2a.setter - def scan_counter_b2a(self, new_scan_counter_b2a): - """ - Configures the scan counter for this connection, discovered by its B node. - - Args: - new_scan_counter_b2a (Integer): The scan counter for this connection, discovered by its - B node. - """ - self.__scan_counter_b2a = new_scan_counter_b2a + super(DigiPointNetwork, self).__init__(device) diff --git a/digi/xbee/exception.py b/digi/xbee/exception.py index 2d734cb..1130d25 100644 --- a/digi/xbee/exception.py +++ b/digi/xbee/exception.py @@ -1,4 +1,4 @@ -# Copyright 2017-2019, Digi International Inc. +# Copyright 2017, 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 @@ -42,11 +42,7 @@ class ATCommandException(CommunicationException): All functionality of this class is the inherited of `Exception `_. """ - __DEFAULT_MESSAGE = "There was a problem sending the AT command packet." - - def __init__(self, message=__DEFAULT_MESSAGE, cmd_status=None): - super().__init__(message) - self.status = cmd_status + pass class ConnectionException(XBeeException): @@ -73,7 +69,7 @@ class XBeeDeviceException(XBeeException): class InvalidConfigurationException(ConnectionException): """ - This exception will be thrown when trying to open an interface with an + This exception will be thrown when trying to open an interface with an invalid configuration. All functionality of this class is the inherited of `Exception @@ -82,7 +78,7 @@ class InvalidConfigurationException(ConnectionException): __DEFAULT_MESSAGE = "The configuration used to open the interface is invalid." def __init__(self, message=__DEFAULT_MESSAGE): - super().__init__(message) + ConnectionException.__init__(self, message) class InvalidOperatingModeException(ConnectionException): @@ -94,17 +90,19 @@ class InvalidOperatingModeException(ConnectionException): `_. """ __DEFAULT_MESSAGE = "The operating mode of the XBee device is not supported by the library." - __DEFAULT_MSG_FORMAT = "Unsupported operating mode: %s (%d)" - def __init__(self, message=None, op_mode=None): - if op_mode and not message: - message = InvalidOperatingModeException.__DEFAULT_MSG_FORMAT \ - % (op_mode.description, op_mode.code) - elif not message: - message = InvalidOperatingModeException.__DEFAULT_MESSAGE + def __init__(self, message=__DEFAULT_MESSAGE): + ConnectionException.__init__(self, message) + + @classmethod + def from_operating_mode(cls, operating_mode): + """ + Class constructor. - super().__init__(message) - self.__op_mode = op_mode + Args: + operating_mode (:class:`.OperatingMode`): the operating mode that generates the exceptions. + """ + return cls("Unsupported operating mode: " + operating_mode.description) class InvalidPacketException(CommunicationException): @@ -118,7 +116,7 @@ class InvalidPacketException(CommunicationException): __DEFAULT_MESSAGE = "The XBee API packet is not properly formed." def __init__(self, message=__DEFAULT_MESSAGE): - super().__init__(message) + CommunicationException.__init__(self, message) class OperationNotSupportedException(XBeeDeviceException): @@ -129,11 +127,11 @@ class OperationNotSupportedException(XBeeDeviceException): All functionality of this class is the inherited of `Exception `_. """ - __DEFAULT_MESSAGE = "The requested operation is not supported by either " \ - "the connection interface or the XBee device." + __DEFAULT_MESSAGE = "The requested operation is not supported by either the connection interface or " \ + "the XBee device." def __init__(self, message=__DEFAULT_MESSAGE): - super().__init__(message) + XBeeDeviceException.__init__(self, message) class TimeoutException(CommunicationException): @@ -146,8 +144,8 @@ class TimeoutException(CommunicationException): """ __DEFAULT_MESSAGE = "There was a timeout while executing the requested operation." - def __init__(self, message=__DEFAULT_MESSAGE): - super().__init__(message) + def __init__(self, _message=__DEFAULT_MESSAGE): + CommunicationException.__init__(self) class TransmitException(CommunicationException): @@ -160,44 +158,5 @@ class TransmitException(CommunicationException): """ __DEFAULT_MESSAGE = "There was a problem with a transmitted packet response (status not ok)" - def __init__(self, message=__DEFAULT_MESSAGE, transmit_status=None): - super().__init__(message) - self.status = transmit_status - - -class XBeeSocketException(XBeeException): - """ - This exception will be thrown when there is an error performing any socket operation. - - All functionality of this class is the inherited of `Exception - `_. - """ - __DEFAULT_MESSAGE = "There was a socket error" - __DEFAULT_STATUS_MESSAGE = "There was a socket error: %s (%d)" - - def __init__(self, message=__DEFAULT_MESSAGE, status=None): - super().__init__(self.__DEFAULT_STATUS_MESSAGE % (status.description, status.code) if status is not None else - message) - self.status = status - - -class FirmwareUpdateException(XBeeException): - """ - This exception will be thrown when any problem related to the firmware update - process of the XBee device occurs. - - All functionality of this class is the inherited of `Exception - `_. - """ - pass - - -class RecoveryException(XBeeException): - """ - This exception will be thrown when any problem related to the auto-recovery - process of the XBee device occurs. - - All functionality of this class is the inherited of `Exception - `_. - """ - pass + def __init__(self, _message=__DEFAULT_MESSAGE): + CommunicationException.__init__(self, _message) diff --git a/digi/xbee/filesystem.py b/digi/xbee/filesystem.py deleted file mode 100644 index 9e87650..0000000 --- a/digi/xbee/filesystem.py +++ /dev/null @@ -1,989 +0,0 @@ -# Copyright 2019, 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 functools -import logging -import os -import re -import string -import time - -from digi.xbee.exception import XBeeException, OperationNotSupportedException -from digi.xbee.models.atcomm import ATStringCommand -from digi.xbee.util import xmodem -from digi.xbee.util.xmodem import XModemException -from enum import Enum, unique -from os import listdir -from os.path import isfile -from serial.serialutil import SerialException - -_ANSWER_ATFS = "AT%s" % ATStringCommand.FS.command -_ANSWER_SHA256 = "sha256" - -_COMMAND_AT = "AT\r" -_COMMAND_ATFS = "AT%s %s" % (ATStringCommand.FS.command, "%s\r") -_COMMAND_FILE_SYSTEM = "AT%s\r" % ATStringCommand.FS.command -_COMMAND_MODE_ANSWER_OK = "OK" -_COMMAND_MODE_CHAR = "+" -_COMMAND_MODE_EXIT = "AT%s\r" % ATStringCommand.CN.command -_COMMAND_MODE_TIMEOUT = 2 - -_ERROR_CONNECT_FILESYSTEM = "Error connecting file system manager: %s" -_ERROR_ENTER_CMD_MODE = "Could not enter AT command mode" -_ERROR_EXECUTE_COMMAND = "Error executing command '%s': %s" -_ERROR_FILESYSTEM_NOT_SUPPORTED = "The device does not support file system feature" -_ERROR_FUNCTION_NOT_SUPPORTED = "Function not supported: %s" -_ERROR_TIMEOUT = "Timeout executing command" - -_FORMAT_TIMEOUT = 10 # Seconds. - -_FUNCTIONS_SEPARATOR = " " - -_GUARD_TIME = 2 # In seconds. - -_NAK_TIMEOUT = 10 # Seconds. - -_PATH_SEPARATOR = "/" -_PATTERN_FILE_SYSTEM_DIRECTORY = "^ + (.+)/$" -_PATTERN_FILE_SYSTEM_ERROR = "^(.*\\s)?(E[A-Z0-9]+)( .*)?\\s*$" -_PATTERN_FILE_SYSTEM_FILE = "^ +([0-9]+) (.+)$" -_PATTERN_FILE_SYSTEM_FUNCTIONS = "^.*AT%s %s" % (ATStringCommand.FS.command, "commands: (.*)$") -_PATTERN_FILE_SYSTEM_INFO = "^ *([0-9]*) (.*)$" - -_READ_BUFFER = 256 -_READ_DATA_TIMEOUT = 3 # Seconds. -_READ_EMPTY_DATA_RETRIES = 10 -_READ_EMPTY_DATA_RETRIES_DEFAULT = 1 -_READ_PORT_TIMEOUT = 0.05 # Seconds. - -_SECURE_ELEMENT_SUFFIX = "#" - -_TRANSFER_TIMEOUT = 5 # Seconds. - -_log = logging.getLogger(__name__) -_printable_ascii_bytes = string.printable.encode() - - -class _FilesystemFunction(Enum): - """ - This class lists the available file system functions for XBee devices. - - | Inherited properties: - | **name** (String): The name of this _FilesystemFunction. - | **value** (Integer): The ID of this _FilesystemFunction. - """ - PWD = ("PWD", "pwd") - CD = ("CD", "cd %s") - MD = ("MD", "md %s") - LS = ("LS", "ls") - LS_DIR = ("LS", "ls %s") - PUT = ("PUT", "put %s") - XPUT = ("XPUT", "xput %s") - GET = ("GET", "get %s") - MV = ("MV", "mv %s %s") - RM = ("RM", "rm %s") - HASH = ("HASH", "hash %s") - INFO = ("INFO", "info") - FORMAT = ("FORMAT", "format confirm") - - def __init__(self, name, command): - self.__name = name - self.__command = command - - @classmethod - def get(cls, name): - """ - Returns the _FilesystemFunction for the given name. - - Args: - name (String): the name of the _FilesystemFunction to get. - - Returns: - :class:`._FilesystemFunction`: the _FilesystemFunction with the given name, ``None`` if - there is not a _FilesystemFunction with that name. - """ - for value in _FilesystemFunction: - if value.name == name: - return value - - return None - - @property - def name(self): - """ - Returns the name of the _FilesystemFunction element. - - Returns: - String: the name of the _FilesystemFunction element. - """ - return self.__name - - @property - def command(self): - """ - Returns the command of the _FilesystemFunction element. - - Returns: - String: the command of the _FilesystemFunction element. - """ - return self.__command - - -class FileSystemElement(object): - """ - Class used to represent XBee file system elements (files and directories). - """ - - def __init__(self, name, path, size=0, is_directory=False): - """ - Class constructor. Instantiates a new :class:`.FileSystemElement` with the given parameters. - - Args: - name (String): the name of the file system element. - path (String): the absolute path of the file system element. - size (Integer): the size of the file system element, only applicable to files. - is_directory (Boolean): ``True`` if the file system element is a directory, ``False`` if it is a file. - """ - self._name = name - self._path = path - self._size = size - self._is_directory = is_directory - self._is_secure = False - # Check if element is 'write-only' (secure) - if self._name.endswith(_SECURE_ELEMENT_SUFFIX): - self._is_secure = True - - def __str__(self): - if self._is_directory: - return " %s/" % self._name - else: - return "%d %s" % (self._size, self._name) - - @property - def name(self): - """ - Returns the file system element name. - - Returns: - String: the file system element name. - """ - return self._name - - @property - def path(self): - """ - Returns the file system element absolute path. - - Returns: - String: the file system element absolute path. - """ - return self._path - - @property - def size(self): - """ - Returns the file system element size. - - Returns: - Integer: the file system element size. If element is a directory, returns '0'. - """ - return self._size if self._is_directory else 0 - - @property - def is_directory(self): - """ - Returns whether the file system element is a directory or not. - - Returns: - Boolean: ``True`` if the file system element is a directory, ``False`` otherwise. - """ - return self._is_directory - - @property - def is_secure(self): - """ - Returns whether the file system element is a secure element or not. - - Returns: - Boolean: ``True`` if the file system element is secure, ``False`` otherwise. - """ - return self._is_secure - - -class FileSystemException(XBeeException): - """ - This exception will be thrown when any problem related with the XBee device file system occurs. - - All functionality of this class is the inherited from `Exception - `_. - """ - pass - - -class FileSystemNotSupportedException(FileSystemException): - """ - This exception will be thrown when the file system feature is not supported in the device. - - All functionality of this class is the inherited from `Exception - `_. - """ - pass - - -class _LocalFileSystemUpdater(object): - """ - Helper class used to handle the local XBee file system update process. - """ - - def __init__(self, xbee_device, filesystem_path, progress_callback=None): - """ - - Args: - xbee_device (:class:`.XBeeDevice`): the local XBee device to update its file system. - filesystem_path (String): local path of the folder containing the filesystem structure to transfer. - progress_callback (Function, optional): function to execute to receive progress information. Receives two - arguments: - - * The current update task as a String - * The current update task percentage as an Integer - - Raises: - UpdateFileSystemException: if there is any error updating the XBee file system. - """ - self._xbee_device = xbee_device - self._serial_port = xbee_device.serial_port - self._filesystem_path = filesystem_path - self._progress_callback = progress_callback - self._supported_functions = [] - - -class LocalXBeeFileSystemManager(object): - """ - Helper class used to manage the local XBee file system. - """ - - def __init__(self, xbee_device): - """ - Class constructor. Instantiates a new :class:`.LocalXBeeFileSystemManager` with the given parameters. - - Args: - xbee_device (:class:`.XBeeDevice`): the local XBee device to manage its file system. - """ - if not xbee_device.serial_port: - raise OperationNotSupportedException("Only supported in local XBee connected by serial.") - - self._xbee_device = xbee_device - self._serial_port = xbee_device.serial_port - self._supported_functions = [] - self._device_was_connected = False - self._is_connected = False - self._old_read_timeout = _READ_PORT_TIMEOUT - - def _read_data(self, timeout=_READ_DATA_TIMEOUT, empty_retries=_READ_EMPTY_DATA_RETRIES_DEFAULT): - """ - Reads data from the serial port waiting for the provided timeout. - - Args: - timeout (Integer, optional): the maximum time to wait for data (seconds). Defaults to 1 second. - empty_retries (Integer, optional): the number of consecutive zero-bytes read before considering no more - data is available. - - Returns: - String: the read data as string. - - Raises: - SerialException: if there is any problem reading data from the serial port. - """ - answer_string = "" - empty_attempts = 0 - deadline = _get_milliseconds() + (timeout * 1000) - read_bytes = self._serial_port.read(_READ_BUFFER) - while (len(answer_string) == 0 or empty_attempts < empty_retries) and _get_milliseconds() < deadline: - read_string = _filter_non_printable(read_bytes) - answer_string += read_string - # Continue reading, maybe there is more data. - read_bytes = self._serial_port.read(_READ_BUFFER) - if len(read_string) == 0: - empty_attempts += 1 - else: - empty_attempts = 0 - - return answer_string - - def _is_in_atcmd_mode(self): - """ - Returns whether the command mode is active or not. - - Returns: - Boolean: ``True`` if the AT command mode is active, ``False`` otherwise. - """ - _log.debug("Checking AT command mode...") - try: - self._serial_port.write(str.encode(_COMMAND_AT, encoding='utf-8')) - answer = self._read_data(timeout=_GUARD_TIME) - - return answer is not None and _COMMAND_MODE_ANSWER_OK in answer - except SerialException as e: - _log.exception(e) - return False - - def _enter_atcmd_mode(self): - """ - Enters in AT command mode. - - Returns: - Boolean: ``True`` if entered command mode successfully, ``False`` otherwise. - """ - _log.debug("Entering AT command mode...") - try: - # In some scenarios where the read buffer is constantly being filled with remote data, it is - # almost impossible to read the 'enter command mode' answer, so purge port before. - self._serial_port.purge_port() - for i in range(3): - self._serial_port.write(str.encode(_COMMAND_MODE_CHAR, encoding='utf-8')) - answer = self._read_data(timeout=_GUARD_TIME, empty_retries=_READ_EMPTY_DATA_RETRIES) - - return answer is not None and _COMMAND_MODE_ANSWER_OK in answer - except SerialException as e: - _log.exception(e) - return False - - def _exit_atcmd_mode(self): - """ - Exits from AT command mode. - """ - _log.debug("Exiting AT command mode...") - try: - self._serial_port.write(str.encode(_COMMAND_MODE_EXIT, encoding='utf-8')) - except SerialException as e: - _log.exception(e) - finally: - time.sleep(_GUARD_TIME) # It is necessary to wait the guard time before sending data again. - - def _check_atcmd_mode(self): - """ - Checks whether AT command mode is active and if not tries to enter AT command mode. - - Returns: - Boolean: ``True`` if AT command mode is active or entered successfully, ``False`` otherwise. - """ - if not self._is_connected: - return False - - if not self._is_in_atcmd_mode(): - time.sleep(_GUARD_TIME) - return self._enter_atcmd_mode() - - return True - - def _supports_filesystem(self): - """ - Returns whether the device supports file system or not. - - Returns: - Boolean: ``True`` if the device supports file system, ``False`` otherwise. - """ - _log.debug("Checking if device supports file system...") - if not self._check_atcmd_mode(): - return False - - try: - self._serial_port.write(str.encode(_COMMAND_FILE_SYSTEM, encoding='utf-8')) - answer = self._read_data() - if answer and _ANSWER_ATFS in answer: - self._parse_filesystem_functions(answer.replace("\r", "")) - return True - - return False - except SerialException as e: - _log.exception(e) - return False - - def _parse_filesystem_functions(self, filesystem_answer): - """ - Parses the file system command response to obtain a list of supported file system functions. - - Args: - filesystem_answer (String): the file system command answer to parse. - """ - result = re.match(_PATTERN_FILE_SYSTEM_FUNCTIONS, filesystem_answer, flags=re.M | re.DOTALL) - if result is None or result.string is not result.group(0) or len(result.groups()) < 1: - return - - self._supported_functions = result.groups()[0].split(_FUNCTIONS_SEPARATOR) - - def _is_function_supported(self, function): - """ - Returns whether the specified file system function is supported or not. - - Args: - function (:class:`._FilesystemFunction`): the file system function to check. - - Returns: - Boolean: ``True`` if the specified file system function is supported, ``False`` otherwise. - """ - if not isinstance(function, _FilesystemFunction): - return False - - return function.name in self._supported_functions - - @staticmethod - def _check_function_error(answer, command): - """ - Checks the given file system command answer and throws an exception if it contains an error. - - Args: - answer (String): the file system command answer to check for errors. - command (String): the file system command executed. - - Raises: - FileSystemException: if any error is found in the answer. - """ - result = re.match(_PATTERN_FILE_SYSTEM_ERROR, answer, flags=re.M | re.DOTALL) - if result is not None and len(result.groups()) > 1: - if len(result.groups()) > 2: - raise FileSystemException(_ERROR_EXECUTE_COMMAND % (command.replace("\r", ""), result.groups()[1] + - " >" + result.groups()[2])) - else: - raise FileSystemException(_ERROR_EXECUTE_COMMAND % (command.replace("\r", ""), result.groups()[1])) - - def _xmodem_write_cb(self, data): - """ - Callback function used to write data to the serial port when requested from the XModem transfer. - - Args: - data (Bytearray): the data to write to serial port from the XModem transfer. - - Returns: - Boolean: ``True`` if the data was successfully written, ``False`` otherwise. - """ - try: - self._serial_port.purge_port() - self._serial_port.write(data) - self._serial_port.flush() - return True - except SerialException as e: - _log.exception(e) - - return False - - def _xmodem_read_cb(self, size, timeout=_READ_DATA_TIMEOUT): - """ - Callback function used to read data from the serial port when requested from the XModem transfer. - - Args: - size (Integer): the size of the data to read. - timeout (Integer, optional): the maximum time to wait to read the requested data (seconds). - - Returns: - Bytearray: the read data, ``None`` if data could not be read. - """ - deadline = _get_milliseconds() + (timeout * 1000) - data = bytearray() - try: - while len(data) < size and _get_milliseconds() < deadline: - read_bytes = self._serial_port.read(size - len(data)) - if len(read_bytes) > 0: - data.extend(read_bytes) - return data - except SerialException as e: - _log.exception(e) - - return None - - def _execute_command(self, cmd_type, *args, wait_for_answer=True): - """ - Executes the given command type with its arguments. - - Args: - cmd_type (:class:`._FilesystemFunction`): the command type to execute. - args (): the command arguments - wait_for_answer (Boolean): ``True`` to wait for command answer, ``False`` otherwise. - - Returns: - String: the command answer. - - Raises: - FileSystemException: if there is any error executing the command. - """ - # Sanity checks. - if not self._is_function_supported(cmd_type): - raise FileSystemException(_ERROR_FUNCTION_NOT_SUPPORTED % cmd_type.name) - if not self._check_atcmd_mode(): - raise FileSystemException(_ERROR_ENTER_CMD_MODE) - - command = _COMMAND_ATFS % (cmd_type.command % args) - try: - self._serial_port.write(str.encode(command, encoding='utf-8')) - answer = None - if wait_for_answer: - answer = self._read_data() - if not answer: - raise FileSystemException(_ERROR_TIMEOUT) - self._check_function_error(answer, command) - - return answer - except SerialException as e: - raise FileSystemException(_ERROR_EXECUTE_COMMAND % (command.replace("\r", ""), str(e))) - - @property - def is_connected(self): - """ - Returns whether the file system manager is connected or not. - - Returns: - Boolean: ``True`` if the file system manager is connected, ``False`` otherwise. - """ - return self._is_connected - - def connect(self): - """ - Connects the file system manager. - - Raises: - FileSystemException: if there is any error connecting the file system manager. - FileSystemNotSupportedException: if the device does not support filesystem feature. - """ - if self._is_connected: - return - - # The file system manager talks directly with the serial port in raw mode, so disconnect the device. - # Not disconnecting the device will cause the internal XBee device frame reader to consume the data - # required by the file system manager from the serial port. - if self._xbee_device.is_open: - self._xbee_device.close() - self._device_was_connected = True - - self._old_read_timeout = self._serial_port.get_read_timeout() - try: - self._serial_port.set_read_timeout(_READ_PORT_TIMEOUT) - self._serial_port.open() - self._is_connected = True - if not self._supports_filesystem(): - raise FileSystemNotSupportedException(_ERROR_FILESYSTEM_NOT_SUPPORTED) - except (SerialException, FileSystemNotSupportedException) as e: - # Close port if it is open. - if self._serial_port.isOpen(): - self._serial_port.close() - self._is_connected = False - - try: - # Restore serial port timeout. - self._serial_port.set_read_timeout(self._old_read_timeout) - except SerialException: - pass # Ignore this error as it is not critical and will not provide much info but confusion. - if isinstance(e, SerialException): - raise FileSystemException(_ERROR_CONNECT_FILESYSTEM % str(e)) - raise e - - def disconnect(self): - """ - Disconnects the file system manager and restores the device connection. - - Raises: - XBeeException: if there is any error restoring the XBee device connection. - """ - if not self._is_connected: - return - - # Exit AT command mode. - self._exit_atcmd_mode() - - # Restore serial port timeout. - try: - self._serial_port.set_read_timeout(self._old_read_timeout) - except SerialException: - pass - self._serial_port.close() - self._is_connected = False - if self._device_was_connected: - time.sleep(0.3) - self._xbee_device.open() - - def get_current_directory(self): - """ - Returns the current device directory. - - Returns: - String: the current device directory. - - Raises: - FileSystemException: if there is any error getting the current directory or the function is not supported. - """ - _log.info("Retrieving working directory") - return self._execute_command(_FilesystemFunction.PWD).replace("\r", "") - - def change_directory(self, directory): - """ - Changes the current device working directory to the given one. - - Args: - directory (String): the new directory to change to. - - Returns: - String: the current device working directory after the directory change. - - Raises: - FileSystemException: if there is any error changing the current directory or the function is not supported. - """ - # Sanity checks. - if not directory: - return - - _log.info("Navigating to directory '%s'" % directory) - return self._execute_command(_FilesystemFunction.CD, directory).replace("\r", "") - - def make_directory(self, directory): - """ - Creates the provided directory. - - Args: - directory (String): the new directory to create. - - Raises: - FileSystemException: if there is any error creating the directory or the function is not supported. - """ - # Sanity checks. - if not directory or directory == "/": - return - - current_dir = self.get_current_directory() - try: - # Create intermediate directories in case it is required. - temp_path = "/" if directory.startswith("/") else current_dir - directory_chunks = directory.split("/") - for chunk in directory_chunks: - if not chunk: - continue - if not temp_path.endswith("/"): - temp_path += "/" - temp_path += chunk - # Check if directory exists by navigating to it. - try: - self.change_directory(temp_path) - except FileSystemException: - # Directory does not exist, create it. - _log.info("Creating directory '%s'" % temp_path) - self._execute_command(_FilesystemFunction.MD, temp_path) - finally: - self.change_directory(current_dir) - - def list_directory(self, directory=None): - """ - Lists the contents of the given directory. - - Args: - directory (String, optional): the directory to list its contents. Optional. If not provided, the current - directory contents are listed. - - Returns: - List: list of ``:class:`.FilesystemElement``` objects contained in the given (or current) directory. - - Raises: - FileSystemException: if there is any error listing the directory contents or the function is not supported. - """ - if not directory: - _log.info("Listing directory contents of current dir") - answer = self._execute_command(_FilesystemFunction.LS) - else: - _log.info("Listing directory contents of '%s'" % directory) - answer = self._execute_command(_FilesystemFunction.LS_DIR, directory) - - path = self.get_current_directory() if directory is None else directory - if path != _PATH_SEPARATOR: - path += _PATH_SEPARATOR - filesystem_elements = [] - lines = answer.split("\r") - for line in lines: - # Ignore empty lines. - if len(str.strip(line)) == 0: - continue - result = re.match(_PATTERN_FILE_SYSTEM_DIRECTORY, line) - if result is not None and len(result.groups()) > 0: - name = result.groups()[0] - filesystem_elements.append(FileSystemElement(name, path + name, is_directory=True)) - else: - result = re.match(_PATTERN_FILE_SYSTEM_FILE, line) - if result is not None and len(result.groups()) > 1: - name = result.groups()[1] - size = int(result.groups()[0]) - filesystem_elements.append(FileSystemElement(name, path + name, size=size)) - else: - _log.warning("Unknown filesystem element entry: %s" % line) - - return filesystem_elements - - def remove_element(self, element_path): - """ - Removes the given file system element path. - - Args: - element_path (String): path of the file system element to remove. - - Raises: - FileSystemException: if there is any error removing the element or the function is not supported. - """ - # Sanity checks. - if not element_path: - return - - _log.info("Removing file '%s'" % element_path) - self._execute_command(_FilesystemFunction.RM, element_path) - - def move_element(self, source_path, dest_path): - """ - Moves the given source element to the given destination path. - - Args: - source_path (String): source path of the element to move. - dest_path (String): destination path of the element to move. - - Raises: - FileSystemException: if there is any error moving the element or the function is not supported. - """ - # Sanity checks. - if not source_path or not dest_path: - return - - _log.info("Moving file '%s' to '%s'" % (source_path, dest_path)) - self._execute_command(_FilesystemFunction.MV, source_path, dest_path) - - def put_file(self, source_path, dest_path, secure=False, progress_callback=None): - """ - Transfers the given file in the specified destination path of the XBee device. - - Args: - source_path (String): the path of the file to transfer. - dest_path (String): the destination path to put the file in. - secure (Boolean, optional): ``True`` if the file should be stored securely, ``False`` otherwise. Defaults to - ``False``. - progress_callback (Function, optional): function to execute to receive progress information. - - Takes the following arguments: - - * The progress percentage as integer. - - Raises: - FileSystemException: if there is any error transferring the file or the function is not supported. - """ - # Sanity checks. - if secure and not self._is_function_supported(_FilesystemFunction.XPUT): - raise FileSystemException(_ERROR_FUNCTION_NOT_SUPPORTED % _FilesystemFunction.XPUT.name) - if not secure and not self._is_function_supported(_FilesystemFunction.PUT): - raise FileSystemException(_ERROR_FUNCTION_NOT_SUPPORTED % _FilesystemFunction.PUT.name) - - # Create intermediate directories if required. - dest_parent = os.path.dirname(dest_path) - if len(dest_parent) == 0: - dest_parent = self.get_current_directory() - self.make_directory(dest_parent) - - # Initial XBee3 firmware does not allow to overwrite existing files. - # If the file to upload already exists, remove it first. - if not self._is_function_supported(_FilesystemFunction.MV): - dest_name = os.path.basename(dest_path) - elements = self.list_directory(dest_parent) - for element in elements: - if not element.is_directory and element.name == dest_name: - self.remove_element(element.path) - break - - _log.info("Uploading file '%s' to '%s'" % (source_path, dest_path)) - command = _COMMAND_ATFS % (_FilesystemFunction.XPUT.command % dest_path) if secure else \ - _COMMAND_ATFS % (_FilesystemFunction.PUT.command % dest_path) - answer = self._execute_command(_FilesystemFunction.XPUT, dest_path) if secure else \ - self._execute_command(_FilesystemFunction.PUT, dest_path) - if not answer.endswith(xmodem.XMODEM_CRC): - raise FileSystemException(_ERROR_EXECUTE_COMMAND % (command.replace("\r", ""), "Transfer not ready")) - # Transfer the file. - try: - xmodem.send_file_ymodem(source_path, self._xmodem_write_cb, self._xmodem_read_cb, - progress_cb=progress_callback, log=_log) - except XModemException as e: - raise FileSystemException(_ERROR_EXECUTE_COMMAND % (command.replace("\r", ""), str(e))) - # Read operation result. - answer = self._read_data(timeout=_READ_DATA_TIMEOUT, empty_retries=_READ_EMPTY_DATA_RETRIES) - if not answer: - raise FileSystemException(_ERROR_TIMEOUT) - self._check_function_error(answer, command) - - def put_dir(self, source_dir, dest_dir=None, progress_callback=None): - """ - Uploads the given source directory contents into the given destination directory in the device. - - Args: - source_dir (String): the local directory to upload its contents. - dest_dir (String, optional): the remote directory to upload the contents to. Defaults to current directory. - progress_callback (Function, optional): function to execute to receive progress information. - - Takes the following arguments: - - * The file being uploaded as string. - * The progress percentage as integer. - - Raises: - FileSystemException: if there is any error uploading the directory or the function is not supported. - """ - # Sanity checks. - if not source_dir: - return - - # First make sure destination directory exists. - if dest_dir is None: - dest_dir = self.get_current_directory() - else: - self.make_directory(dest_dir) - # Upload directory contents. - for file in listdir(source_dir): - if isfile(os.path.join(source_dir, file)): - bound_callback = None if progress_callback is None \ - else functools.partial(progress_callback, *[str(os.path.join(dest_dir, file))]) - self.put_file(str(os.path.join(source_dir, file)), str(os.path.join(dest_dir, file)), - progress_callback=bound_callback) - else: - self.put_dir(str(os.path.join(source_dir, file)), str(os.path.join(dest_dir, file)), - progress_callback=progress_callback) - - def get_file(self, source_path, dest_path, progress_callback=None): - """ - Downloads the given XBee device file in the specified destination path. - - Args: - source_path (String): the path of the XBee device file to download. - dest_path (String): the destination path to store the file in. - progress_callback (Function, optional): function to execute to receive progress information. - - Takes the following arguments: - - * The progress percentage as integer. - - Raises: - FileSystemException: if there is any error downloading the file or the function is not supported. - """ - command = _COMMAND_ATFS % (_FilesystemFunction.GET.command % source_path) - _log.info("Downloading file '%s' to '%s'" % (source_path, dest_path)) - self._execute_command(_FilesystemFunction.GET, source_path, wait_for_answer=False) - try: - # Consume data until 'NAK' is received. - deadline = _get_milliseconds() + (_NAK_TIMEOUT * 1000) - nak_received = False - while not nak_received and _get_milliseconds() < deadline: - data = self._xmodem_read_cb(1, timeout=_TRANSFER_TIMEOUT) - if data and data[0] == xmodem.XMODEM_NAK: - nak_received = True - if not nak_received: - raise FileSystemException(_ERROR_EXECUTE_COMMAND % (command.replace("\r", ""), "Transfer not ready")) - except SerialException as e: - raise FileSystemException(_ERROR_EXECUTE_COMMAND % (command.replace("\r", ""), str(e))) - # Receive the file. - try: - xmodem.get_file_ymodem(dest_path, self._xmodem_write_cb, self._xmodem_read_cb, - progress_cb=progress_callback, log=_log) - except XModemException as e: - raise FileSystemException(_ERROR_EXECUTE_COMMAND % (command.replace("\r", ""), str(e))) - # Read operation result. - answer = self._read_data() - if not answer: - raise FileSystemException(_ERROR_TIMEOUT) - self._check_function_error(answer, command) - - def format_filesystem(self): - """ - Formats the device file system. - - Raises: - FileSystemException: if there is any error formatting the file system. - """ - command = _COMMAND_ATFS % _FilesystemFunction.FORMAT.command - _log.info("Formatting file system...") - self._execute_command(_FilesystemFunction.FORMAT, wait_for_answer=False) - try: - deadline = _get_milliseconds() + (_FORMAT_TIMEOUT * 1000) - ok_received = False - while not ok_received and _get_milliseconds() < deadline: - answer = self._read_data() - self._check_function_error(answer, command) - if _COMMAND_MODE_ANSWER_OK in answer: - ok_received = True - if not ok_received: - raise FileSystemException(_ERROR_TIMEOUT) - except SerialException as e: - raise FileSystemException(_ERROR_EXECUTE_COMMAND % (command.replace("\r", ""), str(e))) - - def get_usage_information(self): - """ - Returns the file system usage information. - - Returns: - Dictionary: collection of pair values describing the usage information. - - Raises: - FileSystemException: if there is any error retrieving the file system usage information. - """ - _log.info("Reading file system usage information...") - answer = self._execute_command(_FilesystemFunction.INFO) - info = {} - parts = str.strip(answer).split("\r") - for part in parts: - result = re.match(_PATTERN_FILE_SYSTEM_INFO, part) - if result is not None and len(result.groups()) > 1: - info[result.groups()[1]] = result.groups()[0] - - return info - - def get_file_hash(self, file_path): - """ - Returns the SHA256 hash of the given file path. - - Args: - file_path (String): path of the file to get its hash. - - Returns: - String: the SHA256 hash of the given file path. - - Raises: - FileSystemException: if there is any error retrieving the file hash. - """ - _log.info("Retrieving SHA256 hash of file '%s'..." % file_path) - answer = self._execute_command(_FilesystemFunction.HASH, file_path) - parts = answer.split(_ANSWER_SHA256) - if len(parts) <= 1: - raise FileSystemException(_ERROR_EXECUTE_COMMAND % - ((_COMMAND_ATFS % (_FilesystemFunction.HASH.command % - file_path)).replace("\r", ""), "Invalid hash received")) - - return str.strip(parts[1]) - - -def _get_milliseconds(): - """ - Returns the current time in milliseconds. - - Returns: - Integer: the current time in milliseconds. - """ - return int(time.time() * 1000.0) - - -def _filter_non_printable(byte_array): - """ - Filters the non printable characters of the given byte array and returns the resulting string. - - Args: - byte_array (Bytearray): the byte array to filter. - - Return: - String: the resulting string after filtering non printable characters of the byte array. - """ - return bytes(x for x in byte_array if x in _printable_ascii_bytes).decode() diff --git a/digi/xbee/firmware.py b/digi/xbee/firmware.py deleted file mode 100644 index 3cfb3df..0000000 --- a/digi/xbee/firmware.py +++ /dev/null @@ -1,2889 +0,0 @@ -# Copyright 2019, 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 logging -import os -import re -import serial -import time - -from abc import ABC, abstractmethod -from digi.xbee.exception import XBeeException, FirmwareUpdateException, TimeoutException -from digi.xbee.devices import AbstractXBeeDevice, RemoteXBeeDevice -from digi.xbee.models.atcomm import ATStringCommand -from digi.xbee.models.hw import HardwareVersion -from digi.xbee.models.protocol import XBeeProtocol -from digi.xbee.models.status import TransmitStatus -from digi.xbee.packets.aft import ApiFrameType -from digi.xbee.packets.common import ExplicitAddressingPacket, TransmitStatusPacket -from digi.xbee.serial import FlowControl -from digi.xbee.serial import XBeeSerialPort -from digi.xbee.util import utils -from digi.xbee.util import xmodem -from digi.xbee.util.xmodem import XModemException, XModemCancelException -from enum import Enum, unique -from pathlib import Path -from serial.serialutil import SerialException -from threading import Event -from threading import Thread -from xml.etree import ElementTree -from xml.etree.ElementTree import ParseError - -_BOOTLOADER_OPTION_RUN_FIRMWARE = "2" -_BOOTLOADER_OPTION_UPLOAD_GBL = "1" -_BOOTLOADER_PROMPT = "BL >" -_BOOTLOADER_PORT_PARAMETERS = {"baudrate": 115200, - "bytesize": serial.EIGHTBITS, - "parity": serial.PARITY_NONE, - "stopbits": serial.STOPBITS_ONE, - "xonxoff": False, - "dsrdtr": False, - "rtscts": False, - "timeout": 0.1, - "write_timeout": None, - "inter_byte_timeout": None - } -_BOOTLOADER_TEST_CHARACTER = "\n" -_BOOTLOADER_TIMEOUT = 60 # seconds -_BOOTLOADER_VERSION_SEPARATOR = "." -_BOOTLOADER_VERSION_SIZE = 3 -_BOOTLOADER_XBEE3_FILE_PREFIX = "xb3-boot-rf_" - -_BUFFER_SIZE_INT = 4 -_BUFFER_SIZE_SHORT = 2 -_BUFFER_SIZE_STRING = 32 - -_COMMAND_EXECUTE_RETRIES = 3 - -_READ_BUFFER_LEN = 256 -_READ_DATA_TIMEOUT = 3 # Seconds. - -_DEFAULT_RESPONSE_PACKET_PAYLOAD_SIZE = 5 - -_DEVICE_BREAK_RESET_TIMEOUT = 10 # seconds -_DEVICE_CONNECTION_RETRIES = 3 - -_ERROR_BOOTLOADER_MODE = "Could not enter in bootloader mode" -_ERROR_COMPATIBILITY_NUMBER = "Device compatibility number (%d) is greater than the firmware one (%d)" -_ERROR_CONNECT_DEVICE = "Could not connect with XBee device after %s retries" -_ERROR_CONNECT_SERIAL_PORT = "Could not connect with serial port: %s" -_ERROR_DEFAULT_RESPONSE_UNKNOWN_ERROR = "Unknown error" -_ERROR_DEVICE_PROGRAMMING_MODE = "Could not put XBee device into programming mode" -_ERROR_FILE_XBEE_FIRMWARE_NOT_FOUND = "Could not find XBee binary firmware file '%s'" -_ERROR_FILE_XML_FIRMWARE_NOT_FOUND = "XML firmware file does not exist" -_ERROR_FILE_XML_FIRMWARE_NOT_SPECIFIED = "XML firmware file must be specified" -_ERROR_FILE_BOOTLOADER_FIRMWARE_NOT_FOUND = "Could not find bootloader binary firmware file '%s'" -_ERROR_FIRMWARE_START = "Could not start the new firmware" -_ERROR_FIRMWARE_UPDATE_BOOTLOADER = "Bootloader update error: %s" -_ERROR_FIRMWARE_UPDATE_XBEE = "XBee firmware update error: %s" -_ERROR_HARDWARE_VERSION_DIFFER = "Device hardware version (%d) differs from the firmware one (%d)" -_ERROR_HARDWARE_VERSION_NOT_SUPPORTED = "XBee hardware version (%d) does not support this firmware update process" -_ERROR_HARDWARE_VERSION_READ = "Could not read device hardware version" -_ERROR_INVALID_OTA_FILE = "Invalid OTA file: %s" -_ERROR_INVALID_BLOCK = "Requested block index '%s' does not exits" -_ERROR_LOCAL_DEVICE_INVALID = "Invalid local XBee device" -_ERROR_NOT_OTA_FILE = "File '%s' is not an OTA file" -_ERROR_PARSING_OTA_FILE = "Error parsing OTA file: %s" -_ERROR_READ_OTA_FILE = "Error reading OTA file: %s" -_ERROR_REGION_LOCK = "Device region (%d) differs from the firmware one (%d)" -_ERROR_REMOTE_DEVICE_INVALID = "Invalid remote XBee device" -_ERROR_RESTORE_TARGET_CONNECTION = "Could not restore target connection: %s" -_ERROR_RESTORE_UPDATER_DEVICE = "Error restoring updater device: %s" -_ERROR_SEND_IMAGE_NOTIFY = "Error sending 'Image notify' frame: %s" -_ERROR_SEND_OTA_BLOCK = "Error sending send OTA block '%s' frame: %s" -_ERROR_SEND_QUERY_NEXT_IMAGE_RESPONSE = "Error sending 'Query next image response' frame: %s" -_ERROR_SEND_UPGRADE_END_RESPONSE = "Error sending 'Upgrade end response' frame: %s" -_ERROR_TARGET_INVALID = "Invalid update target" -_ERROR_TRANSFER_OTA_FILE = "Error transferring OTA file: %s" -_ERROR_UPDATER_READ_PARAMETER = "Error reading updater '%s' parameter" -_ERROR_UPDATER_SET_PARAMETER = "Error setting updater '%s' parameter" -_ERROR_XML_PARSE = "Could not parse XML firmware file %s" -_ERROR_XMODEM_COMMUNICATION = "XModem serial port communication error: %s" -_ERROR_XMODEM_RESTART = "Could not restart firmware transfer sequence" -_ERROR_XMODEM_START = "Could not start XModem firmware upload process" - -_EXPLICIT_PACKET_BROADCAST_RADIUS_MAX = 0x00 -_EXPLICIT_PACKET_CLUSTER_ID = 0x0019 -_EXPLICIT_PACKET_ENDPOINT_DATA = 0xE8 -_EXPLICIT_PACKET_PROFILE_DIGI = 0xC105 -_EXPLICIT_PACKET_EXTENDED_TIMEOUT = 0x40 - -_EXTENSION_GBL = ".gbl" -_EXTENSION_OTA = ".ota" -_EXTENSION_OTB = ".otb" - -_IMAGE_BLOCK_REQUEST_PACKET_PAYLOAD_SIZE = 17 - -_NOTIFY_PACKET_DEFAULT_QUERY_JITTER = 0x64 -_NOTIFY_PACKET_PAYLOAD_SIZE = 12 -_NOTIFY_PACKET_PAYLOAD_TYPE = 0x03 - -_OTA_FILE_IDENTIFIER = 0x0BEEF11E -_OTA_DEFAULT_BLOCK_SIZE = 64 -_OTA_GBL_SIZE_BYTE_COUNT = 6 - -_PACKET_DEFAULT_SEQ_NUMBER = 0x01 - -_PARAMETER_BOOTLOADER_VERSION = ATStringCommand.VH.command # Answer examples: 01 81 -> 1.8.1 - 0F 3E -> 15.3.14 -_PARAMETER_READ_RETRIES = 3 -_PARAMETER_SET_RETRIES = 3 - -_PATTERN_GECKO_BOOTLOADER_COMPATIBILITY_FULL = "^.*Gecko Bootloader.*\\(([0-9a-fA-F]{4})-([0-9a-fA-F]{2})(.*)\\).*$" -_PATTERN_GECKO_BOOTLOADER_VERSION = "^.*Gecko Bootloader v([0-9a-fA-F]{1}\\.[0-9a-fA-F]{1}\\.[0-9a-fA-F]{1}).*$" - -_PROGRESS_TASK_UPDATE_BOOTLOADER = "Updating bootloader" -_PROGRESS_TASK_UPDATE_REMOTE_XBEE = "Updating remote XBee firmware" -_PROGRESS_TASK_UPDATE_XBEE = "Updating XBee firmware" - -_REGION_ALL = 0 - -_REMOTE_FIRMWARE_UPDATE_DEFAULT_TIMEOUT = 20 # Seconds - -_SEND_BLOCK_RETRIES = 5 - -_TIME_DAYS_1970TO_2000 = 10957 -_TIME_SECONDS_1970_TO_2000 = _TIME_DAYS_1970TO_2000 * 24 * 60 * 60 - -_UPGRADE_END_REQUEST_PACKET_PAYLOAD_SIZE = 12 - -_VALUE_API_OUTPUT_MODE_EXPLICIT = 0x01 -_VALUE_BAUDRATE_230400 = 0x08 -_VALUE_BROADCAST_ADDRESS = bytearray([0xFF, 0xFF]) -_VALUE_UNICAST_RETRIES_MEDIUM = 0x06 - -_XML_BOOTLOADER_VERSION = "firmware/bootloader_version" -_XML_COMPATIBILITY_NUMBER = "firmware/compatibility_number" -_XML_FIRMWARE = "firmware" -_XML_FIRMWARE_VERSION_ATTRIBUTE = "fw_version" -_XML_HARDWARE_VERSION = "firmware/hw_version" -_XML_REGION_LOCK = "firmware/region" -_XML_UPDATE_TIMEOUT = "firmware/update_timeout_ms" - -_XMODEM_READY_TO_RECEIVE_CHAR = "C" -_XMODEM_START_TIMEOUT = 3 # seconds - -_ZDO_COMMAND_ID_DEFAULT_RESP = 0x0B -_ZDO_COMMAND_ID_IMG_BLOCK_REQ = 0x03 -_ZDO_COMMAND_ID_IMG_BLOCK_RESP = 0x05 -_ZDO_COMMAND_ID_IMG_NOTIFY_REQ = 0x00 -_ZDO_COMMAND_ID_QUERY_NEXT_IMG_REQ = 0x01 -_ZDO_COMMAND_ID_QUERY_NEXT_IMG_RESP = 0x02 -_ZDO_COMMAND_ID_UPGRADE_END_REQ = 0x06 -_ZDO_COMMAND_ID_UPGRADE_END_RESP = 0x07 - -_ZDO_FRAME_CONTROL_CLIENT_TO_SERVER = 0x01 -_ZDO_FRAME_CONTROL_GLOBAL = 0x00 -_ZDO_FRAME_CONTROL_SERVER_TO_CLIENT = 0x09 - -_ZIGBEE_FW_VERSION_LIMIT_FOR_GBL = int("1003", 16) - -SUPPORTED_HARDWARE_VERSIONS = (HardwareVersion.XBEE3.code, - HardwareVersion.XBEE3_SMT.code, - HardwareVersion.XBEE3_TH.code) - -_log = logging.getLogger(__name__) - - -class _OTAFile(object): - """ - Helper class that represents an OTA firmware file to be used in remote firmware updates. - """ - - def __init__(self, file_path): - """ - Class constructor. Instantiates a new :class:`._OTAFile` with the given parameters. - - Args: - file_path (String): the path of the OTA file. - """ - self._file_path = file_path - self._header_version = None - self._header_length = None - self._header_field_control = None - self._manufacturer_code = None - self._image_type = None - self._file_version = None - self._zigbee_stack_version = None - self._header_string = None - self._total_size = None - self._gbl_size = None - self._chunk_size = _OTA_DEFAULT_BLOCK_SIZE - self._file_size = 0 - self._num_chunks = 0 - self._discard_size = 0 - self._file = None - - def parse_file(self): - """ - Parses the OTA file and stores useful information of the file. - - Raises: - _ParsingOTAException: if there is any problem parsing the OTA file. - """ - _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)): - raise _ParsingOTAException(_ERROR_INVALID_OTA_FILE % self._file_path) - - try: - with open(self._file_path, "rb") as file: - identifier = utils.bytes_to_int(_reverse_bytearray(file.read(_BUFFER_SIZE_INT))) - if identifier != _OTA_FILE_IDENTIFIER: - raise _ParsingOTAException(_ERROR_NOT_OTA_FILE % self._file_path) - _log.debug(" - Identifier: %d" % identifier) - self._header_version = utils.bytes_to_int(_reverse_bytearray(file.read(_BUFFER_SIZE_SHORT))) - _log.debug(" - Header version: %d" % self._header_version) - self._header_length = utils.bytes_to_int(_reverse_bytearray(file.read(_BUFFER_SIZE_SHORT))) - _log.debug(" - Header length: %d" % self._header_length) - self._header_field_control = utils.bytes_to_int(_reverse_bytearray(file.read(_BUFFER_SIZE_SHORT))) - _log.debug(" - Header field control: %d" % self._header_field_control) - self._manufacturer_code = utils.bytes_to_int(_reverse_bytearray(file.read(_BUFFER_SIZE_SHORT))) - _log.debug(" - Manufacturer code: %d" % self._manufacturer_code) - self._image_type = utils.bytes_to_int(_reverse_bytearray(file.read(_BUFFER_SIZE_SHORT))) - _log.debug(" - Image type: %d" % self._image_type) - self._file_version = utils.bytes_to_int(_reverse_bytearray(file.read(_BUFFER_SIZE_INT))) - _log.debug(" - File version: %d" % self._file_version) - self._zigbee_stack_version = utils.bytes_to_int(_reverse_bytearray(file.read(_BUFFER_SIZE_SHORT))) - _log.debug(" - Zigbee stack version: %d" % self._zigbee_stack_version) - self._header_string = _reverse_bytearray(file.read(_BUFFER_SIZE_STRING)).decode(encoding="utf-8") - _log.debug(" - Header string: %s" % self._header_string) - self._total_size = utils.bytes_to_int(_reverse_bytearray(file.read(_BUFFER_SIZE_INT))) - _log.debug(" - Total size: %d" % self._total_size) - self._gbl_size = self._total_size - self._header_length - _OTA_GBL_SIZE_BYTE_COUNT - _log.debug(" - GBL size: %d" % self._gbl_size) - self._file_size = os.path.getsize(self._file_path) - _log.debug(" - File size: %d" % self._file_size) - self._discard_size = self._header_length + _OTA_GBL_SIZE_BYTE_COUNT - _log.debug(" - Discard size: %d" % self._discard_size) - self._num_chunks = (self._file_size - self._discard_size) // self._chunk_size - if (self._file_size - self._discard_size) % self._chunk_size: - self._num_chunks += 1 - _log.debug(" - Number of chunks: %d" % self._num_chunks) - except IOError as e: - raise _ParsingOTAException(_ERROR_PARSING_OTA_FILE % str(e)) - - def get_next_data_chunk(self): - """ - Returns the next data chunk of this file. - - Returns: - Bytearray: the next data chunk of the file as byte array. - - Raises: - _ParsingOTAException: if there is any error reading the OTA file. - """ - try: - if self._file is None: - self._file = open(self._file_path, "rb") - self._file.read(self._discard_size) - - return self._file.read(self._chunk_size) - except IOError as e: - self.close_file() - raise _ParsingOTAException(str(e)) - - def close_file(self): - """ - Closes the file. - """ - if self._file: - self._file.close() - - @property - def file_path(self): - """ - Returns the OTA file path. - - Returns: - String: the OTA file path. - """ - return self._file_path - - @property - def header_version(self): - """ - Returns the OTA file header version. - - Returns: - Integer: the OTA file header version. - """ - return self._header_version - - @property - def header_length(self): - """ - Returns the OTA file header length. - - Returns: - Integer: the OTA file header length. - """ - return self._header_length - - @property - def header_field_control(self): - """ - Returns the OTA file header field control. - - Returns: - Integer: the OTA file header field control. - """ - return self._header_field_control - - @property - def manufacturer_code(self): - """ - Returns the OTA file manufacturer code. - - Returns: - Integer: the OTA file manufacturer code. - """ - return self._manufacturer_code - - @property - def image_type(self): - """ - Returns the OTA file image type. - - Returns: - Integer: the OTA file image type. - """ - return self._image_type - - @property - def file_version(self): - """ - Returns the OTA file version. - - Returns: - Integer: the OTA file version. - """ - return self._file_version - - @property - def zigbee_stack_version(self): - """ - Returns the OTA file zigbee stack version. - - Returns: - Integer: the OTA file zigbee stack version. - """ - return self._zigbee_stack_version - - @property - def header_string(self): - """ - Returns the OTA file header string. - - Returns: - String: the OTA file header string. - """ - return self._header_string - - @property - def total_size(self): - """ - Returns the OTA file total size. - - Returns: - Integer: the OTA file total size. - """ - return self._total_size - - @property - def gbl_size(self): - """ - Returns the OTA file gbl size. - - Returns: - Integer: the OTA file gbl size. - """ - return self._gbl_size - - @property - def chunk_size(self): - """ - Returns the chunk size. - - Returns: - Integer: the chunk size. - """ - return self._chunk_size - - @chunk_size.setter - def chunk_size(self, chunk_size): - """ - Sets the chunk size. - - Args: - chunk_size (Integer): the new chunk size. - """ - self._chunk_size = chunk_size - self._num_chunks = (self._file_size - self._discard_size) // self._chunk_size - if (self._file_size - self._discard_size) % self._chunk_size: - self._num_chunks += 1 - - @property - def num_chunks(self): - """ - Returns the total number of data chunks of this file. - - Returns: - Integer: the total number of data chunks of this file. - """ - return self._num_chunks - - -class _ParsingOTAException(Exception): - """ - This exception will be thrown when any problem related with the parsing of OTA files occurs. - - All functionality of this class is the inherited from `Exception - `_. - """ - pass - - -@unique -class _XBee3OTAStatus(Enum): - """ - This class lists the available file XBee3 OTA status codes. - - | Inherited properties: - | **name** (String): The name of this _XBee3OTAStatus. - | **value** (Integer): The ID of this _XBee3OTAStatus. - """ - SUCCESS = (0x00, "Success") - ERASE_FAILED = (0x05, "Storage erase failed") - NOT_AUTHORIZED = (0x7E, "Not authorized") - MALFORMED_CMD = (0x80, "Malformed command") - UNSUPPORTED_CMD = (0x81, "Unsupported cluster command") - CONTACT_SUPPORT = (0x87, "Contact tech support") - TIMED_OUT = (0x94, "Client timed out") - ABORT = (0x95, "Client aborted upgrade") - INVALID_IMG = (0x96, "Invalid OTA image") - WAIT_FOR_DATA = (0x97, "Wait for data") - NO_IMG_AVAILABLE = (0x98, "No image available") - REQUIRE_MORE_IMG = (0x99, "Require more image") - - def __init__(self, identifier, description): - self.__identifier = identifier - self.__description = description - - @classmethod - def get(cls, identifier): - """ - Returns the _XBee3OTAStatus for the given identifier. - - Args: - identifier (Integer): the identifier of the _XBee3OTAStatus to get. - - Returns: - :class:`._XBee3OTAStatus`: the _XBee3OTAStatus with the given identifier, ``None`` if - there is not a _XBee3OTAStatus with that name. - """ - for value in _XBee3OTAStatus: - if value.identifier == identifier: - return value - - return None - - @property - def identifier(self): - """ - Returns the identifier of the _XBee3OTAStatus element. - - Returns: - Integer: the identifier of the _XBee3OTAStatus element. - """ - return self.__identifier - - @property - def description(self): - """ - Returns the command of the _XBee3OTAStatus element. - - Returns: - String: the command of the _XBee3OTAStatus element. - """ - return self.__description - - -@unique -class _XBeeZigbee3OTAStatus(Enum): - """ - This class lists the available XBee3 Zigbee OTA status codes. - - | Inherited properties: - | **name** (String): The name of this _XBeeZigbee3OTAStatus. - | **value** (Integer): The ID of this _XBeeZigbee3OTAStatus. - """ - SUCCESS = (0x00, "Success") - ZCL_FAILURE = (0x01, "ZCL failure") - NOT_AUTHORIZED = (0x7E, "Server is not authorized to upgrade the client") - INVALID_FIRMWARE = (0x80, "Attempting to upgrade to invalid firmware (Bad Image Type, Wrong Mfg ID, Wrong HW/SW " - "compatibility)") - UNSUPPORTED_CMD_CLUSTER = (0x81, "Such command is not supported on the device cluster") - UNSUPPORTED_CMD_GENERAL = (0x82, "Such command is not a supported general command") - UNSUPPORTED_CMD_MFG_CLUSTER = (0x83, "Such command is not a manufacturer cluster supported command") - UNSUPPORTED_CMD_MFG_GENERAL = (0x84, "Such command is not a manufacturer general supported command") - INVALID_FIELD = (0x85, "Invalid field") - UNSUPPORTED_ATTRIBUTE = (0x86, "Unsupported attribute") - INVALID_VALUE = (0x87, "Invalid value") - READ_ONLY_CMD = (0x88, "Read only command") - INSUFFICIENT_SPACE = (0x89, "Insufficient space") - DUPLICATE_EXISTS = (0x8A, "Duplicate exists") - NOT_FOUND = (0x8B, "Not found") - UNREPORTABLE_ATTRIBUTE = (0x8C, "Unreportable attribute") - INVALID_DATA_TYPE = (0x8D, "Invalid data type") - ABORT = (0x95, "Client aborted upgrade") - INVALID_IMG = (0x96, "Invalid OTA image") - NO_DATA_AVAILABLE = (0x97, "Server does not have data block available yet") - NO_IMG_AVAILABLE = (0x98, "No OTA upgrade image available for a particular client") - REQUIRE_MORE_IMG = (0x99, "The client still requires more OTA upgrade image files in order to successfully upgrade") - HARDWARE_FAILURE = (0xC0, "Hardware failure") - SOFTWARE_FAILURE = (0xC1, "Software failure") - CALIBRATION_ERROR = (0xC2, "Calibration error") - - def __init__(self, identifier, description): - self.__identifier = identifier - self.__description = description - - @classmethod - def get(cls, identifier): - """ - Returns the _XBeeZigbee3OTAStatus for the given identifier. - - Args: - identifier (Integer): the identifier of the _XBeeZigbee3OTAStatus to get. - - Returns: - :class:`._XBeeZigbee3OTAStatus`: the _XBeeZigbee3OTAStatus with the given identifier, ``None`` if - there is not a _XBeeZigbee3OTAStatus with that name. - """ - for value in _XBeeZigbee3OTAStatus: - if value.identifier == identifier: - return value - - return None - - @property - def identifier(self): - """ - Returns the identifier of the _XBee3OTAStatus element. - - Returns: - Integer: the identifier of the _XBee3OTAStatus element. - """ - return self.__identifier - - @property - def description(self): - """ - Returns the command of the _XBee3OTAStatus element. - - Returns: - String: the command of the _XBee3OTAStatus element. - """ - return self.__description - - -@unique -class _NextImageMessageStatus(Enum): - """ - This class lists the available XBee3 OTA next image message status codes. - - | Inherited properties: - | **name** (String): The name of this _NextImageMessageStatus. - | **value** (Integer): The ID of this _NextImageMessageStatus. - """ - OUT_OF_SEQUENCE = (0x01, "ZCL OTA Message Out of Sequence") - INCORRECT_FORMAT = (0x80, "Incorrect Query Next Image Response Format") - INVALID_FIRMWARE = (0x85, "Attempting to upgrade to invalid firmware") - FILE_TOO_BIG = (0x89, "Image size is too big") - SAME_FILE = (0x8A, "Please ensure that the image you are attempting to upgrade has a different version than the " - "current version") - - def __init__(self, identifier, description): - self.__identifier = identifier - self.__description = description - - @classmethod - def get(cls, identifier): - """ - Returns the _NextImageMessageStatus for the given identifier. - - Args: - identifier (Integer): the identifier of the _NextImageMessageStatus to get. - - Returns: - :class:`._NextImageMessageStatus`: the _NextImageMessageStatus with the given identifier, ``None`` if - there is not a _NextImageMessageStatus with that name. - """ - for value in _NextImageMessageStatus: - if value.identifier == identifier: - return value - - return None - - @property - def identifier(self): - """ - Returns the identifier of the _NextImageMessageStatus element. - - Returns: - Integer: the identifier of the _NextImageMessageStatus element. - """ - return self.__identifier - - @property - def description(self): - """ - Returns the command of the _NextImageMessageStatus element. - - Returns: - String: the command of the _NextImageMessageStatus element. - """ - return self.__description - - -@unique -class _ImageBlockMessageStatus(Enum): - """ - This class lists the available XBee3 OTA image block message status codes. - - | Inherited properties: - | **name** (String): The name of this _ImageBlockMessageStatus. - | **value** (Integer): The ID of this _ImageBlockMessageStatus. - """ - OUT_OF_SEQUENCE = (0x01, "ZCL OTA Message Out of Sequence") - INCORRECT_FORMAT = (0x80, "Incorrect Image Block Response Format") - FILE_MISMATCH = (0x87, "Upgrade File Mismatch") - - def __init__(self, identifier, description): - self.__identifier = identifier - self.__description = description - - @classmethod - def get(cls, identifier): - """ - Returns the _ImageBlockMessageStatus for the given identifier. - - Args: - identifier (Integer): the identifier of the _ImageBlockMessageStatus to get. - - Returns: - :class:`._ImageBlockMessageStatus`: the _ImageBlockMessageStatus with the given identifier, ``None`` if - there is not a _ImageBlockMessageStatus with that name. - """ - for value in _ImageBlockMessageStatus: - if value.identifier == identifier: - return value - - return None - - @property - def identifier(self): - """ - Returns the identifier of the _ImageBlockMessageStatus element. - - Returns: - Integer: the identifier of the _ImageBlockMessageStatus element. - """ - return self.__identifier - - @property - def description(self): - """ - Returns the command of the _ImageBlockMessageStatus element. - - Returns: - String: the command of the _ImageBlockMessageStatus element. - """ - return self.__description - - -@unique -class _UpgradeEndMessageStatus(Enum): - """ - This class lists the available XBee3 OTA upgrade end message status codes. - - | Inherited properties: - | **name** (String): The name of this _UpgradeEndMessageStatus. - | **value** (Integer): The ID of this _UpgradeEndMessageStatus. - """ - WRONG_FILE = (0x87, "Wrong upgrade file") - - def __init__(self, identifier, description): - self.__identifier = identifier - self.__description = description - - @classmethod - def get(cls, identifier): - """ - Returns the _UpgradeEndMessageStatus for the given identifier. - - Args: - identifier (Integer): the identifier of the _UpgradeEndMessageStatus to get. - - Returns: - :class:`._UpgradeEndMessageStatus`: the _UpgradeEndMessageStatus with the given identifier, ``None`` if - there is not a _UpgradeEndMessageStatus with that name. - """ - for value in _UpgradeEndMessageStatus: - if value.identifier == identifier: - return value - - return None - - @property - def identifier(self): - """ - Returns the identifier of the _UpgradeEndMessageStatus element. - - Returns: - Integer: the identifier of the _UpgradeEndMessageStatus element. - """ - return self.__identifier - - @property - def description(self): - """ - Returns the command of the _UpgradeEndMessageStatus element. - - Returns: - String: the command of the _UpgradeEndMessageStatus element. - """ - return self.__description - - -class _BreakThread(Thread): - """ - Helper class used to manage serial port break line in a parallel thread. - """ - - _break_running = False - - def __init__(self, serial_port, duration): - """ - Class constructor. Instantiates a new :class:`._BreakThread` with the given parameters. - - Args: - serial_port (:class:`.XBeeSerialPort`): The serial port to send the break signal to. - duration (Integer): the duration of the break in seconds. - """ - super().__init__() - self._xbee_serial_port = serial_port - self.duration = duration - self.lock = Event() - - def run(self): - """ - Override method. - .. seealso:: - | :meth:`.Thread.run` - """ - if self._xbee_serial_port is None or _BreakThread.is_running(): - return - - _log.debug("Break thread started") - _BreakThread._break_running = True - self._xbee_serial_port.break_condition = True - self.lock.wait(self.duration) - self._xbee_serial_port.break_condition = False - _BreakThread._break_running = False - _log.debug("Break thread finished") - - def stop_break(self): - """ - Stops the break thread. - """ - if not self.is_running: - return - - self.lock.set() - # Wait until thread finishes. - self.join() - - @staticmethod - def is_running(): - """ - Returns whether the break thread is running or not. - - Returns: - Boolean: ``True`` if the break thread is running, ``False`` otherwise. - """ - return _BreakThread._break_running - - -class _XBeeFirmwareUpdater(ABC): - """ - Helper class used to handle XBee firmware update processes. - """ - - def __init__(self, xml_firmware_file, timeout=_READ_DATA_TIMEOUT, progress_callback=None): - """ - Class constructor. Instantiates a new :class:`._XBeeFirmwareUpdater` with the given parameters. - - Args: - xml_firmware_file (String): location of the XML firmware file. - timeout (Integer, optional): the process operations timeout. - progress_callback (Function, optional): function to execute to receive progress information. Receives two - arguments: - - * The current update task as a String - * The current update task percentage as an Integer - """ - self._xml_firmware_file = xml_firmware_file - self._progress_callback = progress_callback - self._progress_task = None - self._xml_hardware_version = None - self._xml_compatibility_number = None - self._xml_bootloader_version = None - self._xml_region_lock = None - self._xml_update_timeout_ms = None - self._bootloader_update_required = False - self._timeout = timeout - - def _parse_xml_firmware_file(self): - """ - Parses the XML firmware file and stores the required parameters. - - Raises: - FirmwareUpdateException: if there is any error parsing the XML firmware file. - """ - _log.debug("Parsing XML firmware file %s:" % self._xml_firmware_file) - try: - root = ElementTree.parse(self._xml_firmware_file).getroot() - # Firmware version, required. - element = root.find(_XML_FIRMWARE) - if element is None: - self._exit_with_error(_ERROR_XML_PARSE % self._xml_firmware_file, restore_updater=False) - self._xml_firmware_version = int(element.get(_XML_FIRMWARE_VERSION_ATTRIBUTE), 16) - _log.debug(" - Firmware version: %d" % self._xml_firmware_version) - # Hardware version, required. - element = root.find(_XML_HARDWARE_VERSION) - if element is None: - self._exit_with_error(_ERROR_XML_PARSE % self._xml_firmware_file, restore_updater=False) - self._xml_hardware_version = int(element.text, 16) - _log.debug(" - Hardware version: %d" % self._xml_hardware_version) - # Compatibility number, required. - element = root.find(_XML_COMPATIBILITY_NUMBER) - if element is None: - self._exit_with_error(_ERROR_XML_PARSE % self._xml_firmware_file, restore_updater=False) - self._xml_compatibility_number = int(element.text) - _log.debug(" - Compatibility number: %d" % self._xml_compatibility_number) - # Bootloader version, optional. - element = root.find(_XML_BOOTLOADER_VERSION) - if element is not None: - self._xml_bootloader_version = _bootloader_version_to_bytearray(element.text) - _log.debug(" - Bootloader version: %s" % self._xml_bootloader_version) - # Region lock, required. - element = root.find(_XML_REGION_LOCK) - if element is None: - self._exit_with_error(_ERROR_XML_PARSE % self._xml_firmware_file, restore_updater=False) - self._xml_region_lock = int(element.text) - _log.debug(" - Region lock: %d" % self._xml_region_lock) - # Update timeout, optional. - element = root.find(_XML_UPDATE_TIMEOUT) - if element is not None: - self._xml_update_timeout_ms = int(element.text) - _log.debug(" - Update timeout: %s" % self._xml_update_timeout_ms) - except ParseError as e: - _log.exception(e) - self._exit_with_error(_ERROR_XML_PARSE % self._xml_firmware_file, restore_updater=False) - - def _exit_with_error(self, message, restore_updater=True): - """ - Finishes the process raising a :class`.FirmwareUpdateException` and leaves updater in the initial state. - - Args: - message (String): the error message of the exception to raise. - restore_updater (Boolean): ``True`` to restore updater configuration before exiting, ``False`` otherwise. - - Raises: - FirmwareUpdateException: the exception is always thrown in this method. - """ - # Check if updater restore is required. - if restore_updater: - try: - self._restore_updater() - except (SerialException, XBeeException) as e: - _log.error("ERROR: %s" % (_ERROR_RESTORE_TARGET_CONNECTION % str(e))) - _log.error("ERROR: %s" % message) - raise FirmwareUpdateException(message) - - def _check_target_compatibility(self): - """ - Checks whether the target device is compatible with the firmware to update by checking: - - Bootloader version. - - Compatibility number. - - Region lock. - - Hardware version. - - Raises: - FirmwareUpdateException: if the target device is not compatible with the firmware to update. - """ - # At the moment the target checks are the same for local and remote updates since only XBee3 devices - # are supported. This might need to be changed in the future if other hardware is supported. - - # Read device values required for verification steps prior to firmware update. - _log.debug("Reading device settings:") - self._target_firmware_version = self._get_target_firmware_version() - _log.debug(" - Firmware version: %s" % self._target_firmware_version) - self._target_hardware_version = self._get_target_hardware_version() - if self._target_hardware_version is None: - self._exit_with_error(_ERROR_HARDWARE_VERSION_READ) - _log.debug(" - Hardware version: %s" % self._target_hardware_version) - self._target_compatibility_number = self._get_target_compatibility_number() - _log.debug(" - Compatibility number: %s" % self._target_compatibility_number) - self._target_bootloader_version = self._get_target_bootloader_version() - _log.debug(" - Bootloader version: %s" % self._target_bootloader_version) - self._target_region_lock = self._get_target_region_lock() - _log.debug(" - Region lock: %s" % self._target_region_lock) - - # Check if the hardware version is compatible with the firmware update process. - if self._target_hardware_version not in SUPPORTED_HARDWARE_VERSIONS: - self._exit_with_error(_ERROR_HARDWARE_VERSION_NOT_SUPPORTED % self._target_hardware_version) - - # Check if device hardware version is compatible with the firmware. - if self._target_hardware_version != self._xml_hardware_version: - self._exit_with_error(_ERROR_HARDWARE_VERSION_DIFFER % (self._target_hardware_version, - self._xml_hardware_version)) - - # Check compatibility number. - if self._target_compatibility_number and self._target_compatibility_number > \ - self._xml_compatibility_number: - self._exit_with_error(_ERROR_COMPATIBILITY_NUMBER % (self._target_compatibility_number, - self._xml_compatibility_number)) - - # Check region lock for compatibility numbers greater than 1. - if self._target_compatibility_number and self._target_compatibility_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: - self._exit_with_error(_ERROR_REGION_LOCK % (self._target_region_lock, self._xml_region_lock)) - - # Check whether bootloader update is required. - self._bootloader_update_required = self._check_bootloader_update_required() - - def _check_bootloader_update_required(self): - """ - Checks whether the bootloader needs to be updated or not - - Returns: - Boolean: ``True`` if the bootloader needs to be updated, ``False`` otherwise - """ - # If any bootloader version is None (the XML firmware file one or the device one), update is not required. - if None in (self._xml_bootloader_version, self._target_bootloader_version): - return False - - # At this point we can ensure both bootloader versions are not None and they are 3 bytes long. - # Since the bootloader cannot be downgraded, the XML specifies the minimum required bootloader - # version to update the firmware. Return `True` only if the specified XML bootloader version is - # greater than the target one. - for i in range(len(self._xml_bootloader_version)): - if self._xml_bootloader_version[i] != self._target_bootloader_version[i]: - return self._xml_bootloader_version[i] > self._target_bootloader_version[i] - - return False - - @abstractmethod - def _get_default_reset_timeout(self): - """ - Returns the default timeout to wait for reset. - """ - pass - - def _wait_for_target_reset(self): - """ - Waits for the device to reset using the xml firmware file specified timeout or the default one. - """ - if self._xml_update_timeout_ms is not None: - time.sleep(self._xml_update_timeout_ms / 1000.0) - else: - time.sleep(self._get_default_reset_timeout()) - - def update_firmware(self): - """ - Updates the firmware of the XBee device. - """ - # Start by parsing the XML firmware file. - self._parse_xml_firmware_file() - - # Verify that the binary firmware file exists. - self._check_firmware_binary_file() - - # Configure the updater device. - self._configure_updater() - - # Check if updater is able to perform firmware updates. - self._check_updater_compatibility() - - # Check if target is compatible with the firmware to update. - self._check_target_compatibility() - - # Check bootloader update file exists if required. - _log.debug("Bootloader update required? %s" % self._bootloader_update_required) - if self._bootloader_update_required: - self._check_bootloader_binary_file() - - # Start the firmware update process. - self._start_firmware_update() - - # Transfer firmware file(s). - self._transfer_firmware() - - # Finish the firmware update process. - self._finish_firmware_update() - - # Leave updater in its original state. - try: - self._restore_updater() - except Exception as e: - raise FirmwareUpdateException(_ERROR_RESTORE_TARGET_CONNECTION % str(e)) - - # Wait for target to reset. - self._wait_for_target_reset() - - _log.info("Update process finished successfully") - - @abstractmethod - def _check_updater_compatibility(self): - """ - Verifies whether the updater device is compatible with firmware update or not. - """ - pass - - @abstractmethod - def _check_firmware_binary_file(self): - """ - Verifies that the firmware binary file exists. - """ - pass - - @abstractmethod - def _check_bootloader_binary_file(self): - """ - Verifies that the bootloader binary file exists. - """ - pass - - @abstractmethod - def _get_target_bootloader_version(self): - """ - Returns the update target bootloader version. - - Returns: - Bytearray: the update target version as byte array, ``None`` if it could not be read. - """ - pass - - @abstractmethod - def _get_target_compatibility_number(self): - """ - Returns the update target compatibility number. - - Returns: - Integer: the update target compatibility number as integer, ``None`` if it could not be read. - """ - pass - - @abstractmethod - def _get_target_region_lock(self): - """ - Returns the update target region lock number. - - Returns: - Integer: the update target region lock number as integer, ``None`` if it could not be read. - """ - pass - - @abstractmethod - def _get_target_hardware_version(self): - """ - Returns the update target hardware version. - - Returns: - Integer: the update target hardware version as integer, ``None`` if it could not be read. - """ - pass - - @abstractmethod - def _get_target_firmware_version(self): - """ - Returns the update target firmware version. - - Returns: - Integer: the update target firmware version as integer, ``None`` if it could not be read. - """ - pass - - @abstractmethod - def _configure_updater(self): - """ - Configures the updater device before performing the firmware update operation. - """ - pass - - @abstractmethod - def _restore_updater(self): - """ - Leaves the updater device to its original state before the update operation. - """ - pass - - @abstractmethod - def _start_firmware_update(self): - """ - Starts the firmware update process. Called just before the transfer firmware operation. - """ - pass - - @abstractmethod - def _transfer_firmware(self): - """ - Transfers the firmware file(s) to the target. - """ - pass - - @abstractmethod - def _finish_firmware_update(self): - """ - Finishes the firmware update process. Called just after the transfer firmware operation. - """ - pass - - -class _LocalFirmwareUpdater(_XBeeFirmwareUpdater): - """ - Helper class used to handle the local firmware update process. - """ - - __DEVICE_RESET_TIMEOUT = 3 # seconds - - def __init__(self, target, xml_firmware_file, xbee_firmware_file=None, bootloader_firmware_file=None, - timeout=_READ_DATA_TIMEOUT, progress_callback=None): - """ - Class constructor. Instantiates a new :class:`._LocalFirmwareUpdater` with the given parameters. - - Args: - target (String or :class:`.XBeeDevice`): target of the firmware upload operation. - String: serial port identifier. - :class:`.XBeeDevice`: the XBee device to upload its firmware. - xml_firmware_file (String): location of the XML firmware file. - xbee_firmware_file (String, optional): location of the XBee binary firmware file. - bootloader_firmware_file (String, optional): location of the bootloader binary firmware file. - timeout (Integer, optional): the serial port read data operation timeout. - progress_callback (Function, optional): function to execute to receive progress information. Receives two - arguments: - - * The current update task as a String - * The current update task percentage as an Integer - """ - super(_LocalFirmwareUpdater, self).__init__(xml_firmware_file, timeout=timeout, - progress_callback=progress_callback) - - self._xbee_firmware_file = xbee_firmware_file - self._bootloader_firmware_file = bootloader_firmware_file - self._xbee_serial_port = None - self._device_port_params = None - self._updater_was_connected = False - if isinstance(target, str): - self._port = target - self._xbee_device = None - else: - self._port = None - self._xbee_device = target - - def _check_firmware_binary_file(self): - """ - Verifies that the firmware binary file exists. - - Raises: - FirmwareUpdateException: if the firmware binary file does not exist or is invalid. - """ - # If not already specified, the binary firmware file is usually in the same folder as the XML firmware file. - if self._xbee_firmware_file is None: - path = Path(self._xml_firmware_file) - self._xbee_firmware_file = str(Path(path.parent).joinpath(path.stem + _EXTENSION_GBL)) - - if not _file_exists(self._xbee_firmware_file): - self._exit_with_error(_ERROR_FILE_XBEE_FIRMWARE_NOT_FOUND % self._xbee_firmware_file, restore_updater=False) - - def _check_bootloader_binary_file(self): - """ - Verifies that the bootloader binary file exists. - - Raises: - FirmwareUpdateException: if the bootloader binary file does not exist or is invalid. - """ - # If not already specified, the bootloader firmware file is usually in the same folder as the XML firmware file. - # The file filename starts with a fixed prefix and includes the bootloader version to update to. - if self._bootloader_firmware_file is None: - path = Path(self._xml_firmware_file) - self._bootloader_firmware_file = str(Path(path.parent).joinpath(_BOOTLOADER_XBEE3_FILE_PREFIX + - str(self._xml_bootloader_version[0]) + - _BOOTLOADER_VERSION_SEPARATOR + - str(self._xml_bootloader_version[1]) + - _BOOTLOADER_VERSION_SEPARATOR + - str(self._xml_bootloader_version[2]) + - _EXTENSION_GBL)) - - if not _file_exists(self._bootloader_firmware_file): - self._exit_with_error(_ERROR_FILE_BOOTLOADER_FIRMWARE_NOT_FOUND % self._bootloader_firmware_file) - - def _is_bootloader_active(self): - """ - Returns whether the device is in bootloader mode or not. - - Returns: - Boolean: ``True`` if the device is in bootloader mode, ``False`` otherwise. - """ - for i in range(3): - bootloader_header = self._read_bootloader_header() - # Look for the Ember/Gecko bootloader prompt. - if bootloader_header is not None and _BOOTLOADER_PROMPT in bootloader_header: - return True - time.sleep(0.2) - - return False - - def _read_bootloader_header(self): - """ - Attempts to read the bootloader header. - - Returns: - String: the bootloader header, ``None`` if it could not be read. - """ - try: - self._xbee_serial_port.purge_port() - self._xbee_serial_port.write(str.encode(_BOOTLOADER_TEST_CHARACTER)) - read_bytes = self._xbee_serial_port.read(_READ_BUFFER_LEN) - except SerialException as e: - _log.exception(e) - return None - - if len(read_bytes) > 0: - try: - return bytes.decode(read_bytes) - except UnicodeDecodeError: - pass - - return None - - def _enter_bootloader_mode_with_break(self): - """ - Attempts to put the device in bootloader mode using the Break line. - - Returns: - Boolean: ``True`` if the device was set in bootloader mode, ``False`` otherwise. - """ - _log.debug("Setting device in bootloader mode using the Break line") - # The process requires RTS line to be disabled and Break line to be asserted during some time. - self._xbee_serial_port.rts = 0 - break_thread = _BreakThread(self._xbee_serial_port, _DEVICE_BREAK_RESET_TIMEOUT) - break_thread.start() - # Loop during some time looking for the bootloader header. - deadline = _get_milliseconds() + (_BOOTLOADER_TIMEOUT * 1000) - while _get_milliseconds() < deadline: - if self._is_bootloader_active(): - if break_thread.is_running(): - break_thread.stop_break() - return True - - # Re-assert lines to try break process again until timeout expires. - if not break_thread.is_running(): - self._xbee_serial_port.rts = 0 - break_thread = _BreakThread(self._xbee_serial_port, _DEVICE_BREAK_RESET_TIMEOUT) - break_thread.start() - - # Restore break condition. - if break_thread.is_running(): - break_thread.stop_break() - - return False - - def _get_target_bootloader_version(self): - """ - Returns the update target bootloader version. - - Returns: - Bytearray: the update target bootloader version as byte array, ``None`` if it could not be read. - """ - if self._xbee_serial_port is not None: - bootloader_header = self._read_bootloader_header() - if bootloader_header is None: - return None - result = re.match(_PATTERN_GECKO_BOOTLOADER_VERSION, bootloader_header, flags=re.M | re.DOTALL) - if result is None or result.string is not result.group(0) or len(result.groups()) < 1: - return None - - return _bootloader_version_to_bytearray(result.groups()[0]) - else: - return _read_device_bootloader_version(self._xbee_device) - - def _get_target_compatibility_number(self): - """ - Returns the update target compatibility number. - - Returns: - Integer: the update target compatibility number as integer, ``None`` if it could not be read. - """ - if self._xbee_serial_port is not None: - # Assume the device is already in bootloader mode. - bootloader_header = self._read_bootloader_header() - if bootloader_header is None: - return None - result = re.match(_PATTERN_GECKO_BOOTLOADER_COMPATIBILITY_FULL, bootloader_header, flags=re.M | re.DOTALL) - if result is None or result.string is not result.group(0) or len(result.groups()) < 2: - return None - - return int(result.groups()[1]) - else: - return _read_device_compatibility_number(self._xbee_device) - - def _get_target_region_lock(self): - """ - Returns the update target region lock number. - - Returns: - Integer: the update target region lock number as integer, ``None`` if it could not be read. - """ - if self._xbee_serial_port is not None: - # There is no way to retrieve this number from bootloader. - return None - else: - return _read_device_region_lock(self._xbee_device) - - def _get_target_hardware_version(self): - """ - Returns the update target hardware version. - - Returns: - Integer: the update target hardware version as integer, ``None`` if it could not be read. - """ - if self._xbee_serial_port is not None: - # Assume the device is already in bootloader mode. - bootloader_header = self._read_bootloader_header() - if bootloader_header is None: - return None - result = re.match(_PATTERN_GECKO_BOOTLOADER_COMPATIBILITY_FULL, bootloader_header, flags=re.M | re.DOTALL) - if result is None or result.string is not result.group(0) or len(result.groups()) < 1: - return None - - return int(result.groups()[0][:2], 16) - else: - return _read_device_hardware_version(self._xbee_device) - - def _get_target_firmware_version(self): - """ - Returns the update target firmware version. - - Returns: - Integer: the update target firmware version as integer, ``None`` if it could not be read. - """ - if self._xbee_serial_port is not None: - # Assume the device is already in bootloader mode. - bootloader_header = self._read_bootloader_header() - if bootloader_header is None: - return None - result = re.match(_PATTERN_GECKO_BOOTLOADER_COMPATIBILITY_FULL, bootloader_header, flags=re.M | re.DOTALL) - if result is None or result.string is not result.group(0) or len(result.groups()) < 1: - return None - - return int(result.groups()[0][:2], 16) - else: - return _read_device_firmware_version(self._xbee_device) - - def _check_updater_compatibility(self): - """ - Verifies whether the updater device is compatible with firmware update or not. - """ - # In local firmware updates, the updater device and target device are the same. Just return and - # use the target function check instead. - pass - - def _configure_updater(self): - """ - Configures the updater device before performing the firmware update operation. - - Raises: - FirmwareUpdateException: if there is any error configuring the updater device. - """ - # For local updates, target and update device is the same. - # Depending on the given target, process has a different flow (serial port or XBee device). - if self._xbee_device is None: - # Configure serial port connection with bootloader parameters. - try: - _log.debug("Opening port '%s'" % self._port) - self._xbee_serial_port = XBeeSerialPort(_BOOTLOADER_PORT_PARAMETERS["baudrate"], - self._port, - data_bits=_BOOTLOADER_PORT_PARAMETERS["bytesize"], - stop_bits=_BOOTLOADER_PORT_PARAMETERS["stopbits"], - parity=_BOOTLOADER_PORT_PARAMETERS["parity"], - flow_control=FlowControl.NONE, - timeout=_BOOTLOADER_PORT_PARAMETERS["timeout"]) - self._xbee_serial_port.open() - except SerialException as e: - _log.error(_ERROR_CONNECT_SERIAL_PORT % str(e)) - raise FirmwareUpdateException(_ERROR_CONNECT_SERIAL_PORT % str(e)) - - # Check if device is in bootloader mode. - _log.debug("Checking if bootloader is active") - if not self._is_bootloader_active(): - # If the bootloader is not active, enter in bootloader mode. - if not self._enter_bootloader_mode_with_break(): - self._exit_with_error(_ERROR_BOOTLOADER_MODE) - else: - self._updater_was_connected = self._xbee_device.is_open() - _log.debug("Connecting device '%s'" % self._xbee_device) - if not _connect_device_with_retries(self._xbee_device, _DEVICE_CONNECTION_RETRIES): - if not self._set_device_in_programming_mode(): - self._exit_with_error(_ERROR_CONNECT_DEVICE % _DEVICE_CONNECTION_RETRIES) - - def _restore_updater(self): - """ - Leaves the updater device to its original state before the update operation. - - Raises: - SerialException: if there is any error restoring the serial port connection. - XBeeException: if there is any error restoring the device connection. - """ - # For local updates, target and update device is the same. - if self._xbee_device is not None: - if self._xbee_serial_port is not None: - if self._xbee_serial_port.isOpen(): - self._xbee_serial_port.close() - if self._device_port_params is not None: - self._xbee_serial_port.apply_settings(self._device_port_params) - if self._updater_was_connected and not self._xbee_device.is_open(): - self._xbee_device.open() - elif not self._updater_was_connected and self._xbee_device.is_open(): - self._xbee_device.close() - elif self._xbee_serial_port is not None and self._xbee_serial_port.isOpen(): - self._xbee_serial_port.close() - - def _start_firmware_update(self): - """ - Starts the firmware update process. Called just before the transfer firmware operation. - - Raises: - FirmwareUpdateException: if there is any error configuring the target device. - """ - if self._xbee_device is not None and not self._set_device_in_programming_mode(): - self._exit_with_error(_ERROR_DEVICE_PROGRAMMING_MODE) - - def _transfer_firmware(self): - """ - Transfers the firmware file(s) to the target. - - Raises: - FirmwareUpdateException: if there is any error transferring the firmware to the target device. - """ - # Update the bootloader using XModem protocol if required. - if self._bootloader_update_required: - _log.info("Updating bootloader") - self._progress_task = _PROGRESS_TASK_UPDATE_BOOTLOADER - try: - self._transfer_firmware_file_xmodem(self._bootloader_firmware_file) - except FirmwareUpdateException as e: - self._exit_with_error(_ERROR_FIRMWARE_UPDATE_BOOTLOADER % str(e)) - - # Update the XBee firmware using XModem protocol. - _log.info("Updating XBee firmware") - self._progress_task = _PROGRESS_TASK_UPDATE_XBEE - try: - self._transfer_firmware_file_xmodem(self._xbee_firmware_file) - except FirmwareUpdateException as e: - self._exit_with_error(_ERROR_FIRMWARE_UPDATE_XBEE % str(e)) - - def _finish_firmware_update(self): - """ - Finishes the firmware update process. Called just after the transfer firmware operation. - """ - # Start firmware. - if not self._run_firmware_operation(): - self._exit_with_error(_ERROR_FIRMWARE_START) - - def _set_device_in_programming_mode(self): - """ - Attempts to put the XBee device into programming mode (bootloader). - - Returns: - Boolean: ``True`` if the device was set into programming mode, ``False`` otherwise. - """ - if self._xbee_device is None: - return False - - if self._xbee_serial_port is not None and self._is_bootloader_active(): - return True - - _log.debug("Setting device in programming mode") - try: - self._xbee_device.execute_command(ATStringCommand.PERCENT_P.command) - except XBeeException: - # We can ignore this error as at last instance we will attempt a Break method. - pass - - self._xbee_device.close() - self._xbee_serial_port = self._xbee_device.serial_port - self._device_port_params = self._xbee_serial_port.get_settings() - try: - self._xbee_serial_port.apply_settings(_BOOTLOADER_PORT_PARAMETERS) - self._xbee_serial_port.open() - except SerialException as e: - _log.exception(e) - return False - if not self._is_bootloader_active(): - # This will force the Break mechanism to reboot in bootloader mode in case previous methods failed. - return self._enter_bootloader_mode_with_break() - - return True - - def _start_firmware_upload_operation(self): - """ - Starts the firmware upload operation by selecting option '1' of the bootloader. - - Returns: - Boolean: ``True`` if the upload process started successfully, ``False`` otherwise - """ - try: - # Display bootloader menu and consume it. - self._xbee_serial_port.write(str.encode(_BOOTLOADER_TEST_CHARACTER)) - time.sleep(1) - self._xbee_serial_port.purge_port() - # Write '1' to execute bootloader option '1': Upload gbl and consume answer. - self._xbee_serial_port.write(str.encode(_BOOTLOADER_OPTION_UPLOAD_GBL)) - time.sleep(0.5) - self._xbee_serial_port.purge_port() - # Look for the 'C' character during some time, it indicates device is ready to receive firmware pages. - self._xbee_serial_port.set_read_timeout(0.5) - deadline = _get_milliseconds() + (_XMODEM_START_TIMEOUT * 1000) - while _get_milliseconds() < deadline: - read_bytes = self._xbee_serial_port.read(1) - if len(read_bytes) > 0 and read_bytes[0] == ord(_XMODEM_READY_TO_RECEIVE_CHAR): - return True - time.sleep(0.1) - return False - except SerialException as e: - _log.exception(e) - return False - - def _run_firmware_operation(self): - """ - Runs the firmware by selecting option '2' of the bootloader. - - If XBee firmware is flashed, it will boot. If no firmware is flashed, the bootloader will be reset. - - Returns: - Boolean: ``True`` if the run firmware operation was executed, ``False`` otherwise - """ - try: - # Display bootloader menu and consume it. - self._xbee_serial_port.write(str.encode(_BOOTLOADER_TEST_CHARACTER)) - time.sleep(1) - self._xbee_serial_port.purge_port() - # Write '2' to execute bootloader option '2': Run. - self._xbee_serial_port.write(str.encode(_BOOTLOADER_OPTION_RUN_FIRMWARE)) - - # Look for the '2' character during some time, it indicates firmware was executed. - read_bytes = self._xbee_serial_port.read(1) - while len(read_bytes) > 0 and not read_bytes[0] == ord(_BOOTLOADER_OPTION_RUN_FIRMWARE): - read_bytes = self._xbee_serial_port.read(1) - return True - except SerialException as e: - _log.exception(e) - return False - - def _xmodem_write_cb(self, data): - """ - Callback function used to write data to the serial port when requested from the XModem transfer. - - Args: - data (Bytearray): the data to write to serial port from the XModem transfer. - - Returns: - Boolean: ``True`` if the data was successfully written, ``False`` otherwise. - """ - try: - self._xbee_serial_port.purge_port() - self._xbee_serial_port.write(data) - except SerialException as e: - _log.exception(e) - return False - - return True - - def _xmodem_read_cb(self, size, timeout=None): - """ - Callback function used to read data from the serial port when requested from the XModem transfer. - - Args: - size (Integer): the size of the data to read. - timeout (Integer, optional): the maximum time to wait to read the requested data (seconds). - - Returns: - Bytearray: the read data, ``None`` if data could not be read. - """ - if not timeout: - timeout = self._timeout - deadline = _get_milliseconds() + (timeout * 1000) - data = bytearray() - try: - while len(data) < size and _get_milliseconds() < deadline: - read_bytes = self._xbee_serial_port.read(size - len(data)) - if len(read_bytes) > 0: - data.extend(read_bytes) - return data - except SerialException as e: - _log.exception(e) - - return None - - def _xmodem_progress_cb(self, percent): - """ - Callback function used to be notified about XModem transfer progress. - - Args: - percent (Integer): the XModem transfer percentage. - """ - if self._progress_callback is not None: - self._progress_callback(self._progress_task, percent) - - def _transfer_firmware_file_xmodem(self, firmware_file_path): - """ - Transfers the firmware to the device using XModem protocol. - - Args: - firmware_file_path (String): path of the firmware file to transfer. - - Returns: - Boolean: ``True`` if the firmware was transferred successfully, ``False`` otherwise - - Raises: - FirmwareUpdateException: if there is any error transferring the firmware file. - """ - # Start XModem communication. - if not self._start_firmware_upload_operation(): - raise FirmwareUpdateException(_ERROR_XMODEM_START) - - # Transfer file. - try: - xmodem.send_file_xmodem(firmware_file_path, self._xmodem_write_cb, self._xmodem_read_cb, - progress_cb=self._xmodem_progress_cb, log=_log) - except XModemCancelException: - # Retry at least once after resetting device. - if not self._run_firmware_operation() and not (self._is_bootloader_active() or - self._enter_bootloader_mode_with_break()): - raise FirmwareUpdateException(_ERROR_XMODEM_RESTART) - try: - self._xbee_serial_port.purge_port() - except SerialException as e: - raise FirmwareUpdateException(_ERROR_XMODEM_COMMUNICATION % str(e)) - self._start_firmware_upload_operation() - try: - xmodem.send_file_xmodem(firmware_file_path, self._xmodem_write_cb, self._xmodem_read_cb, - progress_cb=self._xmodem_progress_cb, log=_log) - except XModemException: - raise - except XModemException as e: - raise FirmwareUpdateException(str(e)) - - def _get_default_reset_timeout(self): - """ - Override. - - .. seealso:: - | :meth:`._XBeeFirmwareUpdater._get_default_reset_timeout` - """ - return self.__class__.__DEVICE_RESET_TIMEOUT - - -class _RemoteFirmwareUpdater(_XBeeFirmwareUpdater): - """ - Helper class used to handle the remote firmware update process. - """ - - __DEVICE_RESET_TIMEOUT_ZB = 3 # seconds - __DEVICE_RESET_TIMEOUT_DM = 20 # seconds - __DEVICE_RESET_TIMEOUT_802 = 28 # seconds - - def __init__(self, remote_device, xml_firmware_file, ota_firmware_file=None, otb_firmware_file=None, - timeout=_READ_DATA_TIMEOUT, progress_callback=None): - """ - Class constructor. Instantiates a new :class:`._RemoteFirmwareUpdater` with the given parameters. - - Args: - remote_device (:class:`.RemoteXBeeDevice`): remote XBee device to upload its firmware. - xml_firmware_file (String): path of the XML file that describes the firmware to upload. - ota_firmware_file (String, optional): path of the OTA firmware file to upload. - otb_firmware_file (String, optional): path of the OTB firmware file to upload (bootloader bundle). - timeout (Integer, optional): the timeout to wait for remote frame requests. - progress_callback (Function, optional): function to execute to receive progress information. Receives two - arguments: - - * The current update task as a String - * The current update task percentage as an Integer - - Raises: - FirmwareUpdateException: if there is any error performing the remote firmware update. - """ - super(_RemoteFirmwareUpdater, self).__init__(xml_firmware_file, timeout=timeout, - progress_callback=progress_callback) - - self._remote_device = remote_device - self._local_device = remote_device.get_local_xbee_device() - self._ota_firmware_file = ota_firmware_file - self._otb_firmware_file = otb_firmware_file - self._updater_was_connected = False - self._updater_old_baudrate = None - self._updater_ao_value = None - self._updater_bd_value = None - self._updater_my_value = None - self._updater_rr_value = None - self._ota_file = None - self._receive_lock = Event() - self._transfer_lock = Event() - self._img_req_received = False - self._img_notify_sent = False - self._transfer_status = None - self._response_string = None - self._requested_chunk_index = -1 - self._seq_number = 0 - - def _check_firmware_binary_file(self): - """ - Verifies that the firmware binary file exists. - - Raises: - FirmwareUpdateException: if the firmware binary file does not exist. - """ - # If not already specified, the binary firmware file is usually in the same folder as the XML firmware file. - if self._ota_firmware_file is None: - path = Path(self._xml_firmware_file) - self._ota_firmware_file = str(Path(path.parent).joinpath(path.stem + _EXTENSION_OTA)) - - if not _file_exists(self._ota_firmware_file): - self._exit_with_error(_ERROR_FILE_XBEE_FIRMWARE_NOT_FOUND % self._ota_firmware_file, restore_updater=False) - - self._ota_file = _OTAFile(self._ota_firmware_file) - try: - self._ota_file.parse_file() - except _ParsingOTAException as e: - self._exit_with_error(str(e)) - - def _check_bootloader_binary_file(self): - """ - Verifies that the bootloader binary file exists. - - Raises: - FirmwareUpdateException: if the bootloader binary file does not exist. - """ - if self._otb_firmware_file is None: - path = Path(self._xml_firmware_file) - self._otb_firmware_file = str(Path(path.parent).joinpath(path.stem + _EXTENSION_OTB)) - - if not _file_exists(self._otb_firmware_file): - self._exit_with_error(_ERROR_FILE_XBEE_FIRMWARE_NOT_FOUND % self._otb_firmware_file) - - # If asked to check the bootloader file, replace the OTA file with the .otb one. - # Unlike local firmware updates, remote firmware updates only transfer one file for fw + bootloader. - self._ota_file = _OTAFile(self._otb_firmware_file) - try: - self._ota_file.parse_file() - except _ParsingOTAException as e: - self._exit_with_error(str(e)) - - def _get_target_bootloader_version(self): - """ - Returns the update target bootloader version. - - Returns: - Bytearray: the update target bootloader version as byte array, ``None`` if it could not be read. - """ - return _read_device_bootloader_version(self._remote_device) - - def _get_target_compatibility_number(self): - """ - Returns the update target compatibility number. - - Returns: - Integer: the update target compatibility number as integer, ``None`` if it could not be read. - """ - return _read_device_compatibility_number(self._remote_device) - - def _get_target_region_lock(self): - """ - Returns the update target region lock number. - - Returns: - Integer: the update target region lock number as integer, ``None`` if it could not be read. - """ - return _read_device_region_lock(self._remote_device) - - def _get_target_hardware_version(self): - """ - Returns the update target hardware version. - - Returns: - Integer: the update target hardware version as integer, ``None`` if it could not be read. - """ - return _read_device_hardware_version(self._remote_device) - - def _get_target_firmware_version(self): - """ - Returns the update target firmware version. - - Returns: - Integer: the update target firmware version as integer, ``None`` if it could not be read. - """ - return _read_device_firmware_version(self._remote_device) - - def _check_updater_compatibility(self): - """ - Verifies whether the updater device is compatible with firmware update or not. - """ - # At the moment only XBee3 devices are supported as updater devices for remote updates. - if self._local_device.get_hardware_version().code not in SUPPORTED_HARDWARE_VERSIONS: - self._exit_with_error(_ERROR_HARDWARE_VERSION_NOT_SUPPORTED % self._target_hardware_version) - - def _configure_updater(self): - """ - Configures the updater device before performing the firmware update operation. - - Raises: - FirmwareUpdateException: if there is any error configuring the updater device. - """ - # These configuration steps are specific for XBee3 devices. Since no other hardware is supported - # yet, it is not a problem. If new hardware is supported in a future, this will need to be changed. - - # Change sync ops timeout. - self._old_sync_ops_timeout = self._local_device.get_sync_ops_timeout() - self._local_device.set_sync_ops_timeout(self._timeout) - # Connect device. - self._updater_was_connected = self._local_device.is_open() - _log.debug("Connecting device '%s'" % self._local_device) - if not _connect_device_with_retries(self._local_device, _DEVICE_CONNECTION_RETRIES): - self._exit_with_error(_ERROR_CONNECT_DEVICE % _DEVICE_CONNECTION_RETRIES) - # Store AO value. - self._updater_ao_value = _read_device_parameter_with_retries(self._local_device, ATStringCommand.AO.command) - if self._updater_ao_value is None: - self._exit_with_error(_ERROR_UPDATER_READ_PARAMETER % ATStringCommand.AO.command) - # Store BD value. - self._updater_bd_value = _read_device_parameter_with_retries(self._local_device, ATStringCommand.BD.command) - if self._updater_bd_value is None: - self._exit_with_error(_ERROR_UPDATER_READ_PARAMETER % ATStringCommand.BD.command) - # Set new BD value. - if not _set_device_parameter_with_retries(self._local_device, ATStringCommand.BD.command, - bytearray([_VALUE_BAUDRATE_230400])): - self._exit_with_error(_ERROR_UPDATER_SET_PARAMETER % ATStringCommand.BD.command) - # Change local port baudrate to 230400. - self._updater_old_baudrate = self._local_device.serial_port.get_settings()["baudrate"] - self._local_device.serial_port.set_baudrate(230400) - # Set new AO value. - if not _set_device_parameter_with_retries(self._local_device, ATStringCommand.AO.command, - bytearray([_VALUE_API_OUTPUT_MODE_EXPLICIT])): - self._exit_with_error(_ERROR_UPDATER_SET_PARAMETER % ATStringCommand.AO.command) - # Specific settings per protocol. - if self._local_device.get_protocol() == XBeeProtocol.DIGI_MESH: - # Store RR value. - self._updater_rr_value = _read_device_parameter_with_retries(self._local_device, - ATStringCommand.RR.command) - if self._updater_ao_value is None: - self._exit_with_error(_ERROR_UPDATER_READ_PARAMETER % ATStringCommand.RR.command) - # Set new RR value. - if not _set_device_parameter_with_retries(self._local_device, ATStringCommand.RR.command, - bytearray([_VALUE_UNICAST_RETRIES_MEDIUM])): - self._exit_with_error(_ERROR_UPDATER_SET_PARAMETER % ATStringCommand.RR.command) - elif self._local_device.get_protocol() == XBeeProtocol.RAW_802_15_4: - # Store MY value. - self._updater_my_value = _read_device_parameter_with_retries(self._local_device, - ATStringCommand.MY.command) - if self._updater_my_value is None: - self._exit_with_error(_ERROR_UPDATER_READ_PARAMETER % ATStringCommand.MY.command) - # Set new MY value. - if not _set_device_parameter_with_retries(self._local_device, ATStringCommand.MY.command, - _VALUE_BROADCAST_ADDRESS): - self._exit_with_error(_ERROR_UPDATER_SET_PARAMETER % ATStringCommand.MY.command) - - def _restore_updater(self, raise_exception=False): - """ - Leaves the updater device to its original state before the update operation. - - Args: - raise_exception (Boolean, optional): ``True`` to raise exceptions if they occur, ``False`` otherwise. - - Raises: - XBeeException: if there is any error restoring the device connection. - """ - # Close OTA file. - if self._ota_file: - self._ota_file.close_file() - # Restore sync ops timeout. - self._local_device.set_sync_ops_timeout(self._old_sync_ops_timeout) - # Restore updater params. - try: - if not self._local_device.is_open(): - self._local_device.open() - # Restore AO. - if self._updater_ao_value is not None: - _set_device_parameter_with_retries(self._local_device, ATStringCommand.AO.command, - self._updater_ao_value) - # Restore BD. - if self._updater_bd_value is not None: - _set_device_parameter_with_retries(self._local_device, ATStringCommand.BD.command, - self._updater_bd_value) - # Restore port baudrate. - if self._updater_old_baudrate is not None: - self._local_device.serial_port.set_baudrate(self._updater_old_baudrate) - # Specific settings per protocol. - if self._local_device.get_protocol() == XBeeProtocol.DIGI_MESH: - # Restore RR value. - _set_device_parameter_with_retries(self._local_device, ATStringCommand.RR.command, - self._updater_rr_value) - elif self._local_device.get_protocol() == XBeeProtocol.RAW_802_15_4: - # Restore MY value. - _set_device_parameter_with_retries(self._local_device, ATStringCommand.MY.command, - self._updater_my_value) - except XBeeException as e: - if raise_exception: - raise e - if self._updater_was_connected and not self._local_device.is_open(): - self._local_device.open() - elif not self._updater_was_connected and self._local_device.is_open(): - self._local_device.close() - - def _create_explicit_frame(self, payload): - """ - Creates and returns an explicit addressing frame using the given payload. - - Args: - payload (Bytearray): the payload for the explicit addressing frame. - - Returns: - :class:`.ExplicitAddressingPacket`: the explicit addressing frame with the given payload. - """ - packet = ExplicitAddressingPacket(self._local_device.get_next_frame_id(), - self._remote_device.get_64bit_addr(), - self._remote_device.get_16bit_addr(), - _EXPLICIT_PACKET_ENDPOINT_DATA, - _EXPLICIT_PACKET_ENDPOINT_DATA, - _EXPLICIT_PACKET_CLUSTER_ID, - _EXPLICIT_PACKET_PROFILE_DIGI, - _EXPLICIT_PACKET_BROADCAST_RADIUS_MAX, - _EXPLICIT_PACKET_EXTENDED_TIMEOUT, - payload) - return packet - - def _create_zdo_frame(self, frame_control, seq_number, command_id, payload): - """ - Creates and returns a ZDO frame with the given parameters. - - Args: - frame_control (Integer): the ZDO object frame control. - seq_number (Integer): the ZDO object sequence number. - command_id (Integer): the ZDO object command ID. - payload (Bytearray): the payload for the ZDO object. - - Returns: - Bytearray: the ZDO frame. - """ - zdo_payload = bytearray() - zdo_payload.append(frame_control & 0xFF) - zdo_payload.append(seq_number & 0xFF) - zdo_payload.append(command_id & 0xFF) - zdo_payload.extend(payload) - - return self._create_explicit_frame(zdo_payload) - - def _create_image_notify_request_frame(self): - """ - Creates and returns an image notify request frame for the firmware to transfer. - - Returns: - Bytearray: the image notify request frame. - """ - payload = bytearray() - payload.append(_NOTIFY_PACKET_PAYLOAD_TYPE & 0xFF) - payload.append(_NOTIFY_PACKET_DEFAULT_QUERY_JITTER & 0xFF) - payload.extend(_reverse_bytearray(utils.int_to_bytes(self._ota_file.manufacturer_code, 2))) - payload.extend(_reverse_bytearray(utils.int_to_bytes(self._ota_file.image_type, 2))) - payload.extend(_reverse_bytearray(utils.int_to_bytes(self._ota_file.file_version, 4))) - - return self._create_zdo_frame(_ZDO_FRAME_CONTROL_SERVER_TO_CLIENT, _PACKET_DEFAULT_SEQ_NUMBER, - _ZDO_COMMAND_ID_IMG_NOTIFY_REQ, payload) - - def _create_query_next_image_response_frame(self): - """ - Creates and returns a query next image response frame. - - Returns: - Bytearray: the query next image response frame. - """ - image_size = self._ota_file.total_size - - # If the remote module is an XBee3 using ZigBee protocol and the firmware version - # is 1003 or lower, use the OTA GBL size instead of total size (exclude header size). - if self._remote_device.get_protocol() == XBeeProtocol.ZIGBEE and \ - self._target_hardware_version in SUPPORTED_HARDWARE_VERSIONS and \ - self._target_firmware_version < _ZIGBEE_FW_VERSION_LIMIT_FOR_GBL: - image_size = self._ota_file.gbl_size - - payload = bytearray() - payload.append(_XBee3OTAStatus.SUCCESS.identifier & 0xFF) - payload.extend(_reverse_bytearray(utils.int_to_bytes(self._ota_file.manufacturer_code, 2))) - payload.extend(_reverse_bytearray(utils.int_to_bytes(self._ota_file.image_type, 2))) - payload.extend(_reverse_bytearray(utils.int_to_bytes(self._ota_file.file_version, 4))) - payload.extend(_reverse_bytearray(utils.int_to_bytes(image_size, 4))) - - return self._create_zdo_frame(_ZDO_FRAME_CONTROL_SERVER_TO_CLIENT, _PACKET_DEFAULT_SEQ_NUMBER, - _ZDO_COMMAND_ID_QUERY_NEXT_IMG_RESP, payload) - - def _create_image_block_response_frame(self, chunk_index, current_seq_number): - """ - Creates and returns an image block response frame. - - Args: - chunk_index (Integer): the chunk index to send. - current_seq_number (Integer): the current protocol sequence number. - - Returns: - Bytearray: the image block response frame. - - Raises: - FirmwareUpdateException: if there is any error generating the image block response frame. - """ - # Increment protocol sequence number. - next_seq_number = current_seq_number + 1 - if next_seq_number > 255: - next_seq_number = 0 - - try: - data = self._ota_file.get_next_data_chunk() - except _ParsingOTAException as e: - raise FirmwareUpdateException(_ERROR_READ_OTA_FILE % str(e)) - payload = bytearray() - payload.append(_XBee3OTAStatus.SUCCESS.identifier & 0xFF) - payload.extend(_reverse_bytearray(utils.int_to_bytes(self._ota_file.manufacturer_code, 2))) - payload.extend(_reverse_bytearray(utils.int_to_bytes(self._ota_file.image_type, 2))) - payload.extend(_reverse_bytearray(utils.int_to_bytes(self._ota_file.file_version, 4))) - payload.extend(_reverse_bytearray(utils.int_to_bytes(chunk_index * self._ota_file.chunk_size, 4))) - if data: - payload.append(len(data) & 0xFF) - payload.extend(data) - else: - payload.extend(utils.int_to_bytes(0)) - - return self._create_zdo_frame(_ZDO_FRAME_CONTROL_SERVER_TO_CLIENT, next_seq_number, - _ZDO_COMMAND_ID_IMG_BLOCK_RESP, payload) - - def _create_upgrade_end_response_frame(self): - """ - Creates and returns an upgrade end response frame. - - Returns: - Bytearray: the upgrade end response frame. - """ - payload = bytearray() - payload.extend(_reverse_bytearray(utils.int_to_bytes(self._ota_file.manufacturer_code, 2))) - payload.extend(_reverse_bytearray(utils.int_to_bytes(self._ota_file.image_type, 2))) - payload.extend(_reverse_bytearray(utils.int_to_bytes(self._ota_file.file_version, 4))) - payload.extend(_reverse_bytearray(utils.int_to_bytes(int(time.time()) - _TIME_SECONDS_1970_TO_2000, 4))) - payload.extend(_reverse_bytearray(utils.int_to_bytes(0, 4))) - - return self._create_zdo_frame(_ZDO_FRAME_CONTROL_SERVER_TO_CLIENT, _PACKET_DEFAULT_SEQ_NUMBER, - _ZDO_COMMAND_ID_UPGRADE_END_RESP, payload) - - @staticmethod - def _is_img_req_payload_valid(payload): - """ - Returns whether the given payload is valid for an image request received frame. - - Args: - payload (Bytearray): the payload to check. - - Returns: - Boolean: ``True`` if the given payload is valid for an image request received frame, ``False`` otherwise. - """ - return (len(payload) == _NOTIFY_PACKET_PAYLOAD_SIZE and - payload[0] == _ZDO_FRAME_CONTROL_CLIENT_TO_SERVER and - payload[2] == _ZDO_COMMAND_ID_QUERY_NEXT_IMG_REQ) - - def _image_request_frame_callback(self, xbee_frame): - """ - Callback used to be notified when the image request frame is received by - the target device and it is ready to start receiving image frames. - - Args: - xbee_frame (:class:`.XBeeAPIPacket`): the received packet - """ - if xbee_frame.get_frame_type() == ApiFrameType.TRANSMIT_STATUS: - _log.debug("Received 'Image notify' status frame: %s" % xbee_frame.transmit_status.description) - if xbee_frame.transmit_status == TransmitStatus.SUCCESS: - self._img_notify_sent = True - # Sometimes the transmit status frame is received after the explicit frame - # indicator. Notify only if the transmit status frame was also received. - if self._img_req_received: - # Continue execution. - self._receive_lock.set() - else: - # Remove explicit frame indicator received flag if it was set. - if self._img_req_received: - self._img_req_received = False - # Continue execution, it will exit with error as received flags are not set. - self._receive_lock.set() - elif xbee_frame.get_frame_type() == ApiFrameType.EXPLICIT_RX_INDICATOR: - if self._img_req_received: - return - if not self._is_img_req_payload_valid(xbee_frame.rf_data): - # This is not the explicit frame we were expecting, keep on listening. - return - _log.debug("Received 'Query next image' request frame") - self._img_req_received = True - # Sometimes the transmit status frame is received after the explicit frame - # indicator. Notify only if the transmit status frame was also received. - if self._img_notify_sent: - # Continue execution. - self._receive_lock.set() - - def _firmware_receive_frame_callback(self, xbee_frame): - """ - Callback used to be notified of image block requests and upgrade end request frames during the - firmware transfer operation. - - Args: - xbee_frame (:class:`.XBeeAPIPacket`): the received packet - """ - if xbee_frame.get_frame_type() != ApiFrameType.EXPLICIT_RX_INDICATOR: - return - - # Check the type of frame received. - if self._is_image_block_request_frame(xbee_frame): - # If the received frame is an 'image block request' frame, retrieve the requested index. - max_data_size, file_offset, sequence_number = self._parse_image_block_request_frame(xbee_frame) - # Check if OTA file chunk size must be updated. - if max_data_size != self._ota_file.chunk_size: - self._ota_file.chunk_size = max_data_size - self._requested_chunk_index = file_offset // self._ota_file.chunk_size - _log.debug("Received 'Image block request' frame for file offset %s - Chunk index: %s - Expected index: %s" - % (file_offset, self._requested_chunk_index, self._expected_chunk_index)) - if self._requested_chunk_index != self._expected_chunk_index: - return - self._expected_chunk_index += 1 - self._seq_number = sequence_number - elif self._is_upgrade_end_request_frame(xbee_frame): - _log.debug("Received 'Upgrade end request' frame") - # If the received frame is an 'upgrade end request' frame, set transfer status. - self._transfer_status = _XBee3OTAStatus.get(self._parse_upgrade_end_request_frame(xbee_frame)) - elif self._is_default_response_frame(xbee_frame): - _log.debug("Received 'Default response' frame") - # If the received frame is a 'default response' frame, set the corresponding error. - ota_command, status = self._parse_default_response_frame(xbee_frame) - response_status = None - if self._local_device.get_protocol() == XBeeProtocol.ZIGBEE: - response_status = _XBeeZigbee3OTAStatus.get(status) - else: - if ota_command == _ZDO_COMMAND_ID_QUERY_NEXT_IMG_RESP: - response_status = _NextImageMessageStatus.get(status) - elif ota_command == _ZDO_COMMAND_ID_IMG_BLOCK_RESP: - response_status = _ImageBlockMessageStatus.get(status) - elif ota_command == _ZDO_COMMAND_ID_UPGRADE_END_RESP: - response_status = _UpgradeEndMessageStatus.get(status) - self._response_string = response_status.description if response_status is not None \ - else _ERROR_DEFAULT_RESPONSE_UNKNOWN_ERROR - else: - return - # Notify transfer thread to continue. - self._transfer_lock.set() - - def _is_image_block_request_frame(self, xbee_frame): - """ - Returns whether the given frame is an image block request frame or not. - - Args: - xbee_frame (:class:`.XBeeAPIPacket`): the XBee frame to check. - - Returns: - Boolean: ``True`` if the frame is an image block request frame, ``False`` otherwise. - """ - return self._parse_image_block_request_frame(xbee_frame) is not None - - @staticmethod - def _parse_image_block_request_frame(xbee_frame): - """ - Parses the given image block request frame and returns the frame values. - - Args: - xbee_frame (:class:`.XBeeAPIPacket`): the XBee frame to parse. - - Returns: - Tuple (Integer, Integer, Integer): the max data size, the file offset and the sequence number of the block - request frame. ``None`` if parsing failed. - """ - payload = xbee_frame.rf_data - if len(payload) != _IMAGE_BLOCK_REQUEST_PACKET_PAYLOAD_SIZE or \ - payload[0] != _ZDO_FRAME_CONTROL_CLIENT_TO_SERVER or \ - payload[2] != _ZDO_COMMAND_ID_IMG_BLOCK_REQ: - return None - - sequence_number = payload[1] & 0xFF - file_offset = utils.bytes_to_int(_reverse_bytearray(payload[12:16])) - max_data_size = payload[16] & 0xFF - - return max_data_size, file_offset, sequence_number - - def _is_upgrade_end_request_frame(self, xbee_frame): - """ - Returns whether the given frame is an upgrade end request frame or not. - - Args: - xbee_frame (:class:`.XBeeAPIPacket`): the XBee frame to check. - - Returns: - Boolean: ``True`` if the frame is an upgrade end request frame, ``False`` otherwise. - """ - return self._parse_upgrade_end_request_frame(xbee_frame) is not None - - @staticmethod - def _parse_upgrade_end_request_frame(xbee_frame): - """ - Parses the given upgrade end request frame and returns the frame values. - - Args: - xbee_frame (:class:`.XBeeAPIPacket`): the XBee frame to parse. - - Returns: - Integer: the upgrade end request status, ``None`` if parsing failed. - """ - payload = xbee_frame.rf_data - if len(payload) != _UPGRADE_END_REQUEST_PACKET_PAYLOAD_SIZE or \ - payload[0] != _ZDO_FRAME_CONTROL_CLIENT_TO_SERVER or \ - payload[2] != _ZDO_COMMAND_ID_UPGRADE_END_REQ: - return None - - status = payload[3] & 0xFF - - return status - - def _is_default_response_frame(self, xbee_frame): - """ - Returns whether the given frame is a default response frame or not. - - Args: - xbee_frame (:class:`.XBeeAPIPacket`): the XBee frame to check. - - Returns: - Boolean: ``True`` if the frame is a default response frame, ``False`` otherwise. - """ - return self._parse_default_response_frame(xbee_frame) is not None - - @staticmethod - def _parse_default_response_frame(xbee_frame): - """ - Parses the given image block request frame and returns the frame values. - - Args: - xbee_frame (:class:`.XBeeAPIPacket`): the XBee frame to parse. - - Returns: - Tuple (Integer, Integer): the OTA command and the sstatus of the default response frame. - ``None`` if parsing failed. - """ - payload = xbee_frame.rf_data - if len(payload) != _DEFAULT_RESPONSE_PACKET_PAYLOAD_SIZE or \ - payload[0] != _ZDO_FRAME_CONTROL_GLOBAL or \ - payload[2] != _ZDO_COMMAND_ID_DEFAULT_RESP: - return None - - ota_command = payload[3] & 0xFF - status = payload[4] & 0xFF - - return ota_command, status - - def _send_query_next_img_response(self): - """ - Sends the query next image response frame. - - Raises: - FirmwareUpdateException: if there is any error sending the next image response frame. - """ - retries = _SEND_BLOCK_RETRIES - query_next_image_response_frame = self._create_query_next_image_response_frame() - while retries > 0: - try: - _log.debug("Sending 'Query next image response' frame") - status_frame = self._local_device.send_packet_sync_and_get_response(query_next_image_response_frame) - if not isinstance(status_frame, TransmitStatusPacket): - retries -= 1 - continue - _log.debug("Received 'Query next image response' status frame: %s" % - status_frame.transmit_status.description) - if status_frame.transmit_status != TransmitStatus.SUCCESS: - retries -= 1 - continue - return - except XBeeException as e: - raise FirmwareUpdateException(_ERROR_SEND_QUERY_NEXT_IMAGE_RESPONSE % str(e)) - - raise FirmwareUpdateException(_ERROR_SEND_QUERY_NEXT_IMAGE_RESPONSE % "Timeout sending frame") - - def _send_ota_block(self, chunk_index, seq_number): - """ - Sends the next OTA block frame. - - Args: - chunk_index (Integer): the - - Raises: - FirmwareUpdateException: if there is any error sending the next OTA block frame. - """ - retries = _SEND_BLOCK_RETRIES - next_ota_block_frame = self._create_image_block_response_frame(chunk_index, seq_number) - while retries > 0: - try: - _log.debug("Sending 'Image block response' frame for chunk %s" % chunk_index) - status_frame = self._local_device.send_packet_sync_and_get_response(next_ota_block_frame) - if not isinstance(status_frame, TransmitStatusPacket): - retries -= 1 - continue - _log.debug("Received 'Image block response' status frame for chunk %s: %s" % - (chunk_index, status_frame.transmit_status.description)) - if status_frame.transmit_status != TransmitStatus.SUCCESS: - retries -= 1 - continue - return - except XBeeException as e: - raise FirmwareUpdateException(_ERROR_SEND_OTA_BLOCK % (chunk_index, str(e))) - - raise FirmwareUpdateException(_ERROR_SEND_OTA_BLOCK % (chunk_index, "Timeout sending frame")) - - def _start_firmware_update(self): - """ - Starts the firmware update process. Called just before the transfer firmware operation. - - Raises: - FirmwareUpdateException: if there is any error starting the remote firmware update process. - """ - _log.debug("Sending 'Image notify' frame") - image_notify_request_frame = self._create_image_notify_request_frame() - self._local_device.add_packet_received_callback(self._image_request_frame_callback) - try: - self._local_device.send_packet(image_notify_request_frame) - self._receive_lock.wait(self._timeout) - if not self._img_notify_sent: - self._exit_with_error(_ERROR_SEND_IMAGE_NOTIFY % "Transmit status not received") - elif not self._img_req_received: - self._exit_with_error(_ERROR_SEND_IMAGE_NOTIFY % "Timeout waiting for response") - except XBeeException as e: - self._exit_with_error(_ERROR_SEND_IMAGE_NOTIFY % str(e)) - finally: - self._local_device.del_packet_received_callback(self._image_request_frame_callback) - - def _transfer_firmware(self): - """ - Transfers the firmware to the target. - - Raises: - FirmwareUpdateException: if there is any error transferring the firmware to the target device. - """ - self._transfer_status = None - self._response_string = None - self._expected_chunk_index = 0 - self._requested_chunk_index = -1 - self._progress_task = _PROGRESS_TASK_UPDATE_REMOTE_XBEE - last_chunk_sent = self._requested_chunk_index - previous_seq_number = 0 - previous_percent = None - retries = _SEND_BLOCK_RETRIES - - # Add a packet listener to wait for block request packets and send them. - self._local_device.add_packet_received_callback(self._firmware_receive_frame_callback) - try: - self._send_query_next_img_response() - except FirmwareUpdateException as e: - self._local_device.del_packet_received_callback(self._firmware_receive_frame_callback) - self._exit_with_error(str(e)) - # Wait for answer. - if self._requested_chunk_index == -1: # If chunk index is different than -1 it means callback was executed. - self._transfer_lock.clear() - self._transfer_lock.wait(self._timeout) - while self._requested_chunk_index != -1 and \ - self._transfer_status is None and \ - self._response_string is None and \ - retries > 0: - if self._requested_chunk_index != last_chunk_sent: - # New chunk requested, increase previous values and reset retries. - last_chunk_sent = self._requested_chunk_index - previous_seq_number = self._seq_number - retries = _SEND_BLOCK_RETRIES - else: - # Chunk index was not increased, this means chunk was not sent. Decrease retries. - _log.debug("Chunk %s not sent, retrying..." % self._requested_chunk_index) - retries -= 1 - # Check that the requested index is valid. - if self._requested_chunk_index >= self._ota_file.num_chunks: - self._local_device.del_packet_received_callback(self._firmware_receive_frame_callback) - self._exit_with_error(_ERROR_INVALID_BLOCK % self._requested_chunk_index) - # Calculate percentage and notify. - percent = (self._requested_chunk_index * 100) // self._ota_file.num_chunks - if percent != previous_percent and self._progress_callback: - self._progress_callback(self._progress_task, percent) - previous_percent = percent - # Send the data block. - try: - self._send_ota_block(self._requested_chunk_index, previous_seq_number) - except FirmwareUpdateException as e: - self._local_device.del_packet_received_callback(self._firmware_receive_frame_callback) - self._exit_with_error(str(e)) - # Wait for next request. - if self._requested_chunk_index == last_chunk_sent: - self._transfer_lock.clear() - self._transfer_lock.wait(self._timeout) - # Transfer finished, remove callback. - self._local_device.del_packet_received_callback(self._firmware_receive_frame_callback) - # Close OTA file. - self._ota_file.close_file() - # Check if there was a transfer timeout. - if self._transfer_status is None and self._response_string is None: - if last_chunk_sent + 1 == self._ota_file.num_chunks: - self._exit_with_error(_ERROR_TRANSFER_OTA_FILE % "Timeout waiting for 'Upgrade end request' frame") - else: - self._exit_with_error(_ERROR_TRANSFER_OTA_FILE % "Timeout waiting for next 'Image block request' frame") - # Check if there was a transfer error. - if self._transfer_status and self._transfer_status != _XBee3OTAStatus.SUCCESS: - self._exit_with_error(_ERROR_TRANSFER_OTA_FILE % self._transfer_status.description) - # Check if the client reported an error. - if self._response_string: - self._exit_with_error(_ERROR_TRANSFER_OTA_FILE % self._response_string) - # Reaching this point means the transfer was successful, notify 100% progress. - if self._progress_callback: - self._progress_callback(self._progress_task, 100) - - def _finish_firmware_update(self): - """ - Finishes the firmware update process. Called just after the transfer firmware operation. - - Raises: - FirmwareUpdateException: if there is any error finishing the firmware operation. - """ - retries = _SEND_BLOCK_RETRIES - error_message = None - upgrade_end_response_frame = self._create_upgrade_end_response_frame() - while retries > 0: - try: - _log.debug("Sending 'Upgrade end response' frame") - error_message = None - status_frame = self._local_device.send_packet_sync_and_get_response(upgrade_end_response_frame) - if not isinstance(status_frame, TransmitStatusPacket): - retries -= 1 - continue - _log.debug("Received 'Upgrade end response' status frame: %s" % - status_frame.transmit_status.description) - - # Workaround for 'No ack' error on XBee 3 DigiMesh remote firmware updates - # - # After sending the explicit frame with the 'Upgrade end response' command, - # the received transmit status always has a 'No acknowledgement received' - # error (0x01) instead of a 'Success' (0x00). This happens for 3004 or lower - # firmware versions at least. - # The workaround considers as valid the 'No ack' error only for DigiMesh firmwares. - # - # See https://jira.digi.com/browse/XBHAWKDM-796 - dm_ack_error = (status_frame.transmit_status == TransmitStatus.NO_ACK - and self._remote_device.get_protocol() == XBeeProtocol.DIGI_MESH) - - if status_frame.transmit_status != TransmitStatus.SUCCESS and not dm_ack_error: - retries -= 1 - continue - try: - self._restore_updater(raise_exception=True) - return - except Exception as e: - self._exit_with_error(_ERROR_RESTORE_UPDATER_DEVICE % str(e)) - except XBeeException as e: - retries -= 1 - error_message = str(e) - time.sleep(1.5) # Wait some time between timeout retries. - - if error_message: - self._exit_with_error(_ERROR_SEND_UPGRADE_END_RESPONSE % error_message) - else: - self._exit_with_error(_ERROR_SEND_UPGRADE_END_RESPONSE % "Timeout sending frame") - - def _get_default_reset_timeout(self): - """ - Override. - - .. seealso:: - | :meth:`._XBeeFirmwareUpdater._get_default_reset_timeout` - """ - protocol = self._remote_device.get_protocol() - if protocol == XBeeProtocol.ZIGBEE: - return self.__class__.__DEVICE_RESET_TIMEOUT_ZB - elif protocol == XBeeProtocol.DIGI_MESH: - return self.__class__.__DEVICE_RESET_TIMEOUT_DM - elif protocol == XBeeProtocol.RAW_802_15_4: - return self.__class__.__DEVICE_RESET_TIMEOUT_802 - - return max([self.__class__.__DEVICE_RESET_TIMEOUT_ZB, - self.__class__.__DEVICE_RESET_TIMEOUT_DM, - self.__class__.__DEVICE_RESET_TIMEOUT_802]) - - -def update_local_firmware(target, xml_firmware_file, xbee_firmware_file=None, bootloader_firmware_file=None, - timeout=None, progress_callback=None): - """ - Performs a local firmware update operation in the given target. - - Args: - target (String or :class:`.XBeeDevice`): target of the firmware upload operation. - String: serial port identifier. - :class:`.AbstractXBeeDevice`: the XBee device to upload its firmware. - xml_firmware_file (String): path of the XML file that describes the firmware to upload. - xbee_firmware_file (String, optional): location of the XBee binary firmware file. - bootloader_firmware_file (String, optional): location of the bootloader binary firmware file. - timeout (Integer, optional): the serial port read data timeout. - progress_callback (Function, optional): function to execute to receive progress information. Receives two - arguments: - - * The current update task as a String - * The current update task percentage as an Integer - - Raises: - FirmwareUpdateException: if there is any error performing the firmware update. - """ - # Sanity checks. - if not isinstance(target, str) and not isinstance(target, AbstractXBeeDevice): - _log.error("ERROR: %s" % _ERROR_TARGET_INVALID) - raise FirmwareUpdateException(_ERROR_TARGET_INVALID) - if xml_firmware_file is None: - _log.error("ERROR: %s" % _ERROR_FILE_XML_FIRMWARE_NOT_SPECIFIED) - raise FirmwareUpdateException(_ERROR_FILE_XML_FIRMWARE_NOT_SPECIFIED) - if not _file_exists(xml_firmware_file): - _log.error("ERROR: %s" % _ERROR_FILE_XML_FIRMWARE_NOT_FOUND) - raise FirmwareUpdateException(_ERROR_FILE_XML_FIRMWARE_NOT_FOUND) - if xbee_firmware_file is not None and not _file_exists(xbee_firmware_file): - _log.error("ERROR: %s" % _ERROR_FILE_XBEE_FIRMWARE_NOT_FOUND % xbee_firmware_file) - raise FirmwareUpdateException(_ERROR_FILE_XBEE_FIRMWARE_NOT_FOUND % xbee_firmware_file) - if bootloader_firmware_file is not None and not _file_exists(bootloader_firmware_file): - _log.error("ERROR: %s" % _ERROR_FILE_BOOTLOADER_FIRMWARE_NOT_FOUND) - raise FirmwareUpdateException(_ERROR_FILE_BOOTLOADER_FIRMWARE_NOT_FOUND) - - # Launch the update process. - if not timeout: - timeout = _READ_DATA_TIMEOUT - update_process = _LocalFirmwareUpdater(target, - xml_firmware_file, - xbee_firmware_file=xbee_firmware_file, - bootloader_firmware_file=bootloader_firmware_file, - timeout=timeout, - progress_callback=progress_callback) - update_process.update_firmware() - - -def update_remote_firmware(remote_device, xml_firmware_file, ota_firmware_file=None, otb_firmware_file=None, - timeout=None, progress_callback=None): - """ - Performs a local firmware update operation in the given target. - - Args: - remote_device (:class:`.RemoteXBeeDevice`): remote XBee device to upload its firmware. - xml_firmware_file (String): path of the XML file that describes the firmware to upload. - ota_firmware_file (String, optional): path of the OTA firmware file to upload. - otb_firmware_file (String, optional): path of the OTB firmware file to upload (bootloader bundle). - timeout (Integer, optional): the timeout to wait for remote frame requests. - progress_callback (Function, optional): function to execute to receive progress information. Receives two - arguments: - - * The current update task as a String - * The current update task percentage as an Integer - - Raises: - FirmwareUpdateException: if there is any error performing the remote firmware update. - """ - # Sanity checks. - if not isinstance(remote_device, RemoteXBeeDevice): - _log.error("ERROR: %s" % _ERROR_REMOTE_DEVICE_INVALID) - raise FirmwareUpdateException(_ERROR_TARGET_INVALID) - if xml_firmware_file is None: - _log.error("ERROR: %s" % _ERROR_FILE_XML_FIRMWARE_NOT_SPECIFIED) - raise FirmwareUpdateException(_ERROR_FILE_XML_FIRMWARE_NOT_SPECIFIED) - if not _file_exists(xml_firmware_file): - _log.error("ERROR: %s" % _ERROR_FILE_XML_FIRMWARE_NOT_FOUND) - raise FirmwareUpdateException(_ERROR_FILE_XML_FIRMWARE_NOT_FOUND) - if ota_firmware_file is not None and not _file_exists(ota_firmware_file): - _log.error("ERROR: %s" % _ERROR_FILE_XBEE_FIRMWARE_NOT_FOUND % ota_firmware_file) - raise FirmwareUpdateException(_ERROR_FILE_XBEE_FIRMWARE_NOT_FOUND % ota_firmware_file) - if otb_firmware_file is not None and not _file_exists(otb_firmware_file): - _log.error("ERROR: %s" % _ERROR_FILE_XBEE_FIRMWARE_NOT_FOUND % otb_firmware_file) - raise FirmwareUpdateException(_ERROR_FILE_XBEE_FIRMWARE_NOT_FOUND % otb_firmware_file) - - # Launch the update process. - if not timeout: - timeout = _REMOTE_FIRMWARE_UPDATE_DEFAULT_TIMEOUT - update_process = _RemoteFirmwareUpdater(remote_device, - xml_firmware_file, - ota_firmware_file=ota_firmware_file, - otb_firmware_file=otb_firmware_file, - timeout=timeout, - progress_callback=progress_callback) - update_process.update_firmware() - - -def _file_exists(file): - """ - Returns whether the given file path exists or not. - - Args: - file (String): the file path to check. - - Returns: - Boolean: ``True`` if the path exists, ``False`` otherwise - """ - if file is None: - return False - - return os.path.isfile(file) - - -def _bootloader_version_to_bytearray(bootloader_version): - """ - Transforms the given bootloader version in string format into a byte array. - - Args: - bootloader_version (String): the bootloader version as string. - - Returns: - Bytearray: the bootloader version as byte array, ``None`` if transformation failed. - """ - bootloader_version_array = bytearray(_BOOTLOADER_VERSION_SIZE) - version_split = bootloader_version.split(_BOOTLOADER_VERSION_SEPARATOR) - if len(version_split) < _BOOTLOADER_VERSION_SIZE: - return None - - for i in range(_BOOTLOADER_VERSION_SIZE): - bootloader_version_array[i] = utils.int_to_bytes((int(version_split[i])))[0] - - return bootloader_version_array - - -def _get_milliseconds(): - """ - Returns the current time in milliseconds. - - Returns: - Integer: the current time in milliseconds. - """ - return int(time.time() * 1000.0) - - -def _connect_device_with_retries(xbee_device, retries): - """ - Attempts to connect the XBee device with the given number of retries. - - Args: - xbee_device (:class:`.AbstractXBeeDevice`): the XBee device to connect. - retries (Integer): the number of connection retries. - - Returns: - Boolean: ``True`` if the device connected, ``False`` otherwise. - """ - if xbee_device is None: - return False - - if xbee_device.is_open(): - return True - - while retries > 0: - try: - xbee_device.open() - return True - except XBeeException: - retries -= 1 - if retries != 0: - time.sleep(1) - except SerialException: - return False - - return False - - -def _read_device_parameter_with_retries(xbee_device, parameter, retries=_PARAMETER_READ_RETRIES): - """ - Reads the given parameter from the XBee device with the given number of retries. - - Args: - xbee_device (:class:`.AbstractXBeeDevice`): the XBee device to read the parameter from. - parameter (String): the parameter to read. - retries (Integer, optional): the number of retries to perform after a :class:`.TimeoutException` - - Returns: - Bytearray: the read parameter value, ``None`` if the parameter could not be read. - """ - if xbee_device is None: - return None - - while retries > 0: - try: - return xbee_device.get_parameter(parameter) - except TimeoutException: - # On timeout exceptions perform retries. - retries -= 1 - if retries != 0: - time.sleep(1) - except XBeeException as e: - _log.exception(e) - return None - - return None - - -def _set_device_parameter_with_retries(xbee_device, parameter, value, retries=_PARAMETER_SET_RETRIES): - """ - Reads the given parameter from the XBee device with the given number of retries. - - Args: - xbee_device (:class:`.AbstractXBeeDevice`): the XBee device to read the parameter from. - parameter (String): the parameter to set. - value (Bytearray): the parameter value. - retries (Integer, optional): the number of retries to perform after a :class:`.TimeoutException` - - Returns: - Boolean: ``True`` if the parameter was correctly set, ``False`` otherwise. - """ - if xbee_device is None: - return None - - while retries > 0: - try: - xbee_device.set_parameter(parameter, value) - return True - except TimeoutException: - # On timeout exceptions perform retries. - retries -= 1 - if retries != 0: - time.sleep(1) - except XBeeException as e: - _log.exception(e) - return False - - return False - - -def _read_device_bootloader_version(xbee_device): - """ - Returns the bootloader version of the given XBee device. - - Args: - xbee_device (:class:`.AbstractXBeeDevice`): the XBee device to read the parameter from. - - Returns: - Bytearray: the XBee device bootloader version as byte array, ``None`` if it could not be read. - """ - bootloader_version_array = bytearray(3) - bootloader_version = _read_device_parameter_with_retries(xbee_device, _PARAMETER_BOOTLOADER_VERSION, - _PARAMETER_READ_RETRIES) - if bootloader_version is None or len(bootloader_version) < 2: - return None - 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 - - return bootloader_version_array - - -def _read_device_compatibility_number(xbee_device): - """ - Returns the compatibility number of the given XBee device. - - Args: - xbee_device (:class:`.AbstractXBeeDevice`): the XBee device to read the parameter from. - - Returns: - Integer: the XBee device compatibility number as integer, ``None`` if it could not be read. - """ - compatibility_number = _read_device_parameter_with_retries(xbee_device, - ATStringCommand.PERCENT_C.command, - _PARAMETER_READ_RETRIES) - if compatibility_number is None: - return None - compatibility_number = utils.hex_to_string(compatibility_number)[0:2] - - return int(compatibility_number) - - -def _read_device_region_lock(xbee_device): - """ - Returns the region lock number of the given XBee device. - - Args: - xbee_device (:class:`.AbstractXBeeDevice`): the XBee device to read the parameter from. - - Returns: - Integer: the XBee device region lock number as integer, ``None`` if it could not be read. - """ - region_lock = _read_device_parameter_with_retries(xbee_device, ATStringCommand.R_QUESTION.command, - _PARAMETER_READ_RETRIES) - if region_lock is None: - return None - - return int(region_lock[0]) - - -def _read_device_hardware_version(xbee_device): - """ - Returns the hardware version of the given XBee device. - - Args: - xbee_device (:class:`.AbstractXBeeDevice`): the XBee device to read the parameter from. - - Returns: - Integer: the XBee device hardware version as integer, ``None`` if it could not be read. - """ - hardware_version = _read_device_parameter_with_retries(xbee_device, ATStringCommand.HV.command, - _PARAMETER_READ_RETRIES) - if hardware_version is None: - return None - - return int(hardware_version[0]) - - -def _read_device_firmware_version(xbee_device): - """ - Returns the firmware version of the given XBee device. - - Args: - xbee_device (:class:`.AbstractXBeeDevice`): the XBee device to read the parameter from. - - Returns: - Integer: the XBee device firmware version as integer, ``None`` if it could not be read. - """ - firmware_version = _read_device_parameter_with_retries(xbee_device, ATStringCommand.VR.command, - _PARAMETER_READ_RETRIES) - if firmware_version is None: - return None - - return utils.bytes_to_int(firmware_version) - - -def _reverse_bytearray(byte_array): - """ - Reverses the given byte array order. - - Args: - byte_array (Bytearray): the byte array to reverse. - - Returns: - Bytearray: the reversed byte array. - """ - return bytearray(list(reversed(byte_array))) diff --git a/digi/xbee/io.py b/digi/xbee/io.py index 5289b6b..966d0c3 100644 --- a/digi/xbee/io.py +++ b/digi/xbee/io.py @@ -175,7 +175,6 @@ def get(cls, code): IOValue.lookupTable = {x.code: x for x in IOValue} -IOValue.__doc__ += utils.doc_enum(IOValue) class IOSample(object): @@ -622,7 +621,7 @@ def get_analog_value(self, io_line): class IOMode(Enum): """ - Enumerates the different Input/Output modes that an IO line can be + Enumerates the different Input/Output modes that an IO line can be configured with. """ @@ -646,6 +645,3 @@ class IOMode(Enum): DIGITAL_OUT_HIGH = 5 """Digital output, High""" - - I2C_FUNCTIONALITY = 6 - """I2C functionality""" diff --git a/digi/xbee/models/address.py b/digi/xbee/models/address.py index 88fcb72..628a423 100644 --- a/digi/xbee/models/address.py +++ b/digi/xbee/models/address.py @@ -41,10 +41,8 @@ class XBee16BitAddress(object): """ - PATTERN = "^(0[xX])?[0-9a-fA-F]{1,4}$" - """ - 16-bit address string pattern. - """ + PATTERN = "(0[xX])?[0-9a-fA-F]{1,4}" + __REGEXP = re.compile(PATTERN) COORDINATOR_ADDRESS = None """ @@ -61,8 +59,6 @@ class XBee16BitAddress(object): 16-bit unknown address (value: FFFE). """ - __REGEXP = re.compile(PATTERN) - def __init__(self, address): """ Class constructor. Instantiates a new :class:`.XBee16BitAddress` object with the provided parameters. @@ -72,9 +68,9 @@ def __init__(self, address): Raises: TypeError: if ``address`` is ``None``. - ValueError: if ``address`` is ``None`` or has less than 1 byte or more than 2. + ValueError: if ``address`` has less than 1 byte or more than 2. """ - if not address: + if len(address) < 1: raise ValueError("Address must contain at least 1 byte") if len(address) > 2: raise ValueError("Address can't contain more than 2 bytes") @@ -96,11 +92,10 @@ def from_hex_string(cls, address): ValueError: if ``address`` has less than 1 character. ValueError: if ``address`` contains non-hexadecimal characters. """ - if not address: + if len(address) < 1: raise ValueError("Address must contain at least 1 digit") - if not cls.__REGEXP.match(address): - raise ValueError("Address must follow this pattern: " + cls.PATTERN) - + if not XBee16BitAddress.__REGEXP.match(address): + raise ValueError("Address must match with PATTERN") return cls(utils.hex_string_to_bytes(address)) @classmethod @@ -121,29 +116,8 @@ def from_bytes(cls, hsb, lsb): raise ValueError("HSB must be between 0 and 255.") if lsb > 255 or lsb < 0: raise ValueError("LSB must be between 0 and 255.") - return cls(bytearray([hsb, lsb])) - @classmethod - def is_valid(cls, address): - """ - Checks if the provided hex string is a valid 16-bit address. - - Args: - address (String or Bytearray): String: String containing the address. - Must be made by hex. digits without blanks. Minimum 1 character, maximum 4 (16-bit). - Bytearray: Address as byte array. Must be 1-2 digits. - - Returns: - Boolean: ``True`` for a valid 16-bit address, ``False`` otherwise. - """ - if isinstance(address, bytearray) and 2 < len(address) < 1: - return False - elif isinstance(address, str) and not cls.__REGEXP.match(address): - return False - - return True - def __get_item__(self, index): """ Operator [] @@ -167,18 +141,6 @@ def __str__(self): """ return utils.hex_to_string(self.__address) - def __hash__(self): - """ - Returns a hash code value for the object. - - Returns: - Integer: hash code value for the object. - """ - res = 23 - for b in self.__address: - res = 31 * (res + b) - return res - def __eq__(self, other): """ Operator == @@ -189,9 +151,10 @@ def __eq__(self, other): Returns: Boolean: ``True`` if self and other have the same value and type, ``False`` in other case. """ + if other is None: + return False if not isinstance(other, XBee16BitAddress): return False - return self.__address.__eq__(other.__address) def __iter__(self): @@ -246,11 +209,10 @@ class XBee64BitAddress(object): The 64-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,16}$" - """ - 64-bit address string pattern. - """ + __DEVICE_ID_SEPARATOR = "-" + __DEVICE_ID_MAC_SEPARATOR = "FF" + __XBEE_64_BIT_ADDRESS_PATTERN = "(0[xX])?[0-9a-fA-F]{1,16}" + __REGEXP = re.compile(__XBEE_64_BIT_ADDRESS_PATTERN) COORDINATOR_ADDRESS = None """ @@ -267,10 +229,6 @@ class XBee64BitAddress(object): 64-bit unknown address (value: FFFFFFFFFFFFFFFF). """ - __REGEXP = re.compile(PATTERN) - __DEVICE_ID_SEPARATOR = "-" - __DEVICE_ID_MAC_SEPARATOR = "FF" - def __init__(self, address): """ Class constructor. Instantiates a new :class:`.XBee64BitAddress` object with the provided parameters. @@ -279,9 +237,9 @@ def __init__(self, address): address (Bytearray): the XBee 64-bit address as byte array. Raise: - ValueError: if ``address`` is ``None`` or its length less than 1 or greater than 8. + ValueError: if length of ``address`` is less than 1 or greater than 8. """ - if not address: + if len(address) < 1: raise ValueError("Address must contain at least 1 byte") if len(address) > 8: raise ValueError("Address cannot contain more than 8 bytes") @@ -305,10 +263,10 @@ def from_hex_string(cls, address): ValueError: if the address' length is less than 1 or does not match with the pattern: ``(0[xX])?[0-9a-fA-F]{1,16}``. """ - if not address: + if len(address) < 1: raise ValueError("Address must contain at least 1 byte") if not (cls.__REGEXP.match(address)): - raise ValueError("Address must follow this pattern: " + cls.PATTERN) + raise ValueError("Address must follow this pattern: " + cls.__XBEE_64_BIT_ADDRESS_PATTERN) return cls(utils.hex_string_to_bytes(address)) @@ -328,27 +286,8 @@ def from_bytes(cls, *args): for i in range(len(args)): if args[i] > 255 or args[i] < 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 64-bit address. - - Args: - address (String or Bytearray): The XBee 64-bit address as a string or bytearray. - - Returns: - Boolean: ``True`` for a valid 64-bit address, ``False`` otherwise. - """ - if isinstance(address, bytearray) and 8 < len(address) < 1: - return False - elif isinstance(address, str) and not cls.__REGEXP.match(address): - return False - - return True - def __str__(self): """ Called by the str() built-in function and by the print statement to compute the "informal" string @@ -360,18 +299,6 @@ def __str__(self): """ return "".join(["%02X" % i for i in self.__address]) - def __hash__(self): - """ - Returns a hash code value for the object. - - Returns: - Integer: hash code value for the object. - """ - res = 23 - for b in self.__address: - res = 31 * (res + b) - return res - def __eq__(self, other): """ Operator == @@ -386,7 +313,6 @@ def __eq__(self, other): return False if not isinstance(other, XBee64BitAddress): return False - return self.__address.__eq__(other.__address) def __iter__(self): @@ -423,12 +349,8 @@ class XBeeIMEIAddress(object): This address is only applicable for Cellular protocol. """ - PATTERN = r"^\d{0,15}$" - """ - IMEI address string pattern. - """ - - __REGEXP = re.compile(PATTERN) + __IMEI_PATTERN = "^\d{0,15}$" + __REGEXP = re.compile(__IMEI_PATTERN) def __init__(self, address): """ @@ -463,28 +385,10 @@ def from_string(cls, address): if address is None: raise ValueError("IMEI address cannot be None") if not (cls.__REGEXP.match(address)): - raise ValueError("Address must follow this pattern: " + cls.PATTERN) + raise ValueError("Address must follow this pattern: " + cls.__IMEI_PATTERN) return cls(utils.hex_string_to_bytes(address)) - @classmethod - def is_valid(cls, address): - """ - Checks if the provided hex string is a valid IMEI. - - Args: - address (String or Bytearray): The XBee IMEI address as a string or bytearray. - - Returns: - Boolean: ``True`` for a valid IMEI, ``False`` otherwise. - """ - if isinstance(address, bytearray) and len(address) < 8: - return False - elif isinstance(address, str) and not cls.__REGEXP.match(address): - return False - - return True - @staticmethod def __generate_byte_array(byte_address): """ @@ -518,18 +422,6 @@ def __str__(self): """ return self.__get_value() - def __hash__(self): - """ - Returns a hash code value for the object. - - Returns: - Integer: hash code value for the object. - """ - res = 23 - for b in self.__address: - res = 31 * (res + b) - return res - def __eq__(self, other): """ Operator == @@ -544,7 +436,6 @@ def __eq__(self, other): return False if not isinstance(other, XBeeIMEIAddress): return False - return self.__address.__eq__(other.__address) address = property(__get_value) diff --git a/digi/xbee/models/atcomm.py b/digi/xbee/models/atcomm.py index da4d228..6f5a9c1 100644 --- a/digi/xbee/models/atcomm.py +++ b/digi/xbee/models/atcomm.py @@ -1,4 +1,4 @@ -# Copyright 2017-2019, Digi International Inc. +# Copyright 2017, 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 @@ -27,86 +27,22 @@ class ATStringCommand(Enum): | **value** (String): value of this ATStringCommand. """ - AC = ("AC", "Apply changes") - AI = ("AI", "Association indication") - AO = ("AO", "API options") - AP = ("AP", "API enable") - AS = ("AS", "Active scan") - BD = ("BD", "UART baudrate") - BL = ("BL", "Bluetooth address") - BT = ("BT", "Bluetooth enable") - C0 = ("C0", "Source port") - C8 = ("C8", "Compatibility mode") - CC = ("CC", "Command sequence character") - CE = ("CE", "Device role") - CN = ("CN", "Exit command mode") - DA = ("DA", "Force Disassociation") - DH = ("DH", "Destination address high") - DL = ("DL", "Destination address low") - D7 = ("D7", "CTS configuration") - EE = ("EE", "Encryption enable") - FN = ("FN", "Find neighbors") - FR = ("FR", "Software reset") - FS = ("FS", "File system") - GW = ("GW", "Gateway address") - GT = ("GT", "Guard times") - HV = ("HV", "Hardware version") - IC = ("IC", "Digital change detection") - ID = ("ID", "Network PAN ID/Network ID/SSID") - IR = ("IR", "I/O sample rate") - IS = ("IS", "Force sample") - KY = ("KY", "Link/Encryption key") - MA = ("MA", "IP addressing mode") - MK = ("MK", "IP address mask") - MY = ("MY", "16-bit address/IP address") - NB = ("NB", "Parity") - NI = ("NI", "Node identifier") - ND = ("ND", "Node discover") - NK = ("NK", "Trust Center network key") - NO = ("NO", "Node discover options") - NR = ("NR", "Network reset") - NS = ("NS", "DNS address") - NT = ("NT", "Node discover back-off") - N_QUESTION = ("N?", "Network discovery timeout") - OP = ("OP", "Operating extended PAN ID") - PK = ("PK", "Passphrase") - PL = ("PL", "TX power level") - RE = ("RE", "Restore defaults") - RR = ("RR", "XBee retries") - R_QUESTION = ("R?", "Region lock") - SB = ("SB", "Stop bits") - SH = ("SH", "Serial number high") - SI = ("SI", "Socket info") - SL = ("SL", "Serial number low") - SM = ("SM", "Sleep mode") - SS = ("SS", "Sleep status") - VH = ("VH", "Bootloader version") - VR = ("VR", "Firmware version") - WR = ("WR", "Write") - DOLLAR_S = ("$S", "SRP salt") - DOLLAR_V = ("$V", "SRP salt verifier") - DOLLAR_W = ("$W", "SRP salt verifier") - DOLLAR_X = ("$X", "SRP salt verifier") - DOLLAR_Y = ("$Y", "SRP salt verifier") - PERCENT_C = ("%C", "Hardware/software compatibility") - PERCENT_P = ("%P", "Invoke bootloader") - - def __init__(self, command, description): + NI = "NI" + KY = "KY" + NK = "NK" + ZU = "ZU" + ZV = "ZV" + CC = "CC" + + def __init__(self, command): self.__command = command - self.__description = description def __get_command(self): return self.__command - def __get_description(self): - return self.__description - command = property(__get_command) """String. AT Command alias.""" - description = property(__get_description) - """String. AT Command description""" - ATStringCommand.__doc__ += utils.doc_enum(ATStringCommand) diff --git a/digi/xbee/models/info.py b/digi/xbee/models/info.py deleted file mode 100644 index b8cfa88..0000000 --- a/digi/xbee/models/info.py +++ /dev/null @@ -1,177 +0,0 @@ -# Copyright 2019, 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.models.protocol import IPProtocol -from digi.xbee.models.status import SocketInfoState -from digi.xbee.util import utils - - -class SocketInfo: - """ - This class represents the information of an XBee socket: - - * Socket ID. - * State. - * Protocol. - * Local port. - * Remote port. - * Remote address. - """ - - __SEPARATOR = "\r" - __LIST_LENGTH = 6 - - def __init__(self, socket_id, state, protocol, local_port, remote_port, remote_address): - """ - Class constructor. Instantiates a ``SocketInfo`` object with the given parameters. - - Args: - socket_id (Integer): The ID of the socket. - state (:class:`.SocketInfoState`): The state of the socket. - protocol (:class:`.IPProtocol`): The protocol of the socket. - local_port (Integer): The local port of the socket. - remote_port (Integer): The remote port of the socket. - remote_address (String): The remote IPv4 address of the socket. - """ - self.__socket_id = socket_id - self.__state = state - self.__protocol = protocol - self.__local_port = local_port - self.__remote_port = remote_port - self.__remote_address = remote_address - - @staticmethod - def create_socket_info(raw): - """ - Parses the given bytearray data and returns a ``SocketInfo`` object. - - Args: - raw (Bytearray): received data from the ``SI`` command with a socket ID as argument. - - Returns: - :class:`.SocketInfo`: The socket information, or ``None`` if the provided data is invalid. - """ - info_array = bytearray.fromhex(utils.hex_to_string(raw)).decode("utf8").strip().split(SocketInfo.__SEPARATOR) - if len(info_array) != SocketInfo.__LIST_LENGTH: - return None - socket_id = int(info_array[0], 0) - state = SocketInfoState.get_by_description(info_array[1]) - protocol = IPProtocol.get_by_description(info_array[2]) - local_port = int(info_array[3], 0) - remote_port = int(info_array[4], 0) - remote_address = info_array[5] - return SocketInfo(socket_id, state, protocol, local_port, remote_port, remote_address) - - @staticmethod - def parse_socket_list(raw): - """ - Parses the given bytearray data and returns a list with the active socket IDs. - - Args: - raw (Bytearray): received data from the ``SI`` command. - - Returns: - List: list with the IDs of all active (open) sockets, or empty list if there is not any active socket. - """ - socket_list = list() - ids_array = bytearray.fromhex(utils.hex_to_string(raw)).decode("utf8").strip().split(SocketInfo.__SEPARATOR) - for x in ids_array: - if x != "": - socket_list.append(int(x, 0)) - return socket_list - - def __get_socket_id(self): - """ - Returns the ID of the socket. - - Returns: - Integer: the ID of the socket. - """ - return self.__socket_id - - def __get_state(self): - """ - Returns the state of the socket. - - Returns: - :class:`.SocketInfoState`: the state of the socket. - """ - return self.__state - - def __get_protocol(self): - """ - Returns the protocol of the socket. - - Returns: - :class:`.IPProtocol`: the protocol of the socket. - """ - return self.__protocol - - def __get_local_port(self): - """ - Returns the local port of the socket. - - Returns: - Integer: the local port of the socket. - """ - return self.__local_port - - def __get_remote_port(self): - """ - Returns the remote port of the socket. - - Returns: - Integer: the remote port of the socket. - """ - return self.__remote_port - - def __get_remote_address(self): - """ - Returns the remote IPv4 address of the socket. - - Returns: - String: the remote IPv4 address of the socket. - """ - return self.__remote_address - - def __str__(self): - return "ID: 0x%s\n" \ - "State: %s\n" \ - "Protocol: %s\n" \ - "Local port: 0x%s\n" \ - "Remote port: 0x%s\n" \ - "Remote address: %s"\ - % (utils.hex_to_string(utils.int_to_bytes(self.__socket_id, num_bytes=1), False), - self.__state.description, self.__protocol.description, - utils.hex_to_string(utils.int_to_bytes(self.__local_port, num_bytes=2), False), - utils.hex_to_string(utils.int_to_bytes(self.__remote_port, num_bytes=2), False), - self.__remote_address) - - socket_id = property(__get_socket_id) - """Integer. The ID of the socket.""" - - state = property(__get_state) - """:class:`.SocketInfoState`: The state of the socket.""" - - protocol = property(__get_protocol) - """:class:`.IPProtocol`: The protocol of the socket.""" - - local_port = property(__get_local_port) - """Integer: The local port of the socket. This is 0 unless the socket is explicitly bound to a port.""" - - remote_port = property(__get_remote_port) - """Integer: The remote port of the socket.""" - - remote_address = property(__get_remote_address) - """String: The remote IPv4 address of the socket. This is ``0.0.0.0`` for an unconnected socket.""" diff --git a/digi/xbee/models/message.py b/digi/xbee/models/message.py old mode 100755 new mode 100644 diff --git a/digi/xbee/models/mode.py b/digi/xbee/models/mode.py index 7294e6d..8c46bab 100644 --- a/digi/xbee/models/mode.py +++ b/digi/xbee/models/mode.py @@ -1,4 +1,4 @@ -# Copyright 2017-2019, Digi International Inc. +# Copyright 2017, 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,8 +13,6 @@ # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. from enum import Enum, unique - -from digi.xbee.models.protocol import XBeeProtocol from digi.xbee.util import utils @@ -31,8 +29,6 @@ class OperatingMode(Enum): AT_MODE = (0, "AT mode") API_MODE = (1, "API mode") ESCAPED_API_MODE = (2, "API mode with escaped characters") - MICROPYTHON_MODE = (4, "MicroPython REPL") - BYPASS_MODE = (5, "Bypass mode") UNKNOWN = (99, "Unknown") def __init__(self, code, description): @@ -149,98 +145,6 @@ def get(cls, code): APIOutputMode.__doc__ += utils.doc_enum(APIOutputMode) -@unique -class APIOutputModeBit(Enum): - """ - Enumerates the different API output mode bit options. The API output mode - establishes the way data will be output through the serial interface of an XBee. - - | Inherited properties: - | **name** (String): the name (id) of this APIOutputModeBit. - | **value** (String): the value of this APIOutputModeBit. - """ - - EXPLICIT = (0x01, "Output in Native/Explicit API format") - UNSUPPORTED_ZDO_PASSTHRU = (0x02, "Unsupported ZDO request pass-through") - SUPPORTED_ZDO_PASSTHRU = (0x04, "Supported ZDO request pass-through") - BINDING_PASSTHRU = (0x08, "Binding request pass-through") - - def __init__(self, code, description): - self.__code = code - self.__description = description - - def __get_code(self): - """ - Returns the code of the APIOutputModeBit element. - - Returns: - Integer: the code of the APIOutputModeBit element. - """ - return self.__code - - def __get_description(self): - """ - Returns the description of the APIOutputModeBit element. - - Returns: - String: the description of the APIOutputModeBit element. - """ - return self.__description - - @classmethod - def get(cls, code): - """ - Returns the APIOutputModeBit for the given code. - - Args: - code (Integer): the code corresponding to the API output mode to get. - - Returns: - :class:`.OperatingMode`: the APIOutputModeBit with the given code, ``None`` - if there is not an APIOutputModeBit with that code. - """ - for item in cls: - if code == item.code: - return item - - return None - - @classmethod - def calculate_api_output_mode_value(cls, protocol, options): - """ - Calculates the total value of a combination of several option bits for the - given protocol. - - Args: - protocol (:class:`digi.xbee.models.protocol.XBeeProtocol`): The ``XBeeProtocol`` - to calculate the value of all the given API output options. - options: Collection of option bits to get the final value. - - Returns: - Integer: The value to be configured in the module depending on the given - collection of option bits and the protocol. - """ - if not options: - return 0 - - if protocol == XBeeProtocol.ZIGBEE: - return sum(op.code for op in options) - elif protocol in (XBeeProtocol.DIGI_MESH, XBeeProtocol.DIGI_POINT, - XBeeProtocol.XLR, XBeeProtocol.XLR_DM): - return sum(op.code for op in options if lambda option: option != cls.EXPLICIT) - - return 0 - - code = property(__get_code) - """Integer. The API output mode bit code.""" - - description = property(__get_description) - """String: The API output mode bit description.""" - - -APIOutputModeBit.__doc__ += utils.doc_enum(APIOutputModeBit) - - @unique class IPAddressingMode(Enum): """ @@ -298,72 +202,3 @@ def get(cls, code): IPAddressingMode.lookupTable = {x.code: x for x in IPAddressingMode} IPAddressingMode.__doc__ += utils.doc_enum(IPAddressingMode) - - -@unique -class NeighborDiscoveryMode(Enum): - """ - Enumerates the different neighbor discovery modes. This mode establishes the way the - network discovery process is performed. - - | Inherited properties: - | **name** (String): the name (id) of this OperatingMode. - | **value** (String): the value of this OperatingMode. - """ - - CASCADE = (0, "Cascade") - """ - The discovery of a node neighbors is requested once the previous request finishes. - This means that just one discovery process is running at the same time. - - This mode is recommended for large networks, it might be a slower method but it - generates less traffic than 'Flood'. - """ - - FLOOD = (1, "Flood") - """ - The discovery of a node neighbors is requested when the node is found in the network. - This means that several discovery processes might be running at the same time. - """ - - def __init__(self, code, description): - self.__code = code - self.__description = description - - @property - def code(self): - """ - Returns the code of the NeighborDiscoveryMode element. - - Returns: - String: the code of the NeighborDiscoveryMode element. - """ - return self.__code - - @property - def description(self): - """ - Returns the description of the NeighborDiscoveryMode element. - - Returns: - String: the description of the NeighborDiscoveryMode element. - """ - return self.__description - - @classmethod - def get(cls, code): - """ - Returns the NeighborDiscoveryMode for the given code. - - Args: - code (Integer): the code corresponding to the mode to get. - - Returns: - :class:`.NeighborDiscoveryMode`: the NeighborDiscoveryMode with - the given code. ``None`` if there is not a mode with that code. - """ - for mode in cls: - if mode.code == code: - return mode - - return None diff --git a/digi/xbee/models/options.py b/digi/xbee/models/options.py index 466cdf8..92c1d0e 100644 --- a/digi/xbee/models/options.py +++ b/digi/xbee/models/options.py @@ -468,124 +468,3 @@ def get(cls, code): XBeeLocalInterface.lookupTable = {x.code: x for x in XBeeLocalInterface} XBeeLocalInterface.__doc__ += utils.doc_enum(XBeeLocalInterface) - - -class RegisterKeyOptions(Enum): - """ - This class lists all the possible options that have been set while - receiving an XBee packet. - - The receive options are usually set as a bitfield meaning that the - options can be combined using the '|' operand. - """ - - LINK_KEY = (0x00, "Key is a Link Key (KY on joining node)") - INSTALL_CODE = (0x01, "Key is an Install Code (I? on joining node, DC must be set to 1 on joiner)") - UNKNOWN = (0xFF, "Unknown key option") - - def __init__(self, code, description): - self.__code = code - self.__description = description - - def __get_code(self): - """ - Returns the code of the ``RegisterKeyOptions`` element. - - Returns: - Integer: the code of the ``RegisterKeyOptions`` element. - """ - return self.__code - - def __get_description(self): - """ - Returns the description of the ``RegisterKeyOptions`` element. - - Returns: - String: the description of the ``RegisterKeyOptions`` element. - """ - return self.__description - - @classmethod - def get(cls, code): - """ - Returns the register key option for the given code. - - Args: - code (Integer): the code of the register key option to get. - - Returns: - :class:`.RegisterKeyOptions`: the ``RegisterKeyOptions`` with the given code, - ``UNKNOWN`` if there is not any ``RegisterKeyOptions`` with the provided code. - """ - try: - return cls.lookupTable[code] - except KeyError: - return RegisterKeyOptions.UNKNOWN - - code = property(__get_code) - """Integer. The register key option code.""" - - description = property(__get_description) - """String. The register key option description.""" - - -RegisterKeyOptions.lookupTable = {x.code: x for x in RegisterKeyOptions} -RegisterKeyOptions.__doc__ += utils.doc_enum(RegisterKeyOptions) - - -@unique -class SocketOption(Enum): - """ - Enumerates the different Socket Options. - """ - TLS_PROFILE = (0x00, "TLS Profile") - UNKNOWN = (0xFF, "Unknown") - - def __init__(self, code, description): - self.__code = code - self.__description = description - - def __get_code(self): - """ - Returns the code of the ``SocketOption`` element. - - Returns: - Integer: the code of the ``SocketOption`` element. - """ - return self.__code - - def __get_description(self): - """ - Returns the description of the ``SocketOption`` element. - - Returns: - String: the description of the ``SocketOption`` element. - """ - return self.__description - - @classmethod - def get(cls, code): - """ - Returns the Socket Option for the given code. - - Args: - code (Integer): the code of the Socket Option to get. - - Returns: - :class:`.SocketOption`: the ``SocketOption`` with the given code, - ``SocketOption.UNKNOWN`` if there is not any option with the provided code. - """ - try: - return cls.lookupTable[code] - except KeyError: - return SocketOption.UNKNOWN - - code = property(__get_code) - """Integer. The Socket Option code.""" - - description = property(__get_description) - """String. The Socket Option description.""" - - -SocketOption.lookupTable = {x.code: x for x in SocketOption} -SocketOption.__doc__ += utils.doc_enum(SocketOption) diff --git a/digi/xbee/models/protocol.py b/digi/xbee/models/protocol.py index dfa3f41..4751afe 100644 --- a/digi/xbee/models/protocol.py +++ b/digi/xbee/models/protocol.py @@ -279,7 +279,7 @@ class IPProtocol(Enum): UDP = (0, "UDP") TCP = (1, "TCP") - TCP_SSL = (4, "TLS") + TCP_SSL = (4, "TCP SSL") def __init__(self, code, description): self.__code = code @@ -320,23 +320,6 @@ def get(cls, code): except KeyError: return None - @classmethod - def get_by_description(cls, description): - """ - Returns the IP Protocol for the given description. - - Args: - description (String): the description of the IP Protocol to get. - - Returns: - :class:`.IPProtocol`: IP protocol for the given description or ``None`` if there - is not any ``IPProtocol`` with the given description. - """ - for x in IPProtocol: - if x.description.lower() == description.lower(): - return x - return None - code = property(__get_code) """Integer: IP protocol code.""" @@ -346,63 +329,3 @@ def get_by_description(cls, description): IPProtocol.lookupTable = {x.code: x for x in IPProtocol} IPProtocol.__doc__ += utils.doc_enum(IPProtocol) - - -@unique -class Role(Enum): - """ - Enumerates the available roles for an XBee. - - | Inherited properties: - | **name** (String): the name (id) of this Role. - | **value** (String): the value of this Role. - """ - - COORDINATOR = (0, "Coordinator") - ROUTER = (1, "Router") - END_DEVICE = (2, "End device") - UNKNOWN = (3, "Unknown") - - def __init__(self, identifier, description): - self.__id = identifier - self.__desc = description - - @property - def id(self): - """ - Gets the identifier of the role. - - Returns: - Integer: the role identifier. - """ - return self.__id - - @property - def description(self): - """ - Gets the description of the role. - - Returns: - String: the role description. - """ - return self.__desc - - @classmethod - def get(cls, identifier): - """ - Returns the Role for the given identifier. - - Args: - identifier (Integer): the id value corresponding to the role to get. - - Returns: - :class:`.Role`: the Role with the given identifier. ``None`` if it does not exist. - """ - for item in cls: - if identifier == item.id: - return item - - return None - - -Role.__doc__ += utils.doc_enum(Role) diff --git a/digi/xbee/models/status.py b/digi/xbee/models/status.py index e2da57d..5937789 100644 --- a/digi/xbee/models/status.py +++ b/digi/xbee/models/status.py @@ -66,9 +66,7 @@ def get(cls, code): :class:`.ATCommandStatus`: the AT command status with the given code. """ try: - # For ATCommResponsePacket (0x88) and RemoteATCommandResponsePacket - # (x097), use least significant nibble for status - return cls.lookupTable[code & 0x0F] + return cls.lookupTable[code] except KeyError: return ATCommandStatus.UNKNOWN @@ -756,9 +754,6 @@ class NetworkDiscoveryStatus(Enum): """ SUCCESS = (0x00, "Success") ERROR_READ_TIMEOUT = (0x01, "Read timeout error") - ERROR_NET_DISCOVER = (0x02, "Error executing node discovery") - ERROR_GENERAL = (0x03, "Error while discovering network") - CANCEL = (0x04, "Discovery process cancelled") def __init__(self, code, description): self.__code = code @@ -808,284 +803,3 @@ def get(cls, code): NetworkDiscoveryStatus.lookupTable = {x.code: x for x in NetworkDiscoveryStatus} NetworkDiscoveryStatus.__doc__ += utils.doc_enum(NetworkDiscoveryStatus) - - -@unique -class ZigbeeRegisterStatus(Enum): - """ - Enumerates the different statuses of the Zigbee Device Register process. - """ - SUCCESS = (0x00, "Success") - KEY_TOO_LONG = (0x01, "Key too long") - 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") - KEY_NOT_FOUND = (0xFF, "Key not found") - UNKNOWN = (0xEE, "Unknown") - - def __init__(self, code, description): - self.__code = code - self.__description = description - - def __get_code(self): - """ - Returns the code of the ``ZigbeeRegisterStatus`` element. - - Returns: - Integer: the code of the ``ZigbeeRegisterStatus`` element. - """ - return self.__code - - def __get_description(self): - """ - Returns the description of the ``ZigbeeRegisterStatus`` element. - - Returns: - String: the description of the ``ZigbeeRegisterStatus`` element. - """ - return self.__description - - @classmethod - def get(cls, code): - """ - Returns the Zigbee Device Register status for the given code. - - Args: - code (Integer): the code of the Zigbee Device Register status to get. - - Returns: - :class:`.ZigbeeRegisterStatus`: the ``ZigbeeRegisterStatus`` with the given code, - ``ZigbeeRegisterStatus.UNKNOWN`` if there is not any status with the provided code. - """ - try: - return cls.lookupTable[code] - except KeyError: - return ZigbeeRegisterStatus.UNKNOWN - - code = property(__get_code) - """Integer. The Zigbee Device Register status code.""" - - description = property(__get_description) - """String. The Zigbee Device Register status description.""" - - -ZigbeeRegisterStatus.lookupTable = {x.code: x for x in ZigbeeRegisterStatus} -ZigbeeRegisterStatus.__doc__ += utils.doc_enum(ZigbeeRegisterStatus) - - -@unique -class SocketStatus(Enum): - """ - Enumerates the different Socket statuses. - """ - SUCCESS = (0x00, "Operation successful") - INVALID_PARAM = (0x01, "Invalid parameters") - FAILED_TO_READ = (0x02, "Failed to retrieve option value") - CONNECTION_IN_PROGRESS = (0x03, "Connection already in progress") - ALREADY_CONNECTED = (0x04, "Already connected/bound/listening") - UNKNOWN_ERROR = (0x05, "Unknown error") - BAD_SOCKET = (0x20, "Bad socket ID") - NOT_REGISTERED = (0x22, "Not registered to cell network") - INTERNAL_ERROR = (0x31, "Internal error") - RESOURCE_ERROR = (0x32, "Resource error: retry the operation later") - INVALID_PROTOCOL = (0x7B, "Invalid protocol") - UNKNOWN = (0xFF, "Unknown") - - def __init__(self, code, description): - self.__code = code - self.__description = description - - def __get_code(self): - """ - Returns the code of the ``SocketStatus`` element. - - Returns: - Integer: the code of the ``SocketStatus`` element. - """ - return self.__code - - def __get_description(self): - """ - Returns the description of the ``SocketStatus`` element. - - Returns: - String: the description of the ``SocketStatus`` element. - """ - return self.__description - - @classmethod - def get(cls, code): - """ - Returns the Socket status for the given code. - - Args: - code (Integer): the code of the Socket status to get. - - Returns: - :class:`.SocketStatus`: the ``SocketStatus`` with the given code, - ``SocketStatus.UNKNOWN`` if there is not any status with the provided code. - """ - try: - return cls.lookupTable[code] - except KeyError: - return SocketStatus.UNKNOWN - - code = property(__get_code) - """Integer. The Socket status code.""" - - description = property(__get_description) - """String. The Socket status description.""" - - -SocketStatus.lookupTable = {x.code: x for x in SocketStatus} -SocketStatus.__doc__ += utils.doc_enum(SocketStatus) - - -@unique -class SocketState(Enum): - """ - Enumerates the different Socket states. - """ - CONNECTED = (0x00, "Connected") - FAILED_DNS = (0x01, "Failed DNS lookup") - CONNECTION_REFUSED = (0x02, "Connection refused") - TRANSPORT_CLOSED = (0x03, "Transport closed") - TIMED_OUT = (0x04, "Timed out") - INTERNAL_ERROR = (0x05, "Internal error") - HOST_UNREACHABLE = (0x06, "Host unreachable") - CONNECTION_LOST = (0x07, "Connection lost") - UNKNOWN_ERROR = (0x08, "Unknown error") - UNKNOWN_SERVER = (0x09, "Unknown server") - RESOURCE_ERROR = (0x0A, "Resource error") - LISTENER_CLOSED = (0x0B, "Listener closed") - UNKNOWN = (0xFF, "Unknown") - - def __init__(self, code, description): - self.__code = code - self.__description = description - - def __get_code(self): - """ - Returns the code of the ``SocketState`` element. - - Returns: - Integer: the code of the ``SocketState`` element. - """ - return self.__code - - def __get_description(self): - """ - Returns the description of the ``SocketState`` element. - - Returns: - String: the description of the ``SocketState`` element. - """ - return self.__description - - @classmethod - def get(cls, code): - """ - Returns the Socket state for the given code. - - Args: - code (Integer): the code of the Socket state to get. - - Returns: - :class:`.SocketState`: the ``SocketState`` with the given code, - ``SocketState.UNKNOWN`` if there is not any status with the provided code. - """ - try: - return cls.lookupTable[code] - except KeyError: - return SocketState.UNKNOWN - - code = property(__get_code) - """Integer. The Socket state code.""" - - description = property(__get_description) - """String. The Socket state description.""" - - -SocketState.lookupTable = {x.code: x for x in SocketState} -SocketState.__doc__ += utils.doc_enum(SocketState) - - -@unique -class SocketInfoState(Enum): - """ - Enumerates the different Socket info states. - """ - ALLOCATED = (0x00, "Allocated") - CONNECTING = (0x01, "Connecting") - CONNECTED = (0x02, "Connected") - LISTENING = (0x03, "Listening") - BOUND = (0x04, "Bound") - CLOSING = (0x05, "Closing") - UNKNOWN = (0xFF, "Unknown") - - def __init__(self, code, description): - self.__code = code - self.__description = description - - def __get_code(self): - """ - Returns the code of the ``SocketInfoState`` element. - - Returns: - Integer: the code of the ``SocketInfoState`` element. - """ - return self.__code - - def __get_description(self): - """ - Returns the description of the ``SocketInfoState`` element. - - Returns: - String: the description of the ``SocketInfoState`` element. - """ - return self.__description - - @classmethod - def get(cls, code): - """ - Returns the Socket info state for the given code. - - Args: - code (Integer): the code of the Socket info state to get. - - Returns: - :class:`.SocketInfoState`: the ``SocketInfoState`` with the given code, - ``SocketInfoState.UNKNOWN`` if there is not any state with the provided code. - """ - try: - return cls.lookupTable[code] - except KeyError: - return SocketInfoState.UNKNOWN - - @classmethod - def get_by_description(cls, description): - """ - Returns the Socket info state for the given description. - - Args: - description (String): the description of the Socket info state to get. - - Returns: - :class:`.SocketInfoState`: the ``SocketInfoState`` with the given description, - ``SocketInfoState.UNKNOWN`` if there is not any state with the provided description. - """ - for x in SocketInfoState: - if x.description.lower() == description.lower(): - return x - return SocketInfoState.UNKNOWN - - code = property(__get_code) - """Integer. The Socket info state code.""" - - description = property(__get_description) - """String. The Socket info state description.""" - - -SocketInfoState.lookupTable = {x.code: x for x in SocketInfoState} -SocketInfoState.__doc__ += utils.doc_enum(SocketInfoState) diff --git a/digi/xbee/models/zdo.py b/digi/xbee/models/zdo.py deleted file mode 100644 index a9d4362..0000000 --- a/digi/xbee/models/zdo.py +++ /dev/null @@ -1,1747 +0,0 @@ -# Copyright 2019, 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 threading -from abc import abstractmethod, ABCMeta -import logging -from enum import Enum - -from digi.xbee.devices import XBeeDevice, RemoteXBeeDevice, RemoteZigBeeDevice, RemoteDigiMeshDevice -from digi.xbee.exception import XBeeException, OperationNotSupportedException -from digi.xbee.models.address import XBee64BitAddress, XBee16BitAddress -from digi.xbee.models.atcomm import ATStringCommand -from digi.xbee.models.mode import APIOutputModeBit -from digi.xbee.models.options import TransmitOptions -from digi.xbee.models.protocol import Role, XBeeProtocol -from digi.xbee.models.status import TransmitStatus, ATCommandStatus -from digi.xbee.packets.aft import ApiFrameType -from digi.xbee.packets.common import ExplicitAddressingPacket, RemoteATCommandPacket, ATCommPacket -from digi.xbee.util import utils - - -class __ZDOCommand(metaclass=ABCMeta): - """ - This class represents a ZDO command. - """ - - _SOURCE_ENDPOINT = 0x00 - _DESTINATION_ENDPOINT = 0x00 - _PROFILE_ID = 0x0000 - - __STATUS_SUCCESS = 0x00 - - __global_transaction_id = 1 - - _logger = logging.getLogger(__name__) - - def __init__(self, xbee, cluster_id, receive_cluster_id, configure_ao, timeout): - """ - Class constructor. Instantiates a new :class:`.__ZDOCommand` object with the - provided parameters. - - Args: - xbee (class:`digi.xbee.devices.XBeeDevice` or - class:`digi.xbee.devices.RemoteXBeeDevice`): the XBee to send the ZDO command. - cluster_id (Integer): The ZDO command cluster ID. - receive_cluster_id (Integer): The ZDO command receive cluster ID. - configure_ao (Boolean): ``True`` to configure AO value before and after executing this - ZDO command, ``False`` otherwise. - timeout(Float): The ZDO command timeout in seconds. - - Raises: - OperationNotSupportedException: If ZDO commands are not supported in the XBee protocol. - TypeError: If the ``xbee`` is not a ``digi.xbee.devices.XBeeDevice`` or a - ``digi.xbee.devices.RemoteXBeeDevice``. - ValueError: If ``xbee`` is ``None``. - ValueError: If ``cluster_id``, ``receive_cluster_id``, or ``timeout`` are less than 0. - """ - if not xbee: - raise ValueError("XBee cannot be None") - if isinstance(xbee, (XBeeDevice, RemoteXBeeDevice)): - self._xbee = xbee - else: - raise TypeError("The xbee must be an XBeeDevice or a RemoteXBeeDevice" - "not {!r}".format(xbee.__class__.__name__)) - if xbee.get_protocol() not in [XBeeProtocol.ZIGBEE, XBeeProtocol.SMART_ENERGY]: - raise OperationNotSupportedException("ZDO commands are not supported in %s" - % xbee.get_protocol().description) - if cluster_id < 0: - raise ValueError("Cluster id cannot be negative") - if receive_cluster_id < 0: - raise ValueError("Receive cluster id cannot be negative") - if timeout < 0: - raise ValueError("Timeout cannot be negative") - - self.__cluster_id = cluster_id - self.__receive_cluster_id = receive_cluster_id - self.__configure_ao = configure_ao - self.__timeout = timeout - - self.__saved_ao = None - self._running = False - self._error = None - self.__zdo_thread = None - self._lock = threading.Event() - self._received_status = False - self._received_answer = False - self._data_parsed = False - - self._current_transaction_id = self.__class__.__global_transaction_id - self.__class__.__global_transaction_id = self.__class__.__global_transaction_id + 1 - if self.__class__.__global_transaction_id == 0xFF: - self.__class__.__global_transaction_id = 1 - - @property - def running(self): - """ - Returns if this ZDO command is running. - - Returns: - Boolean: ``True`` if it is running, ``False`` otherwise. - """ - return self._running - - @property - def error(self): - """ - Returns the error string if any. - - Returns: - String: The error string. - """ - return self._error - - def stop(self): - """ - Stops the ZDO command process if it is running. - """ - if not self._lock.is_set(): - self._lock.set() - - if self.__zdo_thread and self._running: - self.__zdo_thread.join() - self.__zdo_thread = None - - def _start_process(self, sync=True, zdo_callback=None): - """ - Starts the ZDO command process. It can be a blocking method depending on `sync``. - - Args: - sync (Boolean): ``True`` for a blocking method, ``False`` to run asynchronously in a - separate thread. - zdo_callback (Function, optional): method to execute when ZDO process finishes. Receives - two arguments: - * The XBee device that executed the ZDO command. - * An error message if something went wrong. - """ - if not sync: - self.__zdo_thread = threading.Thread(target=self._send_zdo, - kwargs={'zdo_callback': zdo_callback}, daemon=True) - self.__zdo_thread.start() - else: - self._send_zdo(zdo_callback=zdo_callback) - - def _send_zdo(self, zdo_callback=None): - """ - Sends the ZDO command. - - Args: - zdo_callback (Function, optional): method to execute when ZDO process finishes. Receives - two arguments: - * The XBee device that executed the ZDO command. - * An error message if something went wrong. - """ - self._running = True - self._error = None - self._received_status = False - self._received_answer = False - self._data_parsed = False - self._lock.clear() - - if not self._xbee.is_remote(): - xb = self._xbee - else: - xb = self._xbee.get_local_xbee_device() - - xb.add_packet_received_callback(self._zdo_packet_callback) - - self._init_variables() - - try: - self.__prepare_device() - - xb.send_packet(self._generate_zdo_packet()) - - self._lock.wait(self.__timeout) - - if not self._received_status: - if not self._error: - self._error = "ZDO command not sent" - return - - if not self._received_answer: - if not self._error: - self._error = "ZDO command answer not received" - return - - self._perform_finish_actions() - except XBeeException as e: - self._error = "Error sending ZDO command: " + str(e) - finally: - xb.del_packet_received_callback(self._zdo_packet_callback) - self.__restore_device() - self._notify_process_finished(zdo_callback) - self._running = False - - @abstractmethod - def _init_variables(self): - """ - Initializes the ZDO command process variables. - """ - pass - - @abstractmethod - def _is_broadcast(self): - """ - Retrieves whether the ZDO is broadcast. - - Returns: - Boolean: ``True`` for broadcasting this ZDO, ``False`` otherwise. - """ - pass - - @abstractmethod - def _get_zdo_command_data(self): - """ - Retrieves the ZDO packet data to be sent. - - Returns: - Bytearray: The packet data. - """ - pass - - @abstractmethod - def _parse_data(self, data): - """ - Handles what to do with the received data of the explicit frame. The status - byte is already consumed. - - Args: - data(bytearray): Byte array containing the frame data. - - Returns: - Boolean: ``True`` if the process finishes, ``False`` otherwise. - """ - pass - - @abstractmethod - def _perform_finish_actions(self): - """ - Performs final actions when the ZDO process has finished successfully. - """ - pass - - def _notify_process_finished(self, zdo_callback): - """ - Notifies that the ZDO process has finished its execution. - - Args: - zdo_callback (Function, optional): method to execute when ZDO process finishes. Receives - two arguments: - * The XBee device that executed the ZDO command. - * An error message if something went wrong. - """ - if zdo_callback: - zdo_callback(self._xbee, self._error) - - def __prepare_device(self): - """ - Performs the local XBee configuration before sending the ZDO command. This saves the - current AO value and sets it to 1. - """ - if not self.__configure_ao: - return - - if not self._xbee.is_remote(): - xb = self._xbee - else: - xb = self._xbee.get_local_xbee_device() - - try: - self.__saved_ao = xb.get_api_output_mode_value() - - # Do not configure AO if it is already - if utils.is_bit_enabled(self.__saved_ao[0], 0): - self.__saved_ao = None - return - - value = APIOutputModeBit.calculate_api_output_mode_value(self._xbee.get_protocol(), - {APIOutputModeBit.EXPLICIT}) - xb.set_api_output_mode_value(value) - - except XBeeException as e: - raise XBeeException("Could not prepare XBee for ZDO: " + str(e)) - - def __restore_device(self): - """ - Performs XBee configuration after sending the ZDO command. - This restores the previous AO value. - """ - if not self.__configure_ao or self.__saved_ao is None: - return - - if not self._xbee.is_remote(): - xb = self._xbee - else: - xb = self._xbee.get_local_xbee_device() - - try: - xb.set_api_output_mode_value(self.__saved_ao[0]) - except XBeeException as e: - self._error = "Could not restore XBee after ZDO: " + str(e) - - def _generate_zdo_packet(self): - """ - Generates the ZDO packet. - - Returns: - :class:`digi.xbee.packets.common.ExplicitAddressingPacket`: The packet to send. - """ - if self._is_broadcast(): - addr64 = XBee64BitAddress.BROADCAST_ADDRESS - addr16 = XBee16BitAddress.BROADCAST_ADDRESS - else: - addr64 = self._xbee.get_64bit_addr() - addr16 = self._xbee.get_16bit_addr() - - return ExplicitAddressingPacket(self._current_transaction_id, addr64, addr16, - self.__class__._SOURCE_ENDPOINT, - self.__class__._DESTINATION_ENDPOINT, self.__cluster_id, - self.__class__._PROFILE_ID, broadcast_radius=0, - transmit_options=TransmitOptions.NONE.value, - rf_data=self._get_zdo_command_data()) - - def _zdo_packet_callback(self, frame): - """ - Callback notified when a new frame is received. - - Args: - frame (:class:`digi.xbee.packets.base.XBeeAPIPacket`): The received packet. - """ - if not self._running: - return - - if frame.get_frame_type() == ApiFrameType.EXPLICIT_RX_INDICATOR: - # Check address - x64 = self._xbee.get_64bit_addr() - x16 = self._xbee.get_16bit_addr() - if (not self._is_broadcast() - and x64 != XBee64BitAddress.UNKNOWN_ADDRESS - and x64 != frame.x64bit_source_addr - and x16 != XBee16BitAddress.UNKNOWN_ADDRESS - and x16 != frame.x16bit_source_addr): - return - # Check profile and endpoints - if frame.profile_id != self.__class__._PROFILE_ID \ - or frame.source_endpoint != self.__class__._SOURCE_ENDPOINT \ - or frame.dest_endpoint != self.__class__._DESTINATION_ENDPOINT: - return - # Check if the cluster ID is correct. - if frame.cluster_id != self.__receive_cluster_id: - return - # If transaction ID does not match, discard: it is not the frame we are waiting for. - if frame.rf_data[0] != self._current_transaction_id: - return - self._received_answer = True - # Status byte - if frame.rf_data[1] != self.__class__.__STATUS_SUCCESS: - self._error = "Error executing ZDO command (status: %d)" % int(frame.rf_data[1]) - self.stop() - return - - self._data_parsed = self._parse_data(frame.rf_data[2:]) - - if self._data_parsed and self._received_status: - self.stop() - elif frame.get_frame_type() == ApiFrameType.TRANSMIT_STATUS: - self._logger.debug("Received 'ZDO' status frame: %s" - % frame.transmit_status.description) - # If transaction ID does not match, discard: it is not the frame we are waiting for. - if frame.frame_id != self._current_transaction_id: - return - - self._received_status = True - if frame.transmit_status != TransmitStatus.SUCCESS \ - and frame.transmit_status != TransmitStatus.SELF_ADDRESSED: - self._error = "Error sending ZDO command: %s" % frame.transmit_status.description - self.stop() - - if self._data_parsed: - self.stop() - - -class NodeDescriptorReader(__ZDOCommand): - """ - This class performs a node descriptor read of the given XBee using a ZDO command. - - The node descriptor read works only with Zigbee devices in API mode. - """ - - __CLUSTER_ID = 0x0002 - __RECEIVE_CLUSTER_ID = 0x8002 - - __DEFAULT_TIMEOUT = 20 # seconds - - def __init__(self, xbee, configure_ao=True, timeout=__DEFAULT_TIMEOUT): - """ - Class constructor. Instantiates a new :class:`.NodeDescriptorReader` object with the - provided parameters. - - Args: - xbee (class:`digi.xbee.devices.XBeeDevice` or - class:`digi.xbee.devices.RemoteXBeeDevice`): the XBee to send the command. - configure_ao (Boolean, optional, default=``True``): ``True`` to configure AO value - before and after executing this command, ``False`` otherwise. - timeout (Float, optional, default=``.__DEFAULT_TIMEOUT``): The ZDO command timeout - in seconds. - - Raises: - ValueError: If ``xbee`` is ``None``. - ValueError: If ``cluster_id``, ``receive_cluster_id``, or ``timeout`` are less than 0. - TypeError: If the ``xbee`` is not a ``digi.xbee.devices.XBeeDevice`` or a - ``digi.xbee.devices.RemoteXBeeDevice``. - """ - super().__init__(xbee, self.__class__.__CLUSTER_ID, self.__class__.__RECEIVE_CLUSTER_ID, - configure_ao, timeout) - - self.__node_descriptor = None - - def get_node_descriptor(self): - """ - Returns the descriptor of the node. - - Returns: - :class:`.NodeDescriptor`: The node descriptor. - """ - self._start_process(sync=True) - - return self.__node_descriptor - - def _init_variables(self): - """ - Override. - - .. seealso:: - | :meth:`.__ZDOCommand._init_variables` - """ - self.__role = Role.UNKNOWN - - def _is_broadcast(self): - """ - Override. - - .. seealso:: - | :meth:`.__ZDOCommand._is_broadcast` - """ - return False - - def _get_zdo_command_data(self): - """ - Override. - - .. seealso:: - | :meth:`.__ZDOCommand._get_zdo_command_data` - """ - return bytearray([self._current_transaction_id, self._xbee.get_16bit_addr().get_lsb(), - self._xbee.get_16bit_addr().get_hsb()]) - - def _parse_data(self, data): - """ - Override. - - .. seealso:: - | :meth:`.__ZDOCommand._parse_data` - """ - # Ensure the 16-bit address received matches the address of the device - x16 = XBee16BitAddress.from_bytes(data[1], data[0]) - if x16 != self._xbee.get_16bit_addr(): - return False - - # Role field: 3 bits (0, 1, 2) of the next byte - role = Role.get(utils.get_int_from_byte(data[2], 0, 3)) - # Complex descriptor available: next bit (3) of the same byte - complex_desc_available = utils.is_bit_enabled(data[2], 3) - # User descriptor available: next bit (4) of the same byte - user_desc_available = utils.is_bit_enabled(data[2], 4) - - # Frequency band: 5 bits of the next byte - freq_band = NodeDescriptorReader.__to_bits(data[3])[-5:] - - # MAC capabilities: next byte - mac_capabilities = NodeDescriptorReader.__to_bits(data[4]) - - # Manufacturer code: next 2 bytes - manufacturer_code = utils.bytes_to_int([data[6], data[5]]) - - # Maximum buffer size: next byte - max_buffer_size = int(data[7]) - - # Maximum incoming transfer size: next 2 bytes - max_in_transfer_size = utils.bytes_to_int([data[9], data[8]]) - - # Maximum outgoing transfer size: next 2 bytes - max_out_transfer_size = utils.bytes_to_int([data[13], data[12]]) - - # Maximum outgoing transfer size: next byte - desc_capabilities = NodeDescriptorReader.__to_bits(data[14]) - - self.__node_descriptor = NodeDescriptor(role, complex_desc_available, user_desc_available, - freq_band, mac_capabilities, manufacturer_code, - max_buffer_size, max_in_transfer_size, - max_out_transfer_size, desc_capabilities) - - return True - - @staticmethod - def __to_bits(data_byte): - """ - Convert the byte to an array of bits. - - Args: - data_byte (Integer): The byte to convert. - - Returns: - List: An array of bits. - """ - return [(int(data_byte) >> i) & 1 for i in range(0, 8)] - - def _perform_finish_actions(self): - """ - Override. - - .. seealso:: - | :meth:`.__ZDOCommand._perform_finish_actions` - """ - pass - - -class NodeDescriptor(object): - """ - This class represents a node descriptor of an XBee. - """ - - def __init__(self, role, complex_desc_supported, user_desc_supported, freq_band, - mac_capabilities, manufacturer_code, max_buffer_size, max_in_transfer_size, - max_out_transfer_size, desc_capabilities): - """ - Class constructor. Instantiates a new :class:`.NodeDescriptor` object with the provided - parameters. - - Args: - role (:class:`digi.xbee.models.protocol.Role`): The device role. - complex_desc_supported (Boolean): ``True`` if the complex descriptor is supported. - user_desc_supported (Boolean): ``True`` if the user descriptor is supported. - freq_band (List): Byte array with the frequency bands. - mac_capabilities (List): Byte array with MAC capabilities. - manufacturer_code (Integer): The manufacturer's code assigned by the Zigbee Alliance. - max_buffer_size (Integer): Maximum size in bytes of a data transmission. - max_in_transfer_size (Integer): Maximum number of bytes that can be received by the - node. - max_out_transfer_size (Integer): Maximum number of bytes that can be transmitted by the - node. - desc_capabilities (List): Byte array with descriptor capabilities. - """ - self.__role = role - self.__complex_desc_available = complex_desc_supported - self.__user_desc_available = user_desc_supported - self.__freq_band = freq_band - self.__mac_capabilities = mac_capabilities - self.__manufacturer_code = manufacturer_code - self.__max_buffer_size = max_buffer_size - self.__max_in_transfer_size = max_in_transfer_size - self.__max_out_transfer_size = max_out_transfer_size - self.__desc_capabilities = desc_capabilities - - @property - def role(self): - """ - Gets the role in this node descriptor. - - Returns: - :class:`digi.xbee.models.protocol.Role`: The role of the node descriptor. - - .. seealso:: - | :class:`digi.xbee.models.protocol.Role` - """ - return self.__role - - @property - def complex_desc_supported(self): - """ - Gets if the complex descriptor is supported. - - Returns: - Boolean: ``True`` if supported, ``False`` otherwise. - """ - return self.__complex_desc_available - - @property - def user_desc_supported(self): - """ - Gets if the user descriptor is supported. - - Returns: - Boolean: ``True`` if supported, ``False`` otherwise. - """ - return self.__user_desc_available - - @property - def freq_band(self): - """ - Gets the frequency bands (LSB - bit0- index 0, MSB - bit4 - index 4): - * Bit0: 868 MHz - * Bit1: Reserved - * Bit2: 900 MHz - * Bit3: 2.4 GHz - * Bit4: Reserved - - Returns: - List: List of integers with the frequency bands bits. - """ - return self.__freq_band - - @property - def mac_capabilities(self): - """ - Gets the MAC capabilities (LSB - bit0- index 0, MSB - bit7 - index 7): - * Bit0: Alternate PAN coordinator - * Bit1: Device Type - * Bit2: Power source - * Bit3: Receiver on when idle - * Bit4-5: Reserved - * Bit6: Security capability - * Bit7: Allocate address - - Returns: - List: List of integers with MAC capabilities bits. - """ - return self.__mac_capabilities - - @property - def manufacturer_code(self): - """ - Gets the manufacturer's code assigned by the Zigbee Alliance. - - Returns: - Integer: The manufacturer's code. - """ - return self.__manufacturer_code - - @property - def max_buffer_size(self): - """ - Gets the maximum size in bytes of a data transmission (including APS bytes). - - Returns: - Integer: Maximum size in bytes. - """ - return self.__max_buffer_size - - @property - def max_in_transfer_size(self): - """ - Gets the maximum number of bytes that can be received by the node. - - Returns: - Integer: Maximum number of bytes that can be received by the node. - """ - return self.__max_in_transfer_size - - @property - def max_out_transfer_size(self): - """ - Gets the maximum number of bytes that can be transmitted by the node, including - fragmentation. - - Returns: - Integer: Maximum number of bytes that can be transmitted by the node. - """ - return self.__max_out_transfer_size - - @property - def desc_capabilities(self): - """ - Gets the descriptor capabilities (LSB - bit0- index 0, MSB - bit1 - index 1): - * Bit0: Extended active endpoint list available - * Bit1: Extended simple descriptor list available - - Returns: - List: List of integers with descriptor capabilities bits. - """ - return self.__desc_capabilities - - -class RouteTableReader(__ZDOCommand): - """ - This class performs a route table read of the given XBee using a ZDO command. - - The node descriptor read works only with Zigbee devices in API mode. - """ - - DEFAULT_TIMEOUT = 20 # seconds - - __CLUSTER_ID = 0x0032 - __RECEIVE_CLUSTER_ID = 0x8032 - - __ROUTE_BYTES_LEN = 5 - - __ST_FIELD_OFFSET = 0 - __ST_FIELD_LEN = 3 - __MEM_FIELD_OFFSET = 3 - __M2O_FIELD_OFFSET = 4 - __RR_FIELD_OFFSET = 5 - - def __init__(self, xbee, configure_ao=True, timeout=DEFAULT_TIMEOUT): - """ - Class constructor. Instantiates a new :class:`.RouteTableReader` object with the - provided parameters. - - Args: - xbee (class:`digi.xbee.devices.XBeeDevice` or - class:`digi.xbee.devices.RemoteXBeeDevice`): the XBee to send the command. - configure_ao (Boolean, optional, default=``True``): ``True`` to configure AO value - before and after executing this command, ``False`` otherwise. - timeout (Float, optional, default=``.DEFAULT_TIMEOUT``): The ZDO command timeout - in seconds. - - Raises: - ValueError: If ``xbee`` is ``None``. - ValueError: If ``cluster_id``, ``receive_cluster_id``, or ``timeout`` are less than 0. - TypeError: If the ``xbee`` is not a ``digi.xbee.devices.XBeeDevice`` or a - ``digi.xbee.devices.RemoteXBeeDevice``. - """ - super().__init__(xbee, self.__class__.__CLUSTER_ID, self.__class__.__RECEIVE_CLUSTER_ID, - configure_ao, timeout) - - self.__routes = None - self.__total_routes = 0 - self.__index = 0 - - self.__cb = None - - def get_route_table(self, route_callback=None, process_finished_callback=None): - """ - Returns the routes of the XBee. If ``route_callback`` is not defined, the process blocks - until the complete routing table is read. - - Args: - route_callback (Function, optional, default=``None``): method called when a new route - is received. Receives two arguments: - - * The XBee that owns this new route. - * The new route. - - process_finished_callback (Function, optional, default=``None``): method to execute when - the process finishes. Receives two arguments: - - * The XBee device that executed the ZDO command. - * A list with the discovered routes. - * An error message if something went wrong. - - Returns: - List: List of :class:`.Route` when ``route_callback`` is not defined, ``None`` - otherwise (in this case routes are received in the callback). - - .. seealso:: - | :class:`.Route` - """ - self.__cb = route_callback - self._start_process(sync=True if not self.__cb else False, - zdo_callback=process_finished_callback) - - return self.__routes - - def _init_variables(self): - """ - Override. - - .. seealso:: - | :meth:`.__ZDOCommand._init_variables` - """ - self.__routes = [] - self.__total_routes = 0 - self.__index = 0 - - def _is_broadcast(self): - """ - Override. - - .. seealso:: - | :meth:`.__ZDOCommand._is_broadcast` - """ - return False - - def _get_zdo_command_data(self): - """ - Override. - - .. seealso:: - | :meth:`.__ZDOCommand._get_zdo_command_data` - """ - return bytearray([self._current_transaction_id, self.__index]) - - def _parse_data(self, data): - """ - Override. - - .. seealso:: - | :meth:`.__ZDOCommand._parse_data` - """ - # Byte 0: Total number of routing table entries - # Byte 1: Starting point in the routing table - # Byte 2: Number of routing table entries in the response - # Byte 3 - end: List of routing table entries (as many as indicated in byte 2) - - self.__total_routes = int(data[0]) - # Ignore start index and get the number of entries in this response. - n_items = int(data[2]) - if not n_items: - # No entries in this response, try again? - self.__get_next_routes() - return True - - # Parse routes - routes_starting_index = 3 - byte_index = routes_starting_index - n_route_data_bytes = len(data) - 3 # Subtract the 3 first bytes: total number of entries, - # start index, and the number of entries in this response - - while byte_index + 1 < n_route_data_bytes: - if byte_index + self.__class__.__ROUTE_BYTES_LEN \ - > n_route_data_bytes + routes_starting_index: - break - - r = self.__parse_route(data[byte_index:byte_index + self.__class__.__ROUTE_BYTES_LEN]) - if r: - self.__routes.append(r) - if self.__cb: - self.__cb(self._xbee, r) - - byte_index += self.__class__.__ROUTE_BYTES_LEN - self.__index += 1 - - # Check if we already have all the routes - if self.__index < self.__total_routes: - self.__get_next_routes() - - return False - - return True - - def _perform_finish_actions(self): - """ - Override. - - .. seealso:: - | :meth:`.__ZDOCommand._perform_finish_actions` - """ - pass - - def _notify_process_finished(self, zdo_callback): - """ - Override. - - .. seealso:: - | :meth:`.__ZDOCommand._notify_process_finished` - """ - if zdo_callback: - zdo_callback(self._xbee, self.__routes, self._error) - - def __parse_route(self, data): - """ - Parses the given bytearray and returns a route. - - Args: - data (bytearray): Bytearray with data to parse. - - Returns: - :class:`.Route`: The route or ``None`` if not found. - """ - # Bytes 0 - 1: 16-bit destination address (little endian) - # Byte 2: Setting byte: - # * Bits 0 - 2: Route status - # * Bit 3: Low-memory concentrator flag - # * Bit 4: Destination is a concentrator flag - # * Bit 5: Route record message should be sent prior to next transmission flag - # Bytes 3 - 4: 16 bit next hop address (little endian) - return Route(XBee16BitAddress.from_bytes(data[1], data[0]), - XBee16BitAddress.from_bytes(data[4], data[3]), - RouteStatus.get(utils.get_int_from_byte(data[2], - self.__class__.__ST_FIELD_OFFSET, - self.__class__.__ST_FIELD_LEN)), - utils.is_bit_enabled(data[2], self.__class__.__MEM_FIELD_OFFSET), - utils.is_bit_enabled(data[2], self.__class__.__M2O_FIELD_OFFSET), - utils.is_bit_enabled(data[2], self.__class__.__RR_FIELD_OFFSET)) - - def __get_next_routes(self): - """ - Sends a new ZDO request to get more route table entries. - """ - if not self._xbee.is_remote(): - xb = self._xbee - else: - xb = self._xbee.get_local_xbee_device() - - try: - xb.send_packet(self._generate_zdo_packet()) - except XBeeException as e: - self._error = "Error sending ZDO command: " + str(e) - - -class RouteStatus(Enum): - """ - Enumerates the available route status. - """ - - ACTIVE = (0, "Active") - DISCOVERY_UNDERWAY = (1, "Discovery Underway") - DISCOVERY_FAILED = (2, "Discovery Failed") - INACTIVE = (3, "Inactive") - VALIDATION_UNDERWAY = (4, "Validation Underway") - UNKNOWN = (-1, "Unknown") - - def __init__(self, identifier, name): - self.__id = identifier - self.__name = name - - def __str__(self): - return self.__name - - @property - def id(self): - """ - Returns the identifier of the RouteStatus. - - Returns: - Integer: the RouteStatus identifier. - """ - return self.__id - - @property - def name(self): - """ - Returns the name of the RouteStatus. - - Returns: - String: the RouteStatus name. - """ - return self.__name - - @classmethod - def get(cls, identifier): - """ - Returns the RouteStatus for the given identifier. - - Args: - identifier (Integer): the id corresponding to the route status to get. - - Returns: - :class:`.RouteStatus`: the RouteStatus with the given id. ``None`` if it does not exist. - """ - for item in cls: - if identifier == item.id: - return item - - return None - - -class Route(object): - """ - This class represents a Zigbee route read from the route table of an XBee. - """ - - def __init__(self, destination, next_hop, status, is_low_memory, is_many_to_one, - is_route_record_required): - """ - Class constructor. Instantiates a new :class:`.Route` object with the provided parameters. - - Args: - destination (:class:`digi.xbee.models.address.XBee16BitAddress`): 16-bit destination - address of the route. - next_hop (:class:`digi.xbee.models.address.XBee16BitAddress`): 16-bit address of the - next hop. - status (:class:`.RouteStatus`): Status of the route. - is_low_memory (Boolean): ``True`` to indicate if the device is a low-memory - concentrator. - is_many_to_one (Boolean): ``True`` to indicate the destination is a concentrator. - is_route_record_required (Boolean): ``True`` to indicate a route record message should - be sent prior to the next data transmission. - - .. seealso:: - | :class:`.RouteStatus` - | :class:`digi.xbee.models.address.XBee16BitAddress` - """ - self.__dest = destination - self.__next = next_hop - self.__status = status - self.__is_low_memory = is_low_memory - self.__is_mto = is_many_to_one - self.__is_rr_required = is_route_record_required - - def __str__(self): - return "Destination: {!s} - Next: {!s} (status: {!s}, low-memory: {!r}, " \ - "many-to-one: {!r}, route record required: {!r})".format(self.__dest, self.__next, - self.__status.name, - self.__is_low_memory, - self.__is_mto, - self.__is_rr_required) - - @property - def destination(self): - """ - Gets the 16-bit address of this route destination. - - Returns: - :class:`digi.xbee.models.address.XBee16BitAddress`: 16-bit address of the destination. - - .. seealso:: - | :class:`digi.xbee.models.address.XBee16BitAddress` - """ - return self.__dest - - @property - def next_hop(self): - """ - Gets the 16-bit address of this route next hop. - - Returns: - :class:`digi.xbee.models.address.XBee16BitAddress`: 16-bit address of the next hop. - - .. seealso:: - | :class:`digi.xbee.models.address.XBee16BitAddress` - """ - return self.__next - - @property - def status(self): - """ - Gets this route status. - - Returns: - :class:`.RouteStatus`: The route status. - - .. seealso:: - | :class:`.RouteStatus` - """ - return self.__status - - @property - def is_low_memory(self): - """ - Gets whether the device is a low-memory concentrator. - - Returns: - Boolean: ``True`` if the device is a low-memory concentrator, ``False`` otherwise. - """ - return self.__is_low_memory - - @property - def is_many_to_one(self): - """ - Gets whether the destination is a concentrator. - - Returns: - Boolean: ``True`` if destination is a concentrator, ``False`` otherwise. - """ - return self.__is_mto - - @property - def is_route_record_required(self): - """ - Gets whether a route record message should be sent prior the next data transmission. - - Returns: - Boolean: ``True`` if a route record message should be sent, ``False`` otherwise. - """ - return self.__is_rr_required - - -class NeighborTableReader(__ZDOCommand): - """ - This class performs a neighbor table read of the given XBee using a ZDO command. - - The node descriptor read works only with Zigbee devices in API mode. - """ - - DEFAULT_TIMEOUT = 20 # seconds - - __CLUSTER_ID = 0x0031 - __RECEIVE_CLUSTER_ID = 0x8031 - - __NEIGHBOR_BYTES_LEN = 22 - - __ROLE_FIELD_OFFSET = 0 - __ROLE_FIELD_LEN = 2 - __RELATIONSHIP_FIELD_OFFSET = 4 - __RELATIONSHIP_FIELD_LEN = 3 - - def __init__(self, xbee, configure_ao=True, timeout=DEFAULT_TIMEOUT): - """ - Class constructor. Instantiates a new :class:`.NeighborTableReader` object with the - provided parameters. - - Args: - xbee (class:`digi.xbee.devices.XBeeDevice` or - class:`digi.xbee.devices.RemoteXBeeDevice`): the XBee to send the command. - configure_ao (Boolean, optional, default=``True``): ``True`` to configure AO value - before and after executing this command, ``False`` otherwise. - timeout (Float, optional, default=``.DEFAULT_TIMEOUT``): The ZDO command timeout - in seconds. - - Raises: - ValueError: If ``xbee`` is ``None``. - ValueError: If ``cluster_id``, ``receive_cluster_id``, or ``timeout`` are less than 0. - TypeError: If the ``xbee`` is not a ``digi.xbee.devices.XBeeDevice`` or a - ``digi.xbee.devices.RemoteXBeeDevice``. - """ - super().__init__(xbee, self.__class__.__CLUSTER_ID, self.__class__.__RECEIVE_CLUSTER_ID, - configure_ao, timeout) - - self.__neighbors = None - self.__total_neighbors = 0 - self.__index = 0 - - self.__cb = None - - def get_neighbor_table(self, neighbor_callback=None, process_finished_callback=None): - """ - Returns the neighbors of the XBee. If ``neighbor_callback`` is not defined, the process - blocks until the complete neighbor table is read. - - Args: - neighbor_callback (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. - - process_finished_callback (Function, optional, default=``None``): method to execute when - the process finishes. Receives two arguments: - - * The XBee device that executed the ZDO command. - * A list with the discovered neighbors. - * An error message if something went wrong. - - Returns: - List: List of :class:`.Neighbor` when ``neighbor_callback`` is not defined, ``None`` - otherwise (in this case neighbors are received in the callback). - - .. seealso:: - | :class:`.Neighbor` - """ - self.__cb = neighbor_callback - self._start_process(sync=True if not self.__cb else False, - zdo_callback=process_finished_callback) - - return self.__neighbors - - def _init_variables(self): - """ - Override. - - .. seealso:: - | :meth:`.__ZDOCommand._init_variables` - """ - self.__neighbors = [] - self.__total_neighbors = 0 - self.__index = 0 - - def _is_broadcast(self): - """ - Override. - - .. seealso:: - | :meth:`.__ZDOCommand._is_broadcast` - """ - return False - - def _get_zdo_command_data(self): - """ - Override. - - .. seealso:: - | :meth:`.__ZDOCommand._get_zdo_command_data` - """ - return bytearray([self._current_transaction_id, self.__index]) - - def _parse_data(self, data): - """ - Override. - - .. seealso:: - | :meth:`.__ZDOCommand._parse_data` - """ - # Byte 0: Total number of neighbor table entries - # Byte 1: Starting point in the neighbor table - # Byte 2: Number of neighbor table entries in the response - # Byte 3 - end: List of neighbor table entries (as many as indicated in byte 2) - - self.__total_neighbors = int(data[0]) - # Ignore start index and get the number of entries in this response. - n_items = int(data[2]) - if not n_items: - # No entries in this response, try again? - self.__get_next_neighbors() - return True - - # Parse neighbors - neighbors_starting_index = 3 - byte_index = neighbors_starting_index - n_neighbor_data_bytes = len(data) - 3 # Subtract the 3 first bytes: total number of - # entries, start index, and the number of entries in this response - - while byte_index + 1 < n_neighbor_data_bytes: - if byte_index + self.__class__.__NEIGHBOR_BYTES_LEN \ - > n_neighbor_data_bytes + neighbors_starting_index: - break - - n = self.__parse_neighbor( - data[byte_index:byte_index + self.__class__.__NEIGHBOR_BYTES_LEN]) - # Do not add the node with Zigbee coordinator address "0000000000000000" - # The coordinator is already received with its real 64-bit address - if n and n.node.get_64bit_addr() != XBee64BitAddress.COORDINATOR_ADDRESS: - self.__neighbors.append(n) - if self.__cb: - self.__cb(self._xbee, n) - - byte_index += self.__class__.__NEIGHBOR_BYTES_LEN - self.__index += 1 - - # Check if we already have all the neighbors - if self.__index < self.__total_neighbors: - self.__get_next_neighbors() - - return False - - return True - - def _perform_finish_actions(self): - """ - Override. - - .. seealso:: - | :meth:`.__ZDOCommand._perform_finish_actions` - """ - pass - - def _notify_process_finished(self, zdo_callback): - """ - Override. - - .. seealso:: - | :meth:`.__ZDOCommand._notify_process_finished` - """ - if zdo_callback: - zdo_callback(self._xbee, self.__neighbors, self._error) - - def __parse_neighbor(self, data): - """ - Parses the given bytearray and returns a neighbor. - - Args: - data (bytearray): Bytearray with data to parse. - - Returns: - :class:`.Neighbor`: The neighbor or ``None`` if not found. - """ - # Bytes 0 - 7: Extended PAN ID (little endian) - # Bytes 8 - 15: 64-bit neighbor address (little endian) - # Bytes 16 - 17: 16-bit neighbor address (little endian) - # Byte 18: First setting byte: - # * Bit 0 - 1: Neighbor role - # * Bit 2 - 3: Receiver on when idle (indicates if the neighbor's receiver is - # enabled during idle times) - # * Bit 4 - 6: Relationship of this neighbor with the node - # * Bit 7: Reserved - # Byte 19: Second setting byte: - # * Bit 0 - 1: Permit joining (indicates if the neighbor accepts join requests) - # * Bit 2 - 7: Reserved - # Byte 20: Depth (Tree depth of the neighbor. A value of 0 indicates the neighbor is the - # coordinator) - # Byte 21: LQI (The estimated link quality of data transmissions from this neighbor) - x64 = XBee64BitAddress.from_bytes(*data[8:16][:: -1]) - x16 = XBee16BitAddress.from_bytes(data[17], data[16]) - role = Role.get(utils.get_int_from_byte(data[18], self.__class__.__ROLE_FIELD_OFFSET, - self.__class__.__ROLE_FIELD_LEN)) - relationship = NeighborRelationship.get( - utils.get_int_from_byte(data[18], self.__class__.__RELATIONSHIP_FIELD_OFFSET, - self.__class__.__RELATIONSHIP_FIELD_LEN)) - depth = int(data[20]) - lqi = int(data[21]) - - if not self._xbee.is_remote(): - xb = self._xbee - else: - xb = self._xbee.get_local_xbee_device() - - # Create a new remote node - n_xb = RemoteZigBeeDevice(xb, x64bit_addr=x64, x16bit_addr=x16) - n_xb._role = role - - return Neighbor(n_xb, relationship, depth, lqi) - - def __get_next_neighbors(self): - """ - Sends a new ZDO request to get more neighbor table entries. - """ - if not self._xbee.is_remote(): - xb = self._xbee - else: - xb = self._xbee.get_local_xbee_device() - - try: - xb.send_packet(self._generate_zdo_packet()) - except XBeeException as e: - self._error = "Error sending ZDO command: " + str(e) - - -class NeighborRelationship(Enum): - """ - Enumerates the available relationships between two nodes of the same network. - """ - - PARENT = (0, "Neighbor is the parent") - CHILD = (1, "Neighbor is a child") - SIBLING = (2, "Neighbor is a sibling") - UNDETERMINED = (3, "Neighbor has an unknown relationship") - PREVIOUS_CHILD = (4, "Previous child") - UNKNOWN = (-1, "Unknown") - - def __init__(self, identifier, name): - self.__id = identifier - self.__name = name - - @property - def id(self): - """ - Returns the identifier of the NeighborRelationship. - - Returns: - Integer: the NeighborRelationship identifier. - """ - return self.__id - - @property - def name(self): - """ - Returns the name of the NeighborRelationship. - - Returns: - String: the NeighborRelationship name. - """ - return self.__name - - @classmethod - def get(cls, identifier): - """ - Returns the NeighborRelationship for the given identifier. - - Args: - identifier (Integer): the id corresponding to the neighbor relationship to get. - - Returns: - :class:`.NeighborRelationship`: the NeighborRelationship with the given id. ``None`` - if it does not exist. - """ - for item in cls: - if identifier == item.id: - return item - - return None - - -class Neighbor(object): - """ - This class represents a Zigbee or DigiMesh neighbor. - - This information is read from the neighbor table of a Zigbee XBee, or provided by the 'FN' - command in a Digimesh XBee. - """ - - def __init__(self, node, relationship, depth, lq): - """ - Class constructor. Instantiates a new :class:`.Neighbor` object with the provided - parameters. - - Args: - node (:class:`digi.xbee.devices.RemoteXBeeDevice`): The neighbor node. - relationship (:class:`.NeighborRelationship`): The relationship of this neighbor with - the node. - depth (Integer): The tree depth of the neighbor. A value of 0 indicates the device is a - Zigbee coordinator for the network. -1 means this is unknown. - lq (Integer): The estimated link quality (LQI or RSSI) of data transmission from this - neighbor. - - .. seealso:: - | :class:`.NeighborRelationship` - | :class:`digi.xbee.devices.RemoteXBeeDevice` - """ - self.__node = node - self.__relationship = relationship - self.__depth = depth - self.__lq = lq - - def __str__(self): - return "Node: {!s} (relationship: {!s}, depth: {!r}, lq: {!r})"\ - .format(self.__node, self.__relationship.name, self.__depth, self.__lq) - - @property - def node(self): - """ - Gets the neighbor node. - - Returns: - :class:`digi.xbee.devices.RemoteXBeeDevice`: The node itself. - - .. seealso:: - | :class:`digi.xbee.devices.RemoteXBeeDevice` - """ - return self.__node - - @property - def relationship(self): - """ - Gets the neighbor node. - - Returns: - :class:`.NeighborRelationship`: The neighbor relationship. - - .. seealso:: - | :class:`.NeighborRelationship` - """ - return self.__relationship - - @property - def depth(self): - """ - Gets the tree depth of the neighbor. - - Returns: - Integer: The tree depth of the neighbor. - """ - return self.__depth - - @property - def lq(self): - """ - Gets the estimated link quality (LQI or RSSI) of data transmission from this neighbor. - - Returns: - Integer: The estimated link quality of data transmission from this neighbor. - """ - return self.__lq - - -class NeighborFinder(object): - """ - This class performs a find neighbors (FN) of an XBee. This action requires an XBee device and - optionally a find timeout. - - The process works only in DigiMesh. - """ - - DEFAULT_TIMEOUT = 20 # seconds - - __global_frame_id = 1 - - _logger = logging.getLogger(__name__) - - def __init__(self, xbee, timeout=DEFAULT_TIMEOUT): - """ - Class constructor. Instantiates a new :class:`.NeighborFinder` object with the - provided parameters. - - Args: - xbee (class:`digi.xbee.devices.XBeeDevice` or - class:`digi.xbee.devices.RemoteXBeeDevice`): The XBee to get neighbors from. - timeout(Float): The timeout for the process in seconds. - - Raises: - OperationNotSupportedException: If the process is not supported in the XBee. - TypeError: If the ``xbee`` is not a ``digi.xbee.devices.AbstracXBeeDevice``. - ValueError: If ``xbee`` is ``None``. - ValueError: If ```timeout`` is less than 0. - """ - if not xbee: - raise ValueError("XBee cannot be None") - if not isinstance(xbee, (XBeeDevice, RemoteXBeeDevice)): - raise TypeError("The xbee must be an XBeeDevice or a RemoteXBeeDevice" - "not {!r}".format(xbee.__class__.__name__)) - if xbee.get_protocol() not in (XBeeProtocol.DIGI_MESH, XBeeProtocol.XLR_DM, - XBeeProtocol.XTEND_DM, XBeeProtocol.SX): - raise OperationNotSupportedException("Find neighbors is not supported in %s" - % xbee.get_protocol().description) - if timeout < 0: - raise ValueError("Timeout cannot be negative") - - self.__xbee = xbee - self.__timeout = timeout - - self.__running = False - self.__error = None - self.__fn_thread = None - self.__lock = threading.Event() - self.__received_answer = False - self.__neighbors = [] - self.__cb = None - - self.__current_frame_id = self.__class__.__global_frame_id - self.__class__.__global_frame_id = self.__class__.__global_frame_id + 1 - if self.__class__.__global_frame_id == 0xFF: - self.__class__.__global_frame_id = 1 - - @property - def running(self): - """ - Returns whether this find neighbors process is running. - - Returns: - Boolean: ``True`` if it is running, ``False`` otherwise. - """ - return self.__running - - @property - def error(self): - """ - Returns the error string if any. - - Returns: - String: The error string. - """ - return self.__error - - def stop(self): - """ - Stops the find neighbors process if it is running. - """ - self.__lock.set() - - if self.__fn_thread and self.__running: - self.__fn_thread.join() - self.__fn_thread = None - - def get_neighbors(self, neighbor_callback=None, process_finished_callback=None): - """ - Returns the neighbors of the XBee. If ``neighbor_callback`` is not defined, the process - blocks until the complete neighbor table is read. - - Args: - neighbor_callback (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. - - process_finished_callback (Function, optional, default=``None``): method to execute when - the process finishes. Receives two arguments: - - * The XBee device that executed the FN command. - * A list with the discovered neighbors. - * An error message if something went wrong. - - Returns: - List: List of :class:`.Neighbor` when ``neighbor_callback`` is not defined, ``None`` - otherwise (in this case neighbors are received in the callback). - - .. seealso:: - | :class:`.Neighbor` - """ - self.__cb = neighbor_callback - - if neighbor_callback: - self.__fn_thread = threading.Thread( - target=self.__send_command, - kwargs={'process_finished_callback': process_finished_callback}, - daemon=True) - self.__fn_thread.start() - else: - self.__send_command(process_finished_callback=process_finished_callback) - - return self.__neighbors - - def __send_command(self, process_finished_callback=None): - """ - Sends the FN command. - - Args: - process_finished_callback (Function, optional): method to execute when - the process finishes. Receives two arguments: - - * The XBee device that executed the FN command. - * A list with the discovered neighbors. - * An error message if something went wrong. - """ - self.__lock.clear() - - self.__running = True - self.__error = None - self.__received_answer = False - self.__neighbors = [] - - if not self.__xbee.is_remote(): - xb = self.__xbee - else: - xb = self.__xbee.get_local_xbee_device() - - xb.add_packet_received_callback(self.__fn_packet_callback) - - try: - xb.send_packet(self.__generate_fn_packet()) - - self.__lock.wait(self.__timeout) - - if not self.__received_answer: - if not self.__error: - self.__error = "%s command answer not received" % ATStringCommand.FN.command - return - except XBeeException as e: - self.__error = "Error sending %s command: %s" % (ATStringCommand.FN.command, str(e)) - finally: - xb.del_packet_received_callback(self.__fn_packet_callback) - if process_finished_callback: - process_finished_callback(self.__xbee, self.__neighbors, self.__error) - self.__running = False - - def __generate_fn_packet(self): - """ - Generates the AT command packet or remote AT command packet. - - Returns: - :class:`digi.xbee.packets.common.RemoteATCommandPacket` or - :class:`digi.xbee.packets.common.ATCommandPacket`: The packet to send. - """ - if self.__xbee.is_remote(): - return RemoteATCommandPacket(self.__current_frame_id, self.__xbee.get_64bit_addr(), - XBee16BitAddress.UNKNOWN_ADDRESS, TransmitOptions.NONE.value, - ATStringCommand.FN.command) - - return ATCommPacket(self.__current_frame_id, ATStringCommand.FN.command) - - def __parse_data(self, data): - """ - Handles what to do with the received data. - - Args: - data (bytearray): Byte array containing the frame data. - - Return - """ - # Bytes 0 - 1: 16-bit neighbor address (always 0xFFFE) - # Bytes 2 - 9: 64-bit neighbor address - # Bytes 10 - x: Node identifier of the neighbor (ends with a 0x00 character) - # Next 2 bytes: Neighbor parent 16-bit address (always 0xFFFE) - # Next byte: Neighbor role: - # * 0: Coordinator - # * 1: Router - # * 2: End device - # Next byte: Status (reserved) - # Next 2 bytes: Profile identifier - # Next 2 bytes: Manufacturer identifier - # Next 4 bytes: Digi device type (optional, depending on 'NO' settings) - # Next byte: RSSI of last hop (optional, depending on 'NO' settings) - - # 64-bit address starts at index 2 - x64 = XBee64BitAddress(data[2:10]) - - # Node ID starts at index 10 - i = 10 - # Node id: from 'i' to the next 0x00 - while data[i] != 0x00: - i += 1 - node_id = data[10:i] - i += 1 # The 0x00 - - i += 2 # The parent address (not needed) - - # Role is the next byte - role = Role.get(utils.bytes_to_int(data[i:i + 1])) - i += 1 - - i += 1 # The status byte - i += 2 # The profile identifier - i += 2 # The manufacturer identifier - - # Check if the Digi device type and/or the RSSI are included - if len(data) >= i + 5: - # Both included - rssi = utils.bytes_to_int(data[i+4:i+5]) - elif len(data) >= i + 4: - # Only Digi device types - rssi = 0 - elif len(data) >= i + 1: - # Only the RSSI - rssi = utils.bytes_to_int(data[i:i+1]) - else: - # None of them - rssi = 0 - - if not self.__xbee.is_remote(): - xb = self.__xbee - else: - xb = self.__xbee.get_local_xbee_device() - - # Create a new remote node - n_xb = RemoteDigiMeshDevice(xb, x64bit_addr=x64, node_id=node_id.decode()) - n_xb._role = role - - neighbor = Neighbor(n_xb, NeighborRelationship.SIBLING, -1, rssi) - self.__neighbors.append(neighbor) - - self.__cb(self.__xbee, neighbor) - - def __fn_packet_callback(self, frame): - """ - Callback notified when a new frame is received. - - Args: - frame (:class:`digi.xbee.packets.base.XBeeAPIPacket`): The received packet. - """ - if not self.__running: - return - - frame_type = frame.get_frame_type() - if frame_type == ApiFrameType.AT_COMMAND_RESPONSE \ - or frame_type == ApiFrameType.REMOTE_AT_COMMAND_RESPONSE: - - self._logger.debug("Received '%s' frame: %s" - % (frame.get_frame_type().description, - utils.hex_to_string(frame.output()))) - - # If frame ID does not match, discard: it is not the frame we are waiting for - if frame.frame_id != self.__current_frame_id: - return - # Check the command - if frame.command != ATStringCommand.FN.command: - return - - self.__received_answer = True - - # Check for error. - if frame.status != ATCommandStatus.OK: - self.__error = "Error executing %s command (status: %s (%d))" \ - % (ATStringCommand.FN.command, - frame.status.description, frame.status.code) - self.stop() - return - - self.__parse_data(frame.command_value) diff --git a/digi/xbee/packets/aft.py b/digi/xbee/packets/aft.py index 29820aa..79e993d 100644 --- a/digi/xbee/packets/aft.py +++ b/digi/xbee/packets/aft.py @@ -36,17 +36,9 @@ class ApiFrameType(Enum): REMOTE_AT_COMMAND_REQUEST = (0x17, "Remote AT Command Request") TX_SMS = (0x1F, "TX SMS") TX_IPV4 = (0x20, "TX IPv4") - REGISTER_JOINING_DEVICE = (0x24, "Register Joining Device") SEND_DATA_REQUEST = (0x28, "Send Data Request") DEVICE_RESPONSE = (0x2A, "Device Response") USER_DATA_RELAY_REQUEST = (0x2D, "User Data Relay Request") - SOCKET_CREATE = (0x40, "Socket Create") - SOCKET_OPTION_REQUEST = (0x41, "Socket Option Request") - SOCKET_CONNECT = (0x42, "Socket Connect") - SOCKET_CLOSE = (0x43, "Socket Close") - SOCKET_SEND = (0x44, "Socket Send (Transmit)") - SOCKET_SENDTO = (0x45, "Socket SendTo (Transmit Explicit Data): IPv4") - SOCKET_BIND = (0x46, "Socket Bind/Listen") RX_64 = (0x80, "RX (Receive) Packet 64-bit Address") RX_16 = (0x81, "RX (Receive) Packet 16-bit Address") RX_IO_64 = (0x82, "IO Data Sample RX 64-bit Address Indicator") @@ -62,21 +54,11 @@ class ApiFrameType(Enum): IO_DATA_SAMPLE_RX_INDICATOR = (0x92, "IO Data Sample RX Indicator") REMOTE_AT_COMMAND_RESPONSE = (0x97, "Remote Command Response") RX_SMS = (0x9F, "RX SMS") - REGISTER_JOINING_DEVICE_STATUS = (0xA4, "Register Joining Device Status") USER_DATA_RELAY_OUTPUT = (0xAD, "User Data Relay Output") RX_IPV4 = (0xB0, "RX IPv4") SEND_DATA_RESPONSE = (0xB8, "Send Data Response") DEVICE_REQUEST = (0xB9, "Device Request") DEVICE_RESPONSE_STATUS = (0xBA, "Device Response Status") - SOCKET_CREATE_RESPONSE = (0xC0, "Socket Create Response") - SOCKET_OPTION_RESPONSE = (0xC1, "Socket Option Response") - SOCKET_CONNECT_RESPONSE = (0xC2, "Socket Connect Response") - SOCKET_CLOSE_RESPONSE = (0xC3, "Socket Close Response") - SOCKET_LISTEN_RESPONSE = (0xC6, "Socket Listen Response") - SOCKET_NEW_IPV4_CLIENT = (0xCC, "Socket New IPv4 Client") - SOCKET_RECEIVE = (0xCD, "Socket Receive") - SOCKET_RECEIVE_FROM = (0xCE, "Socket Receive From") - SOCKET_STATE = (0xCF, "Socket State") FRAME_ERROR = (0xFE, "Frame Error") GENERIC = (0xFF, "Generic") UNKNOWN = (-1, "Unknown Packet") diff --git a/digi/xbee/packets/base.py b/digi/xbee/packets/base.py index f0df64d..19361b6 100644 --- a/digi/xbee/packets/base.py +++ b/digi/xbee/packets/base.py @@ -77,17 +77,6 @@ class DictKeys(Enum): SOURCE_INTERFACE = "source_interface" DEST_INTERFACE = "dest_interface" DATA = "data" - OPTIONS = "options" - KEY = "key" - SOCKET_ID = "socket_id" - OPTION_ID = "option_id" - OPTION_DATA = "option_data" - DEST_ADDR_TYPE = "dest_address_type" - DEST_ADDR = "dest_address" - PAYLOAD = "payload" - CLIENT_SOCKET_ID = "client_socket_id" - REMOTE_ADDR = "remote_address" - REMOTE_PORT = "remote_port" class XBeePacket: @@ -332,7 +321,7 @@ def __init__(self, api_frame_type): | :class:`.ApiFrameType` | :class:`.XBeePacket` """ - super().__init__() + super(XBeeAPIPacket, self).__init__() # Check the type of the API frame type. if isinstance(api_frame_type, ApiFrameType): self._frame_type = api_frame_type @@ -443,22 +432,22 @@ def _check_api_packet(raw, min_length=5): | :mod:`.factory` """ if len(raw) < min_length: - raise InvalidPacketException(message="Bytearray must have, at least, 5 of complete length (header, length, " - "frameType, checksum)") + raise InvalidPacketException("Bytearray must have, at least, 5 of complete length (header, length, " + "frameType, checksum)") if raw[0] & 0xFF != SpecialByte.HEADER_BYTE.code: - raise InvalidPacketException(message="Bytearray must start with the header byte (SpecialByte.HEADER_BYTE.code)") + raise InvalidPacketException("Bytearray must start with the header byte (SpecialByte.HEADER_BYTE.code)") # real frame specific data length real_length = len(raw[3:-1]) # length is specified in the length field. length_field = utils.length_to_int(raw[1:3]) if real_length != length_field: - raise InvalidPacketException(message="The real length of this frame is distinct than the specified by length " + raise InvalidPacketException("The real length of this frame is distinct than the specified by length " "field (bytes 2 and 3)") if 0xFF - (sum(raw[3:-1]) & 0xFF) != raw[-1]: - raise InvalidPacketException(message="Wrong checksum") + raise InvalidPacketException("Wrong checksum") @abstractmethod def _get_api_packet_spec_data(self): @@ -514,7 +503,7 @@ def __init__(self, rf_data): | :mod:`.factory` | :class:`.XBeeAPIPacket` """ - super().__init__(api_frame_type=ApiFrameType.GENERIC) + super(GenericXBeePacket, self).__init__(api_frame_type=ApiFrameType.GENERIC) self.__rf_data = rf_data @staticmethod @@ -540,12 +529,12 @@ def create_packet(raw, operating_mode=OperatingMode.API_MODE): | :meth:`.XBeeAPIPacket._check_api_packet` """ if operating_mode != OperatingMode.ESCAPED_API_MODE and operating_mode != OperatingMode.API_MODE: - raise InvalidOperatingModeException(op_mode=operating_mode) + raise InvalidOperatingModeException(operating_mode + " is not supported.") XBeeAPIPacket._check_api_packet(raw, min_length=GenericXBeePacket.__MIN_PACKET_LENGTH) if raw[3] != ApiFrameType.GENERIC.code: - raise InvalidPacketException(message="Wrong frame type, expected: " + ApiFrameType.GENERIC.description + + raise InvalidPacketException("Wrong frame type, expected: " + ApiFrameType.GENERIC.description + ". Value: " + ApiFrameType.GENERIC.code) return GenericXBeePacket(raw[4:-1]) @@ -600,7 +589,7 @@ def __init__(self, api_frame, rf_data): | :mod:`.factory` | :class:`.XBeeAPIPacket` """ - super().__init__(api_frame_type=api_frame) + super(UnknownXBeePacket, self).__init__(api_frame_type=api_frame) self.__rf_data = rf_data @staticmethod @@ -625,7 +614,7 @@ def create_packet(raw, operating_mode=OperatingMode.API_MODE): | :meth:`.XBeeAPIPacket._check_api_packet` """ if operating_mode != OperatingMode.ESCAPED_API_MODE and operating_mode != OperatingMode.API_MODE: - raise InvalidOperatingModeException(op_mode=operating_mode) + raise InvalidOperatingModeException(operating_mode + " is not supported.") XBeeAPIPacket._check_api_packet(raw, min_length=UnknownXBeePacket.__MIN_PACKET_LENGTH) diff --git a/digi/xbee/packets/cellular.py b/digi/xbee/packets/cellular.py index 3830efd..c754e02 100644 --- a/digi/xbee/packets/cellular.py +++ b/digi/xbee/packets/cellular.py @@ -81,12 +81,12 @@ def create_packet(raw, operating_mode): | :meth:`.XBeePacket.create_packet` """ if operating_mode != OperatingMode.ESCAPED_API_MODE and operating_mode != OperatingMode.API_MODE: - raise InvalidOperatingModeException(op_mode=operating_mode) + raise InvalidOperatingModeException(operating_mode.name + " is not supported.") XBeeAPIPacket._check_api_packet(raw, min_length=RXSMSPacket.__MIN_PACKET_LENGTH) if raw[3] != ApiFrameType.RX_SMS.code: - raise InvalidPacketException(message="This packet is not an RXSMSPacket") + raise InvalidPacketException("This packet is not an RXSMSPacket") return RXSMSPacket(raw[4:23].decode("utf8").replace("\0", ""), raw[24:-1].decode("utf8")) @@ -249,12 +249,12 @@ def create_packet(raw, operating_mode): | :meth:`.XBeePacket.create_packet` """ if operating_mode != OperatingMode.ESCAPED_API_MODE and operating_mode != OperatingMode.API_MODE: - raise InvalidOperatingModeException(op_mode=operating_mode) + raise InvalidOperatingModeException(operating_mode.name + " is not supported.") XBeeAPIPacket._check_api_packet(raw, min_length=TXSMSPacket.__MIN_PACKET_LENGTH) if raw[3] != ApiFrameType.TX_SMS.code: - raise InvalidPacketException(message="This packet is not a TXSMSPacket") + raise InvalidPacketException("This packet is not a TXSMSPacket") return TXSMSPacket(raw[4], raw[6:25].decode("utf8").replace("\0", ""), raw[26:-1].decode("utf8")) diff --git a/digi/xbee/packets/common.py b/digi/xbee/packets/common.py index 48df3fb..40e3a07 100644 --- a/digi/xbee/packets/common.py +++ b/digi/xbee/packets/common.py @@ -1,4 +1,4 @@ -# Copyright 2017-2019, Digi International Inc. +# Copyright 2017, 2018, 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 @@ -21,6 +21,7 @@ from digi.xbee.exception import InvalidOperatingModeException, InvalidPacketException from digi.xbee.io import IOSample, IOLine +import copy class ATCommPacket(XBeeAPIPacket): """ @@ -61,7 +62,7 @@ def __init__(self, frame_id, command, parameter=None): if frame_id < 0 or frame_id > 255: raise ValueError("Frame id must be between 0 and 255.") - super().__init__(ApiFrameType.AT_COMMAND) + super(ATCommPacket, self).__init__(ApiFrameType.AT_COMMAND) self.__command = command self.__parameter = parameter self._frame_id = frame_id @@ -89,14 +90,14 @@ def create_packet(raw, operating_mode): | :meth:`.XBeeAPIPacket._check_api_packet` """ if operating_mode != OperatingMode.ESCAPED_API_MODE and operating_mode != OperatingMode.API_MODE: - raise InvalidOperatingModeException(op_mode=operating_mode) + raise InvalidOperatingModeException(operating_mode.name + " is not supported.") XBeeAPIPacket._check_api_packet(raw, min_length=ATCommPacket.__MIN_PACKET_LENGTH) if raw[3] != ApiFrameType.AT_COMMAND.code: - raise InvalidPacketException(message="This packet is not an AT command packet.") + raise InvalidPacketException("This packet is not an AT command packet.") - return ATCommPacket(raw[4], raw[5:7].decode("utf8"), parameter=raw[7:-1]) + return ATCommPacket(raw[4], raw[5:7].decode("utf8"), raw[7:-1]) def needs_id(self): """ @@ -218,7 +219,7 @@ def __init__(self, frame_id, command, parameter=None): if frame_id < 0 or frame_id > 255: raise ValueError("Frame id must be between 0 and 255.") - super().__init__(ApiFrameType.AT_COMMAND_QUEUE) + super(ATCommQueuePacket, self).__init__(ApiFrameType.AT_COMMAND_QUEUE) self.__command = command self.__parameter = parameter self._frame_id = frame_id @@ -246,14 +247,14 @@ def create_packet(raw, operating_mode): | :meth:`.XBeeAPIPacket._check_api_packet` """ if operating_mode != OperatingMode.ESCAPED_API_MODE and operating_mode != OperatingMode.API_MODE: - raise InvalidOperatingModeException(op_mode=operating_mode) + raise InvalidOperatingModeException(operating_mode.name + " is not supported.") XBeeAPIPacket._check_api_packet(raw, min_length=ATCommQueuePacket.__MIN_PACKET_LENGTH) if raw[3] != ApiFrameType.AT_COMMAND_QUEUE.code: - raise InvalidPacketException(message="This packet is not an AT command Queue packet.") + raise InvalidPacketException("This packet is not an AT command Queue packet.") - return ATCommQueuePacket(raw[4], raw[5:7].decode("utf8"), parameter=raw[7:-1]) + return ATCommQueuePacket(raw[4], raw[5:7].decode("utf8"), raw[7:-1]) def needs_id(self): """ @@ -348,7 +349,7 @@ class ATCommResponsePacket(XBeeAPIPacket): .. seealso:: | :class:`.ATCommPacket` - | :class:`.ATCommandStatus` + | :class:`.ATCommandStatus` | :class:`.XBeeAPIPacket` """ @@ -357,11 +358,11 @@ class ATCommResponsePacket(XBeeAPIPacket): def __init__(self, frame_id, command, response_status=ATCommandStatus.OK, comm_value=None): """ Class constructor. Instantiates a new :class:`.ATCommResponsePacket` object with the provided parameters. - + Args: frame_id (Integer): the frame ID of the packet. Must be between 0 and 255. command (String): the AT command of the packet. Must be a string. - response_status (:class:`.ATCommandStatus` or Integer): the status of the AT command. + response_status (:class:`.ATCommandStatus`): the status of the AT command. comm_value (Bytearray, optional): the AT command response value. Optional. Raises: @@ -376,21 +377,11 @@ def __init__(self, frame_id, command, response_status=ATCommandStatus.OK, comm_v raise ValueError("Frame id must be between 0 and 255.") if len(command) != 2: raise ValueError("Invalid command " + command) - if response_status is None: - response_status = ATCommandStatus.OK.code - elif not isinstance(response_status, (ATCommandStatus, int)): - raise TypeError("Response status must be ATCommandStatus or int not {!r}".format( - response_status.__class__.__name__)) - super().__init__(ApiFrameType.AT_COMMAND_RESPONSE) + super(ATCommResponsePacket, self).__init__(ApiFrameType.AT_COMMAND_RESPONSE) self._frame_id = frame_id self.__command = command - if isinstance(response_status, ATCommandStatus): - self.__response_status = response_status.code - elif 0 <= response_status <= 255: - self.__response_status = response_status - else: - raise ValueError("Response status must be between 0 and 255.") + self.__response_status = response_status self.__comm_value = comm_value @staticmethod @@ -417,16 +408,16 @@ def create_packet(raw, operating_mode): | :meth:`.XBeeAPIPacket._check_api_packet` """ if operating_mode != OperatingMode.ESCAPED_API_MODE and operating_mode != OperatingMode.API_MODE: - raise InvalidOperatingModeException(op_mode=operating_mode) + raise InvalidOperatingModeException(operating_mode.name + " is not supported.") XBeeAPIPacket._check_api_packet(raw, min_length=ATCommResponsePacket.__MIN_PACKET_LENGTH) if raw[3] != ApiFrameType.AT_COMMAND_RESPONSE.code: - raise InvalidPacketException(message="This packet is not an AT command response packet.") + raise InvalidPacketException("This packet is not an AT command response packet.") if ATCommandStatus.get(raw[7]) is None: - raise InvalidPacketException(message="Invalid command status.") + raise InvalidPacketException("Invalid command status.") - return ATCommResponsePacket(raw[4], raw[5:7].decode("utf8"), raw[7], comm_value=raw[8:-1]) + return ATCommResponsePacket(raw[4], raw[5:7].decode("utf8"), ATCommandStatus.get(raw[7]), raw[8:-1]) def needs_id(self): """ @@ -445,7 +436,7 @@ def _get_api_packet_spec_data(self): | :meth:`.XBeeAPIPacket._get_api_packet_spec_data` """ ret = bytearray(self.__command, "utf8") - ret.append(self.__response_status) + ret.append(self.__response_status.code) if self.__comm_value is not None: ret += self.__comm_value return ret @@ -505,49 +496,26 @@ def __set_value(self, __comm_value): def __get_response_status(self): """ Returns the AT command response status of the packet. - + Returns: :class:`.ATCommandStatus`: the AT command response status of the packet. .. seealso:: | :class:`.ATCommandStatus` """ - return ATCommandStatus.get(self.__response_status) - - def __get_real_response_status(self): - """ - Returns the AT command response status of the packet. - - Returns: - Integer: the AT command response status of the packet. - """ return self.__response_status def __set_response_status(self, response_status): """ Sets the AT command response status of the packet - + Args: - response_status (:class:`.ATCommandStatus`) : the new AT command - response status of the packet. + response_status (:class:`.ATCommandStatus`) : the new AT command response status of the packet. .. seealso:: | :class:`.ATCommandStatus` """ - if response_status is None: - raise ValueError("Response status cannot be None") - - if isinstance(response_status, ATCommandStatus): - self.__response_status = response_status.code - elif isinstance(response_status, int): - if 0 <= response_status <= 255: - self.__response_status = response_status - else: - raise ValueError("Response status must be between 0 and 255.") - else: - raise TypeError( - "Response status must be ATCommandStatus or int not {!r}". - format(response_status.__class__.__name__)) + self.__response_status = response_status command = property(__get_command, __set_command) """String. AT command.""" @@ -558,9 +526,6 @@ def __set_response_status(self, response_status): status = property(__get_response_status, __set_response_status) """:class:`.ATCommandStatus`. AT command response status.""" - real_status = property(__get_real_response_status, __set_response_status) - """Integer. AT command response status.""" - class ReceivePacket(XBeeAPIPacket): """ @@ -600,7 +565,7 @@ def __init__(self, x64bit_addr, x16bit_addr, receive_options, rf_data=None): | :class:`.XBee64BitAddress` | :class:`.XBeeAPIPacket` """ - super().__init__(ApiFrameType.RECEIVE_PACKET) + super(ReceivePacket, self).__init__(ApiFrameType.RECEIVE_PACKET) self.__x64bit_addr = x64bit_addr self.__x16bit_addr = x16bit_addr self.__receive_options = receive_options @@ -629,16 +594,16 @@ def create_packet(raw, operating_mode): | :meth:`.XBeeAPIPacket._check_api_packet` """ if operating_mode != OperatingMode.ESCAPED_API_MODE and operating_mode != OperatingMode.API_MODE: - raise InvalidOperatingModeException(op_mode=operating_mode) + raise InvalidOperatingModeException(operating_mode.name + " is not supported.") XBeeAPIPacket._check_api_packet(raw, min_length=ReceivePacket.__MIN_PACKET_LENGTH) if raw[3] != ApiFrameType.RECEIVE_PACKET.code: - raise InvalidPacketException(message="This packet is not a receive packet.") + raise InvalidPacketException("This packet is not a receive packet.") return ReceivePacket(XBee64BitAddress(raw[4:12]), XBee16BitAddress(raw[12:14]), raw[14], - rf_data=raw[15:-1]) + raw[15:-1]) def needs_id(self): """ @@ -649,15 +614,6 @@ def needs_id(self): """ return False - def is_broadcast(self): - """ - Override method. - - .. seealso:: - | :meth:`XBeeAPIPacket.is_broadcast` - """ - return utils.is_bit_enabled(self.__receive_options, 1) - def _get_api_packet_spec_data(self): """ Override method. @@ -765,7 +721,7 @@ def __get_rf_data(self): """ if self.__rf_data is None: return None - return self.__rf_data.copy() + return copy.copy(self.__rf_data) def __set_rf_data(self, rf_data): """ @@ -777,7 +733,7 @@ def __set_rf_data(self, rf_data): if rf_data is None: self.__rf_data = None else: - self.__rf_data = rf_data.copy() + self.__rf_data = copy.copy(rf_data) x64bit_source_addr = property(__get_64bit_addr, __set_64bit_addr) """:class:`.XBee64BitAddress`. 64-bit source address.""" @@ -841,7 +797,7 @@ def __init__(self, frame_id, x64bit_addr, x16bit_addr, transmit_options, command if len(command) != 2: raise ValueError("Invalid command " + command) - super().__init__(ApiFrameType.REMOTE_AT_COMMAND_REQUEST) + super(RemoteATCommandPacket, self).__init__(ApiFrameType.REMOTE_AT_COMMAND_REQUEST) self._frame_id = frame_id self.__x64bit_addr = x64bit_addr self.__x16bit_addr = x16bit_addr @@ -873,12 +829,12 @@ def create_packet(raw, operating_mode): | :meth:`.XBeeAPIPacket._check_api_packet` """ if operating_mode != OperatingMode.ESCAPED_API_MODE and operating_mode != OperatingMode.API_MODE: - raise InvalidOperatingModeException(op_mode=operating_mode) + raise InvalidOperatingModeException(operating_mode.name + " is not supported.") XBeeAPIPacket._check_api_packet(raw, min_length=RemoteATCommandPacket.__MIN_PACKET_LENGTH) if raw[3] != ApiFrameType.REMOTE_AT_COMMAND_REQUEST.code: - raise InvalidPacketException(message="This packet is not a remote AT command request packet.") + raise InvalidPacketException("This packet is not a remote AT command request packet.") return RemoteATCommandPacket( raw[4], @@ -1080,7 +1036,7 @@ def __init__(self, frame_id, x64bit_addr, x16bit_addr, command, response_status, x64bit_addr (:class:`.XBee64BitAddress`): the 64-bit source address x16bit_addr (:class:`.XBee16BitAddress`): the 16-bit source address. command (String): the AT command of the packet. Must be a string. - response_status (:class:`.ATCommandStatus` or Integer): the status of the AT command. + response_status (:class:`.ATCommandStatus`): the status of the AT command. comm_value (Bytearray, optional): the AT command response value. Optional. Raises: @@ -1097,23 +1053,13 @@ def __init__(self, frame_id, x64bit_addr, x16bit_addr, command, response_status, raise ValueError("frame_id must be between 0 and 255.") if len(command) != 2: raise ValueError("Invalid command " + command) - if response_status is None: - response_status = ATCommandStatus.OK.code - elif not isinstance(response_status, (ATCommandStatus, int)): - raise TypeError("Response status must be ATCommandStatus or int not {!r}".format( - response_status.__class__.__name__)) - super().__init__(ApiFrameType.REMOTE_AT_COMMAND_RESPONSE) + super(RemoteATCommandResponsePacket, self).__init__(ApiFrameType.REMOTE_AT_COMMAND_RESPONSE) self._frame_id = frame_id self.__x64bit_addr = x64bit_addr self.__x16bit_addr = x16bit_addr self.__command = command - if isinstance(response_status, ATCommandStatus): - self.__response_status = response_status.code - elif 0 <= response_status <= 255: - self.__response_status = response_status - else: - raise ValueError("Response status must be between 0 and 255.") + self.__response_status = response_status self.__comm_value = comm_value @staticmethod @@ -1140,16 +1086,16 @@ def create_packet(raw, operating_mode): | :meth:`.XBeeAPIPacket._check_api_packet` """ if operating_mode != OperatingMode.ESCAPED_API_MODE and operating_mode != OperatingMode.API_MODE: - raise InvalidOperatingModeException(op_mode=operating_mode) + raise InvalidOperatingModeException(operating_mode.name + " is not supported.") XBeeAPIPacket._check_api_packet(raw, min_length=RemoteATCommandResponsePacket.__MIN_PACKET_LENGTH) if raw[3] != ApiFrameType.REMOTE_AT_COMMAND_RESPONSE.code: - raise InvalidPacketException(message="This packet is not a remote AT command response packet.") + raise InvalidPacketException("This packet is not a remote AT command response packet.") return RemoteATCommandResponsePacket(raw[4], XBee64BitAddress(raw[5:13]), XBee16BitAddress(raw[13:15]), raw[15:17].decode("utf8"), - raw[17], comm_value=raw[18:-1]) + ATCommandStatus.get(raw[17]), raw[18:-1]) def needs_id(self): """ @@ -1170,7 +1116,7 @@ def _get_api_packet_spec_data(self): ret = self.__x64bit_addr.address ret += self.__x16bit_addr.address ret += bytearray(self.__command, "utf8") - ret.append(self.__response_status) + ret.append(self.__response_status.code) if self.__comm_value is not None: ret += self.__comm_value return ret @@ -1233,15 +1179,6 @@ def __get_response_status(self): .. seealso:: | :class:`.ATCommandStatus` """ - return ATCommandStatus.get(self.__response_status) - - def __get_real_response_status(self): - """ - Returns the AT command response status of the packet. - - Returns: - Integer: the AT command response status of the packet. - """ return self.__response_status def __set_response_status(self, response_status): @@ -1249,26 +1186,12 @@ def __set_response_status(self, response_status): Sets the AT command response status of the packet Args: - response_status (:class:`.ATCommandStatus`) : the new AT command - response status of the packet. + response_status (:class:`.ATCommandStatus`) : the new AT command response status of the packet. .. seealso:: | :class:`.ATCommandStatus` """ - if response_status is None: - raise ValueError("Response status cannot be None") - - if isinstance(response_status, ATCommandStatus): - self.__response_status = response_status.code - elif isinstance(response_status, int): - if 0 <= response_status <= 255: - self.__response_status = response_status - else: - raise ValueError("Response status must be between 0 and 255.") - else: - raise TypeError( - "Response status must be ATCommandStatus or int not {!r}". - format(response_status.__class__.__name__)) + self.__response_status = response_status def __get_64bit_addr(self): """ @@ -1333,9 +1256,6 @@ def __set_16bit_addr(self, x16bit_addr): status = property(__get_response_status, __set_response_status) """:class:`.ATCommandStatus`. AT command response status.""" - real_status = property(__get_real_response_status, __set_response_status) - """Integer. AT command response status.""" - class TransmitPacket(XBeeAPIPacket): """ @@ -1407,7 +1327,7 @@ def __init__(self, frame_id, x64bit_addr, x16bit_addr, broadcast_radius, transmi if frame_id > 255 or frame_id < 0: raise ValueError("frame_id must be between 0 and 255.") - super().__init__(ApiFrameType.TRANSMIT_REQUEST) + super(TransmitPacket, self).__init__(ApiFrameType.TRANSMIT_REQUEST) self._frame_id = frame_id self.__x64bit_addr = x64bit_addr self.__x16bit_addr = x16bit_addr @@ -1438,16 +1358,16 @@ def create_packet(raw, operating_mode): | :meth:`.XBeeAPIPacket._check_api_packet` """ if operating_mode != OperatingMode.ESCAPED_API_MODE and operating_mode != OperatingMode.API_MODE: - raise InvalidOperatingModeException(op_mode=operating_mode) + raise InvalidOperatingModeException(operating_mode.name + " is not supported.") XBeeAPIPacket._check_api_packet(raw, min_length=TransmitPacket.__MIN_PACKET_LENGTH) if raw[3] != ApiFrameType.TRANSMIT_REQUEST.code: - raise InvalidPacketException(message="This packet is not a transmit request packet.") + raise InvalidPacketException("This packet is not a transmit request packet.") return TransmitPacket(raw[4], XBee64BitAddress(raw[5:13]), XBee16BitAddress(raw[13:15]), raw[15], - raw[16], rf_data=raw[17:-1]) + raw[16], raw[17:-1]) def needs_id(self): """ @@ -1470,7 +1390,7 @@ def _get_api_packet_spec_data(self): ret.append(self.__broadcast_radius) ret.append(self.__transmit_options) if self.__rf_data is not None: - return ret + self.__rf_data + return ret + bytes(self.__rf_data) return ret def _get_api_packet_spec_data_dict(self): @@ -1495,7 +1415,7 @@ def __get_rf_data(self): """ if self.__rf_data is None: return None - return self.__rf_data.copy() + return copy.copy(self.__rf_data) def __set_rf_data(self, rf_data): """ @@ -1507,7 +1427,7 @@ def __set_rf_data(self, rf_data): if rf_data is None: self.__rf_data = None else: - self.__rf_data = rf_data.copy() + self.__rf_data = copy.copy(rf_data) def __get_transmit_options(self): """ @@ -1657,7 +1577,7 @@ def __init__(self, frame_id, x16bit_addr, transmit_retry_count, transmit_status= if frame_id < 0 or frame_id > 255: raise ValueError("Frame id must be between 0 and 255.") - super().__init__(ApiFrameType.TRANSMIT_STATUS) + super(TransmitStatusPacket, self).__init__(ApiFrameType.TRANSMIT_STATUS) self._frame_id = frame_id self.__x16bit_addr = x16bit_addr self.__transmit_retry_count = transmit_retry_count @@ -1688,16 +1608,15 @@ def create_packet(raw, operating_mode): | :meth:`.XBeeAPIPacket._check_api_packet` """ if operating_mode != OperatingMode.ESCAPED_API_MODE and operating_mode != OperatingMode.API_MODE: - raise InvalidOperatingModeException(op_mode=operating_mode) + raise InvalidOperatingModeException(operating_mode.name + " is not supported.") XBeeAPIPacket._check_api_packet(raw, min_length=TransmitStatusPacket.__MIN_PACKET_LENGTH) if raw[3] != ApiFrameType.TRANSMIT_STATUS.code: - raise InvalidPacketException(message="This packet is not a transmit status packet.") + raise InvalidPacketException("This packet is not a transmit status packet.") return TransmitStatusPacket(raw[4], XBee16BitAddress(raw[5:7]), raw[7], - transmit_status=TransmitStatus.get(raw[8]), - discovery_status=DiscoveryStatus.get(raw[9])) + TransmitStatus.get(raw[8]), DiscoveryStatus.get(raw[9])) def needs_id(self): """ @@ -1861,7 +1780,7 @@ def __init__(self, modem_status): | :class:`.ModemStatus` | :class:`.XBeeAPIPacket` """ - super().__init__(ApiFrameType.MODEM_STATUS) + super(ModemStatusPacket, self).__init__(ApiFrameType.MODEM_STATUS) self.__modem_status = modem_status @staticmethod @@ -1887,12 +1806,12 @@ def create_packet(raw, operating_mode): | :meth:`.XBeeAPIPacket._check_api_packet` """ if operating_mode != OperatingMode.ESCAPED_API_MODE and operating_mode != OperatingMode.API_MODE: - raise InvalidOperatingModeException(op_mode=operating_mode) + raise InvalidOperatingModeException(operating_mode.name + " is not supported.") XBeeAPIPacket._check_api_packet(raw, min_length=ModemStatusPacket.__MIN_PACKET_LENGTH) if raw[3] != ApiFrameType.MODEM_STATUS.code: - raise InvalidPacketException(message="This packet is not a modem status packet.") + raise InvalidPacketException("This packet is not a modem status packet.") return ModemStatusPacket(ModemStatus.get(raw[4])) @@ -1991,7 +1910,7 @@ def __init__(self, x64bit_addr, x16bit_addr, receive_options, rf_data=None): | :class:`.XBee64BitAddress` | :class:`.XBeeAPIPacket` """ - super().__init__(ApiFrameType.IO_DATA_SAMPLE_RX_INDICATOR) + super(IODataSampleRxIndicatorPacket, self).__init__(ApiFrameType.IO_DATA_SAMPLE_RX_INDICATOR) self.__x64bit_addr = x64bit_addr self.__x16bit_addr = x16bit_addr self.__receive_options = receive_options @@ -2021,15 +1940,15 @@ def create_packet(raw, operating_mode): | :meth:`.XBeeAPIPacket._check_api_packet` """ if operating_mode != OperatingMode.ESCAPED_API_MODE and operating_mode != OperatingMode.API_MODE: - raise InvalidOperatingModeException(op_mode=operating_mode) + raise InvalidOperatingModeException(operating_mode.name + " is not supported.") XBeeAPIPacket._check_api_packet(raw, min_length=IODataSampleRxIndicatorPacket.__MIN_PACKET_LENGTH) if raw[3] != ApiFrameType.IO_DATA_SAMPLE_RX_INDICATOR.code: - raise InvalidPacketException(message="This packet is not an IO data sample RX indicator packet.") + raise InvalidPacketException("This packet is not an IO data sample RX indicator packet.") return IODataSampleRxIndicatorPacket(XBee64BitAddress(raw[4:12]), XBee16BitAddress(raw[12:14]), - raw[14], rf_data=raw[15:-1]) + raw[14], raw[15:-1]) def needs_id(self): """ @@ -2181,7 +2100,7 @@ def __get_rf_data(self): """ if self.__rf_data is None: return None - return self.__rf_data.copy() + return copy.copy(self.__rf_data) def __set_rf_data(self, rf_data): """ @@ -2193,7 +2112,7 @@ def __set_rf_data(self, rf_data): if rf_data is None: self.__rf_data = None else: - self.__rf_data = rf_data.copy() + self.__rf_data = copy.copy(rf_data) # Modify the ioSample accordingly if rf_data is not None and len(rf_data) >= 5: @@ -2334,7 +2253,7 @@ def __init__(self, frame_id, x64bit_addr, x16bit_addr, source_endpoint, dest_end if profile_id < 0 or profile_id > 0xFFFF: raise ValueError("Profile id must be between 0 and 0xFFFF.") - super().__init__(ApiFrameType.EXPLICIT_ADDRESSING) + super(ExplicitAddressingPacket, self).__init__(ApiFrameType.EXPLICIT_ADDRESSING) self._frame_id = frame_id self.__x64_addr = x64bit_addr self.__x16_addr = x16bit_addr @@ -2370,16 +2289,16 @@ def create_packet(raw, operating_mode): | :meth:`.XBeeAPIPacket._check_api_packet` """ if operating_mode != OperatingMode.ESCAPED_API_MODE and operating_mode != OperatingMode.API_MODE: - raise InvalidOperatingModeException(op_mode=operating_mode) + raise InvalidOperatingModeException(operating_mode.name + " is not supported.") XBeeAPIPacket._check_api_packet(raw, min_length=ExplicitAddressingPacket.__MIN_PACKET_LENGTH) if raw[3] != ApiFrameType.EXPLICIT_ADDRESSING.code: - raise InvalidPacketException(message="This packet is not an explicit addressing packet") + raise InvalidPacketException("This packet is not an explicit addressing packet") return ExplicitAddressingPacket(raw[4], XBee64BitAddress(raw[5:13]), XBee16BitAddress(raw[13:15]), raw[15], raw[16], utils.bytes_to_int(raw[17:19]), - utils.bytes_to_int(raw[19:21]), raw[21], raw[22], rf_data=raw[23:-1]) + utils.bytes_to_int(raw[19:21]), raw[21], raw[22], raw[23:-1]) def needs_id(self): """ @@ -2401,8 +2320,8 @@ def _get_api_packet_spec_data(self): raw += self.__x16_addr.address raw.append(self.__source_endpoint) raw.append(self.__dest_endpoint) - raw += utils.int_to_bytes(self.__cluster_id, num_bytes=2) - raw += utils.int_to_bytes(self.__profile_id, num_bytes=2) + raw += utils.int_to_bytes(self.__cluster_id, 2) + raw += utils.int_to_bytes(self.__profile_id, 2) raw.append(self.__broadcast_radius) raw.append(self.__transmit_options) if self.__rf_data is not None: @@ -2507,7 +2426,7 @@ def __get_rf_data(self): """ if self.__rf_data is None: return None - return self.__rf_data.copy() + return copy.copy(self.__rf_data) def __set_rf_data(self, rf_data): """ @@ -2519,7 +2438,7 @@ def __set_rf_data(self, rf_data): if rf_data is None: self.__rf_data = None else: - self.__rf_data = rf_data.copy() + self.__rf_data = copy.copy(rf_data) def __get_transmit_options(self): """ @@ -2696,7 +2615,7 @@ def __init__(self, x64bit_addr, x16bit_addr, source_endpoint, if profile_id < 0 or profile_id > 0xFFFF: raise ValueError("Profile id must be between 0 and 0xFFFF.") - super().__init__(ApiFrameType.EXPLICIT_RX_INDICATOR) + super(ExplicitRXIndicatorPacket, self).__init__(ApiFrameType.EXPLICIT_RX_INDICATOR) self.__x64bit_addr = x64bit_addr self.__x16bit_addr = x16bit_addr self.__source_endpoint = source_endpoint @@ -2730,16 +2649,16 @@ def create_packet(raw, operating_mode): | :meth:`.XBeeAPIPacket._check_api_packet` """ if operating_mode != OperatingMode.ESCAPED_API_MODE and operating_mode != OperatingMode.API_MODE: - raise InvalidOperatingModeException(op_mode=operating_mode) + raise InvalidOperatingModeException(operating_mode.name + " is not supported.") XBeeAPIPacket._check_api_packet(raw, min_length=ExplicitRXIndicatorPacket.__MIN_PACKET_LENGTH) if raw[3] != ApiFrameType.EXPLICIT_RX_INDICATOR.code: - raise InvalidPacketException(message="This packet is not an explicit RX indicator packet.") + raise InvalidPacketException("This packet is not an explicit RX indicator packet.") return ExplicitRXIndicatorPacket(XBee64BitAddress(raw[4:12]), XBee16BitAddress(raw[12:14]), raw[14], raw[15], utils.bytes_to_int(raw[16:18]), utils.bytes_to_int(raw[18:20]), - raw[20], rf_data=raw[21:-1]) + raw[20], raw[21:-1]) def needs_id(self): """ @@ -2750,15 +2669,6 @@ def needs_id(self): """ return False - def is_broadcast(self): - """ - Override method. - - .. seealso:: - | :meth:`XBeeAPIPacket.is_broadcast` - """ - return utils.is_bit_enabled(self.__receive_options, 1) - def _get_api_packet_spec_data(self): """ Override method. @@ -2770,8 +2680,8 @@ def _get_api_packet_spec_data(self): raw += self.__x16bit_addr.address raw.append(self.__source_endpoint) raw.append(self.__dest_endpoint) - raw += utils.int_to_bytes(self.__cluster_id, num_bytes=2) - raw += utils.int_to_bytes(self.__profile_id, num_bytes=2) + raw += utils.int_to_bytes(self.__cluster_id, 2) + raw += utils.int_to_bytes(self.__profile_id, 2) raw.append(self.__receive_options) if self.__rf_data is not None: raw += self.__rf_data @@ -2946,7 +2856,7 @@ def __get_rf_data(self): """ if self.__rf_data is None: return None - return self.__rf_data.copy() + return copy.copy(self.__rf_data) def __set_rf_data(self, rf_data): """ @@ -2958,7 +2868,7 @@ def __set_rf_data(self, rf_data): if rf_data is None: self.__rf_data = None else: - self.__rf_data = rf_data.copy() + self.__rf_data = copy.copy(rf_data) x64bit_source_addr = property(__get_64bit_addr, __set_64bit_addr) """:class:`.XBee64BitAddress`. 64-bit source address.""" diff --git a/digi/xbee/packets/devicecloud.py b/digi/xbee/packets/devicecloud.py index 1fb6034..8fb66ce 100644 --- a/digi/xbee/packets/devicecloud.py +++ b/digi/xbee/packets/devicecloud.py @@ -1,4 +1,4 @@ -# Copyright 2017-2019, Digi International Inc. +# Copyright 2017, 2018, 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 @@ -88,17 +88,15 @@ def create_packet(raw, operating_mode): | :meth:`.XBeeAPIPacket._check_api_packet` """ if operating_mode != OperatingMode.ESCAPED_API_MODE and operating_mode != OperatingMode.API_MODE: - raise InvalidOperatingModeException(op_mode=operating_mode) + raise InvalidOperatingModeException(operating_mode.name + " is not supported.") XBeeAPIPacket._check_api_packet(raw, min_length=DeviceRequestPacket.__MIN_PACKET_LENGTH) if raw[3] != ApiFrameType.DEVICE_REQUEST.code: - raise InvalidPacketException(message="This packet is not a device request packet.") + raise InvalidPacketException("This packet is not a device request packet.") target_length = raw[7] - - return DeviceRequestPacket(raw[4], target=raw[8:8 + target_length].decode("utf8"), - request_data=raw[8 + target_length:-1]) + return DeviceRequestPacket(raw[4], raw[8:8 + target_length].decode("utf8"), raw[8 + target_length:-1]) def needs_id(self): """ @@ -311,14 +309,14 @@ def create_packet(raw, operating_mode): | :meth:`.XBeeAPIPacket._check_api_packet` """ if operating_mode != OperatingMode.ESCAPED_API_MODE and operating_mode != OperatingMode.API_MODE: - raise InvalidOperatingModeException(op_mode=operating_mode) + raise InvalidOperatingModeException(operating_mode.name + " is not supported.") XBeeAPIPacket._check_api_packet(raw, min_length=DeviceResponsePacket.__MIN_PACKET_LENGTH) if raw[3] != ApiFrameType.DEVICE_RESPONSE.code: - raise InvalidPacketException(message="This packet is not a device response packet.") + raise InvalidPacketException("This packet is not a device response packet.") - return DeviceResponsePacket(raw[4], raw[5], response_data=raw[7:-1]) + return DeviceResponsePacket(raw[4], raw[5], raw[7:-1]) def needs_id(self): """ @@ -467,12 +465,12 @@ def create_packet(raw, operating_mode): | :meth:`.XBeeAPIPacket._check_api_packet` """ if operating_mode != OperatingMode.ESCAPED_API_MODE and operating_mode != OperatingMode.API_MODE: - raise InvalidOperatingModeException(op_mode=operating_mode) + raise InvalidOperatingModeException(operating_mode.name + " is not supported.") XBeeAPIPacket._check_api_packet(raw, min_length=DeviceResponseStatusPacket.__MIN_PACKET_LENGTH) if raw[3] != ApiFrameType.DEVICE_RESPONSE_STATUS.code: - raise InvalidPacketException(message="This packet is not a device response status packet.") + raise InvalidPacketException("This packet is not a device response status packet.") return DeviceResponseStatusPacket(raw[4], DeviceCloudStatus.get(raw[5])) @@ -582,12 +580,12 @@ def create_packet(raw, operating_mode): | :meth:`.XBeeAPIPacket._check_api_packet` """ if operating_mode != OperatingMode.ESCAPED_API_MODE and operating_mode != OperatingMode.API_MODE: - raise InvalidOperatingModeException(op_mode=operating_mode) + raise InvalidOperatingModeException(operating_mode.name + " is not supported.") XBeeAPIPacket._check_api_packet(raw, min_length=FrameErrorPacket.__MIN_PACKET_LENGTH) if raw[3] != ApiFrameType.FRAME_ERROR.code: - raise InvalidPacketException(message="This packet is not a frame error packet.") + raise InvalidPacketException("This packet is not a frame error packet.") return FrameErrorPacket(FrameError.get(raw[4])) @@ -715,12 +713,12 @@ def create_packet(raw, operating_mode): | :meth:`.XBeeAPIPacket._check_api_packet` """ if operating_mode != OperatingMode.ESCAPED_API_MODE and operating_mode != OperatingMode.API_MODE: - raise InvalidOperatingModeException(op_mode=operating_mode) + raise InvalidOperatingModeException(operating_mode.name + " is not supported.") XBeeAPIPacket._check_api_packet(raw, min_length=SendDataRequestPacket.__MIN_PACKET_LENGTH) if raw[3] != ApiFrameType.SEND_DATA_REQUEST.code: - raise InvalidPacketException(message="This packet is not a send data request packet.") + raise InvalidPacketException("This packet is not a send data request packet.") path_length = raw[5] content_type_length = raw[6 + path_length] @@ -728,7 +726,7 @@ def create_packet(raw, operating_mode): raw[6:6 + path_length].decode("utf8"), raw[6 + path_length + 1:6 + path_length + 1 + content_type_length].decode("utf8"), SendDataRequestOptions.get(raw[6 + path_length + 2 + content_type_length]), - file_data=raw[6 + path_length + 3 + content_type_length:-1]) + raw[6 + path_length + 3 + content_type_length:-1]) def needs_id(self): """ @@ -934,12 +932,12 @@ def create_packet(raw, operating_mode): | :meth:`.XBeeAPIPacket._check_api_packet` """ if operating_mode != OperatingMode.ESCAPED_API_MODE and operating_mode != OperatingMode.API_MODE: - raise InvalidOperatingModeException(op_mode=operating_mode) + raise InvalidOperatingModeException(operating_mode.name + " is not supported.") XBeeAPIPacket._check_api_packet(raw, min_length=SendDataResponsePacket.__MIN_PACKET_LENGTH) if raw[3] != ApiFrameType.SEND_DATA_RESPONSE.code: - raise InvalidPacketException(message="This packet is not a send data response packet.") + raise InvalidPacketException("This packet is not a send data response packet.") return SendDataResponsePacket(raw[4], DeviceCloudStatus.get(raw[5])) diff --git a/digi/xbee/packets/factory.py b/digi/xbee/packets/factory.py index e1df805..3f2ce6f 100644 --- a/digi/xbee/packets/factory.py +++ b/digi/xbee/packets/factory.py @@ -19,11 +19,10 @@ from digi.xbee.packets.network import * from digi.xbee.packets.raw import * from digi.xbee.packets.relay import * -from digi.xbee.packets.socket import * from digi.xbee.packets.wifi import * from digi.xbee.packets.aft import ApiFrameType from digi.xbee.models.mode import OperatingMode -from digi.xbee.packets.zigbee import RegisterJoiningDevicePacket, RegisterDeviceStatusPacket + """ This module provides functionality to build XBee packets from @@ -117,7 +116,7 @@ def build_frame(packet_bytearray, operating_mode=OperatingMode.API_MODE): frame_type = ApiFrameType.get(packet_bytearray[3]) if frame_type == ApiFrameType.GENERIC: - return GenericXBeePacket.create_packet(packet_bytearray, operating_mode=operating_mode) + return GenericXBeePacket.create_packet(packet_bytearray, operating_mode) elif frame_type == ApiFrameType.AT_COMMAND: return ATCommPacket.create_packet(packet_bytearray, operating_mode) @@ -215,59 +214,5 @@ def build_frame(packet_bytearray, operating_mode=OperatingMode.API_MODE): elif frame_type == ApiFrameType.FRAME_ERROR: return FrameErrorPacket.create_packet(packet_bytearray, operating_mode) - elif frame_type == ApiFrameType.REGISTER_JOINING_DEVICE: - return RegisterJoiningDevicePacket.create_packet(packet_bytearray, operating_mode) - - elif frame_type == ApiFrameType.REGISTER_JOINING_DEVICE_STATUS: - return RegisterDeviceStatusPacket.create_packet(packet_bytearray, operating_mode) - - elif frame_type == ApiFrameType.SOCKET_CREATE: - return SocketCreatePacket.create_packet(packet_bytearray, operating_mode) - - elif frame_type == ApiFrameType.SOCKET_CREATE_RESPONSE: - return SocketCreateResponsePacket.create_packet(packet_bytearray, operating_mode) - - elif frame_type == ApiFrameType.SOCKET_OPTION_REQUEST: - return SocketOptionRequestPacket.create_packet(packet_bytearray, operating_mode) - - elif frame_type == ApiFrameType.SOCKET_OPTION_RESPONSE: - return SocketOptionResponsePacket.create_packet(packet_bytearray, operating_mode) - - elif frame_type == ApiFrameType.SOCKET_CONNECT: - return SocketConnectPacket.create_packet(packet_bytearray, operating_mode) - - elif frame_type == ApiFrameType.SOCKET_CONNECT_RESPONSE: - return SocketConnectResponsePacket.create_packet(packet_bytearray, operating_mode) - - elif frame_type == ApiFrameType.SOCKET_CLOSE: - return SocketClosePacket.create_packet(packet_bytearray, operating_mode) - - elif frame_type == ApiFrameType.SOCKET_CLOSE_RESPONSE: - return SocketCloseResponsePacket.create_packet(packet_bytearray, operating_mode) - - elif frame_type == ApiFrameType.SOCKET_SEND: - return SocketSendPacket.create_packet(packet_bytearray, operating_mode) - - elif frame_type == ApiFrameType.SOCKET_SENDTO: - return SocketSendToPacket.create_packet(packet_bytearray, operating_mode) - - elif frame_type == ApiFrameType.SOCKET_BIND: - return SocketBindListenPacket.create_packet(packet_bytearray, operating_mode) - - elif frame_type == ApiFrameType.SOCKET_LISTEN_RESPONSE: - return SocketListenResponsePacket.create_packet(packet_bytearray, operating_mode) - - elif frame_type == ApiFrameType.SOCKET_NEW_IPV4_CLIENT: - return SocketNewIPv4ClientPacket.create_packet(packet_bytearray, operating_mode) - - elif frame_type == ApiFrameType.SOCKET_RECEIVE: - return SocketReceivePacket.create_packet(packet_bytearray, operating_mode) - - elif frame_type == ApiFrameType.SOCKET_RECEIVE_FROM: - return SocketReceiveFromPacket.create_packet(packet_bytearray, operating_mode) - - elif frame_type == ApiFrameType.SOCKET_STATE: - return SocketStatePacket.create_packet(packet_bytearray, operating_mode) - else: - return UnknownXBeePacket.create_packet(packet_bytearray, operating_mode=operating_mode) + return UnknownXBeePacket.create_packet(packet_bytearray, operating_mode) diff --git a/digi/xbee/packets/network.py b/digi/xbee/packets/network.py index fb3d148..0482739 100644 --- a/digi/xbee/packets/network.py +++ b/digi/xbee/packets/network.py @@ -1,4 +1,4 @@ -# Copyright 2017-2019, Digi International Inc. +# Copyright 2017, 2018, 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 @@ -87,16 +87,16 @@ def create_packet(raw, operating_mode): | :meth:`.XBeeAPIPacket._check_api_packet` """ if operating_mode != OperatingMode.ESCAPED_API_MODE and operating_mode != OperatingMode.API_MODE: - raise InvalidOperatingModeException(op_mode=operating_mode) + raise InvalidOperatingModeException(operating_mode.name + " is not supported.") XBeeAPIPacket._check_api_packet(raw, min_length=RXIPv4Packet.__MIN_PACKET_LENGTH) if raw[3] != ApiFrameType.RX_IPV4.code: - raise InvalidPacketException(message="This packet is not an RXIPv4Packet.") + raise InvalidPacketException("This packet is not an RXIPv4Packet.") return RXIPv4Packet(IPv4Address(bytes(raw[4:8])), utils.bytes_to_int(raw[8:10]), utils.bytes_to_int(raw[10:12]), IPProtocol.get(raw[12]), - data=raw[14:-1]) + raw[14:-1]) def needs_id(self): """ @@ -338,16 +338,16 @@ def create_packet(raw, operating_mode): | :meth:`.XBeeAPIPacket._check_api_packet` """ if operating_mode != OperatingMode.ESCAPED_API_MODE and operating_mode != OperatingMode.API_MODE: - raise InvalidOperatingModeException(op_mode =operating_mode) + raise InvalidOperatingModeException(operating_mode.name + " is not supported.") XBeeAPIPacket._check_api_packet(raw, min_length=TXIPv4Packet.__MIN_PACKET_LENGTH) if raw[3] != ApiFrameType.TX_IPV4.code: - raise InvalidPacketException(message="This packet is not an TXIPv4Packet.") + raise InvalidPacketException("This packet is not an TXIPv4Packet.") return TXIPv4Packet(raw[4], IPv4Address(bytes(raw[5:9])), utils.bytes_to_int(raw[9:11]), utils.bytes_to_int(raw[11:13]), IPProtocol.get(raw[13]), - raw[14], data=raw[15:-1]) + raw[14], raw[15:-1]) def needs_id(self): """ diff --git a/digi/xbee/packets/raw.py b/digi/xbee/packets/raw.py index 778e4ad..a454ca7 100644 --- a/digi/xbee/packets/raw.py +++ b/digi/xbee/packets/raw.py @@ -1,4 +1,4 @@ -# Copyright 2017-2019, Digi International Inc. +# Copyright 2017, 2018, 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 @@ -21,6 +21,7 @@ from digi.xbee.io import IOSample, IOLine from digi.xbee.util import utils +import copy class TX64Packet(XBeeAPIPacket): """ @@ -36,7 +37,7 @@ class TX64Packet(XBeeAPIPacket): __MIN_PACKET_LENGTH = 15 - def __init__(self, frame_id, x64bit_addr, transmit_options, rf_data=None): + def __init__(self, frame_id, x64bit_addr, transmit_options, rf_data): """ Class constructor. Instantiates a new :class:`.TX64Packet` object with the provided parameters. @@ -57,7 +58,7 @@ def __init__(self, frame_id, x64bit_addr, transmit_options, rf_data=None): if frame_id < 0 or frame_id > 255: raise ValueError("Frame id must be between 0 and 255.") - super().__init__(ApiFrameType.TX_64) + super(TX64Packet, self).__init__(ApiFrameType.TX_64) self._frame_id = frame_id self.__x64bit_addr = x64bit_addr self.__transmit_options = transmit_options @@ -86,14 +87,14 @@ def create_packet(raw, operating_mode): | :meth:`.XBeeAPIPacket._check_api_packet` """ if operating_mode != OperatingMode.ESCAPED_API_MODE and operating_mode != OperatingMode.API_MODE: - raise InvalidOperatingModeException(op_mode=operating_mode) + raise InvalidOperatingModeException(operating_mode.name + " is not supported.") XBeeAPIPacket._check_api_packet(raw, min_length=TX64Packet.__MIN_PACKET_LENGTH) if raw[3] != ApiFrameType.TX_64.code: - raise InvalidPacketException(message="This packet is not a TX 64 packet.") + raise InvalidPacketException("This packet is not a TX 64 packet.") - return TX64Packet(raw[4], XBee64BitAddress(raw[5:13]), raw[13], rf_data=raw[14:-1]) + return TX64Packet(raw[4], XBee64BitAddress(raw[5:13]), raw[13], raw[14:-1]) def needs_id(self): """ @@ -185,7 +186,7 @@ def __get_rf_data(self): """ if self.__rf_data is None: return None - return self.__rf_data.copy() + return copy.copy(self.__rf_data) def __set_rf_data(self, rf_data): """ @@ -197,7 +198,7 @@ def __set_rf_data(self, rf_data): if rf_data is None: self.__rf_data = None else: - self.__rf_data = rf_data.copy() + self.__rf_data = copy.copy(rf_data) x64bit_dest_addr = property(__get_64bit_addr, __set_64bit_addr) """XBee64BitAddress. 64-bit destination address.""" @@ -244,7 +245,7 @@ def __init__(self, frame_id, x16bit_addr, transmit_options, rf_data=None): if frame_id < 0 or frame_id > 255: raise ValueError("Frame id must be between 0 and 255.") - super().__init__(ApiFrameType.TX_16) + super(TX16Packet, self).__init__(ApiFrameType.TX_16) self._frame_id = frame_id self.__x16bit_addr = x16bit_addr self.__transmit_options = transmit_options @@ -273,14 +274,14 @@ def create_packet(raw, operating_mode): | :meth:`.XBeeAPIPacket._check_api_packet` """ if operating_mode != OperatingMode.ESCAPED_API_MODE and operating_mode != OperatingMode.API_MODE: - raise InvalidOperatingModeException(op_mode=operating_mode) + raise InvalidOperatingModeException(operating_mode.name + " is not supported.") XBeeAPIPacket._check_api_packet(raw, min_length=TX16Packet.__MIN_PACKET_LENGTH) if raw[3] != ApiFrameType.TX_16.code: - raise InvalidPacketException(message="This packet is not a TX 16 packet.") + raise InvalidPacketException("This packet is not a TX 16 packet.") - return TX16Packet(raw[4], XBee16BitAddress(raw[5:7]), raw[7], rf_data=raw[8:-1]) + return TX16Packet(raw[4], XBee16BitAddress(raw[5:7]), raw[7], raw[8:-1]) def needs_id(self): """ @@ -372,7 +373,7 @@ def __get_rf_data(self): """ if self.__rf_data is None: return None - return self.__rf_data.copy() + return copy.copy(self.__rf_data) def __set_rf_data(self, rf_data): """ @@ -384,7 +385,7 @@ def __set_rf_data(self, rf_data): if rf_data is None: self.__rf_data = None else: - self.__rf_data = rf_data.copy() + self.__rf_data = copy.copy(rf_data) x16bit_dest_addr = property(__get_16bit_addr, __set_16bit_addr) """XBee64BitAddress. 16-bit destination address.""" @@ -431,7 +432,7 @@ def __init__(self, frame_id, transmit_status): if frame_id < 0 or frame_id > 255: raise ValueError("Frame id must be between 0 and 255.") - super().__init__(ApiFrameType.TX_STATUS) + super(TXStatusPacket, self).__init__(ApiFrameType.TX_STATUS) self._frame_id = frame_id self.__transmit_status = transmit_status @@ -458,12 +459,12 @@ def create_packet(raw, operating_mode): | :meth:`.XBeeAPIPacket._check_api_packet` """ if operating_mode != OperatingMode.ESCAPED_API_MODE and operating_mode != OperatingMode.API_MODE: - raise InvalidOperatingModeException(op_mode=operating_mode) + raise InvalidOperatingModeException(operating_mode.name + " is not supported.") XBeeAPIPacket._check_api_packet(raw, min_length=TXStatusPacket.__MIN_PACKET_LENGTH) if raw[3] != ApiFrameType.TX_STATUS.code: - raise InvalidPacketException(message="This packet is not a TX status packet.") + raise InvalidPacketException("This packet is not a TX status packet.") return TXStatusPacket(raw[4], TransmitStatus.get(raw[5])) @@ -483,7 +484,7 @@ def _get_api_packet_spec_data(self): .. seealso:: | :meth:`.XBeeAPIPacket._get_api_packet_spec_data` """ - return utils.int_to_bytes(self.__transmit_status.code, num_bytes=1) + return utils.int_to_bytes(self.__transmit_status.code, 1) def _get_api_packet_spec_data_dict(self): """ @@ -556,7 +557,7 @@ def __init__(self, x64bit_addr, rssi, receive_options, rf_data=None): | :class:`.XBeeAPIPacket` """ - super().__init__(ApiFrameType.RX_64) + super(RX64Packet, self).__init__(ApiFrameType.RX_64) self.__x64bit_addr = x64bit_addr self.__rssi = rssi @@ -586,14 +587,14 @@ def create_packet(raw, operating_mode): | :meth:`.XBeeAPIPacket._check_api_packet` """ if operating_mode != OperatingMode.ESCAPED_API_MODE and operating_mode != OperatingMode.API_MODE: - raise InvalidOperatingModeException(op_mode=operating_mode) + raise InvalidOperatingModeException(operating_mode.name + " is not supported.") XBeeAPIPacket._check_api_packet(raw, min_length=RX64Packet.__MIN_PACKET_LENGTH) if raw[3] != ApiFrameType.RX_64.code: - raise InvalidPacketException(message="This packet is not an RX 64 packet.") + raise InvalidPacketException("This packet is not an RX 64 packet.") - return RX64Packet(XBee64BitAddress(raw[4:12]), raw[12], raw[13], rf_data=raw[14:-1]) + return RX64Packet(XBee64BitAddress(raw[4:12]), raw[12], raw[13], raw[14:-1]) def needs_id(self): """ @@ -604,16 +605,6 @@ def needs_id(self): """ return False - def is_broadcast(self): - """ - Override method. - - .. seealso:: - | :meth:`XBeeAPIPacket.is_broadcast` - """ - return (utils.is_bit_enabled(self.__receive_options, 1) - or utils.is_bit_enabled(self.__receive_options, 2)) - def _get_api_packet_spec_data(self): """ Override method. @@ -715,7 +706,7 @@ def __get_rf_data(self): """ if self.__rf_data is None: return None - return self.__rf_data.copy() + return copy.copy(self.__rf_data) def __set_rf_data(self, rf_data): """ @@ -727,7 +718,7 @@ def __set_rf_data(self, rf_data): if rf_data is None: self.__rf_data = None else: - self.__rf_data = rf_data.copy() + self.__rf_data = copy.copy(rf_data) x64bit_source_addr = property(__get_64bit_addr, __set_64bit_addr) """:class:`.XBee64BitAddress`. 64-bit source address.""" @@ -776,7 +767,7 @@ def __init__(self, x16bit_addr, rssi, receive_options, rf_data=None): | :class:`.XBeeAPIPacket` """ - super().__init__(ApiFrameType.RX_16) + super(RX16Packet, self).__init__(ApiFrameType.RX_16) self.__x16bit_addr = x16bit_addr self.__rssi = rssi @@ -806,14 +797,14 @@ def create_packet(raw, operating_mode): | :meth:`.XBeeAPIPacket._check_api_packet` """ if operating_mode != OperatingMode.ESCAPED_API_MODE and operating_mode != OperatingMode.API_MODE: - raise InvalidOperatingModeException(op_mode=operating_mode) + raise InvalidOperatingModeException(operating_mode.name + " is not supported.") XBeeAPIPacket._check_api_packet(raw, min_length=RX16Packet.__MIN_PACKET_LENGTH) if raw[3] != ApiFrameType.RX_16.code: - raise InvalidPacketException(message="This packet is not an RX 16 Packet") + raise InvalidPacketException("This packet is not an RX 16 Packet") - return RX16Packet(XBee16BitAddress(raw[4:6]), raw[6], raw[7], rf_data=raw[8:-1]) + return RX16Packet(XBee16BitAddress(raw[4:6]), raw[6], raw[7], raw[8:-1]) def needs_id(self): """ @@ -824,16 +815,6 @@ def needs_id(self): """ return False - def is_broadcast(self): - """ - Override method. - - .. seealso:: - | :meth:`XBeeAPIPacket.is_broadcast` - """ - return (utils.is_bit_enabled(self.__receive_options, 1) - or utils.is_bit_enabled(self.__receive_options, 2)) - def _get_api_packet_spec_data(self): """ Override method. @@ -936,7 +917,7 @@ def __get_rf_data(self): """ if self.__rf_data is None: return None - return self.__rf_data.copy() + return copy.copy(self.__rf_data) def __set_rf_data(self, rf_data): """ @@ -948,7 +929,7 @@ def __set_rf_data(self, rf_data): if rf_data is None: self.__rf_data = None else: - self.__rf_data = rf_data.copy() + self.__rf_data = copy.copy(rf_data) x16bit_source_addr = property(__get_16bit_addr, __set_16bit_addr) """:class:`.XBee16BitAddress`. 16-bit source address.""" @@ -991,7 +972,7 @@ def __init__(self, x64bit_addr, rssi, receive_options, rf_data): | :class:`.XBee64BitAddress` | :class:`.XBeeAPIPacket` """ - super().__init__(ApiFrameType.RX_IO_64) + super(RX64IOPacket, self).__init__(ApiFrameType.RX_IO_64) self.__x64bit_addr = x64bit_addr self.__rssi = rssi self.__receive_options = receive_options @@ -1021,12 +1002,12 @@ def create_packet(raw, operating_mode): | :meth:`.XBeeAPIPacket._check_api_packet` """ if operating_mode != OperatingMode.ESCAPED_API_MODE and operating_mode != OperatingMode.API_MODE: - raise InvalidOperatingModeException(op_mode=operating_mode) + raise InvalidOperatingModeException(operating_mode.name + " is not supported.") XBeeAPIPacket._check_api_packet(raw, min_length=RX64IOPacket.__MIN_PACKET_LENGTH) if raw[3] != ApiFrameType.RX_IO_64.code: - raise InvalidPacketException(message="This packet is not an RX 64 IO packet.") + raise InvalidPacketException("This packet is not an RX 64 IO packet.") return RX64IOPacket(XBee64BitAddress(raw[4:12]), raw[12], raw[13], raw[14:-1]) @@ -1039,16 +1020,6 @@ def needs_id(self): """ return False - def is_broadcast(self): - """ - Override method. - - .. seealso:: - | :meth:`XBeeAPIPacket.is_broadcast` - """ - return (utils.is_bit_enabled(self.__receive_options, 1) - or utils.is_bit_enabled(self.__receive_options, 2)) - def _get_api_packet_spec_data(self): """ Override method. @@ -1175,7 +1146,7 @@ def __get_rf_data(self): """ if self.__rf_data is None: return None - return self.__rf_data.copy() + return copy.copy(self.__rf_data) def __set_rf_data(self, rf_data): """ @@ -1187,7 +1158,7 @@ def __set_rf_data(self, rf_data): if rf_data is None: self.__rf_data = None else: - self.__rf_data = rf_data.copy() + self.__rf_data = copy.copy(rf_data) # Modify the ioSample accordingly if rf_data is not None and len(rf_data) >= 5: @@ -1264,7 +1235,7 @@ def __init__(self, x16bit_addr, rssi, receive_options, rf_data): | :class:`.XBee16BitAddress` | :class:`.XBeeAPIPacket` """ - super().__init__(ApiFrameType.RX_IO_16) + super(RX16IOPacket, self).__init__(ApiFrameType.RX_IO_16) self.__x16bit_addr = x16bit_addr self.__rssi = rssi self.__options = receive_options @@ -1294,12 +1265,12 @@ def create_packet(raw, operating_mode): | :meth:`.XBeeAPIPacket._check_api_packet` """ if operating_mode != OperatingMode.ESCAPED_API_MODE and operating_mode != OperatingMode.API_MODE: - raise InvalidOperatingModeException(op_mode=operating_mode) + raise InvalidOperatingModeException(operating_mode.name + " is not supported.") XBeeAPIPacket._check_api_packet(raw, min_length=RX16IOPacket.__MIN_PACKET_LENGTH) if raw[3] != ApiFrameType.RX_IO_16.code: - raise InvalidPacketException(message="This packet is not an RX 16 IO packet.") + raise InvalidPacketException("This packet is not an RX 16 IO packet.") return RX16IOPacket(XBee16BitAddress(raw[4:6]), raw[6], raw[7], raw[8:-1]) @@ -1312,16 +1283,6 @@ def needs_id(self): """ return False - def is_broadcast(self): - """ - Override method. - - .. seealso:: - | :meth:`XBeeAPIPacket.is_broadcast` - """ - return (utils.is_bit_enabled(self.__receive_options, 1) - or utils.is_bit_enabled(self.__receive_options, 2)) - def _get_api_packet_spec_data(self): """ Override method. @@ -1449,7 +1410,7 @@ def __get_rf_data(self): """ if self.__rf_data is None: return None - return self.__rf_data.copy() + return copy.copy(self.__rf_data) def __set_rf_data(self, rf_data): """ @@ -1461,7 +1422,7 @@ def __set_rf_data(self, rf_data): if rf_data is None: self.__rf_data = None else: - self.__rf_data = rf_data.copy() + self.__rf_data = copy.copy(rf_data) # Modify the ioSample accordingly if rf_data is not None and len(rf_data) >= 5: diff --git a/digi/xbee/packets/relay.py b/digi/xbee/packets/relay.py index 9fe865e..f681c33 100644 --- a/digi/xbee/packets/relay.py +++ b/digi/xbee/packets/relay.py @@ -88,14 +88,14 @@ def create_packet(raw, operating_mode): | :meth:`.XBeeAPIPacket._check_api_packet` """ if operating_mode != OperatingMode.ESCAPED_API_MODE and operating_mode != OperatingMode.API_MODE: - raise InvalidOperatingModeException(op_mode=operating_mode) + raise InvalidOperatingModeException(operating_mode.name + " is not supported.") XBeeAPIPacket._check_api_packet(raw, min_length=UserDataRelayPacket.__MIN_PACKET_LENGTH) if raw[3] != ApiFrameType.USER_DATA_RELAY_REQUEST.code: - raise InvalidPacketException(message="This packet is not a user data relay packet.") + raise InvalidPacketException("This packet is not a user data relay packet.") - return UserDataRelayPacket(raw[4], XBeeLocalInterface.get([5]), data=raw[6:-1]) + return UserDataRelayPacket(raw[4], XBeeLocalInterface.get([5]), raw[6:-1]) def needs_id(self): """ @@ -248,14 +248,14 @@ def create_packet(raw, operating_mode): | :meth:`.XBeeAPIPacket._check_api_packet` """ if operating_mode != OperatingMode.ESCAPED_API_MODE and operating_mode != OperatingMode.API_MODE: - raise InvalidOperatingModeException(op_mode=operating_mode) + raise InvalidOperatingModeException(operating_mode.name + " is not supported.") XBeeAPIPacket._check_api_packet(raw, min_length=UserDataRelayOutputPacket.__MIN_PACKET_LENGTH) if raw[3] != ApiFrameType.USER_DATA_RELAY_OUTPUT.code: - raise InvalidPacketException(message="This packet is not a user data relay output packet.") + raise InvalidPacketException("This packet is not a user data relay output packet.") - return UserDataRelayOutputPacket(XBeeLocalInterface.get(raw[4]), data=raw[5:-1]) + return UserDataRelayOutputPacket(XBeeLocalInterface.get(raw[4]), raw[5:-1]) def needs_id(self): """ diff --git a/digi/xbee/packets/socket.py b/digi/xbee/packets/socket.py deleted file mode 100644 index 1a5870e..0000000 --- a/digi/xbee/packets/socket.py +++ /dev/null @@ -1,2933 +0,0 @@ -# Copyright 2019, 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 ipaddress import IPv4Address - -from digi.xbee.exception import InvalidOperatingModeException, InvalidPacketException -from digi.xbee.models.mode import OperatingMode -from digi.xbee.models.options import SocketOption -from digi.xbee.models.protocol import IPProtocol -from digi.xbee.models.status import SocketStatus, SocketState -from digi.xbee.packets.aft import ApiFrameType -from digi.xbee.packets.base import XBeeAPIPacket, DictKeys -from digi.xbee.util import utils - - -class SocketCreatePacket(XBeeAPIPacket): - """ - This class represents a Socket Create packet. Packet is built using the - parameters of the constructor. - - Use this frame to create a new socket with the following protocols: TCP, - UDP, or TLS. - - .. seealso:: - | :class:`.SocketCreateResponsePacket` - | :class:`.XBeeAPIPacket` - """ - - __MIN_PACKET_LENGTH = 7 - - def __init__(self, frame_id, protocol): - """ - Class constructor. Instantiates a new :class:`.SocketCreatePacket` object with the provided parameters. - - Args: - frame_id (Integer): the frame ID of the packet. - protocol (:class:`.IPProtocol`): the protocol used to create the socket. - - .. seealso:: - | :class:`.XBeeAPIPacket` - | :class:`.IPProtocol` - - Raises: - ValueError: if ``frame_id`` is less than 0 or greater than 255. - """ - if frame_id < 0 or frame_id > 255: - raise ValueError("Frame ID must be between 0 and 255") - - super().__init__(ApiFrameType.SOCKET_CREATE) - self._frame_id = frame_id - self.__protocol = protocol - - @staticmethod - def create_packet(raw, operating_mode): - """ - Override method. - - Returns: - :class:`.SocketCreatePacket`. - - Raises: - InvalidPacketException: if the bytearray length is less than 7. (start delim. + length (2 bytes) + frame - type + frame id + protocol + checksum = 7 bytes). - InvalidPacketException: if the length field of 'raw' is different than its real length. (length field: bytes - 2 and 3) - InvalidPacketException: if the first byte of 'raw' is not the header byte. See :class:`.SpecialByte`. - InvalidPacketException: if the calculated checksum is different than the checksum field value (last byte). - InvalidPacketException: if the frame type is not :attr:`.ApiFrameType.SOCKET_CREATE`. - InvalidOperatingModeException: if ``operating_mode`` is not supported. - - .. seealso:: - | :meth:`.XBeePacket.create_packet` - | :meth:`.XBeeAPIPacket._check_api_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=SocketCreatePacket.__MIN_PACKET_LENGTH) - - if raw[3] != ApiFrameType.SOCKET_CREATE.code: - raise InvalidPacketException(message="This packet is not a Socket Create packet.") - - return SocketCreatePacket(raw[4], IPProtocol.get(raw[5])) - - def needs_id(self): - """ - Override method. - - .. seealso:: - | :meth:`.XBeeAPIPacket.needs_id` - """ - return True - - def _get_api_packet_spec_data(self): - """ - Override method. - - .. seealso:: - | :meth:`.XBeeAPIPacket._get_api_packet_spec_data` - """ - return bytearray([self.__protocol.code]) - - def _get_api_packet_spec_data_dict(self): - """ - Override method. - - .. seealso:: - | :meth:`.XBeeAPIPacket._get_api_packet_spec_data_dict` - """ - return {DictKeys.IP_PROTOCOL.value: "%s (%s)" % (self.__protocol.code, self.__protocol.description)} - - def __get_protocol(self): - """ - Returns the communication protocol. - - Returns: - :class:`.IPProtocol`: the communication protocol. - - .. seealso:: - | :class:`.IPProtocol` - """ - return self.__protocol - - def __set_protocol(self, protocol): - """ - Sets the communication protocol. - - Args: - protocol (:class:`.IPProtocol`): the new communication protocol. - - .. seealso:: - | :class:`.IPProtocol` - """ - self.__protocol = protocol - - protocol = property(__get_protocol, __set_protocol) - """:class:`.IPProtocol`. Communication protocol.""" - - -class SocketCreateResponsePacket(XBeeAPIPacket): - """ - This class represents a Socket Create Response packet. Packet is built using - the parameters of the constructor. - - The device sends this frame in response to a Socket Create (0x40) frame. It - contains a socket ID that should be used for future transactions with the - socket and a status field. - - If the status field is non-zero, which indicates an error, the socket ID - will be set to 0xFF and the socket will not be opened. - - .. seealso:: - | :class:`.SocketCreatePacket` - | :class:`.XBeeAPIPacket` - """ - - __MIN_PACKET_LENGTH = 8 - - def __init__(self, frame_id, socket_id, status): - """ - Class constructor. Instantiates a new :class:`.SocketCreateResponsePacket` object with the provided parameters. - - Args: - frame_id (Integer): the frame ID of the packet. - socket_id (Integer): the unique socket ID to address the socket. - status (:class:`.SocketStatus`): the socket create status. - - .. seealso:: - | :class:`.XBeeAPIPacket` - | :class:`.SocketStatus` - - Raises: - ValueError: if ``frame_id`` is less than 0 or greater than 255. - ValueError: if ``socket_id`` is less than 0 or greater than 255. - """ - if frame_id < 0 or frame_id > 255: - raise ValueError("Frame ID must be between 0 and 255") - if socket_id < 0 or socket_id > 255: - raise ValueError("Socket ID must be between 0 and 255") - - super().__init__(ApiFrameType.SOCKET_CREATE_RESPONSE) - self._frame_id = frame_id - self.__socket_id = socket_id - self.__status = status - - @staticmethod - def create_packet(raw, operating_mode): - """ - Override method. - - Returns: - :class:`.SocketCreateResponsePacket`. - - Raises: - InvalidPacketException: if the bytearray length is less than 8. (start delim. + length (2 bytes) + frame - type + frame id + socket id + status + checksum = 8 bytes). - InvalidPacketException: if the length field of 'raw' is different than its real length. (length field: bytes - 2 and 3) - InvalidPacketException: if the first byte of 'raw' is not the header byte. See :class:`.SpecialByte`. - InvalidPacketException: if the calculated checksum is different than the checksum field value (last byte). - InvalidPacketException: if the frame type is not :attr:`.ApiFrameType.SOCKET_CREATE_RESPONSE`. - InvalidOperatingModeException: if ``operating_mode`` is not supported. - - .. seealso:: - | :meth:`.XBeePacket.create_packet` - | :meth:`.XBeeAPIPacket._check_api_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=SocketCreateResponsePacket.__MIN_PACKET_LENGTH) - - if raw[3] != ApiFrameType.SOCKET_CREATE_RESPONSE.code: - raise InvalidPacketException(message="This packet is not a Socket Create Response packet.") - - return SocketCreateResponsePacket(raw[4], raw[5], SocketStatus.get(raw[6])) - - def needs_id(self): - """ - Override method. - - .. seealso:: - | :meth:`.XBeeAPIPacket.needs_id` - """ - return True - - def _get_api_packet_spec_data(self): - """ - Override method. - - .. seealso:: - | :meth:`.XBeeAPIPacket._get_api_packet_spec_data` - """ - ret = bytearray() - ret.append(self.__socket_id) - ret.append(self.__status.code) - return ret - - def _get_api_packet_spec_data_dict(self): - """ - Override method. - - .. seealso:: - | :meth:`.XBeeAPIPacket._get_api_packet_spec_data_dict` - """ - return {DictKeys.SOCKET_ID.value: utils.hex_to_string(bytearray([self.__socket_id])), - DictKeys.STATUS.value: "%s (%s)" % (self.__status.code, self.__status.description)} - - def __get_socket_id(self): - """ - Returns the the socket ID. - - Returns: - Integer: the socket ID. - """ - return self.__socket_id - - def __set_socket_id(self, socket_id): - """ - Sets the socket ID. - - Args: - socket_id (Integer): the new socket ID. - - Raises: - ValueError: if ``socket_id`` is less than 0 or greater than 255. - """ - if socket_id < 0 or socket_id > 255: - raise ValueError("Socket ID must be between 0 and 255") - self.__socket_id = socket_id - - def __get_status(self): - """ - Returns the socket create status. - - Returns: - :class:`.SocketStatus`: the status. - - .. seealso:: - | :class:`.SocketStatus` - """ - return self.__status - - def __set_status(self, status): - """ - Sets the socket create status. - - Args: - status (:class:`.SocketStatus`): the new status. - - .. seealso:: - | :class:`.SocketStatus` - """ - self.__status = status - - socket_id = property(__get_socket_id, __set_socket_id) - """Integer. Socket ID.""" - - status = property(__get_status, __set_status) - """:class:`.SocketStatus`. Socket create status.""" - - -class SocketOptionRequestPacket(XBeeAPIPacket): - """ - This class represents a Socket Option Request packet. Packet is built using - the parameters of the constructor. - - Use this frame to modify the behavior of sockets to be different from the - normal default behavior. - - If the Option Data field is zero-length, the Socket Option Response Packet - (0xC1) reports the current effective value. - - .. seealso:: - | :class:`.SocketOptionResponsePacket` - | :class:`.XBeeAPIPacket` - """ - - __MIN_PACKET_LENGTH = 8 - - def __init__(self, frame_id, socket_id, option, option_data=None): - """ - Class constructor. Instantiates a new :class:`.SocketOptionRequestPacket` object with the provided parameters. - - Args: - frame_id (Integer): the frame ID of the packet. - socket_id (Integer): the socket ID to modify. - option (:class:`.SocketOption`): the socket option of the parameter to change. - option_data (Bytearray, optional): the option data. Optional. - - .. seealso:: - | :class:`.XBeeAPIPacket` - | :class:`.SocketOption` - - Raises: - ValueError: if ``frame_id`` is less than 0 or greater than 255. - ValueError: if ``socket_id`` is less than 0 or greater than 255. - """ - if frame_id < 0 or frame_id > 255: - raise ValueError("Frame ID must be between 0 and 255") - if socket_id < 0 or socket_id > 255: - raise ValueError("Socket ID must be between 0 and 255") - - super().__init__(ApiFrameType.SOCKET_OPTION_REQUEST) - self._frame_id = frame_id - self.__socket_id = socket_id - self.__option = option - self.__option_data = option_data - - @staticmethod - def create_packet(raw, operating_mode): - """ - Override method. - - Returns: - :class:`.SocketOptionRequestPacket`. - - Raises: - InvalidPacketException: if the bytearray length is less than 8. (start delim. + length (2 bytes) + frame - type + frame id + socket id + option + checksum = 8 bytes). - InvalidPacketException: if the length field of 'raw' is different than its real length. (length field: bytes - 2 and 3) - InvalidPacketException: if the first byte of 'raw' is not the header byte. See :class:`.SpecialByte`. - InvalidPacketException: if the calculated checksum is different than the checksum field value (last byte). - InvalidPacketException: if the frame type is not :attr:`.ApiFrameType.SOCKET_OPTION_REQUEST`. - InvalidOperatingModeException: if ``operating_mode`` is not supported. - - .. seealso:: - | :meth:`.XBeePacket.create_packet` - | :meth:`.XBeeAPIPacket._check_api_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=SocketOptionRequestPacket.__MIN_PACKET_LENGTH) - - if raw[3] != ApiFrameType.SOCKET_OPTION_REQUEST.code: - raise InvalidPacketException(message="This packet is not a Socket Option Request packet.") - - return SocketOptionRequestPacket(raw[4], raw[5], SocketOption.get(raw[6]), raw[7:-1]) - - def needs_id(self): - """ - Override method. - - .. seealso:: - | :meth:`.XBeeAPIPacket.needs_id` - """ - return True - - def _get_api_packet_spec_data(self): - """ - Override method. - - .. seealso:: - | :meth:`.XBeeAPIPacket._get_api_packet_spec_data` - """ - ret = bytearray() - ret.append(self.__socket_id) - ret.append(self.__option.code) - if self.__option_data is not None: - ret += self.__option_data - return ret - - def _get_api_packet_spec_data_dict(self): - """ - Override method. - - .. seealso:: - | :meth:`.XBeeAPIPacket._get_api_packet_spec_data_dict` - """ - return {DictKeys.SOCKET_ID.value: utils.hex_to_string(bytearray([self.__socket_id])), - DictKeys.OPTION_ID.value: "%s (%s)" % (self.__option.code, self.__option.description), - DictKeys.OPTION_DATA.value: utils.hex_to_string(self.__option_data, - True) if self.__option_data is not None - else None} - - def __get_socket_id(self): - """ - Returns the the socket ID. - - Returns: - Integer: the socket ID. - """ - return self.__socket_id - - def __set_socket_id(self, socket_id): - """ - Sets the socket ID. - - Args: - socket_id (Integer): the new socket ID. - - Raises: - ValueError: if ``socket_id`` is less than 0 or greater than 255. - """ - if socket_id < 0 or socket_id > 255: - raise ValueError("Socket ID must be between 0 and 255") - self.__socket_id = socket_id - - def __get_option(self): - """ - Returns the socket option. - - Returns: - :class:`.SocketOption`: the socket option. - - .. seealso:: - | :class:`.SocketOption` - """ - return self.__option - - def __set_option(self, option): - """ - Sets the socket option. - - Args: - option (:class:`.SocketOption`): the new socket option. - - .. seealso:: - | :class:`.SocketOption` - """ - self.__option = option - - def __get_option_data(self): - """ - Returns the socket option data. - - Returns: - Bytearray: the socket option data. - """ - return self.__option_data if self.__option_data is None else self.__option_data.copy() - - def __set_option_data(self, option_data): - """ - Sets the socket option data. - - Args: - option_data (Bytearray): the new socket option data. - """ - self.__option_data = None if option_data is None else option_data.copy() - - socket_id = property(__get_socket_id, __set_socket_id) - """Integer. Socket ID.""" - - option = property(__get_option, __set_option) - """:class:`.SocketOption`. Socket option.""" - - option_data = property(__get_option_data, __set_option_data) - """Bytearray. Socket option data.""" - - -class SocketOptionResponsePacket(XBeeAPIPacket): - """ - This class represents a Socket Option Response packet. Packet is built using - the parameters of the constructor. - - Reports the status of requests made with the Socket Option Request (0x41) - packet. - - .. seealso:: - | :class:`.SocketOptionRequestPacket` - | :class:`.XBeeAPIPacket` - """ - - __MIN_PACKET_LENGTH = 9 - - def __init__(self, frame_id, socket_id, option, status, option_data=None): - """ - Class constructor. Instantiates a new :class:`.SocketOptionResponsePacket` object with the provided parameters. - - Args: - frame_id (Integer): the frame ID of the packet. - socket_id (Integer): the socket ID for which modification was requested. - option (:class:`.SocketOption`): the socket option of the parameter requested. - status (:class:`.SocketStatus`): the socket option status of the parameter requested. - option_data (Bytearray, optional): the option data. Optional. - - .. seealso:: - | :class:`.XBeeAPIPacket` - | :class:`.SocketOption` - | :class:`.SocketStatus` - - Raises: - ValueError: if ``frame_id`` is less than 0 or greater than 255. - ValueError: if ``socket_id`` is less than 0 or greater than 255. - """ - if frame_id < 0 or frame_id > 255: - raise ValueError("Frame ID must be between 0 and 255") - if socket_id < 0 or socket_id > 255: - raise ValueError("Socket ID must be between 0 and 255") - - super().__init__(ApiFrameType.SOCKET_OPTION_RESPONSE) - self._frame_id = frame_id - self.__socket_id = socket_id - self.__option = option - self.__status = status - self.__option_data = option_data - - @staticmethod - def create_packet(raw, operating_mode): - """ - Override method. - - Returns: - :class:`.SocketOptionResponsePacket`. - - Raises: - InvalidPacketException: if the bytearray length is less than 9. (start delim. + length (2 bytes) + frame - type + frame id + socket id + option + status + checksum = 9 bytes). - InvalidPacketException: if the length field of 'raw' is different than its real length. (length field: bytes - 2 and 3) - InvalidPacketException: if the first byte of 'raw' is not the header byte. See :class:`.SpecialByte`. - InvalidPacketException: if the calculated checksum is different than the checksum field value (last byte). - InvalidPacketException: if the frame type is not :attr:`.ApiFrameType.SOCKET_OPTION_RESPONSE`. - InvalidOperatingModeException: if ``operating_mode`` is not supported. - - .. seealso:: - | :meth:`.XBeePacket.create_packet` - | :meth:`.XBeeAPIPacket._check_api_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=SocketOptionResponsePacket.__MIN_PACKET_LENGTH) - - if raw[3] != ApiFrameType.SOCKET_OPTION_RESPONSE.code: - raise InvalidPacketException(message="This packet is not a Socket Option Response packet.") - - return SocketOptionResponsePacket(raw[4], raw[5], SocketOption.get(raw[6]), SocketStatus.get(raw[7]), - raw[8:-1]) - - def needs_id(self): - """ - Override method. - - .. seealso:: - | :meth:`.XBeeAPIPacket.needs_id` - """ - return True - - def _get_api_packet_spec_data(self): - """ - Override method. - - .. seealso:: - | :meth:`.XBeeAPIPacket._get_api_packet_spec_data` - """ - ret = bytearray() - ret.append(self.__socket_id) - ret.append(self.__option.code) - ret.append(self.__status.code) - if self.__option_data is not None: - ret += self.__option_data - return ret - - def _get_api_packet_spec_data_dict(self): - """ - Override method. - - .. seealso:: - | :meth:`.XBeeAPIPacket._get_api_packet_spec_data_dict` - """ - return {DictKeys.SOCKET_ID.value: utils.hex_to_string(bytearray([self.__socket_id])), - DictKeys.OPTION_ID.value: "%s (%s)" % (self.__option.code, self.__option.description), - DictKeys.STATUS.value: "%s (%s)" % (self.__status.code, self.__status.description), - DictKeys.OPTION_DATA.value: utils.hex_to_string(self.__option_data, - True) if self.__option_data is not None else None} - - def __get_socket_id(self): - """ - Returns the the socket ID. - - Returns: - Integer: the socket ID. - """ - return self.__socket_id - - def __set_socket_id(self, socket_id): - """ - Sets the socket ID. - - Args: - socket_id (Integer): the new socket ID. - - Raises: - ValueError: if ``socket_id`` is less than 0 or greater than 255. - """ - if socket_id < 0 or socket_id > 255: - raise ValueError("Socket ID must be between 0 and 255") - self.__socket_id = socket_id - - def __get_option(self): - """ - Returns the socket option. - - Returns: - :class:`.SocketOption`: the socket option. - - .. seealso:: - | :class:`.SocketOption` - """ - return self.__option - - def __set_option(self, option): - """ - Sets the socket option. - - Args: - option (:class:`.SocketOption`): the new socket option. - - .. seealso:: - | :class:`.SocketOption` - """ - self.__option = option - - def __get_status(self): - """ - Returns the socket option status. - - Returns: - :class:`.SocketStatus`: the socket option status. - - .. seealso:: - | :class:`.SocketStatus` - """ - return self.__status - - def __set_status(self, status): - """ - Sets the socket option status. - - Args: - status (:class:`.SocketStatus`): the new socket option status. - - .. seealso:: - | :class:`.SocketStatus` - """ - self.__status = status - - def __get_option_data(self): - """ - Returns the socket option data. - - Returns: - Bytearray: the socket option data. - """ - return self.__option_data if self.__option_data is None else self.__option_data.copy() - - def __set_option_data(self, option_data): - """ - Sets the socket option data. - - Args: - option_data (Bytearray): the new socket option data. - """ - self.__option_data = None if option_data is None else option_data.copy() - - socket_id = property(__get_socket_id, __set_socket_id) - """Integer. Socket ID.""" - - option = property(__get_option, __set_option) - """:class:`.SocketOption`. Socket option.""" - - status = property(__get_status, __set_status) - """:class:`SocketStatus`. Socket option status""" - - option_data = property(__get_option_data, __set_option_data) - """Bytearray. Socket option data.""" - - -class SocketConnectPacket(XBeeAPIPacket): - """ - This class represents a Socket Connect packet. Packet is built using the - parameters of the constructor. - - Use this frame to create a socket connect message that causes the device to - connect a socket to the given address and port. - - For a UDP socket, this filters out any received responses that are not from - the specified remote address and port. - - Two frames occur in response: - - * Socket Connect Response frame (:class:`SocketConnectResponsePacket`): - Arrives immediately and confirms the request. - * Socket Status frame (:class:`SocketStatePacket`): Indicates if the - connection was successful. - - .. seealso:: - | :class:`.SocketConnectResponsePacket` - | :class:`.SocketStatePacket` - | :class:`.XBeeAPIPacket` - """ - - __MIN_PACKET_LENGTH = 11 - - DEST_ADDRESS_BINARY = 0 - """Indicates the destination address field is a binary IPv4 address in network byte order.""" - - DEST_ADDRESS_STRING = 1 - """Indicates the destination address field is a string containing either a dotted quad value or a domain name to be - resolved.""" - - def __init__(self, frame_id, socket_id, dest_port, dest_address_type, dest_address): - """ - Class constructor. Instantiates a new :class:`.SocketConnectPacket` object with the provided parameters. - - Args: - frame_id (Integer): the frame ID of the packet. - socket_id (Integer): the ID of the socket to connect. - dest_port (Integer): the destination port number. - dest_address_type (Integer): the destination address type. One of - :attr:`SocketConnectPacket.DEST_ADDRESS_BINARY` or - :attr:`SocketConnectPacket.DEST_ADDRESS_STRING`. - dest_address (Bytearray or String): the destination address. - - .. seealso:: - | :attr:`SocketConnectPacket.DEST_ADDRESS_BINARY` - | :attr:`SocketConnectPacket.DEST_ADDRESS_STRING` - | :class:`.XBeeAPIPacket` - - Raises: - ValueError: if ``frame_id`` is less than 0 or greater than 255. - ValueError: if ``socket_id`` is less than 0 or greater than 255. - ValueError: if ``dest_port`` is less than 0 or greater than 65535. - ValueError: if ``dest_address_type`` is different than :attr:`SocketConnectPacket.DEST_ADDRESS_BINARY` and - :attr:`SocketConnectPacket.DEST_ADDRESS_STRING`. - ValueError: if ``dest_address`` is ``None`` or does not follow the format specified in the configured type. - """ - if frame_id < 0 or frame_id > 255: - raise ValueError("Frame ID must be between 0 and 255") - if socket_id < 0 or socket_id > 255: - raise ValueError("Socket ID must be between 0 and 255") - if dest_port < 0 or dest_port > 65535: - raise ValueError("Destination port must be between 0 and 65535") - if dest_address_type not in (SocketConnectPacket.DEST_ADDRESS_BINARY, SocketConnectPacket.DEST_ADDRESS_STRING): - raise ValueError("Destination address type must be %d or %d" % (SocketConnectPacket.DEST_ADDRESS_BINARY, - SocketConnectPacket.DEST_ADDRESS_STRING)) - if (dest_address is None - or (dest_address_type == SocketConnectPacket.DEST_ADDRESS_BINARY - and (type(dest_address) is not bytearray or len(dest_address) != 4)) - or (dest_address_type == SocketConnectPacket.DEST_ADDRESS_STRING - and (type(dest_address) is not str or len(dest_address) < 1))): - raise ValueError("Invalid destination address") - - super().__init__(ApiFrameType.SOCKET_CONNECT) - self._frame_id = frame_id - self.__socket_id = socket_id - self.__dest_port = dest_port - self.__dest_address_type = dest_address_type - self.__dest_address = dest_address - - @staticmethod - def create_packet(raw, operating_mode): - """ - Override method. - - Returns: - :class:`.SocketConnectPacket`. - - Raises: - InvalidPacketException: if the bytearray length is less than 11. (start delim. + length (2 bytes) + frame - type + frame id + socket id + dest port (2 bytes) + dest address type + dest_address + checksum = - 11 bytes). - InvalidPacketException: if the length field of 'raw' is different than its real length. (length field: bytes - 2 and 3) - InvalidPacketException: if the first byte of 'raw' is not the header byte. See :class:`.SpecialByte`. - InvalidPacketException: if the calculated checksum is different than the checksum field value (last byte). - InvalidPacketException: if the frame type is not :attr:`.ApiFrameType.SOCKET_CONNECT`. - InvalidOperatingModeException: if ``operating_mode`` is not supported. - - .. seealso:: - | :meth:`.XBeePacket.create_packet` - | :meth:`.XBeeAPIPacket._check_api_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=SocketConnectPacket.__MIN_PACKET_LENGTH) - - if raw[3] != ApiFrameType.SOCKET_CONNECT.code: - raise InvalidPacketException(message="This packet is not a Socket Connect packet.") - - addr_type = raw[8] - address = raw[9:-1] - if address is not None and addr_type == SocketConnectPacket.DEST_ADDRESS_STRING: - address = address.decode("utf8") - - return SocketConnectPacket(raw[4], raw[5], utils.bytes_to_int(raw[6:8]), addr_type, address) - - def needs_id(self): - """ - Override method. - - .. seealso:: - | :meth:`.XBeeAPIPacket.needs_id` - """ - return True - - def _get_api_packet_spec_data(self): - """ - Override method. - - .. seealso:: - | :meth:`.XBeeAPIPacket._get_api_packet_spec_data` - """ - ret = bytearray() - ret.append(self.__socket_id) - ret += utils.int_to_bytes(self.__dest_port, num_bytes=2) - ret.append(self.__dest_address_type) - ret += self.__dest_address.encode() if type(self.__dest_address) is str else self.__dest_address - return ret - - def _get_api_packet_spec_data_dict(self): - """ - Override method. - - .. seealso:: - | :meth:`.XBeeAPIPacket._get_api_packet_spec_data_dict` - """ - return {DictKeys.SOCKET_ID.value: utils.hex_to_string(bytearray([self.__socket_id])), - DictKeys.DEST_PORT.value: "%s (%s)" % (utils.hex_to_string(utils.int_to_bytes(self.__dest_port, - num_bytes=2)), - self.__dest_port), - DictKeys.DEST_ADDR_TYPE.value: utils.hex_to_string(bytearray([self.__dest_address_type])), - DictKeys.DEST_ADDR.value: ("%s (%s)" % (utils.hex_to_string(self.__dest_address.encode()), - self.__dest_address)) if type(self.__dest_address) is str - else utils.hex_to_string(self.__dest_address)} - - def __get_socket_id(self): - """ - Returns the the socket ID. - - Returns: - Integer: the socket ID. - """ - return self.__socket_id - - def __set_socket_id(self, socket_id): - """ - Sets the socket ID. - - Args: - socket_id (Integer): the new socket ID. - - Raises: - ValueError: if ``socket_id`` is less than 0 or greater than 255. - """ - if socket_id < 0 or socket_id > 255: - raise ValueError("Socket ID must be between 0 and 255") - self.__socket_id = socket_id - - def __get_dest_port(self): - """ - Returns the destination port. - - Returns: - Integer: the destination port. - """ - return self.__dest_port - - def __set_dest_port(self, dest_port): - """ - Sets the destination port. - - Args: - dest_port (Integer): the new destination port. - - Raises: - ValueError: if ``dest_port`` is less than 0 or greater than 65535. - """ - if dest_port < 0 or dest_port > 65535: - raise ValueError("Destination port must be between 0 and 65535") - self.__dest_port = dest_port - - def __get_dest_address_type(self): - """ - Returns the destination address type. - - Returns: - Integer: the destination address type. - """ - return self.__dest_address_type - - def __set_dest_address_type(self, dest_address_type): - """ - Sets the destination address type. - - Args: - dest_address_type (Integer): the new destination address type. - - Raises: - ValueError: if ``dest_address_type`` is different than :attr:`SocketConnectPacket.DEST_ADDRESS_BINARY` and - :attr:`SocketConnectPacket.DEST_ADDRESS_STRING`. - """ - if dest_address_type not in (SocketConnectPacket.DEST_ADDRESS_BINARY, SocketConnectPacket.DEST_ADDRESS_STRING): - raise ValueError("Destination address type must be %d or %d" % (SocketConnectPacket.DEST_ADDRESS_BINARY, - SocketConnectPacket.DEST_ADDRESS_STRING)) - self.__dest_address_type = dest_address_type - - def __get_dest_address(self): - """ - Returns the destination address. - - Returns: - Bytearray or String: the destination address. - """ - return self.__dest_address - - def __set_dest_address(self, dest_address): - """ - Sets the destination address. - - Args: - dest_address (Bytearray or String): the new destination address. - - Raises: - ValueError: if ``dest_address`` is ``None``. - ValueError: if ``dest_address`` does not follow the format specified in the configured type. - """ - if (dest_address is None - or (self.__dest_address_type == SocketConnectPacket.DEST_ADDRESS_BINARY - and (type(dest_address) is not bytearray or len(dest_address) != 4)) - or (self.__dest_address_type == SocketConnectPacket.DEST_ADDRESS_STRING - and (type(dest_address) is not str or len(dest_address) < 1))): - raise ValueError("Invalid destination address") - self.__dest_address = dest_address - - socket_id = property(__get_socket_id, __set_socket_id) - """Integer. Socket ID.""" - - dest_port = property(__get_dest_port, __set_dest_port) - """Integer. Destination port.""" - - dest_address_type = property(__get_dest_address_type, __set_dest_address_type) - """Integer. Destination address type.""" - - dest_address = property(__get_dest_address, __set_dest_address) - """Bytearray. Destination address.""" - - -class SocketConnectResponsePacket(XBeeAPIPacket): - """ - This class represents a Socket Connect Response packet. Packet is built - using the parameters of the constructor. - - The device sends this frame in response to a Socket Connect (0x42) frame. - The frame contains a status regarding the initiation of the connect. - - .. seealso:: - | :class:`.SocketConnectPacket` - | :class:`.XBeeAPIPacket` - """ - - __MIN_PACKET_LENGTH = 8 - - def __init__(self, frame_id, socket_id, status): - """ - Class constructor. Instantiates a new :class:`.SocketConnectPacket` object with the provided parameters. - - Args: - frame_id (Integer): the frame ID of the packet. - socket_id (Integer): the ID of the socket to connect. - status (:class:`.SocketStatus`): the socket connect status. - - .. seealso:: - | :class:`.XBeeAPIPacket` - | :class:`.SocketStatus` - - Raises: - ValueError: if ``frame_id`` is less than 0 or greater than 255. - ValueError: if ``socket_id`` is less than 0 or greater than 255. - """ - if frame_id < 0 or frame_id > 255: - raise ValueError("Frame ID must be between 0 and 255") - if socket_id < 0 or socket_id > 255: - raise ValueError("Socket ID must be between 0 and 255") - - super().__init__(ApiFrameType.SOCKET_CONNECT_RESPONSE) - self._frame_id = frame_id - self.__socket_id = socket_id - self.__status = status - - @staticmethod - def create_packet(raw, operating_mode): - """ - Override method. - - Returns: - :class:`.SocketConnectResponsePacket`. - - Raises: - InvalidPacketException: if the bytearray length is less than 8. (start delim. + length (2 bytes) + frame - type + frame id + socket id + status + checksum = 8 bytes). - InvalidPacketException: if the length field of 'raw' is different than its real length. (length field: bytes - 2 and 3) - InvalidPacketException: if the first byte of 'raw' is not the header byte. See :class:`.SpecialByte`. - InvalidPacketException: if the calculated checksum is different than the checksum field value (last byte). - InvalidPacketException: if the frame type is not :attr:`.ApiFrameType.SOCKET_CONNECT_RESPONSE`. - InvalidOperatingModeException: if ``operating_mode`` is not supported. - - .. seealso:: - | :meth:`.XBeePacket.create_packet` - | :meth:`.XBeeAPIPacket._check_api_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=SocketConnectResponsePacket.__MIN_PACKET_LENGTH) - - if raw[3] != ApiFrameType.SOCKET_CONNECT_RESPONSE.code: - raise InvalidPacketException(message="This packet is not a Socket Connect Response packet.") - - return SocketConnectResponsePacket(raw[4], raw[5], SocketStatus.get(raw[6])) - - def needs_id(self): - """ - Override method. - - .. seealso:: - | :meth:`.XBeeAPIPacket.needs_id` - """ - return True - - def _get_api_packet_spec_data(self): - """ - Override method. - - .. seealso:: - | :meth:`.XBeeAPIPacket._get_api_packet_spec_data` - """ - ret = bytearray() - ret.append(self.__socket_id) - ret.append(self.__status.code) - return ret - - def _get_api_packet_spec_data_dict(self): - """ - Override method. - - .. seealso:: - | :meth:`.XBeeAPIPacket._get_api_packet_spec_data_dict` - """ - return {DictKeys.SOCKET_ID.value: utils.hex_to_string(bytearray([self.__socket_id])), - DictKeys.STATUS.value: "%s (%s)" % (self.__status.code, self.__status.description)} - - def __get_socket_id(self): - """ - Returns the the socket ID. - - Returns: - Integer: the socket ID. - """ - return self.__socket_id - - def __set_socket_id(self, socket_id): - """ - Sets the socket ID. - - Args: - socket_id (Integer): the new socket ID. - - Raises: - ValueError: if ``socket_id`` is less than 0 or greater than 255. - """ - if socket_id < 0 or socket_id > 255: - raise ValueError("Socket ID must be between 0 and 255") - self.__socket_id = socket_id - - def __get_status(self): - """ - Returns the socket connect status. - - Returns: - :class:`.SocketStatus`: the socket connect status. - - .. seealso:: - | :class:`.SocketStatus` - """ - return self.__status - - def __set_status(self, status): - """ - Sets the socket connect status. - - Args: - status (:class:`.SocketStatus`): the new socket connect status. - - .. seealso:: - | :class:`.SocketStatus` - """ - self.__status = status - - socket_id = property(__get_socket_id, __set_socket_id) - """Integer. Socket ID.""" - - status = property(__get_status, __set_status) - """:class:`.SocketStatus`. Socket connect status.""" - - -class SocketClosePacket(XBeeAPIPacket): - """ - This class represents a Socket Close packet. Packet is built using the - parameters of the constructor. - - Use this frame to close a socket when given an identifier. - - .. seealso:: - | :class:`.SocketCloseResponsePacket` - | :class:`.XBeeAPIPacket` - """ - - __MIN_PACKET_LENGTH = 7 - - def __init__(self, frame_id, socket_id): - """ - Class constructor. Instantiates a new :class:`.SocketClosePacket` object with the provided parameters. - - Args: - frame_id (Integer): the frame ID of the packet. - socket_id (Integer): the ID of the socket to close. - - .. seealso:: - | :class:`.XBeeAPIPacket` - - Raises: - ValueError: if ``frame_id`` is less than 0 or greater than 255. - ValueError: if ``socket_id`` is less than 0 or greater than 255. - """ - if frame_id < 0 or frame_id > 255: - raise ValueError("Frame ID must be between 0 and 255") - if socket_id < 0 or socket_id > 255: - raise ValueError("Socket ID must be between 0 and 255") - - super().__init__(ApiFrameType.SOCKET_CLOSE) - self._frame_id = frame_id - self.__socket_id = socket_id - - @staticmethod - def create_packet(raw, operating_mode): - """ - Override method. - - Returns: - :class:`.SocketClosePacket`. - - Raises: - InvalidPacketException: if the bytearray length is less than 7. (start delim. + length (2 bytes) + frame - type + frame id + socket id + checksum = 7 bytes). - InvalidPacketException: if the length field of 'raw' is different than its real length. (length field: bytes - 2 and 3) - InvalidPacketException: if the first byte of 'raw' is not the header byte. See :class:`.SpecialByte`. - InvalidPacketException: if the calculated checksum is different than the checksum field value (last byte). - InvalidPacketException: if the frame type is not :attr:`.ApiFrameType.SOCKET_CLOSE`. - InvalidOperatingModeException: if ``operating_mode`` is not supported. - - .. seealso:: - | :meth:`.XBeePacket.create_packet` - | :meth:`.XBeeAPIPacket._check_api_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=SocketClosePacket.__MIN_PACKET_LENGTH) - - if raw[3] != ApiFrameType.SOCKET_CLOSE.code: - raise InvalidPacketException(message="This packet is not a Socket Close packet.") - - return SocketClosePacket(raw[4], raw[5]) - - def needs_id(self): - """ - Override method. - - .. seealso:: - | :meth:`.XBeeAPIPacket.needs_id` - """ - return True - - def _get_api_packet_spec_data(self): - """ - Override method. - - .. seealso:: - | :meth:`.XBeeAPIPacket._get_api_packet_spec_data` - """ - return bytearray([self.__socket_id]) - - def _get_api_packet_spec_data_dict(self): - """ - Override method. - - .. seealso:: - | :meth:`.XBeeAPIPacket._get_api_packet_spec_data_dict` - """ - return {DictKeys.SOCKET_ID.value: utils.hex_to_string(bytearray([self.__socket_id]))} - - def __get_socket_id(self): - """ - Returns the socket ID. - - Returns: - Integer: the socket ID. - """ - return self.__socket_id - - def __set_socket_id(self, socket_id): - """ - Sets the socket ID. - - Args: - socket_id (Integer): the new socket ID. - - Raises: - ValueError: if ``socket_id`` is less than 0 or greater than 255. - """ - if socket_id < 0 or socket_id > 255: - raise ValueError("Socket ID must be between 0 and 255") - self.__socket_id = socket_id - - socket_id = property(__get_socket_id, __set_socket_id) - """Integer. Socket ID.""" - - -class SocketCloseResponsePacket(XBeeAPIPacket): - """ - This class represents a Socket Close Response packet. Packet is built using - the parameters of the constructor. - - The device sends this frame in response to a Socket Close (0x43) frame. - Since a close will always succeed for a socket that exists, the status can - be only one of two values: - - * Success. - * Bad socket ID. - - .. seealso:: - | :class:`.SocketClosePacket` - | :class:`.XBeeAPIPacket` - """ - - __MIN_PACKET_LENGTH = 8 - - def __init__(self, frame_id, socket_id, status): - """ - Class constructor. Instantiates a new :class:`.SocketCloseResponsePacket` object with the provided parameters. - - Args: - frame_id (Integer): the frame ID of the packet. - socket_id (Integer): the ID of the socket to close. - status (:class:`.SocketStatus`): the socket close status. - - .. seealso:: - | :class:`.XBeeAPIPacket` - | :class:`.SocketStatus` - - Raises: - ValueError: if ``frame_id`` is less than 0 or greater than 255. - ValueError: if ``socket_id`` is less than 0 or greater than 255. - """ - if frame_id < 0 or frame_id > 255: - raise ValueError("Frame ID must be between 0 and 255") - if socket_id < 0 or socket_id > 255: - raise ValueError("Socket ID must be between 0 and 255") - - super().__init__(ApiFrameType.SOCKET_CLOSE_RESPONSE) - self._frame_id = frame_id - self.__socket_id = socket_id - self.__status = status - - @staticmethod - def create_packet(raw, operating_mode): - """ - Override method. - - Returns: - :class:`.SocketCloseResponsePacket`. - - Raises: - InvalidPacketException: if the bytearray length is less than 8. (start delim. + length (2 bytes) + frame - type + frame id + socket id + status + checksum = 8 bytes). - InvalidPacketException: if the length field of 'raw' is different than its real length. (length field: bytes - 2 and 3) - InvalidPacketException: if the first byte of 'raw' is not the header byte. See :class:`.SpecialByte`. - InvalidPacketException: if the calculated checksum is different than the checksum field value (last byte). - InvalidPacketException: if the frame type is not :attr:`.ApiFrameType.SOCKET_CLOSE_RESPONSE`. - InvalidOperatingModeException: if ``operating_mode`` is not supported. - - .. seealso:: - | :meth:`.XBeePacket.create_packet` - | :meth:`.XBeeAPIPacket._check_api_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=SocketCloseResponsePacket.__MIN_PACKET_LENGTH) - - if raw[3] != ApiFrameType.SOCKET_CLOSE_RESPONSE.code: - raise InvalidPacketException(message="This packet is not a Socket Close Response packet.") - - return SocketCloseResponsePacket(raw[4], raw[5], SocketStatus.get(raw[6])) - - def needs_id(self): - """ - Override method. - - .. seealso:: - | :meth:`.XBeeAPIPacket.needs_id` - """ - return True - - def _get_api_packet_spec_data(self): - """ - Override method. - - .. seealso:: - | :meth:`.XBeeAPIPacket._get_api_packet_spec_data` - """ - ret = bytearray() - ret.append(self.__socket_id) - ret.append(self.__status.code) - return ret - - def _get_api_packet_spec_data_dict(self): - """ - Override method. - - .. seealso:: - | :meth:`.XBeeAPIPacket._get_api_packet_spec_data_dict` - """ - return {DictKeys.SOCKET_ID.value: utils.hex_to_string(bytearray([self.__socket_id])), - DictKeys.STATUS.value: "%s (%s)" % (self.__status.code, self.__status.description)} - - def __get_socket_id(self): - """ - Returns the the socket ID. - - Returns: - Integer: the socket ID. - """ - return self.__socket_id - - def __set_socket_id(self, socket_id): - """ - Sets the socket ID. - - Args: - socket_id (Integer): the new socket ID. - - Raises: - ValueError: if ``socket_id`` is less than 0 or greater than 255. - """ - if socket_id < 0 or socket_id > 255: - raise ValueError("Socket ID must be between 0 and 255") - self.__socket_id = socket_id - - def __get_status(self): - """ - Returns the socket close status. - - Returns: - :class:`.SocketStatus`: the socket close status. - - .. seealso:: - | :class:`.SocketStatus` - """ - return self.__status - - def __set_status(self, status): - """ - Sets the socket close status. - - Args: - status (:class:`.SocketStatus`): the new socket close status. - - .. seealso:: - | :class:`.SocketStatus` - """ - self.__status = status - - socket_id = property(__get_socket_id, __set_socket_id) - """Integer. Socket ID.""" - - status = property(__get_status, __set_status) - """:class:`.SocketStatus`. Socket close status.""" - - -class SocketSendPacket(XBeeAPIPacket): - """ - This class represents a Socket Send packet. Packet is built using the - parameters of the constructor. - - A Socket Send message causes the device to transmit data using the - current connection. For a nonzero frame ID, this will elicit a Transmit - (TX) Status - 0x89 frame (:class:`.TransmitStatusPacket`). - - This frame requires a successful Socket Connect - 0x42 frame first - (:class:`.SocketConnectPacket`). For a socket that is not connected, the - device responds with a Transmit (TX) Status - 0x89 frame with an - error. - - .. seealso:: - | :class:`.TransmitStatusPacket` - | :class:`.XBeeAPIPacket` - """ - - __MIN_PACKET_LENGTH = 7 - - def __init__(self, frame_id, socket_id, payload=None): - """ - Class constructor. Instantiates a new :class:`.SocketSendPacket` object with the - provided parameters. - - Args: - frame_id (Integer): the frame ID of the packet. - socket_id (Integer): the socket identifier. - payload (Bytearray, optional): data that is sent. - - Raises: - ValueError: if ``frame_id`` is less than 0 or greater than 255. - ValueError: if ``socket_id`` is less than 0 or greater than 255. - - .. seealso:: - | :class:`.XBeeAPIPacket` - """ - if frame_id < 0 or frame_id > 255: - raise ValueError("Frame ID must be between 0 and 255.") - if socket_id < 0 or socket_id > 255: - raise ValueError("Socket ID must be between 0 and 255.") - - super().__init__(ApiFrameType.SOCKET_SEND) - self._frame_id = frame_id - self.__socket_id = socket_id - self.__payload = payload - - @staticmethod - def create_packet(raw, operating_mode): - """ - Override method. - - Returns: - :class:`.SocketSendPacket`. - - Raises: - InvalidPacketException: if the bytearray length is less than 7. (start delim. + length (2 bytes) + frame - type + frame id + socket ID + checksum = 7 bytes). - InvalidPacketException: if the length field of 'raw' is different than its real length. (length field: bytes - 2 and 3) - InvalidPacketException: if the first byte of 'raw' is not the header byte. See :class:`.SpecialByte`. - InvalidPacketException: if the calculated checksum is different than the checksum field value (last byte). - InvalidPacketException: if the frame type is not :attr:`.ApiFrameType.SOCKET_SEND`. - InvalidOperatingModeException: if ``operating_mode`` is not supported. - - .. seealso:: - | :meth:`.XBeePacket.create_packet` - | :meth:`.XBeeAPIPacket._check_api_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=SocketSendPacket.__MIN_PACKET_LENGTH) - - if raw[3] != ApiFrameType.SOCKET_SEND.code: - raise InvalidPacketException("This packet is not a Socket Send (transmit) packet.") - - return SocketSendPacket(raw[4], raw[5], raw[7:-1]) - - def needs_id(self): - """ - Override method. - - .. seealso:: - | :meth:`.XBeeAPIPacket.needs_id` - """ - return True - - def _get_api_packet_spec_data(self): - """ - Override method. - - .. seealso:: - | :meth:`.XBeeAPIPacket._get_api_packet_spec_data` - """ - ret = bytearray() - ret.append(self.__socket_id) - ret.append(0) # Transmit options (Reserved) - if self.__payload is not None: - ret += self.__payload - return ret - - def _get_api_packet_spec_data_dict(self): - """ - Override method. - - .. seealso:: - | :meth:`.XBeeAPIPacket._get_api_packet_spec_data_dict` - """ - return {DictKeys.SOCKET_ID.value: utils.hex_to_string(bytearray([self.__socket_id])), - DictKeys.TRANSMIT_OPTIONS.value: utils.hex_to_string(bytearray([0])), - DictKeys.PAYLOAD.value: utils.hex_to_string(self.__payload, - True) if self.__payload is not None else None} - - def __get_socket_id(self): - """ - Returns the socket ID. - - Returns: - Integer: the socket ID. - """ - return self.__socket_id - - def __set_socket_id(self, socket_id): - """ - Sets the socket ID. - - Args: - socket_id (Integer): The new socket ID. - - Raises: - ValueError: if ``socket_id`` is less than 0 or greater than 255. - """ - if socket_id < 0 or socket_id > 255: - raise ValueError("Socket ID must be between 0 and 255.") - self.__socket_id = socket_id - - def __get_payload(self): - """ - Returns the payload to send. - - Returns: - Bytearray: the payload to send. - """ - if self.__payload is None: - return None - return self.__payload.copy() - - def __set_payload(self, payload): - """ - Sets the payload to send. - - Args: - payload (Bytearray): the new payload to send. - """ - if payload is None: - self.__payload = None - else: - self.__payload = payload.copy() - - socket_id = property(__get_socket_id, __set_socket_id) - """Integer. Socket ID.""" - - payload = property(__get_payload, __set_payload) - """Bytearray. Payload to send.""" - - -class SocketSendToPacket(XBeeAPIPacket): - """ - This class represents a Socket Send packet. Packet is built using the - parameters of the constructor. - - A Socket SendTo (Transmit Explicit Data) message causes the device to - transmit data using an IPv4 address and port. For a non-zero frame ID, - this will elicit a Transmit (TX) Status - 0x89 frame - (:class:`.TransmitStatusPacket`). - - If this frame is used with a TCP, SSL, or a connected UDP socket, the - address and port fields are ignored. - - .. seealso:: - | :class:`.TransmitStatusPacket` - | :class:`.XBeeAPIPacket` - """ - - __MIN_PACKET_LENGTH = 14 - - def __init__(self, frame_id, socket_id, dest_address, dest_port, payload=None): - """ - Class constructor. Instantiates a new :class:`.SocketSendToPacket` object with the - provided parameters. - - Args: - frame_id (Integer): the frame ID of the packet. - socket_id (Integer): the socket identifier. - dest_address (:class:`.IPv4Address`): IPv4 address of the destination device. - dest_port (Integer): destination port number. - payload (Bytearray, optional): data that is sent. - - Raises: - ValueError: if ``frame_id`` is less than 0 or greater than 255. - ValueError: if ``socket_id`` is less than 0 or greater than 255. - ValueError: if ``dest_port`` is less than 0 or greater than 65535. - - .. seealso:: - | :class:`.XBeeAPIPacket` - """ - if frame_id < 0 or frame_id > 255: - raise ValueError("Frame ID must be between 0 and 255.") - if socket_id < 0 or socket_id > 255: - raise ValueError("Socket ID must be between 0 and 255.") - if dest_port < 0 or dest_port > 65535: - raise ValueError("Destination port must be between 0 and 65535") - - super().__init__(ApiFrameType.SOCKET_SENDTO) - self._frame_id = frame_id - self.__socket_id = socket_id - self.__dest_address = dest_address - self.__dest_port = dest_port - self.__payload = payload - - @staticmethod - def create_packet(raw, operating_mode): - """ - Override method. - - Returns: - :class:`.SocketSendToPacket`. - - Raises: - InvalidPacketException: if the bytearray length is less than 14. (start delim. + length (2 bytes) + frame - type + frame id + socket ID + dest address (4 bytes) + dest port (2 bytes) + transmit options + - checksum = 14 bytes). - InvalidPacketException: if the length field of 'raw' is different than its real length. (length field: bytes - 2 and 3) - InvalidPacketException: if the first byte of 'raw' is not the header byte. See :class:`.SpecialByte`. - InvalidPacketException: if the calculated checksum is different than the checksum field value (last byte). - InvalidPacketException: if the frame type is not :attr:`.ApiFrameType.SOCKET_SENDTO`. - InvalidOperatingModeException: if ``operating_mode`` is not supported. - - .. seealso:: - | :meth:`.XBeePacket.create_packet` - | :meth:`.XBeeAPIPacket._check_api_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=SocketSendToPacket.__MIN_PACKET_LENGTH) - - if raw[3] != ApiFrameType.SOCKET_SENDTO.code: - raise InvalidPacketException("This packet is not a Socket SendTo (Transmit Explicit Data): IPv4 packet.") - - return SocketSendToPacket(raw[4], raw[5], IPv4Address(bytes(raw[6:10])), - utils.bytes_to_int(raw[10:12]), raw[13:-1]) - - def needs_id(self): - """ - Override method. - - .. seealso:: - | :meth:`.XBeeAPIPacket.needs_id` - """ - return True - - def _get_api_packet_spec_data(self): - """ - Override method. - - .. seealso:: - | :meth:`.XBeeAPIPacket._get_api_packet_spec_data` - """ - ret = bytearray() - ret.append(self.__socket_id) - ret += self.__dest_address.packed - ret += utils.int_to_bytes(self.__dest_port, num_bytes=2) - ret.append(0) # Transmit options (Reserved) - if self.__payload is not None: - ret += self.__payload - return ret - - def _get_api_packet_spec_data_dict(self): - """ - Override method. - - .. seealso:: - | :meth:`.XBeeAPIPacket._get_api_packet_spec_data_dict` - """ - return {DictKeys.SOCKET_ID.value: utils.hex_to_string(bytearray([self.__socket_id])), - DictKeys.DEST_IPV4_ADDR.value: "%s (%s)" % (utils.hex_to_string(self.__dest_address.packed, True), - self.__dest_address.exploded), - DictKeys.DEST_PORT.value: "%s (%s)" % (utils.hex_to_string(utils.int_to_bytes(self.__dest_port, - num_bytes=2)), - self.__dest_port), - DictKeys.TRANSMIT_OPTIONS.value: utils.hex_to_string(bytearray([0])), - DictKeys.PAYLOAD.value: utils.hex_to_string(self.__payload, - True) if self.__payload is not None else None} - - def __get_socket_id(self): - """ - Returns the socket ID. - - Returns: - Integer: the socket ID. - """ - return self.__socket_id - - def __set_socket_id(self, socket_id): - """ - Sets the socket ID. - - Args: - socket_id (Integer): The new socket ID. - - Raises: - ValueError: if ``socket_id`` is less than 0 or greater than 255. - """ - if socket_id < 0 or socket_id > 255: - raise ValueError("Socket ID must be between 0 and 255.") - self.__socket_id = socket_id - - def __get_dest_address(self): - """ - Returns the IPv4 address of the destination device. - - Returns: - :class:`ipaddress.IPv4Address`: the IPv4 address of the destination device. - """ - return self.__dest_address - - def __set_dest_address(self, dest_address): - """ - Sets the IPv4 destination address. - - Args: - dest_address (:class:`ipaddress.IPv4Address`): The new IPv4 destination address. - """ - if dest_address is not None: - self.__dest_address = dest_address - - def __get_dest_port(self): - """ - Returns the destination port. - - Returns: - Integer: the destination port. - """ - return self.__dest_port - - def __set_dest_port(self, dest_port): - """ - Sets the destination port. - - Args: - dest_port (Integer): the new destination port. - - Raises: - ValueError: if ``dest_port`` is less than 0 or greater than 65535. - """ - if dest_port < 0 or dest_port > 65535: - raise ValueError("Destination port must be between 0 and 65535") - self.__dest_port = dest_port - - def __get_payload(self): - """ - Returns the payload to send. - - Returns: - Bytearray: the payload to send. - """ - if self.__payload is None: - return None - return self.__payload.copy() - - def __set_payload(self, payload): - """ - Sets the payload to send. - - Args: - payload (Bytearray): the new payload to send. - """ - if payload is None: - self.__payload = None - else: - self.__payload = payload.copy() - - socket_id = property(__get_socket_id, __set_socket_id) - """Integer. Socket ID.""" - - dest_address = property(__get_dest_address, __set_dest_address) - """:class:`ipaddress.IPv4Address`. IPv4 address of the destination device.""" - - dest_port = property(__get_dest_port, __set_dest_port) - """Integer. Destination port.""" - - payload = property(__get_payload, __set_payload) - """Bytearray. Payload to send.""" - - -class SocketBindListenPacket(XBeeAPIPacket): - """ - This class represents a Socket Bind/Listen packet. Packet is built using the - parameters of the constructor. - - Opens a listener socket that listens for incoming connections. - - When there is an incoming connection on the listener socket, a Socket New - IPv4 Client - 0xCC frame (:class:`.SocketNewIPv4ClientPacket`) is sent, - indicating the socket ID for the new connection along with the remote - address information. - - For a UDP socket, this frame binds the socket to a given port. A bound - UDP socket can receive data with a Socket Receive From: IPv4 - 0xCE frame - (:class:`.SocketReceiveFromIPv4Packet`). - - .. seealso:: - | :class:`.SocketNewIPv4ClientPacket` - | :class:`.SocketReceiveFromIPv4Packet` - | :class:`.XBeeAPIPacket` - """ - - __MIN_PACKET_LENGTH = 9 - - def __init__(self, frame_id, socket_id, source_port): - """ - Class constructor. Instantiates a new :class:`.SocketBindListenPacket` object with the - provided parameters. - - Args: - frame_id (Integer): the frame ID of the packet. - socket_id (Integer): socket ID to listen on. - source_port (Integer): the port to listen on. - - Raises: - ValueError: if ``frame_id`` is less than 0 or greater than 255. - ValueError: if ``socket_id`` is less than 0 or greater than 255. - ValueError: if ``source_port`` is less than 0 or greater than 65535. - - .. seealso:: - | :class:`.XBeeAPIPacket` - """ - if frame_id < 0 or frame_id > 255: - raise ValueError("Frame ID must be between 0 and 255.") - if socket_id < 0 or socket_id > 255: - raise ValueError("Socket ID must be between 0 and 255.") - if source_port < 0 or source_port > 65535: - raise ValueError("Source port must be between 0 and 65535") - - super().__init__(ApiFrameType.SOCKET_BIND) - self._frame_id = frame_id - self.__socket_id = socket_id - self.__source_port = source_port - - @staticmethod - def create_packet(raw, operating_mode): - """ - Override method. - - Returns: - :class:`.SocketBindListenPacket`. - - Raises: - InvalidPacketException: if the bytearray length is less than 9. (start delim. + length (2 bytes) + frame - type + frame id + socket ID + source port (2 bytes) + checksum = 9 bytes). - InvalidPacketException: if the length field of 'raw' is different than its real length. (length field: bytes - 2 and 3) - InvalidPacketException: if the first byte of 'raw' is not the header byte. See :class:`.SpecialByte`. - InvalidPacketException: if the calculated checksum is different than the checksum field value (last byte). - InvalidPacketException: if the frame type is not :attr:`.ApiFrameType.SOCKET_BIND`. - InvalidOperatingModeException: if ``operating_mode`` is not supported. - - .. seealso:: - | :meth:`.XBeePacket.create_packet` - | :meth:`.XBeeAPIPacket._check_api_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=SocketBindListenPacket.__MIN_PACKET_LENGTH) - - if raw[3] != ApiFrameType.SOCKET_BIND.code: - raise InvalidPacketException("This packet is not a Socket Bind/Listen packet.") - - return SocketBindListenPacket(raw[4], raw[5], utils.bytes_to_int(raw[6:8])) - - def needs_id(self): - """ - Override method. - - .. seealso:: - | :meth:`.XBeeAPIPacket.needs_id` - """ - return True - - def _get_api_packet_spec_data(self): - """ - Override method. - - .. seealso:: - | :meth:`.XBeeAPIPacket._get_api_packet_spec_data` - """ - ret = bytearray() - ret.append(self.__socket_id) - ret += utils.int_to_bytes(self.__source_port, num_bytes=2) - return ret - - def _get_api_packet_spec_data_dict(self): - """ - Override method. - - .. seealso:: - | :meth:`.XBeeAPIPacket._get_api_packet_spec_data_dict` - """ - return {DictKeys.SOCKET_ID.value: utils.hex_to_string(bytearray([self.__socket_id])), - DictKeys.SRC_PORT.value: "%s (%s)" % (utils.hex_to_string(utils.int_to_bytes(self.__source_port, - num_bytes=2)), - self.__source_port)} - - def __get_socket_id(self): - """ - Returns the socket ID. - - Returns: - Integer: the socket ID. - """ - return self.__socket_id - - def __set_socket_id(self, socket_id): - """ - Sets the socket ID. - - Args: - socket_id (Integer): The new socket ID. - - Raises: - ValueError: if ``socket_id`` is less than 0 or greater than 255. - """ - if socket_id < 0 or socket_id > 255: - raise ValueError("Socket ID must be between 0 and 255.") - self.__socket_id = socket_id - - def __get_source_port(self): - """ - Returns the source port. - - Returns: - Integer: the source port. - """ - return self.__source_port - - def __set_source_port(self, source_port): - """ - Sets the source port. - - Args: - source_port (Integer): the new source port. - - Raises: - ValueError: if ``source_port`` is less than 0 or greater than 65535. - """ - if source_port < 0 or source_port > 65535: - raise ValueError("Source port must be between 0 and 65535") - self.__source_port = source_port - - socket_id = property(__get_socket_id, __set_socket_id) - """Integer. Socket ID.""" - - source_port = property(__get_source_port, __set_source_port) - """Integer. Source port.""" - - -class SocketListenResponsePacket(XBeeAPIPacket): - """ - This class represents a Socket Listen Response packet. Packet is built using - the parameters of the constructor. - - The device sends this frame in response to a Socket Bind/Listen (0x46) - frame (:class:`.SocketBindListenPacket`). - - .. seealso:: - | :class:`.SocketBindListenPacket` - | :class:`.XBeeAPIPacket` - """ - - __MIN_PACKET_LENGTH = 8 - - def __init__(self, frame_id, socket_id, status): - """ - Class constructor. Instantiates a new :class:`.SocketListenResponsePacket` object with the - provided parameters. - - Args: - frame_id (Integer): the frame ID of the packet. - socket_id (Integer): socket ID. - status (:class:`.SocketStatus`): socket listen status. - - Raises: - ValueError: if ``frame_id`` is less than 0 or greater than 255. - ValueError: if ``socket_id`` is less than 0 or greater than 255. - - .. seealso:: - | :class:`.XBeeAPIPacket` - | :class:`.SocketStatus` - """ - if frame_id < 0 or frame_id > 255: - raise ValueError("Socket ID must be between 0 and 255.") - if socket_id < 0 or socket_id > 255: - raise ValueError("Socket ID must be between 0 and 255.") - - super().__init__(ApiFrameType.SOCKET_LISTEN_RESPONSE) - self._frame_id = frame_id - self.__socket_id = socket_id - self.__status = status - - @staticmethod - def create_packet(raw, operating_mode): - """ - Override method. - - Returns: - :class:`.SocketListenResponsePacket`. - - Raises: - InvalidPacketException: if the bytearray length is less than 8. (start delim. + length (2 bytes) + frame - type + frame id + socket ID + status + checksum = 8 bytes). - InvalidPacketException: if the length field of 'raw' is different than its real length. (length field: bytes - 2 and 3) - InvalidPacketException: if the first byte of 'raw' is not the header byte. See :class:`.SpecialByte`. - InvalidPacketException: if the calculated checksum is different than the checksum field value (last byte). - InvalidPacketException: if the frame type is not :attr:`.ApiFrameType.SOCKET_LISTEN_RESPONSE`. - InvalidOperatingModeException: if ``operating_mode`` is not supported. - - .. seealso:: - | :meth:`.XBeePacket.create_packet` - | :meth:`.XBeeAPIPacket._check_api_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=SocketListenResponsePacket.__MIN_PACKET_LENGTH) - - if raw[3] != ApiFrameType.SOCKET_LISTEN_RESPONSE.code: - raise InvalidPacketException("This packet is not a Socket Listen Response packet.") - - return SocketListenResponsePacket(raw[4], raw[5], SocketStatus.get(raw[6])) - - def needs_id(self): - """ - Override method. - - .. seealso:: - | :meth:`.XBeeAPIPacket.needs_id` - """ - return True - - def _get_api_packet_spec_data(self): - """ - Override method. - - .. seealso:: - | :meth:`.XBeeAPIPacket._get_api_packet_spec_data` - """ - ret = bytearray() - ret.append(self.__socket_id) - ret.append(self.__status.code) - return ret - - def _get_api_packet_spec_data_dict(self): - """ - Override method. - - .. seealso:: - | :meth:`.XBeeAPIPacket._get_api_packet_spec_data_dict` - """ - return {DictKeys.SOCKET_ID.value: utils.hex_to_string(bytearray([self.__socket_id])), - DictKeys.STATUS.value: "%s (%s)" % (utils.hex_to_string(bytearray([self.__status.code])), - self.__status.description)} - - def __get_socket_id(self): - """ - Returns the socket ID. - - Returns: - Integer: the socket ID. - """ - return self.__socket_id - - def __set_socket_id(self, socket_id): - """ - Sets the socket ID. - - Args: - socket_id (Integer): The new socket ID. - - Raises: - ValueError: if ``socket_id`` is less than 0 or greater than 255. - """ - if socket_id < 0 or socket_id > 255: - raise ValueError("Socket ID must be between 0 and 255.") - self.__socket_id = socket_id - - def __get_status(self): - """ - Returns the socket listen status. - - Returns: - :class:`.SocketStatus`: The socket listen status. - - .. seealso:: - | :class:`.SocketStatus` - """ - return self.__status - - def __set_status(self, status): - """ - Sets the socket listen status. - - Args: - status (:class:`.SocketStatus`): the new socket listen status. - - .. seealso:: - | :class:`.SocketStatus` - """ - self.__status = status - - socket_id = property(__get_socket_id, __set_socket_id) - """Integer. Socket ID.""" - - status = property(__get_status, __set_status) - """:class:`.SocketStatus`. Socket listen status.""" - - -class SocketNewIPv4ClientPacket(XBeeAPIPacket): - """ - This class represents a Socket New IPv4 Client packet. Packet is built using - the parameters of the constructor. - - XBee Cellular modem uses this frame when an incoming connection is - accepted on a listener socket. - - This frame contains the original listener's socket ID and a new socket ID - of the incoming connection, along with the connection's remote address - information. - - .. seealso:: - | :class:`.XBeeAPIPacket` - """ - - __MIN_PACKET_LENGTH = 13 - - def __init__(self, socket_id, client_socket_id, remote_address, remote_port): - """ - Class constructor. Instantiates a new :class:`.SocketNewIPv4ClientPacket` object with the - provided parameters. - - Args: - socket_id (Integer): the socket ID of the listener socket. - client_socket_id (Integer): the socket ID of the new connection. - remote_address (:class:`.IPv4Address`): the remote IPv4 address. - remote_port (Integer): the remote port number. - - Raises: - ValueError: if ``socket_id`` is less than 0 or greater than 255. - ValueError: if ``client_socket_id`` is less than 0 or greater than 255. - ValueError: if ``remote_port`` is less than 0 or greater than 65535. - - .. seealso:: - | :class:`.XBeeAPIPacket` - """ - if socket_id < 0 or socket_id > 255: - raise ValueError("Socket ID must be between 0 and 255") - if client_socket_id < 0 or client_socket_id > 255: - raise ValueError("Client socket ID must be between 0 and 255") - if remote_port < 0 or remote_port > 65535: - raise ValueError("Remote port must be between 0 and 65535") - - super().__init__(ApiFrameType.SOCKET_NEW_IPV4_CLIENT) - self.__socket_id = socket_id - self.__client_socket_id = client_socket_id - self.__remote_address = remote_address - self.__remote_port = remote_port - - @staticmethod - def create_packet(raw, operating_mode): - """ - Override method. - - Returns: - :class:`.SocketNewIPv4ClientPacket`. - - Raises: - InvalidPacketException: if the bytearray length is less than 13. (start delim. + length (2 bytes) + frame - type + socket ID + client socket ID + remote address (4 bytes) + remote port (2 bytes) - + checksum = 13 bytes). - InvalidPacketException: if the length field of 'raw' is different than its real length. (length field: bytes - 2 and 3) - InvalidPacketException: if the first byte of 'raw' is not the header byte. See :class:`.SpecialByte`. - InvalidPacketException: if the calculated checksum is different than the checksum field value (last byte). - InvalidPacketException: if the frame type is not :attr:`.ApiFrameType.SOCKET_NEW_IPV4_CLIENT`. - InvalidOperatingModeException: if ``operating_mode`` is not supported. - - .. seealso:: - | :meth:`.XBeePacket.create_packet` - | :meth:`.XBeeAPIPacket._check_api_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=SocketNewIPv4ClientPacket.__MIN_PACKET_LENGTH) - - if raw[3] != ApiFrameType.SOCKET_NEW_IPV4_CLIENT.code: - raise InvalidPacketException("This packet is not a Socket New IPv4 Client packet.") - - return SocketNewIPv4ClientPacket(raw[4], raw[5], IPv4Address(bytes(raw[6:10])), - utils.bytes_to_int(raw[10:12])) - - 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.__socket_id) - ret.append(self.__client_socket_id) - ret += self.__remote_address.packed - ret += utils.int_to_bytes(self.__remote_port, num_bytes=2) - return ret - - def _get_api_packet_spec_data_dict(self): - """ - Override method. - - .. seealso:: - | :meth:`.XBeeAPIPacket._get_api_packet_spec_data_dict` - """ - return {DictKeys.SOCKET_ID.value: utils.hex_to_string(bytearray([self.__socket_id])), - DictKeys.CLIENT_SOCKET_ID.value: utils.hex_to_string(bytearray([self.__client_socket_id])), - DictKeys.REMOTE_ADDR.value: "%s (%s)" % (utils.hex_to_string(self.__remote_address.packed, True), - self.__remote_address.exploded), - DictKeys.REMOTE_PORT.value: "%s (%s)" % (utils.hex_to_string(utils.int_to_bytes(self.__remote_port, - num_bytes=2)), - self.__remote_port)} - - def __get_socket_id(self): - """ - Returns the socket ID. - - Returns: - Integer: the socket ID. - """ - return self.__socket_id - - def __set_socket_id(self, socket_id): - """ - Sets the socket ID. - - Args: - socket_id (Integer): The new socket ID. - - Raises: - ValueError: if ``socket_id`` is less than 0 or greater than 255. - """ - if socket_id < 0 or socket_id > 255: - raise ValueError("Socket ID must be between 0 and 255") - self.__socket_id = socket_id - - def __get_client_socket_id(self): - """ - Returns the client socket ID. - - Returns: - Integer: the client socket ID. - """ - return self.__client_socket_id - - def __set_client_socket_id(self, client_socket_id): - """ - Sets the client socket ID. - - Args: - client_socket_id (Integer): The new client socket ID. - - Raises: - ValueError: if ``client_socket_id`` is less than 0 or greater than 255. - """ - if client_socket_id < 0 or client_socket_id > 255: - raise ValueError("Socket ID must be between 0 and 255") - self.__client_socket_id = client_socket_id - - def __get_remote_address(self): - """ - Returns the remote IPv4 address. - - Returns: - :class:`ipaddress.IPv4Address`: the remote IPv4 address. - """ - return self.__remote_address - - def __set_remote_address(self, remote_address): - """ - Sets the remote IPv4 address. - - Args: - remote_address (:class:`ipaddress.IPv4Address`): The new remote IPv4 address. - """ - if remote_address is not None: - self.__remote_address = remote_address - - def __get_remote_port(self): - """ - Returns the remote port. - - Returns: - Integer: the remote port. - """ - return self.__remote_port - - def __set_remote_port(self, remote_port): - """ - Sets the remote port. - - Args: - remote_port (Integer): the new remote port. - - Raises: - ValueError: if ``remote_port`` is less than 0 or greater than 65535. - """ - if remote_port < 0 or remote_port > 65535: - raise ValueError("Remote port must be between 0 and 65535") - self.__remote_port = remote_port - - socket_id = property(__get_socket_id, __set_socket_id) - """Integer. Socket ID.""" - - client_socket_id = property(__get_client_socket_id, __set_client_socket_id) - """Integer. Client socket ID.""" - - remote_address = property(__get_remote_address, __set_remote_address) - """:class:`ipaddress.IPv4Address`. Remote IPv4 address.""" - - remote_port = property(__get_remote_port, __set_remote_port) - """Integer. Remote port.""" - - -class SocketReceivePacket(XBeeAPIPacket): - """ - This class represents a Socket Receive packet. Packet is built using - the parameters of the constructor. - - XBee Cellular modem uses this frame when it receives RF data on the - specified socket. - - .. seealso:: - | :class:`.XBeeAPIPacket` - """ - - __MIN_PACKET_LENGTH = 7 - - def __init__(self, frame_id, socket_id, payload=None): - """ - Class constructor. Instantiates a new :class:`.SocketReceivePacket` object with the - provided parameters. - - Args: - frame_id (Integer): the frame ID of the packet. - socket_id (Integer): the ID of the socket the data has been received on. - payload (Bytearray, optional): data that is received. - - Raises: - ValueError: if ``frame_id`` is less than 0 or greater than 255. - ValueError: if ``socket_id`` is less than 0 or greater than 255. - - .. seealso:: - | :class:`.XBeeAPIPacket` - """ - if frame_id < 0 or frame_id > 255: - raise ValueError("Frame ID must be between 0 and 255.") - if socket_id < 0 or socket_id > 255: - raise ValueError("Socket ID must be between 0 and 255.") - - super().__init__(ApiFrameType.SOCKET_RECEIVE) - self._frame_id = frame_id - self.__socket_id = socket_id - self.__payload = payload - - @staticmethod - def create_packet(raw, operating_mode): - """ - Override method. - - Returns: - :class:`.SocketReceivePacket`. - - Raises: - InvalidPacketException: if the bytearray length is less than 7. (start delim. + length (2 bytes) + frame - type + frame id + socket ID + checksum = 7 bytes). - InvalidPacketException: if the length field of 'raw' is different than its real length. (length field: bytes - 2 and 3) - InvalidPacketException: if the first byte of 'raw' is not the header byte. See :class:`.SpecialByte`. - InvalidPacketException: if the calculated checksum is different than the checksum field value (last byte). - InvalidPacketException: if the frame type is not :attr:`.ApiFrameType.SOCKET_RECEIVE`. - InvalidOperatingModeException: if ``operating_mode`` is not supported. - - .. seealso:: - | :meth:`.XBeePacket.create_packet` - | :meth:`.XBeeAPIPacket._check_api_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=SocketReceivePacket.__MIN_PACKET_LENGTH) - - if raw[3] != ApiFrameType.SOCKET_RECEIVE.code: - raise InvalidPacketException("This packet is not a Socket Receive packet.") - - return SocketReceivePacket(raw[4], raw[5], raw[7:-1]) - - def needs_id(self): - """ - Override method. - - .. seealso:: - | :meth:`.XBeeAPIPacket.needs_id` - """ - return True - - def _get_api_packet_spec_data(self): - """ - Override method. - - .. seealso:: - | :meth:`.XBeeAPIPacket._get_api_packet_spec_data` - """ - ret = bytearray() - ret.append(self.__socket_id) - ret.append(0) # Status (Reserved) - if self.__payload is not None: - ret += self.__payload - return ret - - def _get_api_packet_spec_data_dict(self): - """ - Override method. - - .. seealso:: - | :meth:`.XBeeAPIPacket._get_api_packet_spec_data_dict` - """ - return {DictKeys.SOCKET_ID.value: utils.hex_to_string(bytearray([self.__socket_id])), - DictKeys.STATUS.value: utils.hex_to_string(bytearray([0])), - DictKeys.PAYLOAD.value: utils.hex_to_string(self.__payload) if self.__payload is not None else None} - - def __get_socket_id(self): - """ - Returns the socket ID. - - Returns: - Integer: the socket ID. - """ - return self.__socket_id - - def __set_socket_id(self, socket_id): - """ - Sets the socket ID. - - Args: - socket_id (Integer): The new socket ID. - - Raises: - ValueError: if ``socket_id`` is less than 0 or greater than 255. - """ - if socket_id < 0 or socket_id > 255: - raise ValueError("Socket ID must be between 0 and 255.") - self.__socket_id = socket_id - - def __get_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() - - def __set_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() - - socket_id = property(__get_socket_id, __set_socket_id) - """Integer. Socket ID.""" - - payload = property(__get_payload, __set_payload) - """Bytearray. Payload that was received.""" - - -class SocketReceiveFromPacket(XBeeAPIPacket): - """ - This class represents a Socket Receive From packet. Packet is built using - the parameters of the constructor. - - XBee Cellular modem uses this frame when it receives RF data on the - specified socket. The frame also contains addressing information about - the source. - - .. seealso:: - | :class:`.XBeeAPIPacket` - """ - - __MIN_PACKET_LENGTH = 14 - - def __init__(self, frame_id, socket_id, source_address, source_port, payload=None): - """ - Class constructor. Instantiates a new :class:`.SocketReceiveFromPacket` object with the - provided parameters. - - Args: - frame_id (Integer): the frame ID of the packet. - socket_id (Integer): the ID of the socket the data has been received on. - source_address (:class:`.IPv4Address`): IPv4 address of the source device. - source_port (Integer): source port number. - payload (Bytearray, optional): data that is received. - - Raises: - ValueError: if ``frame_id`` is less than 0 or greater than 255. - ValueError: if ``socket_id`` is less than 0 or greater than 255. - ValueError: if ``source_port`` is less than 0 or greater than 65535. - - .. seealso:: - | :class:`.XBeeAPIPacket` - """ - if frame_id < 0 or frame_id > 255: - raise ValueError("Frame ID must be between 0 and 255.") - if socket_id < 0 or socket_id > 255: - raise ValueError("Socket ID must be between 0 and 255.") - if source_port < 0 or source_port > 65535: - raise ValueError("Source port must be between 0 and 65535") - - super().__init__(ApiFrameType.SOCKET_RECEIVE_FROM) - self._frame_id = frame_id - self.__socket_id = socket_id - self.__source_address = source_address - self.__source_port = source_port - self.__payload = payload - - @staticmethod - def create_packet(raw, operating_mode): - """ - Override method. - - Returns: - :class:`.SocketReceiveFromPacket`. - - Raises: - InvalidPacketException: if the bytearray length is less than 13. (start delim. + length (2 bytes) + frame - type + frame id + socket ID + source address (4 bytes) + source port (2 bytes) + status + - checksum = 14 bytes). - InvalidPacketException: if the length field of 'raw' is different than its real length. (length field: bytes - 2 and 3) - InvalidPacketException: if the first byte of 'raw' is not the header byte. See :class:`.SpecialByte`. - InvalidPacketException: if the calculated checksum is different than the checksum field value (last byte). - InvalidPacketException: if the frame type is not :attr:`.ApiFrameType.SOCKET_RECEIVE_FROM`. - InvalidOperatingModeException: if ``operating_mode`` is not supported. - - .. seealso:: - | :meth:`.XBeePacket.create_packet` - | :meth:`.XBeeAPIPacket._check_api_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=SocketReceiveFromPacket.__MIN_PACKET_LENGTH) - - if raw[3] != ApiFrameType.SOCKET_RECEIVE_FROM.code: - raise InvalidPacketException("This packet is not a Socket Receive From packet.") - - return SocketReceiveFromPacket(raw[4], raw[5], IPv4Address(bytes(raw[6:10])), - utils.bytes_to_int(raw[10:12]), raw[13:-1]) - - def needs_id(self): - """ - Override method. - - .. seealso:: - | :meth:`.XBeeAPIPacket.needs_id` - """ - return True - - def _get_api_packet_spec_data(self): - """ - Override method. - - .. seealso:: - | :meth:`.XBeeAPIPacket._get_api_packet_spec_data` - """ - ret = bytearray() - ret.append(self.__socket_id) - ret += self.__source_address.packed - ret += utils.int_to_bytes(self.__source_port, num_bytes=2) - ret.append(0) # Status (Reserved) - if self.__payload is not None: - ret += self.__payload - return ret - - def _get_api_packet_spec_data_dict(self): - """ - Override method. - - .. seealso:: - | :meth:`.XBeeAPIPacket._get_api_packet_spec_data_dict` - """ - return {DictKeys.SOCKET_ID.value: utils.hex_to_string(bytearray([self.__socket_id])), - DictKeys.SRC_IPV4_ADDR.value: "%s (%s)" % (utils.hex_to_string(self.__source_address.packed), - self.__source_address.exploded), - DictKeys.SRC_PORT.value: "%s (%s)" % (utils.hex_to_string(utils.int_to_bytes(self.__source_port, - num_bytes=2)), - self.__source_port), - DictKeys.STATUS.value: utils.hex_to_string(bytearray([0])), - DictKeys.PAYLOAD.value: utils.hex_to_string(self.__payload, - True) if self.__payload is not None else None} - - def __get_socket_id(self): - """ - Returns the socket ID. - - Returns: - Integer: the socket ID. - """ - return self.__socket_id - - def __set_socket_id(self, socket_id): - """ - Sets the socket ID. - - Args: - socket_id (Integer): The new socket ID. - - Raises: - ValueError: if ``socket_id`` is less than 0 or greater than 255. - """ - if socket_id < 0 or socket_id > 255: - raise ValueError("Socket ID must be between 0 and 255.") - self.__socket_id = socket_id - - def __get_source_address(self): - """ - Returns the IPv4 address of the source device. - - Returns: - :class:`ipaddress.IPv4Address`: the IPv4 address of the source device. - """ - return self.__source_address - - def __set_source_address(self, source_address): - """ - Sets the IPv4 source address. - - Args: - source_address (:class:`ipaddress.IPv4Address`): The new IPv4 source address. - """ - if source_address is not None: - self.__source_address = source_address - - def __get_source_port(self): - """ - Returns the source port. - - Returns: - Integer: the source port. - """ - return self.__source_port - - def __set_source_port(self, source_port): - """ - Sets the destination port. - - Args: - source_port (Integer): the new source port. - - Raises: - ValueError: if ``source_port`` is less than 0 or greater than 65535. - """ - if source_port < 0 or source_port > 65535: - raise ValueError("Source port must be between 0 and 65535") - self.__source_port = source_port - - def __get_payload(self): - """ - Returns the payload to send. - - Returns: - Bytearray: the payload that has been received. - """ - if self.__payload is None: - return None - return self.__payload.copy() - - def __set_payload(self, payload): - """ - Sets the payload to send. - - Args: - payload (Bytearray): the new payload that has been received. - """ - if payload is None: - self.__payload = None - else: - self.__payload = payload.copy() - - socket_id = property(__get_socket_id, __set_socket_id) - """Integer. Socket ID.""" - - source_address = property(__get_source_address, __set_source_address) - """:class:`ipaddress.IPv4Address`. IPv4 address of the source device.""" - - source_port = property(__get_source_port, __set_source_port) - """Integer. Source port.""" - - payload = property(__get_payload, __set_payload) - """Bytearray. Payload that has been received.""" - - -class SocketStatePacket(XBeeAPIPacket): - """ - This class represents a Socket State packet. Packet is built using the - parameters of the constructor. - - This frame is sent out the device's serial port to indicate the state - related to the socket. - - .. seealso:: - | :class:`.XBeeAPIPacket` - """ - - __MIN_PACKET_LENGTH = 7 - - def __init__(self, socket_id, state): - """ - Class constructor. Instantiates a new :class:`.SocketStatePacket` object with the - provided parameters. - - Args: - socket_id (Integer): the socket identifier. - state (:class:`.SocketState`): socket status. - - Raises: - ValueError: if ``socket_id`` is less than 0 or greater than 255. - - .. seealso:: - | :class:`.SockeState` - | :class:`.XBeeAPIPacket` - """ - if socket_id < 0 or socket_id > 255: - raise ValueError("Socket ID must be between 0 and 255.") - - super().__init__(ApiFrameType.SOCKET_STATE) - self.__socket_id = socket_id - self.__state = state - - @staticmethod - def create_packet(raw, operating_mode): - """ - Override method. - - Returns: - :class:`.SocketStatePacket`. - - Raises: - InvalidPacketException: if the bytearray length is less than 7. (start delim. + length (2 bytes) + frame - type + socket ID + state + checksum = 7 bytes). - InvalidPacketException: if the length field of 'raw' is different than its real length. (length field: bytes - 2 and 3) - InvalidPacketException: if the first byte of 'raw' is not the header byte. See :class:`.SpecialByte`. - InvalidPacketException: if the calculated checksum is different than the checksum field value (last byte). - InvalidPacketException: if the frame type is not :attr:`.ApiFrameType.SOCKET_STATUS`. - InvalidOperatingModeException: if ``operating_mode`` is not supported. - - .. seealso:: - | :meth:`.XBeePacket.create_packet` - | :meth:`.XBeeAPIPacket._check_api_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=SocketStatePacket.__MIN_PACKET_LENGTH) - - if raw[3] != ApiFrameType.SOCKET_STATE.code: - raise InvalidPacketException("This packet is not a Socket State packet.") - - return SocketStatePacket(raw[4], SocketState.get(raw[5])) - - 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.__socket_id) - ret.append(self.__state.code) - return ret - - def _get_api_packet_spec_data_dict(self): - """ - Override method. - - .. seealso:: - | :meth:`.XBeeAPIPacket._get_api_packet_spec_data_dict` - """ - return {DictKeys.SOCKET_ID.value: utils.hex_to_string(bytearray([self.__socket_id])), - DictKeys.STATUS.value: "%s (%s)" % (utils.hex_to_string(bytearray([self.__state.code])), - self.__state.description)} - - def __get_socket_id(self): - """ - Returns the socket ID. - - Returns: - Integer: the socket ID. - """ - return self.__socket_id - - def __set_socket_id(self, socket_id): - """ - Sets the socket ID. - - Args: - socket_id (Integer): The new socket ID. - - Raises: - ValueError: if ``socket_id`` is less than 0 or greater than 255. - """ - if socket_id < 0 or socket_id > 255: - raise ValueError("Socket ID must be between 0 and 255.") - self.__socket_id = socket_id - - def __get_state(self): - """ - Returns the socket state. - - Returns: - :class:`.SocketState`: The socket state. - - .. seealso:: - | :class:`.SocketState` - """ - return self.__state - - def __set_state(self, status): - """ - Sets the socket state. - - Args: - status (:class:`.SocketState`): the new socket state. - - .. seealso:: - | :class:`.SocketState` - """ - self.__state = status - - socket_id = property(__get_socket_id, __set_socket_id) - """Integer. Socket ID.""" - - state = property(__get_state, __set_state) - """:class:`.SocketState`. Socket state.""" diff --git a/digi/xbee/packets/wifi.py b/digi/xbee/packets/wifi.py index 0950048..a77d388 100644 --- a/digi/xbee/packets/wifi.py +++ b/digi/xbee/packets/wifi.py @@ -1,4 +1,4 @@ -# Copyright 2017-2019, Digi International Inc. +# Copyright 2017, 2018, 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 @@ -91,14 +91,14 @@ def create_packet(raw, operating_mode): | :meth:`.XBeeAPIPacket._check_api_packet` """ if operating_mode != OperatingMode.ESCAPED_API_MODE and operating_mode != OperatingMode.API_MODE: - raise InvalidOperatingModeException(op_mode=operating_mode) + raise InvalidOperatingModeException(operating_mode.name + " is not supported.") XBeeAPIPacket._check_api_packet(raw, min_length=IODataSampleRxIndicatorWifiPacket.__MIN_PACKET_LENGTH) if raw[3] != ApiFrameType.IO_DATA_SAMPLE_RX_INDICATOR_WIFI.code: - raise InvalidPacketException(message="This packet is not an IO data sample RX indicator Wi-Fi packet.") + raise InvalidPacketException("This packet is not an IO data sample RX indicator Wi-Fi packet.") - return IODataSampleRxIndicatorWifiPacket(IPv4Address(bytes(raw[4:8])), raw[7], raw[8], rf_data=raw[9:-1]) + return IODataSampleRxIndicatorWifiPacket(IPv4Address(bytes(raw[4:8])), raw[7], raw[8], raw[9:-1]) def needs_id(self): """ @@ -374,19 +374,19 @@ def create_packet(raw, operating_mode): | :meth:`.XBeeAPIPacket._check_api_packet` """ if operating_mode != OperatingMode.ESCAPED_API_MODE and operating_mode != OperatingMode.API_MODE: - raise InvalidOperatingModeException(op_mode=operating_mode) + raise InvalidOperatingModeException(operating_mode.name + " is not supported.") XBeeAPIPacket._check_api_packet(raw, min_length=RemoteATCommandWifiPacket.__MIN_PACKET_LENGTH) if raw[3] != ApiFrameType.REMOTE_AT_COMMAND_REQUEST_WIFI.code: - raise InvalidPacketException(message="This packet is not a remote AT command request Wi-Fi packet.") + raise InvalidPacketException("This packet is not a remote AT command request Wi-Fi packet.") return RemoteATCommandWifiPacket( raw[4], IPv4Address(bytes(raw[9:13])), raw[13], raw[14:16].decode("utf8"), - parameter=raw[16:-1] + raw[16:-1] ) def needs_id(self): @@ -601,18 +601,18 @@ def create_packet(raw, operating_mode): | :meth:`.XBeeAPIPacket._check_api_packet` """ if operating_mode != OperatingMode.ESCAPED_API_MODE and operating_mode != OperatingMode.API_MODE: - raise InvalidOperatingModeException(op_mode=operating_mode) + raise InvalidOperatingModeException(operating_mode.name + " is not supported.") XBeeAPIPacket._check_api_packet(raw, min_length=RemoteATCommandResponseWifiPacket.__MIN_PACKET_LENGTH) if raw[3] != ApiFrameType.REMOTE_AT_COMMAND_RESPONSE_WIFI.code: - raise InvalidPacketException(message="This packet is not a remote AT command response Wi-Fi packet.") + raise InvalidPacketException("This packet is not a remote AT command response Wi-Fi packet.") return RemoteATCommandResponseWifiPacket(raw[4], IPv4Address(bytes(raw[9:13])), raw[13:15].decode("utf8"), ATCommandStatus.get(raw[15]), - comm_value=raw[16:-1]) + raw[16:-1]) def needs_id(self): """ diff --git a/digi/xbee/packets/zigbee.py b/digi/xbee/packets/zigbee.py deleted file mode 100644 index 47cc880..0000000 --- a/digi/xbee/packets/zigbee.py +++ /dev/null @@ -1,360 +0,0 @@ -# Copyright 2019, 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 - -from digi.xbee.exception import InvalidOperatingModeException, InvalidPacketException -from digi.xbee.models.address import XBee64BitAddress, XBee16BitAddress -from digi.xbee.models.mode import OperatingMode -from digi.xbee.models.options import RegisterKeyOptions -from digi.xbee.models.status import ZigbeeRegisterStatus -from digi.xbee.packets.aft import ApiFrameType -from digi.xbee.packets.base import XBeeAPIPacket, DictKeys - - -class RegisterJoiningDevicePacket(XBeeAPIPacket): - """ - This class represents a Register Joining Device packet. Packet is built - using the parameters of the constructor or providing a valid API - payload. - - Use this frame to securely register a joining device to a trust center. - Registration is the process by which a node is authorized to join the - network using a preconfigured link key or installation code that is - conveyed to the trust center out-of-band (using a physical interface and - not over-the-air). - - If registering a device with a centralized trust center (EO = 2), then the - key entry will only persist for KT seconds before expiring. - - Registering devices in a distributed trust center (EO = 0) is persistent - and the key entry will never expire unless explicitly removed. - - To remove a key entry on a distributed trust center, this frame should be - issued with a null (None) key. In a centralized trust center you cannot - use this method to explicitly remove the key entries. - - .. seealso:: - | :class:`.XBeeAPIPacket` - """ - - __MIN_PACKET_LENGTH = 17 - - def __init__(self, frame_id, registrant_address, options, key): - """ - Class constructor. Instantiates a new :class:`.RegisterJoiningDevicePacket` object with the - provided parameters. - - Args: - frame_id (integer): the frame ID of the packet. - registrant_address (:class:`.XBee64BitAddress`): the 64-bit address of the destination device. - options (:class:`.RegisterKeyOptions`): the register options indicating the key source. - key (Bytearray): key of the device to register. Up to 16 bytes if entering a Link Key or up to - 18 bytes (16-byte code + 2 byte CRC) if entering an Install Code. - - Raises: - ValueError: if ``frame_id`` is less than 0 or greater than 255. - - .. seealso:: - | :class:`.XBee64BitAddress` - | :class:`.XBeeAPIPacket` - | :class:`.RegisterKeyOptions` - """ - if frame_id < 0 or frame_id > 255: - raise ValueError("Frame id must be between 0 and 255.") - - super().__init__(ApiFrameType.REGISTER_JOINING_DEVICE) - self._frame_id = frame_id - self.__registrant_address = registrant_address - self.__options = options - self.__key = key - - @staticmethod - def create_packet(raw, operating_mode): - """ - Override method. - - Returns: - :class:`.RegisterJoiningDevicePacket`. - - Raises: - InvalidPacketException: if the bytearray length is less than 17. (start delim. + length (2 bytes) + frame - type + frame id + 64-bit registrant addr. (8 bytes) + 16-bit registrant addr. (2 bytes) + options - + checksum = 17 bytes). - InvalidPacketException: if the length field of 'raw' is different than its real length. (length field: bytes - 2 and 3) - InvalidPacketException: if the first byte of 'raw' is not the header byte. See :class:`.SpecialByte`. - InvalidPacketException: if the calculated checksum is different than the checksum field value (last byte). - InvalidPacketException: if the frame type is not :attr:`.ApiFrameType.REGISTER_JOINING_DEVICE`. - InvalidOperatingModeException: if ``operating_mode`` is not supported. - - .. seealso:: - | :meth:`.XBeePacket.create_packet` - | :meth:`.XBeeAPIPacket._check_api_packet` - """ - if operating_mode not in [OperatingMode.ESCAPED_API_MODE, OperatingMode.API_MODE]: - raise InvalidOperatingModeException(operating_mode.name + " is not supported.") - - XBeeAPIPacket._check_api_packet(raw, min_length=RegisterJoiningDevicePacket.__MIN_PACKET_LENGTH) - - if raw[3] != ApiFrameType.REGISTER_JOINING_DEVICE.code: - raise InvalidPacketException("This packet is not a Register Joining Device packet.") - - return RegisterJoiningDevicePacket(raw[4], - XBee64BitAddress(raw[5:13]), - RegisterKeyOptions.get(raw[15]), - raw[16:-1]) - - def needs_id(self): - """ - Override method. - - .. seealso:: - | :meth:`.XBeeAPIPacket.needs_id` - """ - return True - - def _get_api_packet_spec_data(self): - """ - Override method. - - .. seealso:: - | :meth:`.XBeeAPIPacket._get_api_packet_spec_data` - """ - ret = self.__registrant_address.address - ret += XBee16BitAddress.UNKNOWN_ADDRESS.address - ret.append(self.__options.code) - if self.__key is not None: - ret += self.__key - return ret - - def _get_api_packet_spec_data_dict(self): - """ - Override method. - - .. seealso:: - | :meth:`.XBeeAPIPacket._get_api_packet_spec_data_dict` - """ - return {DictKeys.X64BIT_ADDR: "%s (%s)" % (self.__registrant_address.packed, - self.__registrant_address.exploded), - DictKeys.RESERVED: XBee16BitAddress.UNKNOWN_ADDRESS.address, - DictKeys.OPTIONS: "%s (%s)" % (self.__options.code, - self.__options.description), - DictKeys.KEY: list(self.__key) if self.__key is not None else None} - - def __get_registrant_address(self): - """ - Returns the 64-bit registrant address. - - Returns: - :class:`.XBee64BitAddress`: the 64-bit registrant address. - - .. seealso:: - | :class:`.XBee64BitAddress` - """ - return self.__registrant_address - - def __set_registrant_address(self, registrant_address): - """ - Sets the 64-bit registrant address. - - Args: - registrant_address (:class:`.XBee64BitAddress`): The new 64-bit registrant address. - - .. seealso:: - | :class:`.XBee64BitAddress` - """ - if registrant_address is not None: - self.__registrant_address = registrant_address - - def __get_options(self): - """ - Returns the register options value. - - Returns: - :class:`.RegisterKeyOptions`: the register options indicating the key source. - - .. seealso:: - | :class:`.RegisterKeyOptions` - """ - return self.__options - - def __set_options(self, options): - """ - Sets the register options value. - - Args: - options (:class:`.RegisterKeyOptions`): the new register options. - - .. seealso:: - | :class:`.RegisterKeyOptions` - """ - self.__options = options - - def __get_key(self): - """ - Returns the register key. - - Returns: - Bytearray: the register key. - """ - if self.__key is None: - return None - return self.__key.copy() - - def __set_key(self, key): - """ - Sets the register key. - - Args: - key (Bytearray): the new register key. - """ - if key is None: - self.__key = None - else: - self.__key = key.copy() - - registrant_address = property(__get_registrant_address, __set_registrant_address) - """:class:`.XBee64BitAddress`. Registrant 64-bit address.""" - - options = property(__get_options, __set_options) - """:class:`.RegisterKeyOptions`. Register options.""" - - key = property(__get_key, __set_key) - """Bytearray. Register key.""" - - -class RegisterDeviceStatusPacket(XBeeAPIPacket): - """ - This class represents a Register Device Status packet. Packet is built - using the parameters of the constructor or providing a valid API - payload. - - This frame is sent out of the UART of the trust center as a response to - a 0x24 Register Device frame, indicating whether the registration was - successful or not. - - .. seealso:: - | :class:`.RegisterJoiningDevicePacket` - | :class:`.XBeeAPIPacket` - """ - - __MIN_PACKET_LENGTH = 7 - - def __init__(self, frame_id, status): - """ - Class constructor. Instantiates a new :class:`.RegisterDeviceStatusPacket` object with the - provided parameters. - - Args: - frame_id (integer): the frame ID of the packet. - status (:class:`.ZigbeeRegisterStatus`): status of the register device operation. - - Raises: - ValueError: if ``frame_id`` is less than 0 or greater than 255. - - .. seealso:: - | :class:`.XBeeAPIPacket` - | :class:`.ZigbeeRegisterStatus` - """ - if frame_id < 0 or frame_id > 255: - raise ValueError("Frame id must be between 0 and 255.") - - super().__init__(ApiFrameType.REGISTER_JOINING_DEVICE_STATUS) - self._frame_id = frame_id - self.__status = status - - @staticmethod - def create_packet(raw, operating_mode): - """ - Override method. - - Returns: - :class:`.RegisterDeviceStatusPacket`. - - Raises: - InvalidPacketException: if the bytearray length is less than 17. (start delim. + length (2 bytes) + frame - type + frame id + status + checksum = 7 bytes). - InvalidPacketException: if the length field of 'raw' is different than its real length. (length field: bytes - 1 and 3) - InvalidPacketException: if the first byte of 'raw' is not the header byte. See :class:`.SpecialByte`. - InvalidPacketException: if the calculated checksum is different than the checksum field value (last byte). - InvalidPacketException: if the frame type is not :attr:`.ApiFrameType.REGISTER_JOINING_DEVICE_STATUS`. - InvalidOperatingModeException: if ``operating_mode`` is not supported. - - .. seealso:: - | :meth:`.XBeePacket.create_packet` - | :meth:`.XBeeAPIPacket._check_api_packet` - """ - if operating_mode not in [OperatingMode.ESCAPED_API_MODE, OperatingMode.API_MODE]: - raise InvalidOperatingModeException(operating_mode.name + " is not supported.") - - XBeeAPIPacket._check_api_packet(raw, min_length=RegisterDeviceStatusPacket.__MIN_PACKET_LENGTH) - - if raw[3] != ApiFrameType.REGISTER_JOINING_DEVICE_STATUS.code: - raise InvalidPacketException("This packet is not a Register Device Status packet.") - - return RegisterDeviceStatusPacket(raw[4], ZigbeeRegisterStatus.get(raw[5])) - - def needs_id(self): - """ - Override method. - - .. seealso:: - | :meth:`.XBeeAPIPacket.needs_id` - """ - return True - - def _get_api_packet_spec_data(self): - """ - Override method. - - .. seealso:: - | :meth:`.XBeeAPIPacket._get_api_packet_spec_data` - """ - return bytearray([self.__status.code]) - - def _get_api_packet_spec_data_dict(self): - """ - Override method. - - .. seealso:: - | :meth:`.XBeeAPIPacket._get_api_packet_spec_data_dict` - """ - return {DictKeys.STATUS: "%s (%s)" % (self.__status.code, - self.__status.description)} - - def __get_status(self): - """ - Returns the register device status. - - Returns: - :class:`.ZigbeeRegisterStatus`: the register device status. - - .. seealso:: - | :class:`.ZigbeeRegisterStatus` - """ - return self.__status - - def __set_status(self, status): - """ - Sets the register device status. - - Args: - status (:class:`.ZigbeeRegisterStatus`): the new register device status. - - .. seealso:: - | :class:`.ZigbeeRegisterStatus` - """ - self.__status = status - - status = property(__get_status, __set_status) - """:class:`.ZigbeeRegisterStatus`. Register device status.""" diff --git a/digi/xbee/profile.py b/digi/xbee/profile.py deleted file mode 100644 index e3de9c1..0000000 --- a/digi/xbee/profile.py +++ /dev/null @@ -1,1330 +0,0 @@ -# Copyright 2019, 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 logging -import os -import shutil -import tempfile -import serial -import time -import zipfile - -from digi.xbee import firmware -from digi.xbee.devices import XBeeDevice, RemoteXBeeDevice -from digi.xbee.exception import XBeeException, TimeoutException, FirmwareUpdateException, ATCommandException -from digi.xbee.filesystem import LocalXBeeFileSystemManager, FileSystemException, FileSystemNotSupportedException -from digi.xbee.models.atcomm import ATStringCommand -from digi.xbee.models.hw import HardwareVersion -from digi.xbee.util import utils -from enum import Enum, unique -from pathlib import Path -from serial.serialutil import SerialException -from xml.etree import ElementTree -from xml.etree.ElementTree import ParseError - -_ERROR_ACCESS_FILESYSTEM = "Could not access XBee device file system" -_ERROR_DEVICE_NOT_VALID = "The XBee device is not valid" -_ERROR_FILESYSTEM_NOT_SUPPORTED = "XBee device does not have file system support" -_ERROR_FIRMWARE_FOLDER_NOT_EXIST = "Firmware folder does not exist" -_ERROR_FIRMWARE_NOT_COMPATIBLE = "The XBee profile is not compatible with the device firmware" -_ERROR_FIRMWARE_SETTING_NOT_EXIST = "Firmware setting '%s' does not exist" -_ERROR_FIRMWARE_XML_INVALID = "Invalid firmware XML file contents: %s" -_ERROR_FIRMWARE_XML_NOT_EXIST = "Firmware XML file does not exist" -_ERROR_FIRMWARE_XML_PARSE = "Error parsing firmware XML file: %s" -_ERROR_HARDWARE_NOT_COMPATIBLE = "The XBee profile is not compatible with the device hardware" -_ERROR_HARDWARE_NOT_COMPATIBLE_XBEE3 = "Only XBee 3 devices support firmware update in the XBee profile" -_ERROR_OPEN_DEVICE = "Error opening XBee device: %s" -_ERROR_PROFILE_NOT_VALID = "The XBee profile is not valid" -_ERROR_PROFILE_INVALID = "Invalid XBee profile: %s" -_ERROR_PROFILE_PATH_INVALID = "Profile path '%s' is not valid" -_ERROR_PROFILE_UNCOMPRESS = "Error un-compressing profile file: %s" -_ERROR_PROFILE_TEMP_DIR = "Error creating temporary directory: %s" -_ERROR_PROFILE_XML_NOT_EXIST = "Profile XML file does not exist" -_ERROR_PROFILE_XML_INVALID = "Invalid profile XML file contents: %s" -_ERROR_PROFILE_XML_PARSE = "Error parsing profile XML file: %s" -_ERROR_PROFILES_NOT_SUPPORTED = "XBee profiles are only supported in XBee 3 devices" -_ERROR_READ_REMOTE_PARAMETER = "Error reading remote parameter: %s" -_ERROR_UPDATE_FILESYSTEM = "Error updating XBee filesystem: %s" -_ERROR_UPDATE_FIRMWARE = "Error updating XBee firmware: %s" -_ERROR_UPDATE_SERIAL_PORT = "Error re-configuring XBee device serial port: %s" -_ERROR_UPDATE_SETTINGS = "Error updating XBee settings: %s" - -_FILESYSTEM_FOLDER = "filesystem" - -_FIRMWARE_FOLDER_NAME = "radio_fw" -_FIRMWARE_XML_FILE_NAME = "radio_fw.xml" - -_IPV4_SEPARATOR = "." -_IPV6_SEPARATOR = ":" - -_PARAMETER_READ_RETRIES = 3 -_PARAMETER_WRITE_RETRIES = 3 -_PARAMETERS_SERIAL_PORT = [ATStringCommand.BD.command, - ATStringCommand.NB.command, - ATStringCommand.SB.command, - ATStringCommand.D7.command] - -_PROFILE_XML_FILE_NAME = "profile.xml" - -SUPPORTED_HARDWARE_VERSIONS = (HardwareVersion.XBEE3.code, - HardwareVersion.XBEE3_SMT.code, - HardwareVersion.XBEE3_TH.code) - -_TASK_CONNECT_FILESYSTEM = "Connecting with device filesystem" -_TASK_FORMAT_FILESYSTEM = "Formatting filesystem" -_TASK_READING_DEVICE_PARAMETERS = "Reading device parameters" -_TASK_UPDATE_FILE = "Updating file '%s'" -_TASK_UPDATE_SETTINGS = "Updating XBee settings" - -_VALUE_CTS_ON = "1" - -_WILDCARD_BOOTLOADER = "xb3-boot*.gbl" -_WILDCARD_CELLULAR_FIRMWARE = "fw_.*" -_WILDCARD_CELLULAR_BOOTLOADER = "bl_.*" -_WILDCARD_EBIN = "*.ebin" -_WILDCARD_EHX2 = "*.ehx2" -_WILDCARD_GBL = "*.gbl" -_WILDCARD_OTA = "*.ota" -_WILDCARD_OTB = "*.otb" -_WILDCARD_XML = "*.xml" -_WILDCARDS_FIRMWARE_BINARY_FILES = [_WILDCARD_EBIN, _WILDCARD_EHX2, _WILDCARD_GBL, _WILDCARD_OTA, _WILDCARD_OTB] - -_XML_COMMAND = "command" -_XML_CONTROL_TYPE = "control_type" -_XML_DEFAULT_VALUE = "default_value" -_XML_FIRMWARE_FIRMWARE = "firmware" -_XML_FIRMWARE_FIRMWARE_VERSION = "fw_version" -_XML_FIRMWARE_HARDWARE_VERSION = "firmware/hw_version" -_XML_FIRMWARE_SETTING = ".//setting" -_XML_FORMAT = "format" -_XML_PROFILE_AT_SETTING = "profile/settings/setting" -_XML_PROFILE_DESCRIPTION = "profile/description" -_XML_PROFILE_FLASH_FIRMWARE_OPTION = "profile/flash_fw_action" -_XML_PROFILE_RESET_SETTINGS = "profile/reset_settings" -_XML_PROFILE_ROOT = "data" -_XML_PROFILE_VERSION = "profile/profile_version" -_XML_PROFILE_XML_FIRMWARE_FILE = "profile/description_file" - -_log = logging.getLogger(__name__) - - -@unique -class FirmwareBaudrate(Enum): - """ - This class lists the available firmware baudrate options for XBee Profiles. - - | Inherited properties: - | **name** (String): The name of this FirmwareBaudrate. - | **value** (Integer): The ID of this FirmwareBaudrate. - """ - BD_1200 = (0x0, 1200) - BD_2400 = (0x1, 2400) - BD_4800 = (0x2, 4800) - BD_9600 = (0x3, 9600) - BD_19200 = (0x4, 19200) - BD_38400 = (0x5, 38400) - BD_57600 = (0x6, 57600) - BD_115200 = (0x7, 115200) - BD_230400 = (0x8, 230400) - BD_460800 = (0x9, 460800) - BD_921600 = (0xA, 921600) - - def __init__(self, index, baudrate): - self.__index = index - self.__baudrate = baudrate - - @classmethod - def get(cls, index): - """ - Returns the FirmwareBaudrate for the given index. - - Args: - index (Integer): the index of the FirmwareBaudrate to get. - - Returns: - :class:`.FirmwareBaudrate`: the FirmwareBaudrate with the given index, ``None`` if - there is not a FirmwareBaudrate with that index. - """ - if index is None: - return FirmwareBaudrate.BD_9600 - for value in FirmwareBaudrate: - if value.index == index: - return value - - return None - - @property - def index(self): - """ - Returns the index of the FirmwareBaudrate element. - - Returns: - Integer: the index of the FirmwareBaudrate element. - """ - return self.__index - - @property - def baudrate(self): - """ - Returns the baudrate of the FirmwareBaudrate element. - - Returns: - Integer: the baudrate of the FirmwareBaudrate element. - """ - return self.__baudrate - - -FirmwareBaudrate.__doc__ += utils.doc_enum(FirmwareBaudrate) - - -@unique -class FirmwareParity(Enum): - """ - This class lists the available firmware parity options for XBee Profiles. - - | Inherited properties: - | **name** (String): The name of this FirmwareParity. - | **value** (Integer): The ID of this FirmwareParity. - """ - NONE = (0, serial.PARITY_NONE) - EVEN = (1, serial.PARITY_EVEN) - ODD = (2, serial.PARITY_ODD) - MARK = (3, serial.PARITY_MARK) - SPACE = (4, serial.PARITY_SPACE) - - def __init__(self, index, parity): - self.__index = index - self.__parity = parity - - @classmethod - def get(cls, index): - """ - Returns the FirmwareParity for the given index. - - Args: - index (Integer): the index of the FirmwareParity to get. - - Returns: - :class:`.FirmwareParity`: the FirmwareParity with the given index, ``None`` if - there is not a FirmwareParity with that index. - """ - if index is None: - return FirmwareParity.NONE - for value in FirmwareParity: - if value.index == index: - return value - - return None - - @property - def index(self): - """ - Returns the index of the FirmwareParity element. - - Returns: - Integer: the index of the FirmwareParity element. - """ - return self.__index - - @property - def parity(self): - """ - Returns the parity of the FirmwareParity element. - - Returns: - String: the parity of the FirmwareParity element. - """ - return self.__parity - - -FirmwareParity.__doc__ += utils.doc_enum(FirmwareParity) - - -@unique -class FirmwareStopbits(Enum): - """ - This class lists the available firmware stop bits options for XBee Profiles. - - | Inherited properties: - | **name** (String): The name of this FirmwareStopbits. - | **value** (Integer): The ID of this FirmwareStopbits. - """ - SB_1 = (0, serial.STOPBITS_ONE) - SB_2 = (1, serial.STOPBITS_TWO) - SB_1_5 = (2, serial.STOPBITS_ONE_POINT_FIVE) - - def __init__(self, index, stop_bits): - self.__index = index - self.__stop_bits = stop_bits - - @classmethod - def get(cls, index): - """ - Returns the FirmwareStopbits for the given index. - - Args: - index (Integer): the index of the FirmwareStopbits to get. - - Returns: - :class:`.FirmwareStopbits`: the FirmwareStopbits with the given index, ``None`` if - there is not a FirmwareStopbits with that index. - """ - if index is None: - return FirmwareStopbits.SB_1 - for value in FirmwareStopbits: - if value.index == index: - return value - - return None - - @property - def index(self): - """ - Returns the index of the FirmwareStopbits element. - - Returns: - Integer: the index of the FirmwareStopbits element. - """ - return self.__index - - @property - def stop_bits(self): - """ - Returns the stop bits of the FirmwareStopbits element. - - Returns: - Float: the stop bits of the FirmwareStopbits element. - """ - return self.__stop_bits - - -FirmwareStopbits.__doc__ += utils.doc_enum(FirmwareStopbits) - - -@unique -class FlashFirmwareOption(Enum): - """ - This class lists the available flash firmware options for XBee Profiles. - - | Inherited properties: - | **name** (String): The name of this FlashFirmwareOption. - | **value** (Integer): The ID of this FlashFirmwareOption. - """ - FLASH_ALWAYS = (0, "Flash always") - FLASH_DIFFERENT = (1, "Flash firmware if it is different") - DONT_FLASH = (2, "Do not flash firmware") - - def __init__(self, code, description): - self.__code = code - self.__description = description - - @classmethod - def get(cls, code): - """ - Returns the FlashFirmwareOption for the given code. - - Args: - code (Integer): the code of the flash firmware option to get. - - Returns: - :class:`.FlashFirmwareOption`: the FlashFirmwareOption with the given code, ``None`` if - there is not a FlashFirmwareOption with that code. - """ - for value in FlashFirmwareOption: - if value.code == code: - return value - - return None - - @property - def code(self): - """ - Returns the code of the FlashFirmwareOption element. - - Returns: - Integer: the code of the FlashFirmwareOption element. - """ - return self.__code - - @property - def description(self): - """ - Returns the description of the FlashFirmwareOption element. - - Returns: - String: the description of the FlashFirmwareOption element. - """ - return self.__description - - -FlashFirmwareOption.__doc__ += utils.doc_enum(FlashFirmwareOption) - - -@unique -class XBeeSettingType(Enum): - """ - This class lists the available firmware setting types. - - | Inherited properties: - | **name** (String): The name of this XBeeSettingType. - | **value** (Integer): The ID of this XBeeSettingType. - """ - NUMBER = ("number", "Number") - COMBO = ("combo", "Combo") - TEXT = ("text", "Text") - BUTTON = ("button", "Button") - NO_TYPE = ("none", "No type") - - def __init__(self, tag, description): - self.__tag = tag - self.__description = description - - @classmethod - def get(cls, tag): - """ - Returns the XBeeSettingType for the given tag. - - Args: - tag (String): the tag of the XBeeSettingType to get. - - Returns: - :class:`.XBeeSettingType`: the XBeeSettingType with the given tag, ``None`` if - there is not a XBeeSettingType with that tag. - """ - for value in XBeeSettingType: - if value.tag == tag: - return value - - return None - - @property - def tag(self): - """ - Returns the tag of the XBeeSettingType element. - - Returns: - String: the tag of the XBeeSettingType element. - """ - return self.__tag - - @property - def description(self): - """ - Returns the description of the XBeeSettingType element. - - Returns: - String: the description of the XBeeSettingType element. - """ - return self.__description - - -XBeeSettingType.__doc__ += utils.doc_enum(XBeeSettingType) - - -@unique -class XBeeSettingFormat(Enum): - """ - This class lists the available text firmware setting formats. - - | Inherited properties: - | **name** (String): The name of this XBeeSettingFormat. - | **value** (Integer): The ID of this XBeeSettingFormat. - """ - HEX = ("HEX", "Hexadecimal") - ASCII = ("ASCII", "ASCII") - IPV4 = ("IPV4", "IPv4") - IPV6 = ("IPV6", "IPv6") - PHONE = ("PHONE", "phone") - NO_FORMAT = ("none", "No format") - - def __init__(self, tag, description): - self.__tag = tag - self.__description = description - - @classmethod - def get(cls, tag): - """ - Returns the XBeeSettingFormat for the given tag. - - Args: - tag (String): the tag of the XBeeSettingFormat to get. - - Returns: - :class:`.XBeeSettingFormat`: the XBeeSettingFormat with the given tag, ``None`` if - there is not a XBeeSettingFormat with that tag. - """ - for value in XBeeSettingFormat: - if value.tag == tag: - return value - - return None - - @property - def tag(self): - """ - Returns the tag of the XBeeSettingFormat element. - - Returns: - String: the tag of the XBeeSettingFormat element. - """ - return self.__tag - - @property - def description(self): - """ - Returns the description of the XBeeSettingFormat element. - - Returns: - String: the description of the XBeeSettingFormat element. - """ - return self.__description - - -XBeeSettingFormat.__doc__ += utils.doc_enum(XBeeSettingFormat) - - -class XBeeProfileSetting(object): - """ - This class represents an XBee profile setting and provides information like - the setting name, type, format and value. - """ - - def __init__(self, name, setting_type, setting_format, value): - """ - Class constructor. Instantiates a new :class:`.XBeeProfileSetting` with the given parameters. - - Args: - name (String): the setting name - setting_type (:class:`.XBeeSettingType`): the setting type - setting_format (:class:`.XBeeSettingType`): the setting format - value (String): the setting value - """ - self._name = name - self._type = setting_type - self._format = setting_format - self._value = value - self._bytearray_value = self._setting_value_to_bytearray() - - def _setting_value_to_bytearray(self): - """ - Transforms the setting value to a byte array to be written in the XBee device. - - Returns: - (Bytearray): the setting value formatted as byte array - """ - if self._type in (XBeeSettingType.COMBO, XBeeSettingType.NUMBER): - return utils.hex_string_to_bytes(self._value) - elif self._type is XBeeSettingType.TEXT: - if self._format in (XBeeSettingFormat.ASCII, XBeeSettingFormat.PHONE): - return bytearray(self._value, 'utf8') - elif self._format in (XBeeSettingFormat.HEX, XBeeSettingFormat.NO_FORMAT): - return utils.hex_string_to_bytes(self._value) - elif self._format is XBeeSettingFormat.IPV4: - octets = list(map(int, self._value.split(_IPV4_SEPARATOR))) - return bytearray(octets) - elif self._format is XBeeSettingFormat.IPV6: - if _IPV6_SEPARATOR in self._value: - return bytearray(self._value, 'utf8') - elif self._type in (XBeeSettingType.BUTTON, XBeeSettingType.NO_TYPE): - return bytearray(0) - - return self._value - - @property - def name(self): - """ - Returns the XBee setting name. - - Returns: - String: the XBee setting name. - """ - return self._name - - @property - def type(self): - """ - Returns the XBee setting type. - - Returns: - :class:`.XBeeSettingType`: the XBee setting type. - """ - return self._type - - @property - def format(self): - """ - Returns the XBee setting format. - - Returns: - :class:`.XBeeSettingFormat`: the XBee setting format. - """ - return self._format - - @property - def value(self): - """ - Returns the XBee setting value as string. - - Returns: - String: the XBee setting value as string. - """ - return self._value - - @property - def bytearray_value(self): - """ - Returns the XBee setting value as bytearray to be set in the device. - - Returns: - Bytearray: the XBee setting value as bytearray to be set in the device. - """ - return self._bytearray_value - - -class ReadProfileException(XBeeException): - """ - This exception will be thrown when any problem reading the XBee profile occurs. - - All functionality of this class is the inherited from `Exception - `_. - """ - pass - - -class UpdateProfileException(XBeeException): - """ - This exception will be thrown when any problem updating the XBee profile into a device occurs. - - All functionality of this class is the inherited from `Exception - `_. - """ - pass - - -class XBeeProfile(object): - """ - Helper class used to manage serial port break line in a parallel thread. - """ - - def __init__(self, profile_file): - """ - Class constructor. Instantiates a new :class:`.XBeeProfile` with the given parameters. - - Args: - profile_file (String): path of the '.xpro' profile file. - - Raises: - ProfileReadException: if there is any error reading the profile file. - ValueError: if the provided profile file is not valid - """ - if not os.path.isfile(profile_file): - raise ValueError(_ERROR_PROFILE_PATH_INVALID % profile_file) - self._profile_file = profile_file - self._profile_folder = None - self._profile_xml_file = None - self._firmware_xml_file = None - self._bootloader_file = None - self._version = 0 - self._flash_firmware_option = FlashFirmwareOption.FLASH_DIFFERENT - self._description = None - self._reset_settings = True - self._profile_settings = [] - self._firmware_binary_files = [] - self._file_system_path = None - self._cellular_firmware_files = [] - self._cellular_bootloader_files = [] - self._firmware_version = None - self._hardware_version = None - - self._uncompress_profile() - self._check_profile_integrity() - self._parse_xml_profile_file() - self._parse_xml_firmware_file() - - def __del__(self): - if not hasattr(self, 'profile_folder'): - return - - if self._profile_folder is not None and os.path.isdir(self._profile_folder): - shutil.rmtree(self._profile_folder) - - def _parse_xml_profile_file(self): - """ - Parses the XML profile file and stores the required parameters. - - Raises: - ProfileReadException: if there is any error parsing the XML profile file. - """ - _log.debug("Parsing XML profile file %s:" % self._profile_xml_file) - try: - root = ElementTree.parse(self._profile_xml_file).getroot() - # XML firmware file. Mandatory. - firmware_xml_file_element = root.find(_XML_PROFILE_XML_FIRMWARE_FILE) - if firmware_xml_file_element is None: - self._throw_read_exception(_ERROR_PROFILE_XML_INVALID % "missing firmware file element") - self._firmware_xml_file = os.path.join(self._profile_folder, _FIRMWARE_FOLDER_NAME, - firmware_xml_file_element.text) - if not os.path.isfile(self._firmware_xml_file): - self._throw_read_exception(_ERROR_FIRMWARE_XML_NOT_EXIST) - _log.debug(" - XML firmware file: %s" % self._firmware_xml_file) - # Version. Optional. - version_element = root.find(_XML_PROFILE_VERSION) - if version_element is not None: - self._version = int(version_element.text) - _log.debug(" - Version: %d" % self._version) - # Flash firmware option. Required. - flash_firmware_option_element = root.find(_XML_PROFILE_FLASH_FIRMWARE_OPTION) - if flash_firmware_option_element is not None: - self._flash_firmware_option = FlashFirmwareOption.get(int(flash_firmware_option_element.text)) - if self._flash_firmware_option is None: - self._throw_read_exception(_ERROR_PROFILE_XML_INVALID % "invalid flash firmware option") - _log.debug(" - Flash firmware option: %s" % self._flash_firmware_option.description) - # Description. Optional. - description_element = root.find(_XML_PROFILE_DESCRIPTION) - if description_element is not None: - self._description = description_element.text - _log.debug(" - Description: %s" % self._description) - # Reset settings. Optional. - reset_settings_element = root.find(_XML_PROFILE_RESET_SETTINGS) - if reset_settings_element is not None: - self._reset_settings = reset_settings_element.text in ("True", "true", "1") - _log.debug(" - Reset settings: %s" % self._reset_settings) - # Parse AT settings. - _log.debug(" - AT settings:") - firmware_root = ElementTree.parse(self._firmware_xml_file).getroot() - setting_elements = root.findall(_XML_PROFILE_AT_SETTING) - if not setting_elements: - _log.debug(" - None") - return - for setting_element in setting_elements: - setting_name = setting_element.get(_XML_COMMAND) - setting_value = setting_element.text - for firmware_setting_element in firmware_root.findall(_XML_FIRMWARE_SETTING): - if firmware_setting_element.get(_XML_COMMAND) == setting_name: - setting_type_element = firmware_setting_element.find(_XML_CONTROL_TYPE) - setting_type = XBeeSettingType.NO_TYPE - if setting_type_element is not None: - setting_type = XBeeSettingType.get(setting_type_element.text) - setting_format_element = firmware_setting_element.find(_XML_FORMAT) - setting_format = XBeeSettingFormat.NO_FORMAT - if setting_format_element is not None: - setting_format = XBeeSettingFormat.get(setting_format_element.text) - profile_setting = XBeeProfileSetting(setting_name, setting_type, setting_format, - setting_value) - _log.debug(" - Setting '%s' - type: %s - format: %s - value: %s" % - (profile_setting.name, profile_setting.type.description, - profile_setting.format.description, profile_setting.value)) - self._profile_settings.append(profile_setting) - - except ParseError as e: - self._throw_read_exception(_ERROR_PROFILE_XML_PARSE % str(e)) - - def _uncompress_profile(self): - """ - Un-compresses the profile into a temporary folder and saves the folder location. - - Raises: - ProfileReadException: if there is any error un-compressing the profile file. - """ - try: - self._profile_folder = tempfile.mkdtemp() - except (PermissionError, FileExistsError) as e: - self._throw_read_exception(_ERROR_PROFILE_TEMP_DIR % str(e)) - - _log.debug("Un-compressing profile into '%s'" % self._profile_folder) - try: - with zipfile.ZipFile(self._profile_file, "r") as zip_ref: - zip_ref.extractall(self._profile_folder) - except Exception as e: - _log.error(_ERROR_PROFILE_UNCOMPRESS % str(e)) - self._throw_read_exception(_ERROR_PROFILE_UNCOMPRESS % str(e)) - - def _check_profile_integrity(self): - """ - Checks the profile integrity and stores the required information. - - Raises: - ProfileReadException: if there is any error checking the profile integrity. - """ - # Profile XML file. - self._profile_xml_file = os.path.join(self._profile_folder, _PROFILE_XML_FILE_NAME) - if not os.path.isfile(self._profile_xml_file): - self._throw_read_exception(_ERROR_PROFILE_XML_NOT_EXIST) - # Firmware folder. - if not os.path.isdir(os.path.join(self._profile_folder, _FIRMWARE_FOLDER_NAME)): - self._throw_read_exception(_ERROR_FIRMWARE_FOLDER_NOT_EXIST) - # Firmware XML file pattern. - firmware_path = Path(os.path.join(self._profile_folder, _FIRMWARE_FOLDER_NAME)) - if len(list(firmware_path.rglob(_WILDCARD_XML))) is 0: - self._throw_read_exception(_ERROR_FIRMWARE_XML_NOT_EXIST) - # Filesystem folder. - if os.path.isdir(os.path.join(self._profile_folder, _FILESYSTEM_FOLDER)): - self._file_system_path = os.path.join(self._profile_folder, _FILESYSTEM_FOLDER) - # Bootloader file. - if len(list(firmware_path.rglob(_WILDCARD_BOOTLOADER))) is not 0: - self._bootloader_file = str(list(firmware_path.rglob(_WILDCARD_BOOTLOADER))[0]) - # Firmware binary files. - for wildcard in _WILDCARDS_FIRMWARE_BINARY_FILES: - for file in list(firmware_path.rglob(wildcard)): - self._firmware_binary_files.append(str(file)) - # Cellular firmware files. - for file in list(firmware_path.rglob(_WILDCARD_CELLULAR_FIRMWARE)): - self._cellular_firmware_files.append(str(file)) - # Cellular bootloader files. - for file in list(firmware_path.rglob(_WILDCARD_CELLULAR_BOOTLOADER)): - self._cellular_bootloader_files.append(str(file)) - - def _parse_xml_firmware_file(self): - """ - Parses the XML firmware file and stores the required parameters. - - Raises: - ProfileReadException: if there is any error parsing the XML firmware file. - """ - _log.debug("Parsing XML firmware file %s:" % self._firmware_xml_file) - try: - root = ElementTree.parse(self._firmware_xml_file).getroot() - # Firmware version. - firmware_element = root.find(_XML_FIRMWARE_FIRMWARE) - if firmware_element is None: - self._throw_read_exception(_ERROR_FIRMWARE_XML_INVALID % "missing firmware element") - self._firmware_version = int(firmware_element.get(_XML_FIRMWARE_FIRMWARE_VERSION)) - if self._firmware_version is None: - self._throw_read_exception(_ERROR_FIRMWARE_XML_INVALID % "missing firmware version") - _log.debug(" - Firmware version: %s" % self._firmware_version) - # Hardware version. - hardware_version_element = root.find(_XML_FIRMWARE_HARDWARE_VERSION) - if hardware_version_element is None: - self._throw_read_exception(_ERROR_FIRMWARE_XML_INVALID % "missing hardware version element") - self._hardware_version = int(hardware_version_element.text, 16) - _log.debug(" - Hardware version: %s" % self._hardware_version) - except ParseError as e: - self._throw_read_exception(_ERROR_FIRMWARE_XML_PARSE % str(e)) - - def get_setting_default_value(self, setting_name): - """ - Returns the default value of the given firmware setting. - - Args: - setting_name (String): the name of the setting to retrieve its default value. - - Returns: - String: the default value of the setting, ``None`` if the setting is not found or it has no default value. - """ - try: - firmware_root = ElementTree.parse(self._firmware_xml_file).getroot() - for firmware_setting_element in firmware_root.findall(_XML_FIRMWARE_SETTING): - if firmware_setting_element.get(_XML_COMMAND) == setting_name: - default_value_element = firmware_setting_element.find(_XML_DEFAULT_VALUE) - if default_value_element is None: - return None - return default_value_element.text - except ParseError as e: - _log.exception(e) - - return None - - @staticmethod - def _throw_read_exception(message): - """ - Throws an XBee profile read exception with the given message and logs it. - - Args: - message (String): the exception message - - Raises: - ProfileReadException: the exception thrown wit the given message. - """ - _log.error("ERROR: %s" % message) - raise ReadProfileException(message) - - @property - def profile_file(self): - """ - Returns the profile file. - - Returns: - String: the profile file. - """ - return self._profile_file - - @property - def version(self): - """ - Returns the profile version. - - Returns: - String: the profile version. - """ - return self._version - - @property - def flash_firmware_option(self): - """ - Returns the profile flash firmware option. - - Returns: - :class:`.FlashFirmwareOption`: the profile flash firmware option. - - .. seealso:: - | :class:`.FlashFirmwareOption` - """ - return self._flash_firmware_option - - @property - def description(self): - """ - Returns the profile description. - - Returns: - String: the profile description. - """ - return self._description - - @property - def reset_settings(self): - """ - Returns whether the settings of the XBee device will be reset before applying the profile ones or not. - - Returns: - Boolean: ``True`` if the settings of the XBee device will be reset before applying the profile ones, - ``False`` otherwise. - """ - return self._reset_settings - - @property - def has_filesystem(self): - """ - Returns whether the profile has filesystem information or not. - - Returns: - Boolean: ``True`` if the profile has filesystem information, ``False`` otherwise. - """ - return self._file_system_path is not None - - @property - def profile_settings(self): - """ - Returns all the firmware settings that the profile configures. - - Returns: - List: a list with all the firmware settings that the profile configures (:class:`.XBeeProfileSetting`). - """ - return self._profile_settings - - @property - def firmware_version(self): - """ - Returns the compatible firmware version of the profile. - - Returns: - Integer: the compatible firmware version of the profile. - """ - return self._firmware_version - - @property - def hardware_version(self): - """ - Returns the compatible hardware version of the profile. - - Returns: - Integer: the compatible hardware version of the profile. - """ - return self._hardware_version - - @property - def firmware_description_file(self): - """ - Returns the path of the profile firmware description file. - - Returns: - String: the path of the profile firmware description file. - """ - return self._firmware_xml_file - - @property - def file_system_path(self): - """ - Returns the profile file system path. - - Returns: - String: the path of the profile file system directory. - """ - return self._file_system_path - - -class _ProfileUpdater(object): - """ - Helper class used to handle the update XBee profile process. - """ - - def __init__(self, xbee_device, xbee_profile, progress_callback=None): - """ - Class constructor. Instantiates a new :class:`._ProfileUpdater` with the given parameters. - - Args: - xbee_device (:class:`.XBeeDevice` or :class:`.RemoteXBeeDevice`): The XBee device to apply profile to. - xbee_profile (:class:`.XBeeProfile`): The XBee profile to apply. - progress_callback (Function, optional): function to execute to receive progress information. Receives two - arguments: - - * The current update task as a String - * The current update task percentage as an Integer - """ - self._xbee_profile = xbee_profile - self._xbee_device = xbee_device - self._progress_callback = progress_callback - self._was_connected = True - self._device_firmware_version = None - self._device_hardware_version = None - self._old_port_parameters = None - self._is_local = True - if isinstance(self._xbee_device, RemoteXBeeDevice): - self._is_local = False - - def _firmware_progress_callback(self, task, percent): - """ - Receives firmware update progress information - - Args: - task (String): the current firmware update task. - percent (Integer): the current firmware update progress percent. - """ - if self._progress_callback is not None: - self._progress_callback(task, percent) - - def _read_device_parameters(self): - """ - Reads and stores the required XBee device parameters in order to apply the XBee profile. - - Raises: - UpdateProfileException: if there is any error reading the required XBee device parameters. - """ - _log.debug("Reading device parameters:") - if self._progress_callback is not None: - self._progress_callback(_TASK_READING_DEVICE_PARAMETERS, None) - if self._is_local: - # Connect the device. - if not self._xbee_device.is_open(): - self._was_connected = False - try: - self._xbee_device.open() - except XBeeException as e: - raise UpdateProfileException(_ERROR_OPEN_DEVICE % str(e)) - # For local devices, required parameters are read on 'open()' method, just use them. - self._device_firmware_version = self._xbee_device.get_firmware_version() - self._device_hardware_version = self._xbee_device.get_hardware_version() - else: - # For remote devices, parameters are read with 'get_parameter()' method. - try: - self._device_firmware_version = self._read_parameter_with_retries(ATStringCommand.VR.command, - _PARAMETER_READ_RETRIES) - self._device_hardware_version = HardwareVersion.get(self._read_parameter_with_retries( - ATStringCommand.HV.command, _PARAMETER_READ_RETRIES)[0]) - except XBeeException as e: - raise UpdateProfileException(_ERROR_READ_REMOTE_PARAMETER % str(e)) - - # Sanitize firmware version. - self._device_firmware_version = int(utils.hex_to_string(self._device_firmware_version).replace(" ", "")) - _log.debug(" - Firmware version: %s" % self._device_firmware_version) - _log.debug(" - Hardware version: %s" % self._device_hardware_version.code) - - def _read_parameter_with_retries(self, parameter, retries): - """ - Reads the given parameter from the XBee device within the given number of retries. - - Args: - parameter (String): the parameter to read. - retries (Integer): the number of retries to read the parameter. - - Returns: - Bytearray: the read parameter value. - - Raises: - XBeeException: if there is any error reading the parameter. - """ - while retries > 0: - try: - return self._xbee_device.get_parameter(parameter) - except TimeoutException: - retries -= 1 - time.sleep(0.2) - except XBeeException: - raise - - raise XBeeException("Timeout reading parameter '%s'" % parameter) - - def _set_parameter_with_retries(self, parameter, value, retries): - """ - Sets the given parameter in the XBee device within the given number of retries. - - Args: - parameter (String): the parameter to set. - value (Bytearray): the parameter value to set. - retries (Integer): the number of retries to set the parameter. - - Raises: - XBeeException: if there is any error setting the parameter. - """ - _log.debug("Setting parameter '%s' to '%s'" % (parameter, value)) - msg = "" - while retries > 0: - try: - return self._xbee_device.set_parameter(parameter, value) - except (TimeoutException, ATCommandException) as e: - msg = str(e) - retries -= 1 - time.sleep(0.2) - except XBeeException: - raise - - raise XBeeException("Error setting parameter '%s': %s" % parameter, msg) - - def _update_firmware(self): - """ - Updates the XBee device firmware. - - Raises: - UpdateProfileException: if there is any error updating the XBee firmware. - """ - try: - self._xbee_device.update_firmware(self._xbee_profile.firmware_description_file, - progress_callback=self._firmware_progress_callback) - except FirmwareUpdateException as e: - raise UpdateProfileException(_ERROR_UPDATE_FIRMWARE % str(e)) - - def _check_port_settings_changed(self): - """ - Checks whether the port settings of the device have changed in order to update serial port connection. - - Raises: - UpdateProfileException: if there is any error checking serial port settings changes. - """ - port_parameters = self._xbee_device.serial_port.get_settings() - baudrate_changed = False - parity_changed = False - stop_bits_changed = False - cts_flow_control_changed = False - for setting in self._xbee_profile.profile_settings: - if setting.name in _PARAMETERS_SERIAL_PORT: - if setting.name == ATStringCommand.BD.command: - baudrate_changed = True - port_parameters["baudrate"] = FirmwareBaudrate.get(int(setting.value, 16)).baudrate - elif setting.name == ATStringCommand.NB.command: - parity_changed = True - port_parameters["parity"] = FirmwareParity.get(int(setting.value, 16)).parity - elif setting.name == ATStringCommand.SB.command: - stop_bits_changed = True - port_parameters["stopbits"] = FirmwareStopbits.get(int(setting.value, 16)).stop_bits - elif setting.name == ATStringCommand.D7.command: - cts_flow_control_changed = True - if setting.value == _VALUE_CTS_ON: - port_parameters["rtscts"] = True - else: - port_parameters["rtscts"] = False - if self._xbee_profile.reset_settings: - if not baudrate_changed: - baudrate_changed = True - default_baudrate = self._xbee_profile.get_setting_default_value( - ATStringCommand.BD.command) - port_parameters["baudrate"] = FirmwareBaudrate.get(int(default_baudrate, 16)).baudrate - if not parity_changed: - parity_changed = True - default_parity = self._xbee_profile.get_setting_default_value(ATStringCommand.NB.command) - port_parameters["parity"] = FirmwareParity.get(int(default_parity, 16)).parity - if not stop_bits_changed: - stop_bits_changed = True - default_stop_bits = self._xbee_profile.get_setting_default_value( - ATStringCommand.SB.command) - port_parameters["stopbits"] = FirmwareStopbits.get(int(default_stop_bits, 16)).stop_bits - if not cts_flow_control_changed: - cts_flow_control_changed = True - port_parameters["rtscts"] = True # Default CTS value is always on. - - if baudrate_changed or parity_changed or stop_bits_changed or cts_flow_control_changed: - # Apply the new port configuration. - try: - self._xbee_device.close() # This is necessary to stop the frames read thread. - self._xbee_device.serial_port.apply_settings(port_parameters) - self._xbee_device.open() - except (XBeeException, SerialException) as e: - raise UpdateProfileException(_ERROR_UPDATE_SERIAL_PORT % str(e)) - - def _update_device_settings(self): - """ - Updates the device settings using the profile. - - Raises: - UpdateProfileException: if there is any error updating device settings from the profile. - """ - # Disable apply settings so Queue AT commands are issued instead of AT commands - old_apply_settings_value = self._xbee_device.is_apply_changes_enabled - self._xbee_device.enable_apply_changes(False) - try: - previous_percent = 0 - percent = 0 - setting_index = 1 - num_settings = len(self._xbee_profile.profile_settings) + 2 # 2 more settings for 'WR' and 'AC' - _log.info("Updating device settings") - if self._progress_callback is not None: - self._progress_callback(_TASK_UPDATE_SETTINGS, percent) - # Check if reset settings is required. - if self._xbee_profile.reset_settings: - num_settings += 1 # One more setting for 'RE' - percent = setting_index * 100 // num_settings - if self._progress_callback is not None and percent != previous_percent: - self._progress_callback(_TASK_UPDATE_SETTINGS, percent) - previous_percent = percent - self._set_parameter_with_retries(ATStringCommand.RE.command, - bytearray(0), _PARAMETER_WRITE_RETRIES) - setting_index += 1 - # Set settings. - for setting in self._xbee_profile.profile_settings: - percent = setting_index * 100 // num_settings - if self._progress_callback is not None and percent != previous_percent: - self._progress_callback(_TASK_UPDATE_SETTINGS, percent) - previous_percent = percent - self._set_parameter_with_retries(setting.name, setting.bytearray_value, _PARAMETER_WRITE_RETRIES) - setting_index += 1 - # Write settings. - percent = setting_index * 100 // num_settings - if self._progress_callback is not None and percent != previous_percent: - self._progress_callback(_TASK_UPDATE_SETTINGS, percent) - previous_percent = percent - self._set_parameter_with_retries(ATStringCommand.WR.command, - bytearray(0), _PARAMETER_WRITE_RETRIES) - setting_index += 1 - # Apply changes. - percent = setting_index * 100 // num_settings - if self._progress_callback is not None and percent != previous_percent: - self._progress_callback(_TASK_UPDATE_SETTINGS, percent) - self._set_parameter_with_retries(ATStringCommand.AC.command, bytearray(0), - _PARAMETER_WRITE_RETRIES) - except XBeeException as e: - raise UpdateProfileException(_ERROR_UPDATE_SETTINGS % str(e)) - - # Restore apply changes state. - self._xbee_device.enable_apply_changes(old_apply_settings_value) - - # Check if port settings have changed on local devices. - if self._is_local: - self._check_port_settings_changed() - - def _update_file_system(self): - """ - Updates the device file system. - - Raises: - UpdateProfileException: if there is any error during updating the device file system. - """ - _log.info("Updating device file system") - if self._is_local: - filesystem_manager = LocalXBeeFileSystemManager(self._xbee_device) - try: - if self._progress_callback is not None: - self._progress_callback(_TASK_CONNECT_FILESYSTEM, None) - time.sleep(0.2) - filesystem_manager.connect() - # Format file system to ensure resulting file system is exactly the same as the profile one. - if self._progress_callback is not None: - self._progress_callback(_TASK_FORMAT_FILESYSTEM, None) - filesystem_manager.format_filesystem() - # Transfer the file system folder. - filesystem_manager.put_dir(self._xbee_profile.file_system_path, dest_dir=None, - progress_callback=lambda file, percent: - self._progress_callback(_TASK_UPDATE_FILE % file, percent) if - self._progress_callback is not None else None) - except FileSystemNotSupportedException: - raise UpdateProfileException(_ERROR_FILESYSTEM_NOT_SUPPORTED) - except FileSystemException as e: - raise UpdateProfileException(_ERROR_UPDATE_FILESYSTEM % str(e)) - finally: - filesystem_manager.disconnect() - else: - # TODO: remote filesystem update is not implemented yet. - _log.info("Remote filesystem update is not yet supported, skipping.") - pass - - def update_profile(self): - """ - Starts the update profile process. - - Raises: - UpdateProfileException: if there is any error during the update XBee profile operation. - """ - # Retrieve device parameters. - self._read_device_parameters() - # Check if device supports profiles. - # TODO: reduce limitations when more hardware is supported. - if self._device_hardware_version.code not in SUPPORTED_HARDWARE_VERSIONS: - raise UpdateProfileException(_ERROR_PROFILES_NOT_SUPPORTED) - # Verify hardware compatibility of the profile. - if self._device_hardware_version.code != self._xbee_profile.hardware_version: - raise UpdateProfileException(_ERROR_HARDWARE_NOT_COMPATIBLE) - # Check flash firmware option. - flash_firmware = False - firmware_is_the_same = self._device_firmware_version == self._xbee_profile.firmware_version - if self._xbee_profile.flash_firmware_option == FlashFirmwareOption.FLASH_ALWAYS: - flash_firmware = True - elif self._xbee_profile.flash_firmware_option == FlashFirmwareOption.FLASH_DIFFERENT: - flash_firmware = not firmware_is_the_same - elif self._xbee_profile.flash_firmware_option == FlashFirmwareOption.DONT_FLASH and not firmware_is_the_same: - raise UpdateProfileException(_ERROR_FIRMWARE_NOT_COMPATIBLE) - # Update firmware if required. - if flash_firmware: - if self._device_hardware_version.code not in firmware.SUPPORTED_HARDWARE_VERSIONS: - raise UpdateProfileException(_ERROR_HARDWARE_NOT_COMPATIBLE_XBEE3) - self._update_firmware() - # Update the settings. - self._update_device_settings() - # Update the file system if required. - if self._xbee_profile.has_filesystem: - self._update_file_system() - - -def apply_xbee_profile(xbee_device, profile_path, progress_callback=None): - """ - Applies the given XBee profile into the given XBee device. - - Args: - xbee_device (:class:`.XBeeDevice` or :class:`.RemoteXBeeDevice`): the XBee device to apply profile to. - profile_path (String): path of the XBee profile file to apply. - progress_callback (Function, optional): function to execute to receive progress information. Receives two - arguments: - - * The current update task as a String - * The current update task percentage as an Integer - - Raises: - ValueError: if the XBee profile or the XBee device is not valid. - UpdateProfileException: if there is any error during the update XBee profile operation. - """ - # Sanity checks. - if profile_path is None or not isinstance(profile_path, str): - _log.error("ERROR: %s" % _ERROR_PROFILE_NOT_VALID) - raise ValueError(_ERROR_PROFILE_NOT_VALID) - if xbee_device is None or (not isinstance(xbee_device, XBeeDevice) and - not isinstance(xbee_device, RemoteXBeeDevice)): - _log.error("ERROR: %s" % _ERROR_DEVICE_NOT_VALID) - raise ValueError(_ERROR_DEVICE_NOT_VALID) - - try: - xbee_profile = XBeeProfile(profile_path) - except (ValueError, ReadProfileException) as e: - error = _ERROR_PROFILE_INVALID % str(e) - _log.error("ERROR: %s" % error) - raise UpdateProfileException(error) - - profile_updater = _ProfileUpdater(xbee_device, xbee_profile, progress_callback=progress_callback) - profile_updater.update_profile() diff --git a/digi/xbee/reader.py b/digi/xbee/reader.py index 900d5d8..b1f5dbd 100644 --- a/digi/xbee/reader.py +++ b/digi/xbee/reader.py @@ -13,21 +13,23 @@ # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. from concurrent.futures import ThreadPoolExecutor -from queue import Queue, Empty -from threading import Event +from Queue import Queue, Empty import logging import threading import time import digi.xbee.devices +from digi.xbee.models.atcomm import SpecialByte from digi.xbee.models.address import XBee64BitAddress, XBee16BitAddress from digi.xbee.models.message import XBeeMessage, ExplicitXBeeMessage, IPMessage, \ SMSMessage, UserDataRelayMessage -from digi.xbee.models.options import XBeeLocalInterface +from digi.xbee.models.mode import OperatingMode +from digi.xbee.models.options import ReceiveOptions, XBeeLocalInterface from digi.xbee.models.protocol import XBeeProtocol from digi.xbee.packets import factory from digi.xbee.packets.aft import ApiFrameType -from digi.xbee.packets.common import ReceivePacket, IODataSampleRxIndicatorPacket +from digi.xbee.packets.base import XBeePacket, XBeeAPIPacket +from digi.xbee.packets.common import ReceivePacket from digi.xbee.packets.raw import RX64Packet, RX16Packet from digi.xbee.util import utils from digi.xbee.exception import TimeoutException, InvalidPacketException @@ -152,28 +154,6 @@ class IOSampleReceived(XBeeEvent): pass -class NetworkModified(XBeeEvent): - """ - This event is fired when the network is being modified by the addition of a new node, an - existing node information is updated, a node removal, or when the network items are cleared. - - The callbacks that handle this event will receive the following arguments: - 1. event_type (:class:`digi.xbee.devices.NetworkEventType`): the network event type. - 2. reason (:class:`digi.xbee.devices.NetworkEventReason`): The reason of the event. - 3. node (:class:`digi.xbee.devices.XBeeDevice` or - :class:`digi.xbee.devices.RemoteXBeeDevice`): The node added, updated or removed from - the network. - - .. seealso:: - | :class:`digi.xbee.devices.NetworkEventReason` - | :class:`digi.xbee.devices.NetworkEventType` - | :class:`digi.xbee.devices.RemoteXBeeDevice` - | :class:`digi.xbee.devices.XBeeDevice` - | :class:`.XBeeEvent` - """ - pass - - class DeviceDiscovered(XBeeEvent): """ This event is fired when an XBee discovers another remote XBee @@ -290,51 +270,6 @@ class MicroPythonDataReceived(XBeeEvent): pass -class SocketStateReceived(XBeeEvent): - """ - This event is fired when an XBee receives a socket state packet. - - The callbacks to handle these events will receive the following arguments: - 1. socket_id (Integer): socket ID for state reported. - 2. state (:class:`.SocketState`): received state. - - .. seealso:: - | :class:`.XBeeEvent` - """ - pass - - -class SocketDataReceived(XBeeEvent): - """ - This event is fired when an XBee receives a socket receive data packet. - - The callbacks to handle these events will receive the following arguments: - 1. socket_id (Integer): ID of the socket that received the data. - 2. payload (Bytearray): received data. - - .. seealso:: - | :class:`.XBeeEvent` - """ - pass - - -class SocketDataReceivedFrom(XBeeEvent): - """ - This event is fired when an XBee receives a socket receive from data packet. - - The callbacks to handle these events will receive the following arguments: - 1. socket_id (Integer): ID of the socket that received the data. - 2. address (Tuple): a pair (host, port) of the source address where - host is a string representing an IPv4 address like '100.50.200.5', - and port is an integer. - 3. payload (Bytearray): received data. - - .. seealso:: - | :class:`.XBeeEvent` - """ - pass - - class PacketListener(threading.Thread): """ This class represents a packet listener, which is a thread that's always @@ -356,6 +291,7 @@ class methods. This callbacks must have a certain header, see each event 1. PacketReceived: 1.1 received_packet (:class:`.XBeeAPIPacket`): the received packet. + 1.2 sender (:class:`.RemoteXBeeDevice`): the remote XBee device who has sent the packet. 2. DataReceived 2.1 message (:class:`.XBeeMessage`): message containing the data received, the sender and the time. 3. ModemStatusReceived @@ -367,7 +303,7 @@ class methods. This callbacks must have a certain header, see each event Default max. size that the queue has. """ - _LOG_PATTERN = "{comm_iface:<6s}{event:<12s}{fr_type:<10s}{sender:<18s}{more_data:<50s}" + _LOG_PATTERN = "{port:<6s}{event:<12s}{fr_type:<10s}{sender:<18s}{more_data:<50s}" """ Generic pattern for display received messages (high-level) with logger. """ @@ -377,19 +313,17 @@ class methods. This callbacks must have a certain header, see each event Logger. """ - def __init__(self, comm_iface, xbee_device, queue_max_size=None): + def __init__(self, serial_port, xbee_device, queue_max_size=None): """ Class constructor. Instantiates a new :class:`.PacketListener` object with the provided parameters. Args: - comm_iface (:class:`.XBeeCommunicationInterface`): the hardware interface to listen to. + serial_port (:class:`.XbeeSerialPort`): the COM port to which this listener will be listening. xbee_device (:class:`.XBeeDevice`): the XBee that is the listener owner. queue_max_size (Integer): the maximum size of the XBee queue. """ threading.Thread.__init__(self) - self.daemon = True - # User callbacks: self.__packet_received = PacketReceived() self.__data_received = DataReceived() @@ -401,17 +335,13 @@ def __init__(self, comm_iface, xbee_device, queue_max_size=None): self.__relay_data_received = RelayDataReceived() self.__bluetooth_data_received = BluetoothDataReceived() self.__micropython_data_received = MicroPythonDataReceived() - self.__socket_state_received = SocketStateReceived() - self.__socket_data_received = SocketDataReceived() - self.__socket_data_received_from = SocketDataReceivedFrom() # API internal callbacks: self.__packet_received_API = xbee_device.get_xbee_device_callbacks() self.__xbee_device = xbee_device - self.__comm_iface = comm_iface + self.__serial_port = serial_port self.__stop = True - self.__started = Event() self.__queue_max_size = queue_max_size if queue_max_size is not None else self.__DEFAULT_QUEUE_MAX_SIZE self.__xbee_queue = XBeeQueue(self.__queue_max_size) @@ -419,16 +349,11 @@ def __init__(self, comm_iface, xbee_device, queue_max_size=None): self.__explicit_xbee_queue = XBeeQueue(self.__queue_max_size) self.__ip_xbee_queue = XBeeQueue(self.__queue_max_size) - def wait_until_started(self, timeout=None): - """ - Blocks until the thread has fully started. If already started, returns - immediately. + self._log_handler = logging.StreamHandler() + self._log.addHandler(self._log_handler) - Args: - timeout (Float): timeout for the operation in seconds. - """ - - self.__started.wait(timeout) + def __del__(self): + self._log.removeHandler(self._log_handler) def run(self): """ @@ -438,10 +363,9 @@ def run(self): """ try: self.__stop = False - self.__started.set() while not self.__stop: # Try to read a packet. Read packet is unescaped. - raw_packet = self.__comm_iface.wait_for_frame(self.__xbee_device.operating_mode) + raw_packet = self.__try_read_packet(self.__xbee_device.operating_mode) if raw_packet is not None: # If the current protocol is 802.15.4, the packet may have to be discarded. @@ -456,7 +380,7 @@ def run(self): self._log.error("Error processing packet '%s': %s" % (utils.hex_to_string(raw_packet), str(e))) continue - self._log.debug(self.__xbee_device.LOG_PATTERN.format(comm_iface=str(self.__xbee_device.comm_iface), + self._log.debug(self.__xbee_device.LOG_PATTERN.format(port=self.__xbee_device.serial_port.port, event="RECEIVED", opmode=self.__xbee_device.operating_mode, content=utils.hex_to_string(raw_packet))) @@ -479,17 +403,14 @@ def run(self): finally: if not self.__stop: self.__stop = True - if self.__comm_iface.is_interface_open: - self.__comm_iface.close() + if self.__serial_port.isOpen(): + self.__serial_port.close() def stop(self): """ Stops listening. """ - self.__comm_iface.quit_reading() self.__stop = True - # Wait until thread fully stops. - self.join() def is_running(self): """ @@ -541,189 +462,113 @@ def add_packet_received_callback(self, callback): Adds a callback for the event :class:`.PacketReceived`. Args: - callback (Function or List of functions): the callback. Receives two arguments. + callback (Function): the callback. Receives two arguments. * The received packet as a :class:`.XBeeAPIPacket` + * The sender as a :class:`.RemoteXBeeDevice` """ - if isinstance(callback, list): - self.__packet_received.extend(callback) - elif callback: - self.__packet_received += callback + self.__packet_received += callback def add_data_received_callback(self, callback): """ Adds a callback for the event :class:`.DataReceived`. Args: - callback (Function or List of functions): the callback. Receives one argument. + callback (Function): the callback. Receives one argument. * The data received as an :class:`.XBeeMessage` """ - if isinstance(callback, list): - self.__data_received.extend(callback) - elif callback: - self.__data_received += callback + self.__data_received += callback def add_modem_status_received_callback(self, callback): """ Adds a callback for the event :class:`.ModemStatusReceived`. Args: - callback (Function or List of functions): the callback. Receives one argument. + callback (Function): the callback. Receives one argument. * The modem status as a :class:`.ModemStatus` """ - if isinstance(callback, list): - self.__modem_status_received.extend(callback) - elif callback: - self.__modem_status_received += callback + self.__modem_status_received += callback def add_io_sample_received_callback(self, callback): """ Adds a callback for the event :class:`.IOSampleReceived`. Args: - callback (Function or List of functions): the callback. Receives three arguments. + callback (Function): the callback. Receives three arguments. * The received IO sample as an :class:`.IOSample` * The remote XBee device who has sent the packet as a :class:`.RemoteXBeeDevice` * The time in which the packet was received as an Integer """ - if isinstance(callback, list): - self.__io_sample_received.extend(callback) - elif callback: - self.__io_sample_received += callback + self.__io_sample_received += callback def add_explicit_data_received_callback(self, callback): """ Adds a callback for the event :class:`.ExplicitDataReceived`. Args: - callback (Function or List of functions): the callback. Receives one argument. + callback (Function): the callback. Receives one argument. * The explicit data received as an :class:`.ExplicitXBeeMessage` """ - if isinstance(callback, list): - self.__explicit_packet_received.extend(callback) - elif callback: - self.__explicit_packet_received += callback + self.__explicit_packet_received += callback def add_ip_data_received_callback(self, callback): """ Adds a callback for the event :class:`.IPDataReceived`. Args: - callback (Function or List of functions): the callback. Receives one argument. + callback (Function): the callback. Receives one argument. * The data received as an :class:`.IPMessage` """ - if isinstance(callback, list): - self.__ip_data_received.extend(callback) - elif callback: - self.__ip_data_received += callback + self.__ip_data_received += callback def add_sms_received_callback(self, callback): """ Adds a callback for the event :class:`.SMSReceived`. Args: - callback (Function or List of functions): the callback. Receives one argument. + callback (Function): the callback. Receives one argument. * The data received as an :class:`.SMSMessage` """ - if isinstance(callback, list): - self.__sms_received.extend(callback) - elif callback: - self.__sms_received += callback + self.__sms_received += callback def add_user_data_relay_received_callback(self, callback): """ Adds a callback for the event :class:`.RelayDataReceived`. Args: - callback (Function or List of functions): the callback. Receives one argument. + callback (Function): the callback. Receives one argument. * The data received as a :class:`.UserDataRelayMessage` """ - if isinstance(callback, list): - self.__relay_data_received.extend(callback) - elif callback: - self.__relay_data_received += callback + self.__relay_data_received += callback def add_bluetooth_data_received_callback(self, callback): """ Adds a callback for the event :class:`.BluetoothDataReceived`. Args: - callback (Function or List of functions): the callback. Receives one argument. + callback (Function): the callback. Receives one argument. * The data received as a Bytearray """ - if isinstance(callback, list): - self.__bluetooth_data_received.extend(callback) - elif callback: - self.__bluetooth_data_received += callback + self.__bluetooth_data_received += callback def add_micropython_data_received_callback(self, callback): """ Adds a callback for the event :class:`.MicroPythonDataReceived`. Args: - callback (Function or List of functions): the callback. Receives one argument. + callback (Function): the callback. Receives one argument. * The data received as a Bytearray """ - if isinstance(callback, list): - self.__micropython_data_received.extend(callback) - elif callback: - self.__micropython_data_received += callback - - def add_socket_state_received_callback(self, callback): - """ - Adds a callback for the event :class:`.SocketStateReceived`. - - Args: - callback (Function or List of functions): the callback. Receives two arguments. - - * The socket ID as an Integer. - * The state received as a :class:`.SocketState` - """ - if isinstance(callback, list): - self.__socket_state_received.extend(callback) - elif callback: - self.__socket_state_received += callback - - def add_socket_data_received_callback(self, callback): - """ - Adds a callback for the event :class:`.SocketDataReceived`. - - Args: - callback (Function or List of functions): the callback. Receives two arguments. - - * The socket ID as an Integer. - * The status received as a :class:`.SocketStatus` - """ - if isinstance(callback, list): - self.__socket_data_received.extend(callback) - elif callback: - self.__socket_data_received += callback - - def add_socket_data_received_from_callback(self, callback): - """ - Adds a callback for the event :class:`.SocketDataReceivedFrom`. - - Args: - callback (Function or List of functions): the callback. Receives three arguments. - - * The socket ID as an Integer. - * A pair (host, port) of the source address where host is a string representing an IPv4 address - like '100.50.200.5', and port is an integer. - * The status received as a :class:`.SocketStatus` - """ - if isinstance(callback, list): - self.__socket_data_received_from.extend(callback) - elif callback: - self.__socket_data_received_from += callback + self.__micropython_data_received += callback def del_packet_received_callback(self, callback): """ @@ -733,8 +578,7 @@ def del_packet_received_callback(self, callback): callback (Function): the callback to delete. Raises: - ValueError: if ``callback`` is not in the callback list of - :class:`.PacketReceived` event. + ValueError: if ``callback`` is not in the callback list of :class:`.PacketReceived` event. """ self.__packet_received -= callback @@ -758,8 +602,7 @@ def del_modem_status_received_callback(self, callback): callback (Function): the callback to delete. Raises: - ValueError: if ``callback`` is not in the callback list of - :class:`.ModemStatusReceived` event. + ValueError: if ``callback`` is not in the callback list of :class:`.ModemStatusReceived` event. """ self.__modem_status_received -= callback @@ -771,8 +614,7 @@ def del_io_sample_received_callback(self, callback): callback (Function): the callback to delete. Raises: - ValueError: if ``callback`` is not in the callback list of - :class:`.IOSampleReceived` event. + ValueError: if ``callback`` is not in the callback list of :class:`.IOSampleReceived` event. """ self.__io_sample_received -= callback @@ -784,8 +626,7 @@ def del_explicit_data_received_callback(self, callback): callback (Function): the callback to delete. Raises: - ValueError: if ``callback`` is not in the callback list of - :class:`.ExplicitDataReceived` event. + ValueError: if ``callback`` is not in the callback list of :class:`.ExplicitDataReceived` event. """ self.__explicit_packet_received -= callback @@ -797,8 +638,7 @@ def del_ip_data_received_callback(self, callback): callback (Function): the callback to delete. Raises: - ValueError: if ``callback`` is not in the callback list of :class:`.IPDataReceived` - event. + ValueError: if ``callback`` is not in the callback list of :class:`.IPDataReceived` event. """ self.__ip_data_received -= callback @@ -822,8 +662,7 @@ def del_user_data_relay_received_callback(self, callback): callback (Function): the callback to delete. Raises: - ValueError: if ``callback`` is not in the callback list of - :class:`.RelayDataReceived` event. + ValueError: if ``callback`` is not in the callback list of :class:`.RelayDataReceived` event. """ self.__relay_data_received -= callback @@ -835,8 +674,7 @@ def del_bluetooth_data_received_callback(self, callback): callback (Function): the callback to delete. Raises: - ValueError: if ``callback`` is not in the callback list of - :class:`.BluetoothDataReceived` event. + ValueError: if ``callback`` is not in the callback list of :class:`.BluetoothDataReceived` event. """ self.__bluetooth_data_received -= callback @@ -848,140 +686,10 @@ def del_micropython_data_received_callback(self, callback): callback (Function): the callback to delete. Raises: - ValueError: if ``callback`` is not in the callback list of - :class:`.MicroPythonDataReceived` event. + ValueError: if ``callback`` is not in the callback list of :class:`.MicroPythonDataReceived` event. """ self.__micropython_data_received -= callback - def del_socket_state_received_callback(self, callback): - """ - Deletes a callback for the callback list of :class:`.SocketStateReceived` event. - - Args: - callback (Function): the callback to delete. - - Raises: - ValueError: if ``callback`` is not in the callback list of - :class:`.SocketStateReceived` event. - """ - self.__socket_state_received -= callback - - def del_socket_data_received_callback(self, callback): - """ - Deletes a callback for the callback list of :class:`.SocketDataReceived` event. - - Args: - callback (Function): the callback to delete. - - Raises: - ValueError: if ``callback`` is not in the callback list of - :class:`.SocketDataReceived` event. - """ - self.__socket_data_received -= callback - - def del_socket_data_received_from_callback(self, callback): - """ - Deletes a callback for the callback list of :class:`.SocketDataReceivedFrom` event. - - Args: - callback (Function): the callback to delete. - - Raises: - ValueError: if ``callback`` is not in the callback list of - :class:`.SocketDataReceivedFrom` event. - """ - self.__socket_data_received_from -= callback - - def get_packet_received_callbacks(self): - """ - Returns the list of registered callbacks for received packets. - - Returns: - List: List of :class:`.PacketReceived` events. - """ - return self.__packet_received - - def get_data_received_callbacks(self): - """ - Returns the list of registered callbacks for received data. - - Returns: - List: List of :class:`.DataReceived` events. - """ - return self.__data_received - - def get_modem_status_received_callbacks(self): - """ - Returns the list of registered callbacks for received modem status. - - Returns: - List: List of :class:`.ModemStatusReceived` events. - """ - return self.__modem_status_received - - def get_io_sample_received_callbacks(self): - """ - Returns the list of registered callbacks for received IO samples. - - Returns: - List: List of :class:`.IOSampleReceived` events. - """ - return self.__io_sample_received - - def get_explicit_data_received_callbacks(self): - """ - Returns the list of registered callbacks for received explicit data. - - Returns: - List: List of :class:`.ExplicitDataReceived` events. - """ - return self.__explicit_packet_received - - def get_ip_data_received_callbacks(self): - """ - Returns the list of registered callbacks for received IP data. - - Returns: - List: List of :class:`.IPDataReceived` events. - """ - return self.__ip_data_received - - def get_sms_received_callbacks(self): - """ - Returns the list of registered callbacks for received SMS. - - Returns: - List: List of :class:`.SMSReceived` events. - """ - return self.__sms_received - - def get_user_data_relay_received_callbacks(self): - """ - Returns the list of registered callbacks for received user data relay. - - Returns: - List: List of :class:`.RelayDataReceived` events. - """ - return self.__relay_data_received - - def get_bluetooth_data_received_callbacks(self): - """ - Returns the list of registered callbacks for received Bluetooth data. - - Returns: - List: List of :class:`.BluetoothDataReceived` events. - """ - return self.__bluetooth_data_received - - def get_micropython_data_received_callbacks(self): - """ - Returns the list of registered callbacks for received micropython data. - - Returns: - List: List of :class:`.MicroPythonDataReceived` events. - """ - return self.__micropython_data_received - def __execute_user_callbacks(self, xbee_packet, remote=None): """ Executes callbacks corresponding to the received packet. @@ -997,20 +705,20 @@ def __execute_user_callbacks(self, xbee_packet, remote=None): if (xbee_packet.get_frame_type() == ApiFrameType.RX_64 or xbee_packet.get_frame_type() == ApiFrameType.RX_16 or xbee_packet.get_frame_type() == ApiFrameType.RECEIVE_PACKET): - data = xbee_packet.rf_data - is_broadcast = xbee_packet.is_broadcast() - self.__data_received(XBeeMessage(data, remote, time.time(), broadcast=is_broadcast)) - self._log.info(self._LOG_PATTERN.format(comm_iface=str(self.__xbee_device.comm_iface), + _data = xbee_packet.rf_data + is_broadcast = xbee_packet.receive_options == ReceiveOptions.BROADCAST_PACKET + self.__data_received(XBeeMessage(_data, remote, time.time(), is_broadcast)) + self._log.info(self._LOG_PATTERN.format(port=self.__xbee_device.serial_port.port, event="RECEIVED", fr_type="DATA", sender=str(remote.get_64bit_addr()) if remote is not None else "None", - more_data=utils.hex_to_string(data))) + more_data=utils.hex_to_string(xbee_packet.rf_data))) # Modem status callbacks elif xbee_packet.get_frame_type() == ApiFrameType.MODEM_STATUS: self.__modem_status_received(xbee_packet.modem_status) - self._log.info(self._LOG_PATTERN.format(comm_iface=str(self.__xbee_device.comm_iface), + self._log.info(self._LOG_PATTERN.format(port=self.__xbee_device.serial_port.port, event="RECEIVED", fr_type="MODEM STATUS", sender=str(remote.get_64bit_addr()) if remote is not None @@ -1022,7 +730,7 @@ def __execute_user_callbacks(self, xbee_packet, remote=None): xbee_packet.get_frame_type() == ApiFrameType.RX_IO_64 or xbee_packet.get_frame_type() == ApiFrameType.IO_DATA_SAMPLE_RX_INDICATOR): self.__io_sample_received(xbee_packet.io_sample, remote, time.time()) - self._log.info(self._LOG_PATTERN.format(comm_iface=str(self.__xbee_device.comm_iface), + self._log.info(self._LOG_PATTERN.format(port=self.__xbee_device.serial_port.port, event="RECEIVED", fr_type="IOSAMPLE", sender=str(remote.get_64bit_addr()) if remote is not None @@ -1031,27 +739,24 @@ def __execute_user_callbacks(self, xbee_packet, remote=None): # Explicit packet callbacks elif xbee_packet.get_frame_type() == ApiFrameType.EXPLICIT_RX_INDICATOR: - data = xbee_packet.rf_data - is_broadcast = xbee_packet.is_broadcast() + is_broadcast = False # If it's 'special' packet, notify the data_received callbacks too: - if self.__is_explicit_data_packet(xbee_packet): - self.__data_received(XBeeMessage(data, remote, time.time(), broadcast=is_broadcast)) - elif self.__is_explicit_io_packet(xbee_packet): - self.__io_sample_received(IOSample(data), remote, time.time()) + if self.__is_special_explicit_packet(xbee_packet): + self.__data_received(XBeeMessage(xbee_packet.rf_data, remote, time.time(), is_broadcast)) self.__explicit_packet_received(PacketListener.__expl_to_message(remote, is_broadcast, xbee_packet)) - self._log.info(self._LOG_PATTERN.format(comm_iface=str(self.__xbee_device.comm_iface), + self._log.info(self._LOG_PATTERN.format(port=self.__xbee_device.serial_port.port, event="RECEIVED", fr_type="EXPLICIT DATA", sender=str(remote.get_64bit_addr()) if remote is not None else "None", - more_data=utils.hex_to_string(data))) + more_data=utils.hex_to_string(xbee_packet.rf_data))) # IP data elif xbee_packet.get_frame_type() == ApiFrameType.RX_IPV4: self.__ip_data_received( IPMessage(xbee_packet.source_address, xbee_packet.source_port, xbee_packet.dest_port, xbee_packet.ip_protocol, xbee_packet.data)) - self._log.info(self._LOG_PATTERN.format(comm_iface=str(self.__xbee_device.comm_iface), + self._log.info(self._LOG_PATTERN.format(port=self.__xbee_device.serial_port.port, event="RECEIVED", fr_type="IP DATA", sender=str(xbee_packet.source_address), @@ -1060,7 +765,7 @@ def __execute_user_callbacks(self, xbee_packet, remote=None): # SMS elif xbee_packet.get_frame_type() == ApiFrameType.RX_SMS: self.__sms_received(SMSMessage(xbee_packet.phone_number, xbee_packet.data)) - self._log.info(self._LOG_PATTERN.format(comm_iface=str(self.__xbee_device.comm_iface), + self._log.info(self._LOG_PATTERN.format(port=self.__xbee_device.serial_port.port, event="RECEIVED", fr_type="SMS", sender=str(xbee_packet.phone_number), @@ -1075,40 +780,85 @@ def __execute_user_callbacks(self, xbee_packet, remote=None): self.__bluetooth_data_received(xbee_packet.data) elif xbee_packet.src_interface == XBeeLocalInterface.MICROPYTHON: self.__micropython_data_received(xbee_packet.data) - self._log.info(self._LOG_PATTERN.format(comm_iface=str(self.__xbee_device.comm_iface), + self._log.info(self._LOG_PATTERN.format(port=self.__xbee_device.serial_port.port, event="RECEIVED", fr_type="RELAY DATA", sender=xbee_packet.src_interface.description, more_data=utils.hex_to_string(xbee_packet.data))) - # Socket state - elif xbee_packet.get_frame_type() == ApiFrameType.SOCKET_STATE: - self.__socket_state_received(xbee_packet.socket_id, xbee_packet.state) - self._log.info(self._LOG_PATTERN.format(comm_iface=str(self.__xbee_device.comm_iface), - event="RECEIVED", - fr_type="SOCKET STATE", - sender=str(xbee_packet.socket_id), - more_data=xbee_packet.state)) - - # Socket receive data - elif xbee_packet.get_frame_type() == ApiFrameType.SOCKET_RECEIVE: - self.__socket_data_received(xbee_packet.socket_id, xbee_packet.payload) - self._log.info(self._LOG_PATTERN.format(comm_iface=str(self.__xbee_device.comm_iface), - event="RECEIVED", - fr_type="SOCKET DATA", - sender=str(xbee_packet.socket_id), - more_data=utils.hex_to_string(xbee_packet.payload))) - - # Socket receive data from - elif xbee_packet.get_frame_type() == ApiFrameType.SOCKET_RECEIVE_FROM: - address = (str(xbee_packet.source_address), xbee_packet.source_port) - self.__socket_data_received_from(xbee_packet.socket_id, address, xbee_packet.payload) - self._log.info(self._LOG_PATTERN.format(comm_iface=str(self.__xbee_device.comm_iface), - event="RECEIVED", - fr_type="SOCKET DATA", - sender=str(xbee_packet.socket_id), - more_data="%s - %s" % (address, - utils.hex_to_string(xbee_packet.payload)))) + def __read_next_byte(self, operating_mode): + """ + Returns the next byte in bytearray format. If the operating mode is + OperatingMode.ESCAPED_API_MODE, the bytearray could contain 2 bytes. + + If in escaped API mode and the byte that was read was the escape byte, + it will also read the next byte. + + Args: + operating_mode (:class:`.OperatingMode`): the operating mode in which the byte should be read. + + Returns: + Bytearray: the read byte or bytes as bytearray, ``None`` otherwise. + """ + read_data = bytearray() + read_byte = self.__serial_port.read_byte() + read_data.append(read_byte) + # Read escaped bytes in API escaped mode. + if operating_mode == OperatingMode.ESCAPED_API_MODE and read_byte == XBeePacket.ESCAPE_BYTE: + read_data.append(self.__serial_port.read_byte()) + + return read_data + + def __try_read_packet(self, operating_mode=OperatingMode.API_MODE): + """ + Reads the next packet. Starts to read when finds the start delimiter. + The last byte read is the checksum. + + If there is something in the COM buffer after the + start delimiter, this method discards it. + + If the method can't read a complete and correct packet, + it will return ``None``. + + Args: + operating_mode (:class:`.OperatingMode`): the operating mode in which the packet should be read. + + Returns: + Bytearray: the read packet as bytearray if a packet is read, ``None`` otherwise. + """ + try: + xbee_packet = bytearray(1) + # Add packet delimiter. + xbee_packet[0] = self.__serial_port.read_byte() + while xbee_packet[0] != SpecialByte.HEADER_BYTE.value: + xbee_packet[0] = self.__serial_port.read_byte() + + # Add packet length. + packet_length_byte = bytearray() + for _ in range(0, 2): + packet_length_byte += self.__read_next_byte(operating_mode) + xbee_packet += packet_length_byte + # Length needs to be un-escaped in API escaped mode to obtain its integer equivalent. + if operating_mode == OperatingMode.ESCAPED_API_MODE: + length = utils.length_to_int(XBeeAPIPacket.unescape_data(packet_length_byte)) + else: + length = utils.length_to_int(packet_length_byte) + + # Add packet payload. + for _ in range(0, length): + xbee_packet += self.__read_next_byte(operating_mode) + + # Add packet checksum. + for _ in range(0, 1): + xbee_packet += self.__read_next_byte(operating_mode) + + # Return the packet unescaped. + if operating_mode == OperatingMode.ESCAPED_API_MODE: + return XBeeAPIPacket.unescape_data(xbee_packet) + else: + return xbee_packet + except TimeoutException: + return None def __create_remote_device_from_packet(self, xbee_packet): """ @@ -1119,8 +869,7 @@ def __create_remote_device_from_packet(self, xbee_packet): :class:`.RemoteXBeeDevice` """ x64bit_addr, x16bit_addr = self.__get_remote_device_data_from_packet(xbee_packet) - return digi.xbee.devices.RemoteXBeeDevice(self.__xbee_device, x64bit_addr=x64bit_addr, - x16bit_addr=x16bit_addr) + return digi.xbee.devices.RemoteXBeeDevice(self.__xbee_device, x64bit_addr, x16bit_addr) @staticmethod def __get_remote_device_data_from_packet(xbee_packet): @@ -1172,18 +921,16 @@ def __try_add_remote_device(self, xbee_packet): remote = None x64, x16 = self.__get_remote_device_data_from_packet(xbee_packet) if x64 is not None or x16 is not None: - remote = self.__xbee_device.get_network()._XBeeNetwork__add_remote_from_attr( - digi.xbee.devices.NetworkEventReason.RECEIVED_MSG, x64bit_addr=x64, x16bit_addr=x16) + remote = self.__xbee_device.get_network().add_if_not_exist(x64, x16) return remote @staticmethod - def __is_explicit_data_packet(xbee_packet): + def __is_special_explicit_packet(xbee_packet): """ - Checks if the provided explicit data packet is directed to the data cluster. + Checks if an explicit data packet is 'special'. - This means that this XBee has its API Output Mode distinct than Native (it's expecting - explicit data packets), but some device has sent it a non-explicit data packet - (TransmitRequest f.e.). + 'Special' means that this XBee has its API Output Mode distinct than Native (it's expecting + explicit data packets), but some device has sent it a non-explicit data packet (TransmitRequest f.e.). In this case, this XBee will receive a explicit data packet with the following values: 1. Source endpoint = 0xE8 @@ -1191,26 +938,10 @@ def __is_explicit_data_packet(xbee_packet): 3. Cluster ID = 0x0011 4. Profile ID = 0xC105 """ - return (xbee_packet.source_endpoint == 0xE8 and xbee_packet.dest_endpoint == 0xE8 - and xbee_packet.cluster_id == 0x0011 and xbee_packet.profile_id == 0xC105) - - @staticmethod - def __is_explicit_io_packet(xbee_packet): - """ - Checks if the provided explicit data packet is directed to the IO cluster. - - This means that this XBee has its API Output Mode distinct than Native (it's expecting - explicit data packets), but some device has sent an IO sample packet - (IODataSampleRxIndicatorPacket f.e.). - In this case, this XBee will receive a explicit data packet with the following values: - - 1. Source endpoint = 0xE8 - 2. Destination endpoint = 0xE8 - 3. Cluster ID = 0x0092 - 4. Profile ID = 0xC105 - """ - return (xbee_packet.source_endpoint == 0xE8 and xbee_packet.dest_endpoint == 0xE8 - and xbee_packet.cluster_id == 0x0092 and xbee_packet.profile_id == 0xC105) + if (xbee_packet.source_endpoint == 0xE8 and xbee_packet.dest_endpoint == 0xE8 and + xbee_packet.cluster_id == 0x0011 and xbee_packet.profile_id == 0xC105): + return True + return False def __expl_to_no_expl(self, xbee_packet): """ @@ -1218,35 +949,22 @@ def __expl_to_no_expl(self, xbee_packet): this listener's XBee device protocol. Returns: - :class:`.XBeeAPIPacket`: the proper receive packet depending on the current protocol and - the available information (inside the packet). + :class:`.XBeeAPIPacket`: the proper receive packet depending on the current protocol and the + available information (inside the packet). """ x64addr = xbee_packet.x64bit_source_addr x16addr = xbee_packet.x16bit_source_addr if self.__xbee_device.get_protocol() == XBeeProtocol.RAW_802_15_4: if x64addr != XBee64BitAddress.UNKNOWN_ADDRESS: - new_pkt = RX64Packet(x64addr, 0, xbee_packet.receive_options, rf_data=xbee_packet.rf_data) + new_packet = RX64Packet(x64addr, 0, xbee_packet.receive_options, xbee_packet.rf_data) elif x16addr != XBee16BitAddress.UNKNOWN_ADDRESS: - new_pkt = RX16Packet(x16addr, 0, xbee_packet.receive_options, rf_data=xbee_packet.rf_data) + new_packet = RX16Packet(x16addr, 0, xbee_packet.receive_options, xbee_packet.rf_data) else: # both address UNKNOWN - new_pkt = RX64Packet(x64addr, 0, xbee_packet.receive_options, rf_data=xbee_packet.rf_data) + new_packet = RX64Packet(x64addr, 0, xbee_packet.receive_options, xbee_packet.rf_data) else: - new_pkt = ReceivePacket(x64addr, x16addr, xbee_packet.receive_options, - rf_data=xbee_packet.rf_data) - return new_pkt - - def __expl_to_io(self, xbee_packet): - """ - Creates a IO packet from the given explicit packet depending on this listener's XBee device - protocol. - - Returns: - :class:`.XBeeAPIPacket`: the proper receive packet depending on the current protocol and - the available information (inside the packet). - """ - return IODataSampleRxIndicatorPacket(xbee_packet.x64bit_source_addr, - xbee_packet.x16bit_source_addr, - xbee_packet.receive_options, rf_data=xbee_packet.rf_data) + new_packet = ReceivePacket(xbee_packet.x64bit_source_addr, xbee_packet.x16bit_source_addr, + xbee_packet.receive_options, xbee_packet.rf_data) + return new_packet def __add_packet_queue(self, xbee_packet): """ @@ -1268,12 +986,9 @@ def __add_packet_queue(self, xbee_packet): self.__explicit_xbee_queue.get() self.__explicit_xbee_queue.put_nowait(xbee_packet) # Check if the explicit packet is 'special'. - if self.__is_explicit_data_packet(xbee_packet): + if self.__is_special_explicit_packet(xbee_packet): # Create the non-explicit version of this packet and add it to the queue. self.__add_packet_queue(self.__expl_to_no_expl(xbee_packet)) - elif self.__is_explicit_io_packet(xbee_packet): - # Create the IO packet corresponding to this packet and add it to the queue. - self.__add_packet_queue(self.__expl_to_io(xbee_packet)) # IP packets. elif xbee_packet.get_frame_type() == ApiFrameType.RX_IPV4: if self.__ip_xbee_queue.full(): @@ -1301,7 +1016,7 @@ def __expl_to_message(remote, broadcast, xbee_packet): """ return ExplicitXBeeMessage(xbee_packet.rf_data, remote, time.time(), xbee_packet.source_endpoint, xbee_packet.dest_endpoint, xbee_packet.cluster_id, - xbee_packet.profile_id, broadcast=broadcast) + xbee_packet.profile_id, broadcast) class XBeeQueue(Queue): @@ -1381,11 +1096,11 @@ def get_by_remote(self, remote_xbee_device, timeout=None): return xbee_packet return None else: - xbee_packet = self.get_by_remote(remote_xbee_device) + xbee_packet = self.get_by_remote(remote_xbee_device, None) dead_line = time.time() + timeout while xbee_packet is None and dead_line > time.time(): time.sleep(0.1) - xbee_packet = self.get_by_remote(remote_xbee_device) + xbee_packet = self.get_by_remote(remote_xbee_device, None) if xbee_packet is None: raise TimeoutException() return xbee_packet @@ -1419,11 +1134,11 @@ def get_by_ip(self, ip_addr, timeout=None): return xbee_packet return None else: - xbee_packet = self.get_by_ip(ip_addr) + xbee_packet = self.get_by_ip(ip_addr, None) dead_line = time.time() + timeout while xbee_packet is None and dead_line > time.time(): time.sleep(0.1) - xbee_packet = self.get_by_ip(ip_addr) + xbee_packet = self.get_by_ip(ip_addr, None) if xbee_packet is None: raise TimeoutException() return xbee_packet @@ -1457,11 +1172,11 @@ def get_by_id(self, frame_id, timeout=None): return xbee_packet return None else: - xbee_packet = self.get_by_id(frame_id) + xbee_packet = self.get_by_id(frame_id, None) dead_line = time.time() + timeout while xbee_packet is None and dead_line > time.time(): time.sleep(0.1) - xbee_packet = self.get_by_id(frame_id) + xbee_packet = self.get_by_id(frame_id, None) if xbee_packet is None: raise TimeoutException() return xbee_packet diff --git a/digi/xbee/recovery.py b/digi/xbee/recovery.py deleted file mode 100644 index 9ac1441..0000000 --- a/digi/xbee/recovery.py +++ /dev/null @@ -1,291 +0,0 @@ -# Copyright 2019, 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 logging -import time - -from serial import EIGHTBITS, STOPBITS_ONE, PARITY_NONE -from serial.serialutil import SerialException - -from digi.xbee.devices import XBeeDevice -from digi.xbee.models.atcomm import ATStringCommand -from digi.xbee.models.hw import HardwareVersion -from digi.xbee.models.mode import OperatingMode -from digi.xbee.profile import FirmwareBaudrate, FirmwareParity, FirmwareStopbits -from digi.xbee.exception import RecoveryException, XBeeException -from digi.xbee.util import utils - - -SUPPORTED_HARDWARE_VERSIONS = (HardwareVersion.XBEE3.code, - HardwareVersion.XBEE3_SMT.code, - HardwareVersion.XBEE3_TH.code) - -_BAUDRATE_KEY = "baudrate" -_PARITY_KEY = "parity" -_STOPBITS_KEY = "stopbits" -_API_ENABLE_KEY = "api_enable" -_CMD_SEQ_CHAR_KEY = "cmd_seq_char" -_GUARD_TIME_KEY = "guard_time" -_APPLY_CHANGES_KEY = "apply_changes" -_WRITE_REGISTER_KEY = "write_register" -_EXIT_MODE_KEY = "exit_mode" - -_RECOVERY_PORT_PARAMETERS = {_BAUDRATE_KEY: 38400, - "bytesize": EIGHTBITS, - _PARITY_KEY: PARITY_NONE, - _STOPBITS_KEY: STOPBITS_ONE, - "xonxoff": False, - "dsrdtr": False, - "rtscts": False, - "timeout": 0.1, - "write_timeout": None, - "inter_byte_timeout": None - } - -_RECOVERY_CHAR_TO_BAUDRATE = { - 0xf8: 9600, - 0x80: 9600, - 0xfe: 19200, - 0x30: 38400, - 0x7e: 38400, - 0x63: 115200 -} - -_DEFAULT_GUARD_TIME = 1 # seconds -_DEVICE_BREAK_RESET_TIMEOUT = 10 # seconds -_BOOTLOADER_CONTINUE_KEY = "2" -_RECOVERY_DETECTION_TRIES = 2 -_BOOTLOADER_BAUDRATE = 115200 -_AT_COMMANDS = {_BAUDRATE_KEY: "at%s" % ATStringCommand.BD.command, - _PARITY_KEY: "at%s" % ATStringCommand.NB.command, - _STOPBITS_KEY: "at%s" % ATStringCommand.SB.command, - _API_ENABLE_KEY: "at%s" % ATStringCommand.AP.command, - _CMD_SEQ_CHAR_KEY: "at%s" % ATStringCommand.CC.command, - _GUARD_TIME_KEY: "at%s" % ATStringCommand.GT.command, - _APPLY_CHANGES_KEY: "at%s\r" % ATStringCommand.AC.command, - _WRITE_REGISTER_KEY: "at%s\r" % ATStringCommand.WR.command, - _EXIT_MODE_KEY: "at%s\r" % ATStringCommand.CN.command - } -AT_OK_RESPONSE = b'OK\r' -_BAUDS_LIST = tuple(e.value[1] for e in FirmwareBaudrate) -_PARITY_LIST = tuple(e.value[1] for e in FirmwareParity) -_STOPBITS_LIST = tuple(e.value[1] for e in FirmwareStopbits) - -_log = logging.getLogger(__name__) - - -class _LocalRecoverDevice(object): - """ - Helper class used to handle the local recovery process. - """ - - def __init__(self, target): - """ - Class constructor. Instantiates a new :class:`._LocalRecoverDevice` with the given parameters. - - Args: - target (String or :class:`.XBeeDevice`): target of the recovery operation. - String: serial port identifier. - :class:`.XBeeDevice`: the XBee device. - """ - self._xbee_serial_port = None - if isinstance(target, XBeeDevice): - self._xbee_device = target - self._device_was_connected = self._xbee_device.is_open() - self._xbee_serial_port = self._xbee_device.serial_port - else: - self._xbee_serial_port = target - self._xbee_device = None - self._device_was_connected = False - - self._desired_cfg = self._xbee_serial_port.get_settings() - self._desired_cfg[_CMD_SEQ_CHAR_KEY] = hex(ord('+'))[2:] - self._desired_cfg[_GUARD_TIME_KEY] = hex(1000)[2:] # 1000ms in hex - - if isinstance(target, XBeeDevice) \ - and self._xbee_device.operating_mode in \ - (OperatingMode.API_MODE, OperatingMode.ESCAPED_API_MODE): - self._desired_cfg[_API_ENABLE_KEY] = self._xbee_device.operating_mode.code - else: - self._desired_cfg[_API_ENABLE_KEY] = 1 - - def _enter_in_recovery(self): - """ - Enters the device in recovery mode. - - Returns: - Int: The baudrate if success or ``None`` in case of failure. - """ - - # Set break line and baudrate - self._xbee_serial_port.apply_settings(_RECOVERY_PORT_PARAMETERS) - self._xbee_serial_port.purge_port() - self._xbee_serial_port.break_condition = True - - recovery_baudrate = None - timeout = time.time() + _DEVICE_BREAK_RESET_TIMEOUT - while time.time() < timeout: - time.sleep(0.2) - try: - # The first byte indicates the baudrate - if self._xbee_serial_port.in_waiting > 0: - read_bytes = self._xbee_serial_port.read(self._xbee_serial_port.in_waiting) - _log.debug("Databytes read from recovery are %s" % repr(utils.hex_to_string(read_bytes))) - if read_bytes[0] in _RECOVERY_CHAR_TO_BAUDRATE.keys(): - recovery_baudrate = _RECOVERY_CHAR_TO_BAUDRATE[read_bytes[0]] - # The valid byte is only the first one, so do not retry the loop - break - except SerialException as e: - _log.exception(e) - - self._xbee_serial_port.break_condition = False - return recovery_baudrate - - def autorecover_device(self): - """ - Recovers the XBee from an unknown state. - - Raises: - RecoveryException: if there is any error performing the recovery action. - """ - if self._xbee_device is not None: - if self._xbee_device.is_open: - self._xbee_device.close() - self._xbee_serial_port.open() - self._xbee_serial_port.purge_port() - - _log.debug("Autorecovering the device by entering in recovery mode") - # Enter in recovery mode - recovery_baudrate = None - for tries in range(_RECOVERY_DETECTION_TRIES): - recovery_baudrate = self._enter_in_recovery() - if recovery_baudrate is None: - _log.debug("[try %d] Could not determine the baudrate to get the values in recovery mode" % tries) - else: - _log.debug("Recovery baudrate is %d" % recovery_baudrate) - break - - # If we couldn't enter in recovery mode, assume we are in bootloader and retry - if recovery_baudrate is None: - _log.error("Could not determine the baudrate in recovery mode, assuming device is in bootloader mode and " - "retrying") - self._xbee_serial_port.apply_settings({_BAUDRATE_KEY: _BOOTLOADER_BAUDRATE}) - self._xbee_serial_port.write(str.encode(_BOOTLOADER_CONTINUE_KEY)) - - _log.debug("Retrying to determine the baudrate in recovery mode") - for tries in range(_RECOVERY_DETECTION_TRIES): - recovery_baudrate = self._enter_in_recovery() - if recovery_baudrate is None: - _log.debug("[try %d] Could not determine the baudrate to get the values in recovery mode" % tries) - else: - _log.debug("Recovery baudrate is %d" % recovery_baudrate) - break - - if recovery_baudrate is None: - self._do_exception("Could not determine the baudrate in recovery mode") - - # Here we are in recovery mode - _log.debug("Reconfiguring the serial port to recovery baudrate of %d" % recovery_baudrate) - self._xbee_serial_port.apply_settings({_BAUDRATE_KEY: recovery_baudrate}) - - # Set the desired configuration permanently. - _log.debug("Forcing the current setup to {!r}".format(self._desired_cfg)) - - for command in ("%s%s\r" % ( - _AT_COMMANDS[_BAUDRATE_KEY], - _BAUDS_LIST.index(self._desired_cfg[_BAUDRATE_KEY])), - "%s%s\r" % ( - _AT_COMMANDS[_PARITY_KEY], - _PARITY_LIST.index(self._desired_cfg[_PARITY_KEY])), - "%s%s\r" % ( - _AT_COMMANDS[_STOPBITS_KEY], - _STOPBITS_LIST.index(self._desired_cfg[_STOPBITS_KEY])), - "%s%s\r" % ( - _AT_COMMANDS[_API_ENABLE_KEY], self._desired_cfg[_API_ENABLE_KEY]), - "%s%s\r" % ( - _AT_COMMANDS[_CMD_SEQ_CHAR_KEY], - self._desired_cfg[_CMD_SEQ_CHAR_KEY]), - "%s%s\r" % ( - _AT_COMMANDS[_GUARD_TIME_KEY], self._desired_cfg[_GUARD_TIME_KEY]), - _AT_COMMANDS[_APPLY_CHANGES_KEY], - _AT_COMMANDS[_WRITE_REGISTER_KEY], - _AT_COMMANDS[_EXIT_MODE_KEY]): - self._xbee_serial_port.write(str.encode(command)) - if command in (_AT_COMMANDS[_EXIT_MODE_KEY]): - time.sleep(_DEFAULT_GUARD_TIME) - timeout = time.time() + 2 - while self._xbee_serial_port.inWaiting() == 0 and time.time() < timeout: - time.sleep(0.1) - read = self._xbee_serial_port.read(self._xbee_serial_port.inWaiting()) - _log.debug("command {!r} = {!r}".format(command, read)) - if AT_OK_RESPONSE not in read: - self._do_exception( - "Command {!r} failed, non OK returned value of {!r}".format(command, read)) - if command == _AT_COMMANDS[_APPLY_CHANGES_KEY]: - self._xbee_serial_port.apply_settings(self._desired_cfg) - - self._restore_target_connection() - - def _do_exception(self, msg): - """ - Logs the "msg" at error level and restores the target connection - - Args: - msg (String): message to log - - Raises: - RecoveryException: if the restore of the connection was successful. - XBeeException: if there is any error restoring the device connection. - """ - _log.error(msg) - try: - self._restore_target_connection() - except XBeeException as e: - _log.error("Could not restore connection: %s" % e) - raise RecoveryException(msg) - - def _restore_target_connection(self): - """ - Leaves the firmware update target connection (XBee device or serial port) in its original state. - - Raises: - SerialException: if there is any error restoring the serial port connection. - XBeeException: if there is any error restoring the device connection. - """ - if self._xbee_device is not None: - if self._xbee_serial_port is not None: - if self._xbee_serial_port.isOpen(): - self._xbee_serial_port.close() - if self._device_was_connected and not self._xbee_device.is_open(): - self._xbee_device.open() - elif not self._device_was_connected and self._xbee_device.is_open(): - self._xbee_device.close() - elif self._xbee_serial_port is not None and self._xbee_serial_port.isOpen(): - self._xbee_serial_port.close() - _log.debug("Restored target connection") - - -def recover_device(target): - """ - Recovers the XBee from an unknown state and leaves if configured for normal operations. - - Args: - target (String or :class:`.XBeeDevice`): target of the recovery operation. - - Raises: - RecoveryException: if there is any error performing the recovery action. - """ - # Launch the recover process. - recovery_process = _LocalRecoverDevice(target) - recovery_process.autorecover_device() diff --git a/digi/xbee/serial.py b/digi/xbee/serial.py deleted file mode 100644 index 9504a6e..0000000 --- a/digi/xbee/serial.py +++ /dev/null @@ -1,300 +0,0 @@ -# Copyright 2017-2019, 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 abc -import time -from abc import abstractmethod, ABCMeta - -from digi.xbee.comm_interface import XBeeCommunicationInterface -from digi.xbee.models.atcomm import SpecialByte -from digi.xbee.models.mode import OperatingMode -from digi.xbee.packets.base import XBeeAPIPacket, XBeePacket -from digi.xbee.util import utils -from serial import Serial, EIGHTBITS, STOPBITS_ONE, PARITY_NONE -import enum -import digi.xbee.exception - - -class FlowControl(enum.Enum): - """ - This class represents all available flow controls. - """ - - NONE = None - SOFTWARE = 0 - HARDWARE_RTS_CTS = 1 - HARDWARE_DSR_DTR = 2 - UNKNOWN = 99 - - -class XBeeSerialPort(Serial, XBeeCommunicationInterface): - """ - This class extends the functionality of Serial class (PySerial). - - It also introduces a minor change in its behaviour: the serial port is not automatically open when an object is - instantiated, only when calling open(). - - .. seealso:: - | _PySerial: https://github.com/pyserial/pyserial - """ - - __DEFAULT_PORT_TIMEOUT = 0.1 # seconds - __DEFAULT_DATA_BITS = EIGHTBITS - __DEFAULT_STOP_BITS = STOPBITS_ONE - __DEFAULT_PARITY = PARITY_NONE - __DEFAULT_FLOW_CONTROL = FlowControl.NONE - - def __init__(self, baud_rate, port, - data_bits=__DEFAULT_DATA_BITS, stop_bits=__DEFAULT_STOP_BITS, parity=__DEFAULT_PARITY, - flow_control=__DEFAULT_FLOW_CONTROL, timeout=__DEFAULT_PORT_TIMEOUT): - """ - Class constructor. Instantiates a new ``XBeeSerialPort`` object with the given - port parameters. - - Args: - baud_rate (Integer): serial port baud rate. - port (String): serial port name to use. - data_bits (Integer, optional): serial data bits. Default to 8. - stop_bits (Float, optional): serial stop bits. Default to 1. - parity (Char, optional): serial parity. Default to 'N' (None). - flow_control (Integer, optional): serial flow control. Default to ``None``. - timeout (Integer, optional): read timeout. Default to 0.1 seconds. - - .. seealso:: - | _PySerial: https://github.com/pyserial/pyserial - """ - if flow_control == FlowControl.SOFTWARE: - Serial.__init__(self, port=None, baudrate=baud_rate, - bytesize=data_bits, stopbits=stop_bits, parity=parity, timeout=timeout, xonxoff=True) - elif flow_control == FlowControl.HARDWARE_DSR_DTR: - Serial.__init__(self, port=None, baudrate=baud_rate, - bytesize=data_bits, stopbits=stop_bits, parity=parity, timeout=timeout, dsrdtr=True) - elif flow_control == FlowControl.HARDWARE_RTS_CTS: - Serial.__init__(self, port=None, baudrate=baud_rate, - bytesize=data_bits, stopbits=stop_bits, parity=parity, timeout=timeout, rtscts=True) - else: - Serial.__init__(self, port=None, baudrate=baud_rate, - bytesize=data_bits, stopbits=stop_bits, parity=parity, timeout=timeout) - self.__port_to_open = port - self._isReading = False - - def __str__(self): - return '{name} {p.portstr!r}'.format(name=self.__class__.__name__, p=self) - - def open(self): - """ - Opens port with current settings. This may throw a SerialException - if the port cannot be opened. - """ - self.port = self.__port_to_open - super().open() - - @property - def is_interface_open(self): - """ - Returns whether the underlying hardware communication interface is active or not. - - Returns: - Boolean. ``True`` if the interface is active, ``False`` otherwise. - """ - return self.isOpen() - - def write_frame(self, frame): - """ - Writes an XBee frame to the underlying hardware interface. - - Subclasses may throw specific exceptions to signal implementation specific - hardware errors. - - Args: - frame (:class:`.Bytearray`): The XBee API frame packet to write. If the bytearray does not - correctly represent an XBee frame, the behaviour is undefined. - """ - self.write(frame) - - def read_byte(self): - """ - Synchronous. Reads one byte from serial port. - - Returns: - Integer: the read byte. - - Raises: - TimeoutException: if there is no bytes ins serial port buffer. - """ - byte = bytearray(self.read(1)) - if len(byte) == 0: - raise digi.xbee.exception.TimeoutException() - else: - return byte[0] - - def read_bytes(self, num_bytes): - """ - Synchronous. Reads the specified number of bytes from the serial port. - - Args: - num_bytes (Integer): the number of bytes to read. - - Returns: - Bytearray: the read bytes. - - Raises: - TimeoutException: if the number of bytes read is less than ``num_bytes``. - """ - read_bytes = bytearray(self.read(num_bytes)) - if len(read_bytes) != num_bytes: - raise digi.xbee.exception.TimeoutException() - return read_bytes - - def __read_next_byte(self, operating_mode=OperatingMode.API_MODE): - """ - Returns the next byte in bytearray format. If the operating mode is - OperatingMode.ESCAPED_API_MODE, the bytearray could contain 2 bytes. - - If in escaped API mode and the byte that was read was the escape byte, - it will also read the next byte. - - Args: - operating_mode (:class:`.OperatingMode`): the operating mode in which the byte should be read. - - Returns: - Bytearray: the read byte or bytes as bytearray, ``None`` otherwise. - """ - read_data = bytearray() - read_byte = self.read_byte() - read_data.append(read_byte) - # Read escaped bytes in API escaped mode. - if operating_mode == OperatingMode.ESCAPED_API_MODE and read_byte == XBeePacket.ESCAPE_BYTE: - read_data.append(self.read_byte()) - - return read_data - - def quit_reading(self): - """ - Makes the thread (if any) blocking on wait_for_frame return. - - If a thread was blocked on wait_for_frame, this method blocks (for a maximum of 'timeout' seconds) until - the blocked thread is resumed. - """ - if self._isReading: - # As this is the only way to stop reading, self._isReading is reused to signal the stop reading request. - self._isReading = False - - # Ensure we block until the reading thread resumes. - # (could be improved using locks in the future) - time.sleep(self.timeout) - - def wait_for_frame(self, operating_mode=OperatingMode.API_MODE): - """ - Reads the next packet. Starts to read when finds the start delimiter. - The last byte read is the checksum. - - If there is something in the COM buffer after the - start delimiter, this method discards it. - - If the method can't read a complete and correct packet, - it will return ``None``. - - Args: - operating_mode (:class:`.OperatingMode`): the operating mode in which the packet should be read. - - Returns: - Bytearray: the read packet as bytearray if a packet is read, ``None`` otherwise. - """ - self._isReading = True - - try: - xbee_packet = bytearray(1) - # Add packet delimiter. - xbee_packet[0] = self.read_byte() - while xbee_packet[0] != SpecialByte.HEADER_BYTE.value: - # May be set to false by self.quit_reading() as a stop reading request. - if not self._isReading: - return None - xbee_packet[0] = self.read_byte() - - # Add packet length. - packet_length_byte = bytearray() - for _ in range(2): - packet_length_byte += self.__read_next_byte(operating_mode) - xbee_packet += packet_length_byte - # Length needs to be un-escaped in API escaped mode to obtain its integer equivalent. - if operating_mode == OperatingMode.ESCAPED_API_MODE: - length = utils.length_to_int(XBeeAPIPacket.unescape_data(packet_length_byte)) - else: - length = utils.length_to_int(packet_length_byte) - - # Add packet payload. - for _ in range(length): - xbee_packet += self.__read_next_byte(operating_mode) - - # Add packet checksum. - xbee_packet += self.__read_next_byte(operating_mode) - - # Return the packet unescaped. - if operating_mode == OperatingMode.ESCAPED_API_MODE: - return XBeeAPIPacket.unescape_data(xbee_packet) - else: - return xbee_packet - except digi.xbee.exception.TimeoutException: - return None - - def read_existing(self): - """ - Asynchronous. Reads all bytes in the serial port buffer. May read 0 bytes. - - Returns: - Bytearray: the bytes read. - """ - return bytearray(self.read(self.inWaiting())) - - def get_read_timeout(self): - """ - Returns the serial port read timeout. - - Returns: - Integer: read timeout in seconds. - """ - return self.timeout - - def set_read_timeout(self, read_timeout): - """ - Sets the serial port read timeout in seconds. - - Args: - read_timeout (Integer): the new serial port read timeout in seconds. - """ - self.timeout = read_timeout - - def set_baudrate(self, new_baudrate): - """ - Changes the serial port baudrate. - - Args: - new_baudrate (Integer): the new baudrate to set. - """ - if new_baudrate is None: - return - - port_settings = self.get_settings() - port_settings["baudrate"] = new_baudrate - self.apply_settings(port_settings) - - def purge_port(self): - """ - Purges the serial port by cleaning the input and output buffers. - """ - - self.reset_input_buffer() - self.reset_output_buffer() diff --git a/digi/xbee/util/utils.py b/digi/xbee/util/utils.py index 4971c67..3983855 100644 --- a/digi/xbee/util/utils.py +++ b/digi/xbee/util/utils.py @@ -1,4 +1,4 @@ -# Copyright 2017-2019, Digi International Inc. +# Copyright 2017, 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,7 +13,7 @@ # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. import logging -from functools import wraps + # Number of bits to extract with the mask (__MASK) __MASK_NUM_BITS = 8 @@ -36,44 +36,6 @@ def is_bit_enabled(number, position): return ((number & 0xFFFFFFFF) >> position) & 0x01 == 0x01 -def get_int_from_byte(number, offset, length): - """ - Reads an integer value from the given byte using the provived bit offset and length. - - Args: - number (Integer): Byte to read the integer from. - offset (Integer): Bit offset inside the byte to start reading (LSB = 0, MSB = 7). - length (Integer): Number of bits to read. - - Returns: - Integer: The integer value read. - - Raises: - ValueError: If ``number`` is lower than 0 or higher than 255. - If ``offset`` is lower than 0 or higher than 7. - If ``length`` is lower than 0 or higher than 8. - If ``offset + length`` is higher than 8. - """ - if number < 0 or number > 255: - raise ValueError("Number must be between 0 and 255") - if offset < 0 or offset > 7: - raise ValueError("Offset must be between 0 and 7") - if length < 0 or length > 8: - raise ValueError("Length must be between 0 and 8") - if offset + length > 8: - raise ValueError( - "Starting at offset=%d, length must be between 0 and %d" % (offset, 8 - offset)) - - if not length: - return 0 - - binary = "{0:08b}".format(number) - end = len(binary) - offset - 1 - start = end - length + 1 - - return int(binary[start:end + 1], 2) - - def hex_string_to_bytes(hex_string): """ Converts a String (composed by hex. digits) into a bytearray with same digits. @@ -343,46 +305,3 @@ def disable_logger(name): """ log = logging.getLogger(name) log.disabled = True - - -def deprecated(version, details="None"): - """ - Decorates a method to mark as deprecated. - This adds a deprecation note to the method docstring and also raises a - :class:``warning.DeprecationWarning``. - - Args: - version (String): Version that deprecates this feature. - details (String, optional, default=``None``): Extra details to be added to the - method docstring and warning. - """ - def _function_wrapper(func): - docstring = func.__doc__ or "" - msg = ".. deprecated:: %s\n" % version - - doc_list = docstring.split(sep="\n", maxsplit=1) - leading_spaces = 0 - if len(doc_list) > 1: - leading_spaces = len(doc_list[1]) - len(doc_list[1].lstrip()) - - doc_list.insert(0, "\n\n") - doc_list.insert(0, ' ' * (leading_spaces + 4) + details if details else "") - doc_list.insert(0, ' ' * leading_spaces + msg) - doc_list.insert(0, "\n") - - func.__doc__ = "".join(doc_list) - - @wraps(func) - def _inner(*args, **kwargs): - message = "'%s' is deprecated." % func.__name__ - if details: - message = "%s %s" % (message, details) - import warnings - warnings.simplefilter("default") - warnings.warn(message, category=DeprecationWarning, stacklevel=2) - - return func(*args, **kwargs) - - return _inner - - return _function_wrapper diff --git a/digi/xbee/util/xmodem.py b/digi/xbee/util/xmodem.py deleted file mode 100644 index 5b473d9..0000000 --- a/digi/xbee/util/xmodem.py +++ /dev/null @@ -1,1136 +0,0 @@ -# Copyright 2019, 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 collections -import os -import time - -from enum import Enum - -_ERROR_VALUE_DEST_PATH = "Destination path must be a non empty String" -_ERROR_VALUE_READ_CB = "Read callback must be a valid callable function" -_ERROR_VALUE_SRC_PATH = "Source path must be a non empty String" -_ERROR_VALUE_WRITE_CB = "Write callback must be a valid callable function" -_ERROR_XMODEM_BAD_BLOCK_NUMBER = "Bad block number in block #%d (received %d)" -_ERROR_XMODEM_BAD_DATA = "Data verification failed" -_ERROR_XMODEM_CANCELLED = "XModem transfer was cancelled by the remote end" -_ERROR_XMODEM_FINISH_TRANSFER = "Could not finish XModem transfer after %s retries" -_ERROR_XMODEM_READ_PACKET = "XModem packet could not be read after %s retries" -_ERROR_XMODEM_READ_PACKET_TIMEOUT = "Timeout reading XModem packet" -_ERROR_XMODEM_READ_VERIFICATION = "Could not read XModem verification byte after %s retries" -_ERROR_XMODEM_SEND_ACK_BYTE = "Could not send XModem ACK byte" -_ERROR_XMODEM_SEND_NAK_BYTE = "Could not send XModem NAK byte" -_ERROR_XMODEM_SEND_VERIFICATION_BYTE = "Could not send XModem verification byte" -_ERROR_XMODEM_UNEXPECTED_EOT = "Unexpected end of transmission" -_ERROR_XMODEM_TRANSFER_NAK = "XModem packet not acknowledged after %s retries" -_ERROR_XMODEM_WRITE_TO_FILE = "Could not write data to file '%s': %s" - -_PADDING_BYTE_XMODEM = 0xFF -_PADDING_BYTE_YMODEM = 0x1A - -XMODEM_ACK = 0x06 # Packet acknowledged. -XMODEM_CAN = 0x18 # Cancel transmission. -XMODEM_CRC = "C" -XMODEM_CRC_POLYNOMINAL = 0x1021 -XMODEM_EOT = 0x04 # End of transmission. -XMODEM_NAK = 0x15 # Packet not acknowledged. -XMODEM_SOH = 0x01 # Start of header (128 data bytes). -XMODEM_STX = 0x02 # Start of header (1024 data bytes). - -_XMODEM_BLOCK_SIZE_128 = 128 -_XMODEM_BLOCK_SIZE_1K = 1024 -_XMODEM_READ_HEADER_TIMEOUT = 3 # Seconds -_XMODEM_READ_DATA_TIMEOUT = 1 # Seconds. -_XMODEM_READ_RETRIES = 10 -_XMODEM_WRITE_RETRIES = 10 - - -class XModemException(Exception): - """ - This exception will be thrown when any problem related with the XModem/YModem transfer occurs. - - All functionality of this class is the inherited from `Exception - `_. - """ - pass - - -class XModemCancelException(XModemException): - """ - This exception will be thrown when the XModem/YModem transfer is cancelled by the remote end. - - All functionality of this class is the inherited from `Exception - `_. - """ - pass - - -class _XModemMode(Enum): - """ - This class lists the available XModem modes. - - | Inherited properties: - | **name** (String): The name of this _XModemMode. - | **value** (Integer): The ID of this _XModemMode. - """ - XMODEM = ("XModem", _XMODEM_BLOCK_SIZE_128, _PADDING_BYTE_XMODEM) - YMODEM = ("YModem", _XMODEM_BLOCK_SIZE_1K, _PADDING_BYTE_YMODEM) - - def __init__(self, name, block_size, eof_pad): - self.__name = name - self.__block_size = block_size - self.__eof_pad = eof_pad - - @property - def name(self): - """ - Returns the name of the _XModemMode element. - - Returns: - String: the name of the _XModemMode element. - """ - return self.__name - - @property - def block_size(self): - """ - Returns the block size of the _XModemMode element. - - Returns: - Integer: the block size of the _XModemMode element. - """ - return self.__block_size - - @property - def eof_pad(self): - """ - Returns the end of file padding byte of the _XModemMode element. - - Returns: - Integer: the end of file padding byte of the _XModemMode element. - """ - return self.__eof_pad - - -class _XModemVerificationMode(Enum): - """ - This class lists the available XModem verification modes. - - | Inherited properties: - | **name** (String): The name of this _XModemVerificationMode. - | **value** (Integer): The ID of this _XModemVerificationMode. - """ - CHECKSUM = ("Checksum", 1, XMODEM_NAK) - CRC_16 = ("16-bit CRC", 2, ord(XMODEM_CRC)) - - def __init__(self, name, length, byte): - self.__name = name - self.__length = length - self.__byte = byte - - @property - def name(self): - """ - Returns the name of the _XModemVerificationMode element. - - Returns: - String: the name of the _XModemVerificationMode element. - """ - return self.__name - - @property - def length(self): - """ - Returns the byte length of the _XModemVerificationMode element. - - Returns: - Integer: the byte length of the _XModemVerificationMode element. - """ - return self.__length - - @property - def byte(self): - """ - Returns the _XModemVerificationMode element byte. - - Returns: - Integer: the _XModemVerificationMode element byte. - """ - return self.__byte - - -class _TransferFile(object): - """ - Helper class used to read and split the file to transfer in data chunks. - """ - - def __init__(self, file_path, mode): - """ - Class constructor. Instantiates a new :class:`._TransferFile` with the given parameters. - - Args: - file_path (String): location of the file. - mode (:class:`._XModemMode`): the XModem transfer mode. - """ - self._file_path = file_path - self._mode = mode - # Calculate the total number of chunks (for percentage purposes later). - file_size = os.stat(file_path).st_size - self._chunk_index = 1 - self._num_chunks = file_size // mode.block_size - if file_size % mode.block_size: - self._num_chunks += 1 - - def get_next_data_chunk(self): - """ - Returns the next data chunk of this file. - - Returns: - Bytearray: the next data chunk of the file as byte array. - """ - with open(self._file_path, "rb") as file: - while True: - read_bytes = file.read(self._mode.block_size) - if not read_bytes: - break - if len(read_bytes) < self._mode.block_size: - # Since YModem allows for mixed block sizes transmissions, optimize - # the packet size if the last block is < 128 bytes. - if len(read_bytes) < _XMODEM_BLOCK_SIZE_128: - data = bytearray([self._mode.eof_pad] * _XMODEM_BLOCK_SIZE_128) - else: - data = bytearray([self._mode.eof_pad] * self._mode.block_size) - data[0:len(read_bytes)] = read_bytes - yield data - else: - yield read_bytes - self._chunk_index += 1 - - @property - def num_chunks(self): - """ - Returns the total number of data chunks of this file. - - Returns: - Integer: the total number of data chunks of this file. - """ - return self._num_chunks - - @property - def chunk_index(self): - """ - Returns the current data chunk index. - - Returns: - Integer: the current data chunk index. - """ - return self._chunk_index - - @property - def percent(self): - """ - Returns the transfer file progress percent. - - Returns: - Integer: the transfer file progress percent. - """ - return (self._chunk_index * 100) // self._num_chunks - - -class _DownloadFile(object): - """ - Helper class used to create and write the download file from the given data chunks. - """ - - def __init__(self, file_path, mode): - """ - Class constructor. Instantiates a new :class:`._DownloadFile` with the given parameters. - - Args: - file_path (String): location of the file. - mode (:class:`._XModemMode`): the XModem transfer mode. - """ - self._file_path = file_path - self._mode = mode - self._size = 0 - self._name = None - self._num_chunks = 0 - self._chunk_index = 1 - self._written_bytes = 0 - self._file = None - - def write_data_chunk(self, data): - """ - Writes the given data chunk in the file. - - Args: - data (Bytearray): the data chunk to write in the file. - """ - try: - if self._file is None: - self._file = open(self._file_path, "wb+") - - bytes_to_write = len(data) - # It might be the case that the last data block contains padding data. - # Get rid of it by calculating remaining bytes to write. - if self._size != 0: - bytes_to_write = min(bytes_to_write, self.size - self._written_bytes) - self._file.write(data[0:bytes_to_write]) - self._written_bytes += bytes_to_write - self._chunk_index += 1 - except Exception as e: - self.close_file() - raise XModemException(_ERROR_XMODEM_WRITE_TO_FILE % (self._file_path, str(e))) - - def close_file(self): - """ - Closes the file. - """ - if self._file: - self._file.close() - - @property - def num_chunks(self): - """ - Returns the total number of data chunks of this file. - - Returns: - Integer: the total number of data chunks of this file. - """ - return self._num_chunks - - @property - def chunk_index(self): - """ - Returns the current data chunk index. - - Returns: - Integer: the current data chunk index. - """ - return self._chunk_index - - @property - def size(self): - """ - Returns the size of the download file. - - Returns: - Integer: the size of the download file. - """ - return self._size - - @size.setter - def size(self, size): - """ - Sets the download file size. - - Args: - size (Integer): the download file size. - """ - self._size = size - self._num_chunks = self._size // self._mode.block_size - if self._size % self._mode.block_size: - self._num_chunks += 1 - - @property - def name(self): - """ - Returns the name of the download file. - - Returns: - String: the name of the download file. - """ - return self._name - - @name.setter - def name(self, name): - """ - Sets the download file name. - - Args: - name (String): the download file name. - """ - self._name = name - - @property - def percent(self): - """ - Returns the download file progress percent. - - Returns: - Integer: the download file progress percent. - """ - if self.size == 0: - return 0 - - return (self._chunk_index * 100) // self._num_chunks - - -class _XModemTransferSession(object): - """ - Helper class used to manage a XModem file transfer session. - """ - - def __init__(self, src_path, write_cb, read_cb, mode=_XModemMode.XMODEM, progress_cb=None, log=None): - """ - Class constructor. Instantiates a new :class:`._XModemTransferSession` with the given parameters. - - Args: - src_path (String): absolute path of the file to transfer. - write_cb (Function): function to execute in order to write data to the remote end. - Takes the following arguments: - - * The data to write as byte array. - - The function returns the following: - - Boolean: ``True`` if the write succeeded, ``False`` otherwise - - read_cb (Function): function to execute in order to read data from the remote end. - Takes the following arguments: - - * The size of the data to read. - * The timeout to wait for data. (seconds) - - The function returns the following: - - Bytearray: the read data, ``None`` if data could not be read - - mode (:class:`._XModemMode`, optional): the XModem transfer mode. Defaults to XModem. - progress_cb (Function, optional): function to execute in order to receive transfer progress information. - - Takes the following arguments: - - * The progress percentage as integer. - - log (:class:`.Logger`, optional): logger used to log transfer debug messages - """ - self._src_path = src_path - self._write_cb = write_cb - self._read_cb = read_cb - self._mode = mode - self._progress_cb = progress_cb - self._log = log - self._seq_index = 0 - self._transfer_file = None - self._verification_mode = _XModemVerificationMode.CHECKSUM - - def _read_verification_mode(self): - """ - Reads the transmission verification mode. - - Raises: - XModemCancelException: if the transfer is cancelled by the remote end. - XModemException: if there is any error reading the verification mode. - """ - if self._log: - self._log.debug("Reading verification mode...") - retries = _XMODEM_WRITE_RETRIES - while retries > 0: - verification = self._read_cb(1, timeout=_XMODEM_READ_DATA_TIMEOUT) - if not verification: - retries -= 1 - continue - verification = verification[0] - if verification == ord(XMODEM_CRC): - self._verification_mode = _XModemVerificationMode.CRC_16 - break - elif verification == XMODEM_NAK: - self._verification_mode = _XModemVerificationMode.CHECKSUM - break - elif verification == XMODEM_CAN: - # Cancel requested from remote device. - raise XModemCancelException(_ERROR_XMODEM_CANCELLED) - else: - # We got either NAK or something unexpected. - retries -= 1 - - # Check result. - if retries <= 0: - raise XModemException(_ERROR_XMODEM_READ_VERIFICATION % _XMODEM_WRITE_RETRIES) - if self._log: - self._log.debug("Verification mode is '%s'" % self._verification_mode.name) - - def _send_block_0(self): - """ - Sends the special YModem block 0 to the remote end. - - Raises: - XModemCancelException: if the transfer is cancelled by the remote end. - XModemException: if there is any error transferring the block 0. - """ - self._seq_index = 0 - name = str.encode(os.path.basename(self._src_path), encoding='utf-8') - size = str.encode(str(os.path.getsize(self._src_path)), encoding='utf-8') - mod_time = str.encode(str(oct(int(os.path.getctime(self._src_path)))), encoding='utf-8') - if (len(name) + len(size) + len(mod_time)) > 110: - data = bytearray(_XMODEM_BLOCK_SIZE_1K) - else: - data = bytearray(_XMODEM_BLOCK_SIZE_128) - data[0:len(name)] = name - data[len(name) + 1:len(name) + 1 + len(size)] = size - data[len(name) + len(size) + 1] = str.encode(" ", encoding='utf-8')[0] - data[len(name) + len(size) + 2:len(name) + len(size) + len(mod_time)] = mod_time[2:] - self._send_next_block(data) - - def _send_empty_block_0(self): - """ - Sends an empty YModem block 0 indicating YModem transmission has ended. - - Raises: - XModemCancelException: if the transfer is cancelled by the remote end. - XModemException: if there is any error transferring the empty header block 0. - """ - self._seq_index = 0 - data = bytearray([0] * _XMODEM_BLOCK_SIZE_128) - self._send_next_block(data) - - def _send_next_block(self, data): - """ - Sends the next XModem block using the given data chunk. - - Args: - data (Bytearray): data to send in the next block. - - Raises: - XModemCancelException: if the transfer is cancelled by the remote end. - XModemException: if there is any error transferring the next block. - """ - # Build XModem packet. - packet_size = len(data) + 3 + self._verification_mode.length # Extra 3 bytes for header and seq bytes. - packet = bytearray(packet_size) - # Write header, depends on the data block size. - if len(data) == _XMODEM_BLOCK_SIZE_1K: - packet[0] = XMODEM_STX - else: - packet[0] = XMODEM_SOH - # Write sequence index. - packet[1] = self._seq_index - # Write diff sequence index. - packet[2] = (255 - self._seq_index) & 0xFF - # Write data. - packet[3: 3 + len(data)] = data - # Write verification byte(s). - if self._verification_mode == _XModemVerificationMode.CHECKSUM: - packet[packet_size - _XModemVerificationMode.CHECKSUM.length:packet_size] = _calculate_checksum(data) - elif self._verification_mode == _XModemVerificationMode.CRC_16: - packet[packet_size - _XModemVerificationMode.CRC_16.length:packet_size] = _calculate_crc16_ccitt(data) - # Send XModem packet. - retries = _XMODEM_WRITE_RETRIES - answer = None - while retries > 0: - if self._log: - if self._seq_index == 0: - if self._mode == _XModemMode.YMODEM and len(data) == _XModemMode.XMODEM.block_size and data[0] == 0: - self._log.debug("Sending empty header - retry %d" % (_XMODEM_WRITE_RETRIES - retries + 1)) - else: - self._log.debug("Sending block 0 - retry %d" % (_XMODEM_WRITE_RETRIES - retries + 1)) - else: - self._log.debug("Sending chunk %d/%d %d%% - retry %d" % (self._transfer_file.chunk_index, - self._transfer_file.num_chunks, - self._transfer_file.percent, - _XMODEM_WRITE_RETRIES - retries + 1)) - if not self._write_cb(packet): - retries -= 1 - continue - answer = self._read_cb(1, timeout=_XMODEM_READ_DATA_TIMEOUT) - if not answer: - retries -= 1 - continue - answer = answer[0] - if answer == XMODEM_ACK: - # Block was sent successfully. - break - elif answer == XMODEM_CAN: - # Cancel requested from remote device. - raise XModemCancelException(_ERROR_XMODEM_CANCELLED) - else: - # We got either NAK or something unexpected. - retries -= 1 - - # Check result. - if answer == XMODEM_NAK or retries <= 0: - raise XModemException(_ERROR_XMODEM_TRANSFER_NAK % _XMODEM_WRITE_RETRIES) - self._seq_index = (self._seq_index + 1) & 0xFF - - def _send_eot(self): - """ - Sends the XModem end of transfer request (EOT). - - Raises: - XModemCancelException: if the transfer is cancelled by the remote end. - XModemException: if there is any error sending the end of transfer request. - """ - if self._log: - self._log.debug("Sending EOT") - retries = _XMODEM_WRITE_RETRIES - answer = None - while retries > 0: - if not self._write_cb(bytes([XMODEM_EOT])): - retries -= 1 - continue - # Read answer. - answer = self._read_cb(1, timeout=_XMODEM_READ_DATA_TIMEOUT) - if not answer: - retries -= 1 - continue - answer = answer[0] - if answer == XMODEM_ACK: - # Block was sent successfully. - break - elif answer == XMODEM_CAN: - # Transfer cancelled by the remote end. - raise XModemCancelException(_ERROR_XMODEM_CANCELLED) - else: - # We got either NAK or something unexpected. - retries -= 1 - - # Check result. - if answer == XMODEM_NAK or retries <= 0: - raise XModemException(_ERROR_XMODEM_FINISH_TRANSFER % _XMODEM_WRITE_RETRIES) - - def transfer_file(self): - """ - Performs the file transfer operation. - - Raises: - XModemCancelException: if the transfer is cancelled by the remote end. - XModemException: if there is any error during the file transfer. - """ - if self._log: - self._log.debug("Sending '%s' file through XModem" % self._src_path) - self._transfer_file = _TransferFile(self._src_path, self._mode) - # Read requested verification mode. - self._read_verification_mode() - # Execute special protocol pre-actions. - if self._mode == _XModemMode.YMODEM: - self._send_block_0() - else: - self._seq_index = 1 - # Perform file transfer. - previous_percent = None - for data_chunk in self._transfer_file.get_next_data_chunk(): - if self._progress_cb is not None and self._transfer_file.percent != previous_percent: - self._progress_cb(self._transfer_file.percent) - previous_percent = self._transfer_file.percent - self._send_next_block(data_chunk) - # Finish transfer. - self._send_eot() - # Execute special protocol post-actions. - if self._mode == _XModemMode.YMODEM: - self._read_verification_mode() - self._send_empty_block_0() - - -class _XModemReadSession(object): - """ - Helper class used to manage a XModem file read session. - """ - - def __init__(self, dest_path, write_cb, read_cb, mode=_XModemMode.XMODEM, - verification_mode=_XModemVerificationMode.CRC_16, progress_cb=None, log=None): - """ - Class constructor. Instantiates a new :class:`._XModemReadSession` with the given parameters. - - Args: - dest_path (String): absolute path to store downloaded file in. - write_cb (Function): function to execute in order to write data to the remote end. - Takes the following arguments: - - * The data to write as byte array. - - The function returns the following: - - Boolean: ``True`` if the write succeeded, ``False`` otherwise - - read_cb (Function): function to execute in order to read data from the remote end. - Takes the following arguments: - - * The size of the data to read. - * The timeout to wait for data. (seconds) - - The function returns the following: - - Bytearray: the read data, ``None`` if data could not be read - - mode (:class:`._XModemMode`, optional): the XModem transfer mode. Defaults to XModem. - verification_mode (:class:`._XModemVerificationMode`, optional): the XModem verification mode to use. - Defaults to 16-bit CRC. - progress_cb (Function, optional): function to execute in order to receive progress information. - - Takes the following arguments: - - * The progress percentage as integer. - - log (:class:`.Logger`, optional): logger used to log download debug messages - """ - self._dest_path = dest_path - self._write_cb = write_cb - self._read_cb = read_cb - self._mode = mode - self._verification_mode = verification_mode - self._progress_cb = progress_cb - self._log = log - self._seq_index = 0 - self._download_file = None - - def _send_data_with_retries(self, data, retries=_XMODEM_WRITE_RETRIES): - """ - Sends the given data to the remote end using the given number of retries. - - Args: - data (Bytearray): the data to send to the remote end. - retries (Integer, optional): the number of retries to perform. - - Returns: - Boolean: ``True`` if the data was sent successfully, ``False`` otherwise. - """ - _retries = retries - while _retries > 0: - if self._write_cb(data): - return True - time.sleep(0.1) - _retries -= 1 - - return False - - def _send_verification_char(self): - """ - Sends the verification request byte to indicate we are ready to receive data. - - Raises: - XModemException: if there is any error sending the verification request byte. - """ - if self._log: - self._log.debug("Sending verification character") - if not self._send_data_with_retries(bytearray([self._verification_mode.byte])): - raise XModemException(_ERROR_XMODEM_SEND_VERIFICATION_BYTE) - - def _send_ack(self): - """ - Sends the ACK byte to acknowledge the received data. - - Raises: - XModemException: if there is any error sending the ACK byte. - """ - if not self._send_data_with_retries(bytes([XMODEM_ACK])): - raise XModemException(_ERROR_XMODEM_SEND_ACK_BYTE) - - def _send_nak(self): - """ - Sends the NAK byte to discard received data. - - Raises: - XModemException: if there is any error sending the NAK byte. - """ - if not self._send_data_with_retries(bytes([XMODEM_NAK])): - raise XModemException(_ERROR_XMODEM_SEND_NAK_BYTE) - - def _purge(self): - """ - Purges the remote end by consuming all data until timeout (no data) is received. - """ - if self._log: - self._log.debug("Purging remote end...") - data = self._read_cb(1, timeout=1) - while data: - data = self._read_cb(1, timeout=1) - - def _read_packet(self): - """ - Reads an XModem packet from the remote end. - - Returns: - Bytearray: the packet data without protocol overheads. If data size is 0, it means end of transmission. - - Raises: - XModemCancelException: if the transfer is cancelled by the remote end. - XModemException: if there is any error reading the XModem packet. - """ - block_size = _XModemMode.XMODEM.block_size - retries = _XMODEM_READ_RETRIES - # Keep reading until a valid packet is received or retries are consumed. - while retries > 0: - if self._log: - if self._seq_index == 0: - self._log.debug("Reading block 0 - retry %d" % (_XMODEM_READ_RETRIES - retries + 1)) - elif self._download_file.size != 0 and \ - self._download_file.chunk_index <= self._download_file.num_chunks: - self._log.debug("Reading chunk %d/%d %d%% - retry %d" % (self._download_file.chunk_index, - self._download_file.num_chunks, - self._download_file.percent, - _XMODEM_WRITE_RETRIES - retries + 1)) - # Read the packet header (first byte). Use a timeout strategy to read it. - header = 0 - deadline = _get_milliseconds() + (_XMODEM_READ_HEADER_TIMEOUT * 1000) - while _get_milliseconds() < deadline: - header = self._read_cb(1, timeout=_XMODEM_READ_DATA_TIMEOUT) - if not header or len(header) == 0: - # Wait a bit and continue reading. - time.sleep(0.2) - continue - header = header[0] - if header == XMODEM_STX: - block_size = _XModemMode.YMODEM.block_size - break - elif header == XMODEM_SOH: - block_size = _XModemMode.XMODEM.block_size - break - elif header == XMODEM_EOT: - # Transmission from the remote end has finished. ACK it and return an empty byte array. - self._send_ack() - return bytearray(0) - elif header == XMODEM_CAN: - # The remote end has cancelled the transfer. - raise XModemCancelException(_ERROR_XMODEM_CANCELLED) - else: - # Unexpected content, read again. - continue - # If header is not valid, consume one retry and try again. - if header not in (XMODEM_STX, XMODEM_SOH): - retries -= 1 - continue - # At this point we have the packet header, SOH/STX. Read the sequence bytes. - seq_byte = self._read_cb(1, timeout=_XMODEM_READ_DATA_TIMEOUT) - if not seq_byte or len(seq_byte) == 0: - raise XModemException(_ERROR_XMODEM_READ_PACKET_TIMEOUT) - seq_byte = seq_byte[0] - seq_byte_2 = self._read_cb(1, timeout=_XMODEM_READ_DATA_TIMEOUT) - if not seq_byte_2 or len(seq_byte_2) == 0: - raise XModemException(_ERROR_XMODEM_READ_PACKET_TIMEOUT) - # Second sequence byte should be the same as first as 1's complement - seq_byte_2 = 0xff - seq_byte_2[0] - if not (seq_byte == seq_byte_2 == self._seq_index): - # Invalid block index. - if self._log: - self._log.error(_ERROR_XMODEM_BAD_BLOCK_NUMBER % (self._seq_index, seq_byte)) - # Consume data. - self._read_cb(block_size + self._verification_mode.length) - else: - data = self._read_cb(block_size, timeout=_XMODEM_READ_DATA_TIMEOUT) - if not data or len(data) != block_size: - raise XModemException(_ERROR_XMODEM_READ_PACKET_TIMEOUT) - verification = self._read_cb(self._verification_mode.length, timeout=_XMODEM_READ_DATA_TIMEOUT) - if not verification or len(verification) != self._verification_mode.length: - raise XModemException(_ERROR_XMODEM_READ_PACKET_TIMEOUT) - data_valid = True - if self._verification_mode == _XModemVerificationMode.CHECKSUM: - checksum = _calculate_checksum(data) - if checksum != verification[0]: - data_valid = False - else: - crc = _calculate_crc16_ccitt(data) - if crc[0] != verification[0] or crc[1] != verification[1]: - data_valid = False - if data_valid: - # ACK packet - self._send_ack() - self._seq_index = (self._seq_index + 1) & 0xFF - return data - else: - # Checksum/CRC is invalid. - if self._log: - self._log.error(_ERROR_XMODEM_BAD_DATA) - - # Reaching this point means the packet is not valid. Purge port and send NAK before trying again. - self._purge() - self._send_nak() - retries -= 1 - - # All read retries are consumed, throw exception. - raise XModemException(_ERROR_XMODEM_READ_PACKET % _XMODEM_READ_RETRIES) - - def _read_block_0(self): - """ - Reads the block 0 of the file download process and extract file information. - - Raises: - XModemCancelException: if the transfer is cancelled by the remote end. - XModemException: if there is any error reading the XModem block 0. - """ - self._seq_index = 0 - data = self._read_packet() - if not data or len(data) == 0: - raise XModemException(_ERROR_XMODEM_UNEXPECTED_EOT) - # If it is an empty header just ACK it and return. - if all(byte == 0 for byte in data): - self._send_ack() - return - # File name is the first data block until a '0' (0x00) is found. - index = 0 - name = bytearray() - for byte in data: - if byte == 0: - break - name.append(byte) - index += 1 - name = name.decode(encoding='utf-8') - self._download_file.name = name - # File size is the next data block until a '0' (0x00) is found. - size = bytearray() - for byte in data[index + 1:]: - if byte == 0: - break - size.append(byte) - index += 1 - size = int(size.decode(encoding='utf-8')) - self._download_file.size = size - - self._send_ack() - self._seq_index += 1 - - def get_file(self): - """ - Performs the file read operation. - - Raises: - XModemCancelException: if the transfer is cancelled by the remote end. - XModemException: if there is any error during the file read process. - """ - if self._log: - self._log.debug("Downloading '%s' file through XModem" % self._dest_path) - self._download_file = _DownloadFile(self._dest_path, self._mode) - # Notify we are ready to receive data. - self._send_verification_char() - # Execute special protocol pre-actions. - if self._mode == _XModemMode.YMODEM: - self._read_block_0() - else: - self._seq_index = 1 - # Perform file download process. - data = self._read_packet() - previous_percent = None - while len(data) > 0: - if self._progress_cb is not None and self._download_file.percent != previous_percent: - self._progress_cb(self._download_file.percent) - previous_percent = self._download_file.percent - self._download_file.write_data_chunk(data) - data = self._read_packet() - self._download_file.close_file() - # Execute special protocol post-actions. - if self._mode == _XModemMode.YMODEM: - self._send_verification_char() - self._read_block_0() - - -def _calculate_crc16_ccitt(data): - """ - Calculates and returns the CRC16 CCITT verification sequence of the given data. - - Args: - data (Bytearray): the data to calculate its CRC16 CCITT verification sequence. - - Returns: - Bytearray: the CRC16 CCITT verification sequence of the given data as a 2 bytes byte array. - """ - crc = 0x0000 - for i in range(0, len(data)): - crc ^= data[i] << 8 - for j in range(0, 8): - if (crc & 0x8000) > 0: - crc = (crc << 1) ^ XMODEM_CRC_POLYNOMINAL - else: - crc = crc << 1 - crc &= 0xFFFF - - return (crc & 0xFFFF).to_bytes(2, byteorder='big') - - -def _calculate_checksum(data): - """ - Calculates and returns the checksum verification byte of the given data. - - Args: - data (Bytearray): the data to calculate its checksum verification byte. - - Returns: - Integer: the checksum verification byte of the given data. - """ - checksum = 0 - for byte in data: - ch = byte & 0xFF - checksum += ch - - return checksum & 0xFF - - -def _get_milliseconds(): - """ - Returns the current time in milliseconds. - - Returns: - Integer: the current time in milliseconds. - """ - return int(time.time() * 1000.0) - - -def send_file_xmodem(src_path, write_cb, read_cb, progress_cb=None, log=None): - """ - Sends a file using the XModem protocol to a remote end. - - Args: - src_path (String): absolute path of the file to transfer. - write_cb (Function): function to execute in order to write data to the remote end. - Takes the following arguments: - - * The data to write as byte array. - - The function returns the following: - - Boolean: ``True`` if the write succeeded, ``False`` otherwise - - read_cb (Function): function to execute in order to read data from the remote end. - Takes the following arguments: - - * The size of the data to read. - * The timeout to wait for data. (seconds) - - The function returns the following: - - Bytearray: the read data, ``None`` if data could not be read - - progress_cb (Function, optional): function to execute in order to receive progress information. - - Takes the following arguments: - - * The progress percentage as integer. - - log (:class:`.Logger`, optional): logger used to log transfer debug messages - - Raises: - ValueError: if any input value is not valid. - XModemCancelException: if the transfer is cancelled by the remote end. - XModemException: if there is any error during the file transfer. - """ - # Sanity checks. - if not isinstance(src_path, str) or len(src_path) == 0: - raise ValueError(_ERROR_VALUE_SRC_PATH) - if not isinstance(write_cb, collections.Callable): - raise ValueError(_ERROR_VALUE_WRITE_CB) - if not isinstance(read_cb, collections.Callable): - raise ValueError(_ERROR_VALUE_READ_CB) - - session = _XModemTransferSession(src_path, write_cb, read_cb, mode=_XModemMode.XMODEM, progress_cb=progress_cb, - log=log) - session.transfer_file() - - -def send_file_ymodem(src_path, write_cb, read_cb, progress_cb=None, log=None): - """ - Sends a file using the YModem protocol to a remote end. - - Args: - src_path (String): absolute path of the file to transfer. - write_cb (Function): function to execute in order to write data to the remote end. - Takes the following arguments: - - * The data to write as byte array. - - The function returns the following: - - Boolean: ``True`` if the write succeeded, ``False`` otherwise - - read_cb (Function): function to execute in order to read data from the remote end. - Takes the following arguments: - - * The size of the data to read. - * The timeout to wait for data. (seconds) - - The function returns the following: - - Bytearray: the read data, ``None`` if data could not be read - - progress_cb (Function, optional): function to execute in order to receive progress information. - - Takes the following arguments: - - * The progress percentage as integer. - - log (:class:`.Logger`, optional): logger used to log transfer debug messages - - Raises: - ValueError: if any input value is not valid. - XModemCancelException: if the transfer is cancelled by the remote end. - XModemException: if there is any error during the file transfer. - """ - # Sanity checks. - if not isinstance(src_path, str) or len(src_path) == 0: - raise ValueError(_ERROR_VALUE_SRC_PATH) - if not isinstance(write_cb, collections.Callable): - raise ValueError(_ERROR_VALUE_WRITE_CB) - if not isinstance(read_cb, collections.Callable): - raise ValueError(_ERROR_VALUE_READ_CB) - - session = _XModemTransferSession(src_path, write_cb, read_cb, mode=_XModemMode.YMODEM, progress_cb=progress_cb, - log=log) - session.transfer_file() - - -def get_file_ymodem(dest_path, write_cb, read_cb, crc=True, progress_cb=None, log=None): - """ - Retrieves a file using the YModem protocol from a remote end. - - Args: - dest_path (String): absolute path to store downloaded file in. - write_cb (Function): function to execute in order to write data to the remote end. - Takes the following arguments: - - * The data to write as byte array. - - The function returns the following: - - Boolean: ``True`` if the write succeeded, ``False`` otherwise - - read_cb (Function): function to execute in order to read data from the remote end. - Takes the following arguments: - - * The size of the data to read. - * The timeout to wait for data. (seconds) - - The function returns the following: - - Bytearray: the read data, ``None`` if data could not be read - - crc (Boolean, optional): ``True`` to use 16-bit CRC verification, ``False`` for standard 1 byte checksum. - Defaults to ``True`` - progress_cb (Function, optional): function to execute in order to receive progress information. - - Takes the following arguments: - - * The progress percentage as integer. - - log (:class:`.Logger`, optional): logger used to log download debug messages - - Raises: - ValueError: if any input value is not valid. - XModemCancelException: if the file download is cancelled by the remote end. - XModemException: if there is any error during the file download process. - """ - # Sanity checks. - if not isinstance(dest_path, str) or len(dest_path) == 0: - raise ValueError(_ERROR_VALUE_DEST_PATH) - if not isinstance(write_cb, collections.Callable): - raise ValueError(_ERROR_VALUE_WRITE_CB) - if not isinstance(read_cb, collections.Callable): - raise ValueError(_ERROR_VALUE_READ_CB) - - if crc: - session = _XModemReadSession(dest_path, write_cb, read_cb, mode=_XModemMode.YMODEM, - verification_mode=_XModemVerificationMode.CRC_16, - progress_cb=progress_cb, log=log) - else: - session = _XModemReadSession(dest_path, write_cb, read_cb, mode=_XModemMode.YMODEM, - verification_mode=_XModemVerificationMode.CHECKSUM, - progress_cb=progress_cb, log=log) - session.get_file() diff --git a/digi/xbee/xbeeserial.py b/digi/xbee/xbeeserial.py new file mode 100644 index 0000000..f2a657c --- /dev/null +++ b/digi/xbee/xbeeserial.py @@ -0,0 +1,138 @@ +# Copyright 2017, 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 serial import Serial, EIGHTBITS, STOPBITS_ONE, PARITY_NONE +import enum +import digi.xbee.exception + + +class FlowControl(enum.Enum): + """ + This class represents all available flow controls. + """ + + NONE = None + SOFTWARE = 0 + HARDWARE_RTS_CTS = 1 + HARDWARE_DSR_DTR = 2 + UNKNOWN = 99 + + +class XBeeSerialPort(Serial): + """ + This class extends the functionality of Serial class (PySerial). + + .. seealso:: + | _PySerial: https://github.com/pyserial/pyserial + """ + + __DEFAULT_PORT_TIMEOUT = 0.1 # seconds + __DEFAULT_DATA_BITS = EIGHTBITS + __DEFAULT_STOP_BITS = STOPBITS_ONE + __DEFAULT_PARITY = PARITY_NONE + __DEFAULT_FLOW_CONTROL = FlowControl.NONE + + def __init__(self, baud_rate, port, + data_bits=__DEFAULT_DATA_BITS, stop_bits=__DEFAULT_STOP_BITS, parity=__DEFAULT_PARITY, + flow_control=__DEFAULT_FLOW_CONTROL, timeout=__DEFAULT_PORT_TIMEOUT): + """ + Class constructor. Instantiates a new ``XBeeSerialPort`` object with the given + port parameters. + + Args: + baud_rate (Integer): serial port baud rate. + port (String): serial port name to use. + data_bits (Integer, optional): serial data bits. Default to 8. + stop_bits (Float, optional): serial stop bits. Default to 1. + parity (Char, optional): serial parity. Default to 'N' (None). + flow_control (Integer, optional): serial flow control. Default to ``None``. + timeout (Integer, optional): read timeout. Default to 0.1 seconds. + + .. seealso:: + | _PySerial: https://github.com/pyserial/pyserial + """ + if flow_control == FlowControl.SOFTWARE: + Serial.__init__(self, port=port, baudrate=baud_rate, + bytesize=data_bits, stopbits=stop_bits, parity=parity, timeout=timeout, xonxoff=True) + elif flow_control == FlowControl.HARDWARE_DSR_DTR: + Serial.__init__(self, port=port, baudrate=baud_rate, + bytesize=data_bits, stopbits=stop_bits, parity=parity, timeout=timeout, dsrdtr=True) + elif flow_control == FlowControl.HARDWARE_RTS_CTS: + Serial.__init__(self, port=port, baudrate=baud_rate, + bytesize=data_bits, stopbits=stop_bits, parity=parity, timeout=timeout, rtscts=True) + else: + Serial.__init__(self, port=port, baudrate=baud_rate, + bytesize=data_bits, stopbits=stop_bits, parity=parity, timeout=timeout) + self._isOpen = True if port is not None else False + + def read_byte(self): + """ + Synchronous. Reads one byte from serial port. + + Returns: + Integer: the read byte. + + Raises: + TimeoutException: if there is no bytes ins serial port buffer. + """ + byte = bytearray(self.read(1)) + if len(byte) == 0: + raise digi.xbee.exception.TimeoutException() + else: + return byte[0] + + def read_bytes(self, num_bytes): + """ + Synchronous. Reads the specified number of bytes from the serial port. + + Args: + num_bytes (Integer): the number of bytes to read. + + Returns: + Bytearray: the read bytes. + + Raises: + TimeoutException: if the number of bytes read is less than ``num_bytes``. + """ + read_bytes = bytearray(self.read(num_bytes)) + if len(read_bytes) != num_bytes: + raise digi.xbee.exception.TimeoutException() + return read_bytes + + def read_existing(self): + """ + Asynchronous. Reads all bytes in the serial port buffer. May read 0 bytes. + + Returns: + Bytearray: the bytes read. + """ + return bytearray(self.read(self.inWaiting())) + + def get_read_timeout(self): + """ + Returns the serial port read timeout. + + Returns: + Integer: read timeout in seconds. + """ + return self.timeout + + def set_read_timeout(self, read_timeout): + """ + Sets the serial port read timeout in seconds. + + Args: + read_timeout (Integer): the new serial port read timeout in seconds. + """ + self.timeout = read_timeout diff --git a/digi/xbee/xsocket.py b/digi/xbee/xsocket.py deleted file mode 100644 index a1fd77e..0000000 --- a/digi/xbee/xsocket.py +++ /dev/null @@ -1,837 +0,0 @@ -# Copyright 2019, 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 threading -import time -from collections import OrderedDict -from ipaddress import IPv4Address - -from digi.xbee.devices import CellularDevice -from digi.xbee.exception import TimeoutException, XBeeSocketException, XBeeException -from digi.xbee.models.protocol import IPProtocol -from digi.xbee.models.status import SocketState, SocketStatus, TransmitStatus -from digi.xbee.packets.raw import TXStatusPacket -from digi.xbee.packets.socket import SocketConnectPacket, SocketCreatePacket, SocketSendPacket, SocketClosePacket, \ - SocketBindListenPacket, SocketNewIPv4ClientPacket, SocketOptionRequestPacket, SocketSendToPacket - - -class socket: - """ - This class represents an XBee socket and provides methods to create, - connect, bind and close a socket, as well as send and receive data with it. - """ - - __DEFAULT_TIMEOUT = 5 - __MAX_PAYLOAD_BYTES = 1500 - - def __init__(self, xbee_device, ip_protocol=IPProtocol.TCP): - """ - Class constructor. Instantiates a new XBee socket object for the given XBee device. - - Args: - xbee_device (:class:`.XBeeDevice`): XBee device of the socket. - ip_protocol (:class:`.IPProtocol`): protocol of the socket. - - Raises: - ValueError: if ``xbee_device`` is ``None`` or if ``xbee_device`` is not an instance of ``CellularDevice``. - ValueError: if ``ip_protocol`` is ``None``. - XBeeException: if the connection with the XBee device is not open. - """ - if xbee_device is None: - raise ValueError("XBee device cannot be None") - if not isinstance(xbee_device, CellularDevice): - raise ValueError("XBee device must be a Cellular device") - if ip_protocol is None: - raise ValueError("IP protocol cannot be None") - if not xbee_device.is_open(): - raise XBeeException("XBee device must be open") - - # Initialize internal vars. - self.__xbee_device = xbee_device - self.__ip_protocol = ip_protocol - self.__socket_id = None - self.__connected = False - self.__source_port = None - self.__is_listening = False - self.__backlog = None - self.__timeout = self.__DEFAULT_TIMEOUT - self.__data_received = bytearray() - self.__data_received_lock = threading.Lock() - self.__data_received_from_dict = OrderedDict() - self.__data_received_from_dict_lock = threading.Lock() - # Initialize socket callbacks. - self.__socket_state_callback = None - self.__data_received_callback = None - self.__data_received_from_callback = None - - def __enter__(self): - return self - - def __exit__(self, exc_type, exc_val, exc_tb): - self.close() - - def connect(self, address): - """ - Connects to a remote socket at the given address. - - Args: - address (Tuple): A pair ``(host, port)`` where ``host`` is the domain name or string representation of an - IPv4 and ``port`` is the numeric port value. - - Raises: - TimeoutException: if the connect response is not received in the configured timeout. - ValueError: if ``address`` is ``None`` or not a pair ``(host, port)``. - ValueError: if ``port`` is less than 1 or greater than 65535. - XBeeException: if the connection with the XBee device is not open. - XBeeSocketException: if the connect status is not ``SUCCESS``. - """ - # Check address and its contents. - if address is None or len(address) != 2: - raise ValueError("Invalid address, it must be a pair (host, port).") - - host = address[0] - port = address[1] - if isinstance(host, IPv4Address): - host = str(host) - if port < 1 or port > 65535: - raise ValueError("Port number must be between 1 and 65535.") - - # If the socket is not created, create it first. - if self.__socket_id is None: - self.__create_socket() - - lock = threading.Condition() - received_state = list() - - # Define the socket state received callback. - def socket_state_received_callback(socket_id, state): - # Check the socket ID. - if socket_id != self.__socket_id: - return - - # Add the state to the list and notify the lock. - received_state.append(state) - lock.acquire() - lock.notify() - lock.release() - - # Add the socket state received callback. - self.__xbee_device.add_socket_state_received_callback(socket_state_received_callback) - - try: - # Create, send and check the socket connect packet. - connect_packet = SocketConnectPacket(self.__xbee_device.get_next_frame_id(), self.__socket_id, port, - SocketConnectPacket.DEST_ADDRESS_STRING, host) - response_packet = self.__xbee_device.send_packet_sync_and_get_response(connect_packet, - timeout=self.__get_timeout()) - self.__check_response(response_packet) - - # Wait until the socket state frame is received confirming the connection. - if not received_state: - lock.acquire() - lock.wait(self.__timeout) - lock.release() - - # Check if the socket state has been received. - if not received_state: - raise TimeoutException("Timeout waiting for the socket connection") - - # Check if the socket is connected successfully. - if received_state[0] != SocketState.CONNECTED: - raise XBeeSocketException(status=received_state[0]) - - self.__connected = True - - # Register internal socket state and data reception callbacks. - self.__register_state_callback() - self.__register_data_received_callback() - finally: - # Always remove the socket state callback. - self.__xbee_device.del_socket_state_received_callback(socket_state_received_callback) - - def bind(self, address): - """ - Binds the socket to the given address. The socket must not already be bound. - - Args: - address (Tuple): A pair ``(host, port)`` where ``host`` is the local interface (not used) and ``port`` is - the numeric port value. - - Raises: - TimeoutException: if the bind response is not received in the configured timeout. - ValueError: if ``address`` is ``None`` or not a pair ``(host, port)``. - ValueError: if ``port`` is less than 1 or greater than 65535. - XBeeException: if the connection with the XBee device is not open. - XBeeSocketException: if the bind status is not ``SUCCESS``. - XBeeSocketException: if the socket is already bound. - """ - # Check address and its contents. - if address is None or len(address) != 2: - raise ValueError("Invalid address, it must be a pair (host, port).") - - port = address[1] - if port < 1 or port > 65535: - raise ValueError("Port number must be between 1 and 65535.") - if self.__source_port: - raise XBeeSocketException(status=SocketStatus.ALREADY_CONNECTED) - - # If the socket is not created, create it first. - if self.__socket_id is None: - self.__create_socket() - - # Create, send and check the socket create packet. - bind_packet = SocketBindListenPacket(self.__xbee_device.get_next_frame_id(), self.__socket_id, port) - response_packet = self.__xbee_device.send_packet_sync_and_get_response(bind_packet, - timeout=self.__get_timeout()) - self.__check_response(response_packet) - - # Register the internal data 'reception from' callback. - self.__register_data_received_from_callback() - - # Store the source port. - self.__source_port = port - - def listen(self, backlog=1): - """ - Enables a server to accept connections. - - Args: - backlog (Integer, optional): The number of unaccepted connections that the system will allow before refusing - new connections. If specified, it must be at least 0 (if it is lower, it is set to 0). - - Raises: - XBeeSocketException: if the socket is not bound. - """ - if self.__source_port is None: - raise XBeeSocketException(message="Socket must be bound") - - self.__is_listening = True - self.__backlog = backlog - - def accept(self): - """ - Accepts a connection. The socket must be bound to an address and listening for connections. - - Returns: - Tuple: A pair ``(conn, address)`` where ``conn`` is a new socket object usable to send and receive data on - the connection, and ``address`` is a pair ``(host, port)`` with the address bound to the socket on the - other end of the connection. - - Raises: - XBeeException: if the connection with the XBee device is not open. - XBeeSocketException: if the socket is not bound or not listening. - """ - if self.__source_port is None: - raise XBeeSocketException(message="Socket must be bound") - if not self.__is_listening: - raise XBeeSocketException(message="Socket must be listening") - - lock = threading.Condition() - received_packet = list() - - # Define the IPv4 client callback. - def ipv4_client_callback(packet): - if not isinstance(packet, SocketNewIPv4ClientPacket) or packet.socket_id != self.__socket_id: - return - - # Add the packet to the list and notify the lock. - received_packet.append(packet) - lock.acquire() - lock.notify() - lock.release() - - # Add the socket IPv4 client callback. - self.__xbee_device.add_packet_received_callback(ipv4_client_callback) - - try: - # Wait until an IPv4 client packet is received. - lock.acquire() - lock.wait() - lock.release() - - conn = socket(self.__xbee_device, self.__ip_protocol) - conn.__socket_id = received_packet[0].client_socket_id - conn.__connected = True - - # Register internal socket state and data reception callbacks. - conn.__register_state_callback() - conn.__register_data_received_callback() - - return conn, (received_packet[0].remote_address, received_packet[0].remote_port) - finally: - # Always remove the socket IPv4 client callback. - self.__xbee_device.del_packet_received_callback(ipv4_client_callback) - - def gettimeout(self): - """ - Returns the configured socket timeout in seconds. - - Returns: - Integer: the configured timeout in seconds. - """ - return self.__timeout - - def settimeout(self, timeout): - """ - Sets the socket timeout in seconds. - - Args: - timeout (Integer): the new socket timeout in seconds. - """ - self.__timeout = timeout - - def getblocking(self): - """ - Returns whether the socket is in blocking mode or not. - - Returns: - Boolean: ``True`` if the socket is in blocking mode, ``False`` otherwise. - """ - return self.gettimeout() is None - - def setblocking(self, flag): - """ - Sets the socket in blocking or non-blocking mode. - - Args: - flag (Boolean): ``True`` to set the socket in blocking mode, ``False`` to set it in no blocking mode and - configure the timeout with the default value (``5`` seconds). - """ - self.settimeout(None if flag else self.__DEFAULT_TIMEOUT) - - def recv(self, bufsize): - """ - Receives data from the socket. - - Args: - bufsize (Integer): The maximum amount of data to be received at once. - - Returns: - Bytearray: the data received. - - Raises: - ValueError: if ``bufsize`` is less than ``1``. - """ - if bufsize < 1: - raise ValueError("Number of bytes to receive must be grater than 0") - - data_received = bytearray() - - # Wait until data is available or the timeout configured in the socket expires. - if self.getblocking(): - while len(self.__data_received) == 0: - time.sleep(0.1) - else: - dead_line = time.time() + self.__timeout - while len(self.__data_received) == 0 and dead_line > time.time(): - time.sleep(0.1) - # Get the number of bytes specified in 'bufsize' from the internal var. - if len(self.__data_received) > 0: - self.__data_received_lock.acquire() - data_received = self.__data_received[0:bufsize].copy() - self.__data_received = self.__data_received[bufsize:] - self.__data_received_lock.release() - # Return the data received. - return data_received - - def recvfrom(self, bufsize): - """ - Receives data from the socket. - - Args: - bufsize (Integer): the maximum amount of data to be received at once. - - Returns: - Tuple (Bytearray, Tuple): Pair containing the data received (Bytearray) and the address of the socket - sending the data. The address is also a pair ``(host, port)`` where ``host`` is the string - representation of an IPv4 and ``port`` is the numeric port value. - - Raises: - ValueError: if ``bufsize`` is less than ``1``. - """ - if bufsize < 1: - raise ValueError("Number of bytes to receive must be grater than 0") - - data_received = bytearray() - address = None - - # Wait until data is received from any address or the timeout configured in the socket expires. - if self.getblocking(): - while len(self.__data_received_from_dict) == 0: - time.sleep(0.1) - else: - dead_line = time.time() + self.__timeout - while len(self.__data_received_from_dict) == 0 and dead_line > time.time(): - time.sleep(0.1) - # Get the number of bytes specified in 'bufsize' from the first address stored. - if len(self.__data_received_from_dict) > 0: - self.__data_received_from_dict_lock.acquire() - # Get 'bufsize' bytes from the first stored address in the internal dict. - address = list(self.__data_received_from_dict)[0] - data_received = self.__data_received_from_dict[address][0:bufsize].copy() - # Update the number of bytes left for 'address' in the dictionary. - self.__data_received_from_dict[address] = self.__data_received_from_dict[address][bufsize:] - # If the number of bytes left for 'address' is 0, remove it from the dictionary. - if len(self.__data_received_from_dict[address]) == 0: - self.__data_received_from_dict.pop(address) - self.__data_received_from_dict_lock.release() - # Return the data received for 'address'. - return data_received, address - - def send(self, data): - """ - Sends data to the socket and returns the number of bytes sent. The socket must be connected to a remote socket. - Applications are responsible for checking that all data has been sent; if only some of the data was - transmitted, the application needs to attempt delivery of the remaining data. - - Args: - data (Bytearray): the data to send. - - Returns: - Integer: the number of bytes sent. - - Raises: - ValueError: if the data to send is ``None``. - ValueError: if the number of bytes to send is ``0``. - XBeeException: if the connection with the XBee device is not open. - XBeeSocketException: if the socket is not valid. - XBeeSocketException: if the socket is not open. - """ - self.__send(data, False) - - def sendall(self, data): - """ - Sends data to the socket. The socket must be connected to a remote socket. Unlike ``send()``, this method - continues to send data from bytes until either all data has been sent or an error occurs. None is returned - on success. On error, an exception is raised, and there is no way to determine how much data, if any, was - successfully sent. - - Args: - data (Bytearray): the data to send. - - Raises: - TimeoutException: if the send status response is not received in the configured timeout. - ValueError: if the data to send is ``None``. - ValueError: if the number of bytes to send is ``0``. - XBeeException: if the connection with the XBee device is not open. - XBeeSocketException: if the socket is not valid. - XBeeSocketException: if the send status is not ``SUCCESS``. - XBeeSocketException: if the socket is not open. - """ - self.__send(data) - - def sendto(self, data, address): - """ - Sends data to the socket. The socket should not be connected to a remote socket, since the destination socket - is specified by ``address``. - - Args: - data (Bytearray): the data to send. - address (Tuple): the address of the destination socket. It must be a pair ``(host, port)`` where ``host`` - is the domain name or string representation of an IPv4 and ``port`` is the numeric port value. - - Returns: - Integer: the number of bytes sent. - - Raises: - TimeoutException: if the send status response is not received in the configured timeout. - ValueError: if the data to send is ``None``. - ValueError: if the number of bytes to send is ``0``. - XBeeException: if the connection with the XBee device is not open. - XBeeSocketException: if the socket is already open. - XBeeSocketException: if the send status is not ``SUCCESS``. - """ - if data is None: - raise ValueError("Data to send cannot be None") - if len(data) == 0: - raise ValueError("The number of bytes to send must be at least 1") - if not self.__xbee_device.is_open(): - raise XBeeException("XBee device must be open") - if self.__connected: - raise XBeeSocketException(message="Socket is already connected") - - sent_bytes = 0 - - # If the socket is not created, create it first. - if self.__socket_id is None: - self.__create_socket() - # Send as many packets as needed to deliver all the provided data. - for chunk in self.__split_payload(data): - send_packet = SocketSendToPacket(self.__xbee_device.get_next_frame_id(), self.__socket_id, - IPv4Address(address[0]), address[1], chunk) - response_packet = self.__xbee_device.send_packet_sync_and_get_response(send_packet, - timeout=self.__get_timeout()) - self.__check_response(response_packet) - sent_bytes += len(chunk) - # Return the number of bytes sent. - return sent_bytes - - def close(self): - """ - Closes the socket. - - Raises: - TimeoutException: if the close response is not received in the configured timeout. - XBeeException: if the connection with the XBee device is not open. - XBeeSocketException: if the close status is not ``SUCCESS``. - """ - if self.__socket_id is None or (not self.__connected and not self.__source_port): - return - if not self.__xbee_device.is_open(): - raise XBeeException("XBee device must be open") - - close_packet = SocketClosePacket(self.__xbee_device.get_next_frame_id(), self.__socket_id) - response_packet = self.__xbee_device.send_packet_sync_and_get_response(close_packet, - timeout=self.__get_timeout()) - self.__check_response(response_packet) - - self.__connected = False - self.__socket_id = None - self.__source_port = None - self.__data_received = bytearray() - self.__data_received_from_dict = OrderedDict() - self.__unregister_state_callback() - self.__unregister_data_received_callback() - self.__unregister_data_received_from_callback() - - def setsocketopt(self, option, value): - """ - Sets the value of the given socket option. - - Args: - option (:class:`.SocketOption`): the socket option to set its value. - value (Bytearray): the new value of the socket option. - - Raises: - TimeoutException: if the socket option response is not received in the configured timeout. - ValueError: if the option to set is ``None``. - ValueError: if the value of the option is ``None``. - XBeeException: if the connection with the XBee device is not open. - XBeeSocketException: if the socket option response status is not ``SUCCESS``. - """ - if option is None: - raise ValueError("Option to set cannot be None") - if value is None: - raise ValueError("Option value cannot be None") - if not self.__xbee_device.is_open(): - raise XBeeException("XBee device must be open") - - # If the socket is not created, create it first. - if self.__socket_id is None: - self.__create_socket() - - # Create, send and check the socket option packet. - option_packet = SocketOptionRequestPacket(self.__xbee_device.get_next_frame_id(), self.__socket_id, - option, value) - response_packet = self.__xbee_device.send_packet_sync_and_get_response(option_packet, - timeout=self.__get_timeout()) - self.__check_response(response_packet) - - def getsocketopt(self, option): - """ - Returns the value of the given socket option. - - Args: - option (:class:`.SocketOption`): the socket option to get its value. - - Returns: - Bytearray: the value of the socket option. - - Raises: - TimeoutException: if the socket option response is not received in the configured timeout. - ValueError: if the option to set is ``None``. - XBeeException: if the connection with the XBee device is not open. - XBeeSocketException: if the socket option response status is not ``SUCCESS``. - """ - if option is None: - raise ValueError("Option to get cannot be None") - if not self.__xbee_device.is_open(): - raise XBeeException("XBee device must be open") - - # If the socket is not created, create it first. - if self.__socket_id is None: - self.__create_socket() - - # Create, send and check the socket option packet. - option_packet = SocketOptionRequestPacket(self.__xbee_device.get_next_frame_id(), self.__socket_id, option) - response_packet = self.__xbee_device.send_packet_sync_and_get_response(option_packet, - timeout=self.__get_timeout()) - self.__check_response(response_packet) - - # Return the option data. - return response_packet.option_data - - def add_socket_state_callback(self, callback): - """ - Adds a callback for the event :class:`digi.xbee.reader.SocketStateReceived`. - - Args: - callback (Function): the callback. Receives two arguments. - - * The socket ID as an Integer. - * The state received as a :class:`.SocketState` - """ - self.__xbee_device.add_socket_state_received_callback(callback) - - def del_socket_state_callback(self, callback): - """ - Deletes a callback for the callback list of :class:`digi.xbee.reader.SocketStateReceived` event. - - Args: - callback (Function): the callback to delete. - - Raises: - ValueError: if ``callback`` is not in the callback list of - :class:`digi.xbee.reader.SocketStateReceived` event. - """ - self.__xbee_device.del_socket_state_received_callback(callback) - - def get_sock_info(self): - """ - Returns the information of this socket. - - Returns: - :class:`.SocketInfo`: The socket information. - - Raises: - 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. - TimeoutException: if the response is not received before the read timeout expires. - XBeeException: if the XBee device's serial port is closed. - - .. seealso:: - | :class:`.SocketInfo` - """ - return self.__xbee_device.get_socket_info(self.__socket_id) - - def __create_socket(self): - """ - Creates a new socket by sending a :class:`.SocketCreatePacket`. - - Raises: - TimeoutException: if the response is not received in the configured timeout. - XBeeSocketException: if the response contains any error. - """ - # Create, send and check the socket create packet. - create_packet = SocketCreatePacket(self.__xbee_device.get_next_frame_id(), self.__ip_protocol) - response_packet = self.__xbee_device.send_packet_sync_and_get_response(create_packet, - timeout=self.__get_timeout()) - self.__check_response(response_packet) - - # Store the received socket ID. - self.__socket_id = response_packet.socket_id - - def __register_state_callback(self): - """ - Registers the socket state callback to be notified when an error occurs. - """ - if self.__socket_state_callback is not None: - return - - def socket_state_callback(socket_id, state): - if self.__socket_id != socket_id: - return - if state != SocketState.CONNECTED: - self.__connected = False - self.__socket_id = None - self.__source_port = None - self.__data_received = bytearray() - self.__data_received_from_dict = OrderedDict() - self.__unregister_state_callback() - self.__unregister_data_received_callback() - self.__unregister_data_received_from_callback() - - self.__socket_state_callback = socket_state_callback - self.__xbee_device.add_socket_state_received_callback(socket_state_callback) - - def __unregister_state_callback(self): - """ - Unregisters the socket state callback. - """ - if self.__socket_state_callback is None: - return - - self.__xbee_device.del_socket_state_received_callback(self.__socket_state_callback) - self.__socket_state_callback = None - - def __register_data_received_callback(self): - """ - Registers the data received callback to be notified when data is received in the socket. - """ - if self.__data_received_callback is not None: - return - - def data_received_callback(socket_id, payload): - if self.__socket_id != socket_id: - return - - self.__data_received_lock.acquire() - self.__data_received += payload - self.__data_received_lock.release() - - self.__data_received_callback = data_received_callback - self.__xbee_device.add_socket_data_received_callback(data_received_callback) - - def __unregister_data_received_callback(self): - """ - Unregisters the data received callback. - """ - if self.__data_received_callback is None: - return - - self.__xbee_device.del_socket_data_received_callback(self.__data_received_callback) - self.__data_received_callback = None - - def __register_data_received_from_callback(self): - """ - Registers the data received from callback to be notified when data from a specific address is received - in the socket. - """ - if self.__data_received_from_callback is not None: - return - - def data_received_from_callback(socket_id, address, payload): - if self.__socket_id != socket_id: - return - - payload_added = False - # Check if the address already exists in the dictionary to append the payload or insert a new entry. - self.__data_received_from_dict_lock.acquire() - for addr in self.__data_received_from_dict.keys(): - if addr[0] == address[0] and addr[1] == address[1]: - self.__data_received_from_dict[addr] += payload - payload_added = True - break - if not payload_added: - self.__data_received_from_dict[address] = payload - self.__data_received_from_dict_lock.release() - - self.__data_received_from_callback = data_received_from_callback - self.__xbee_device.add_socket_data_received_from_callback(data_received_from_callback) - - def __unregister_data_received_from_callback(self): - """ - Unregisters the data received from callback. - """ - if self.__data_received_from_callback is None: - return - - self.__xbee_device.del_socket_data_received_from_callback(self.__data_received_from_callback) - self.__data_received_from_callback = None - - def __send(self, data, send_all=True): - """ - Sends data to the socket. The socket must be connected to a remote socket. Depending on the value of - ``send_all``, the method will raise an exception or return the number of bytes sent when there is an error - sending a data packet. - - Args: - data (Bytearray): the data to send. - send_all (Boolean): ``True`` to raise an exception when there is an error sending a data packet. ``False`` - to return the number of bytes sent when there is an error sending a data packet. - - Raises: - TimeoutException: if the send status response is not received in the configured timeout. - ValueError: if the data to send is ``None``. - ValueError: if the number of bytes to send is ``0``. - XBeeException: if the connection with the XBee device is not open. - XBeeSocketException: if the socket is not valid. - XBeeSocketException: if the send status is not ``SUCCESS``. - XBeeSocketException: if the socket is not open. - """ - if data is None: - raise ValueError("Data to send cannot be None") - if len(data) == 0: - raise ValueError("The number of bytes to send must be at least 1") - if self.__socket_id is None: - raise XBeeSocketException(status=SocketStatus.BAD_SOCKET) - if not self.__xbee_device.is_open(): - raise XBeeException("XBee device must be open") - if not self.__connected: - raise XBeeSocketException(message="Socket is not connected") - - sent_bytes = None if send_all else 0 - - # Send as many packets as needed to deliver all the provided data. - for chunk in self.__split_payload(data): - send_packet = SocketSendPacket(self.__xbee_device.get_next_frame_id(), self.__socket_id, chunk) - try: - response_packet = self.__xbee_device.send_packet_sync_and_get_response(send_packet, - timeout=self.__get_timeout()) - self.__check_response(response_packet) - except (TimeoutException, XBeeSocketException) as e: - # Raise the exception only if 'send_all' flag is set, otherwise return the number of bytes sent. - if send_all: - raise e - return sent_bytes - # Increase the number of bytes sent. - if not send_all: - sent_bytes += len(chunk) - # Return the number of bytes sent. - return sent_bytes - - def __is_connected(self): - """ - Returns whether the socket is connected or not. - - Returns: - Boolean: ``True`` if the socket is connected ``False`` otherwise. - """ - return self.__connected - - @staticmethod - def __check_response(response_packet): - """ - Checks the status of the given response packet and throws an :class:`.XBeeSocketException` if it is not - :attr:`SocketStatus.SUCCESS`. - - Args: - response_packet (:class:`.XBeeAPIPacket`): the socket response packet. - - Raises: - XBeeSocketException: if the socket status is not ``SUCCESS``. - """ - if isinstance(response_packet, TXStatusPacket): - if response_packet.transmit_status != TransmitStatus.SUCCESS: - raise XBeeSocketException(status=response_packet.transmit_status) - elif response_packet.status != SocketStatus.SUCCESS: - raise XBeeSocketException(status=response_packet.status) - - @staticmethod - def __split_payload(payload, size=__MAX_PAYLOAD_BYTES): - """ - Splits the given array of bytes in chunks of the specified size. - - Args: - payload (Bytearray): the data to split. - size (Integer, Optional): the size of the chunks. - - Returns: - Generator: the generator with all the chunks. - """ - for i in range(0, len(payload), size): - yield payload[i:i + size] - - def __get_timeout(self): - """ - Returns the socket timeout in seconds based on the blocking state. - - Returns: - Integer: the socket timeout in seconds if the socket is configured to be non blocking or ``-1`` if the - socket is configured to be blocking. - """ - return -1 if self.getblocking() else self.__timeout - - is_connected = property(__is_connected) - """Boolean. Indicates whether the socket is connected or not.""" diff --git a/requirements.txt b/requirements.txt index 2f9b639..6a0cb21 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,4 @@ pyserial>=3 sphinxcontrib-napoleon -srp \ No newline at end of file +srp +copy \ No newline at end of file diff --git a/setup.py b/setup.py index f004c3d..09921e0 100644 --- a/setup.py +++ b/setup.py @@ -24,13 +24,13 @@ version='1.3.0', description='Digi XBee Python library', long_description=long_description, - url='https://github.com/digidotcom/xbee-python', - author='Digi International Inc.', - author_email='tech.support@digi.com', + url='https://github.com/alexglzg/xbee-python', + author='Digi International Inc., corrections by Alejandro Gonzalez', + author_email='vanttecmty@gmail.com', packages=find_packages(exclude=('unit_test*', 'functional_tests*', 'demos*')), keywords=['xbee', 'IOT', 'wireless', 'radio frequency'], license='Mozilla Public License 2.0 (MPL 2.0)', - python_requires='>=3', + python_requires='>=2', install_requires=[ 'pyserial>=3', 'srp',