From 07cd9156997cd82946ff5b08854a1d859ebf41e5 Mon Sep 17 00:00:00 2001 From: EdisonAltamirano Date: Sat, 21 Dec 2019 17:04:03 -0600 Subject: [PATCH 1/7] python 2 library --- digi/xbee/comm_interface.py | 124 - digi/xbee/devices.py | 5172 +++++------------------------- digi/xbee/exception.py | 87 +- digi/xbee/filesystem.py | 989 ------ digi/xbee/firmware.py | 2889 ----------------- digi/xbee/io.py | 6 +- digi/xbee/models/address.py | 149 +- digi/xbee/models/atcomm.py | 82 +- digi/xbee/models/info.py | 177 - digi/xbee/models/message.py | 0 digi/xbee/models/mode.py | 167 +- digi/xbee/models/options.py | 121 - digi/xbee/models/protocol.py | 79 +- digi/xbee/models/status.py | 288 +- digi/xbee/models/zdo.py | 1747 ---------- digi/xbee/packets/aft.py | 18 - digi/xbee/packets/base.py | 33 +- digi/xbee/packets/cellular.py | 8 +- digi/xbee/packets/common.py | 225 +- digi/xbee/packets/devicecloud.py | 34 +- digi/xbee/packets/factory.py | 61 +- digi/xbee/packets/network.py | 14 +- digi/xbee/packets/raw.py | 121 +- digi/xbee/packets/relay.py | 12 +- digi/xbee/packets/socket.py | 2933 ----------------- digi/xbee/packets/wifi.py | 20 +- digi/xbee/packets/zigbee.py | 360 --- digi/xbee/profile.py | 1330 -------- digi/xbee/reader.py | 609 +--- digi/xbee/recovery.py | 291 -- digi/xbee/serial.py | 300 -- digi/xbee/util/utils.py | 85 +- digi/xbee/util/xmodem.py | 1136 ------- digi/xbee/xbeeserial.py | 138 + digi/xbee/xsocket.py | 837 ----- 35 files changed, 1373 insertions(+), 19269 deletions(-) delete mode 100644 digi/xbee/comm_interface.py mode change 100755 => 100644 digi/xbee/devices.py delete mode 100644 digi/xbee/filesystem.py delete mode 100644 digi/xbee/firmware.py delete mode 100644 digi/xbee/models/info.py mode change 100755 => 100644 digi/xbee/models/message.py delete mode 100644 digi/xbee/models/zdo.py delete mode 100644 digi/xbee/packets/socket.py delete mode 100644 digi/xbee/packets/zigbee.py delete mode 100644 digi/xbee/profile.py delete mode 100644 digi/xbee/recovery.py delete mode 100644 digi/xbee/serial.py delete mode 100644 digi/xbee/util/xmodem.py create mode 100644 digi/xbee/xbeeserial.py delete mode 100644 digi/xbee/xsocket.py 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..8fba785 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 @@ -61,7 +61,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 +89,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 +218,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 +246,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 +348,7 @@ class ATCommResponsePacket(XBeeAPIPacket): .. seealso:: | :class:`.ATCommPacket` - | :class:`.ATCommandStatus` + | :class:`.ATCommandStatus` | :class:`.XBeeAPIPacket` """ @@ -357,11 +357,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 +376,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 +407,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 +435,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 +495,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 +525,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 +564,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 +593,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 +613,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. @@ -841,7 +796,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 +828,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 +1035,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 +1052,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 +1085,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 +1115,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 +1178,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 +1185,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 +1255,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 +1326,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 +1357,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 +1389,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): @@ -1657,7 +1576,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 +1607,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 +1779,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 +1805,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 +1909,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 +1939,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): """ @@ -2334,7 +2252,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 +2288,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 +2319,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: @@ -2696,7 +2614,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 +2648,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 +2668,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 +2679,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 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.""" From 44ae9f622a2df0a5525f2007d934f80274a83a83 Mon Sep 17 00:00:00 2001 From: alexglzg Date: Sat, 21 Dec 2019 17:11:00 -0600 Subject: [PATCH 2/7] python 2 library --- digi/__init__.py | 13 - digi/xbee/__init__.py | 13 - digi/xbee/devices.py | 6351 ------------------------------ digi/xbee/exception.py | 162 - digi/xbee/io.py | 647 --- digi/xbee/models/__init__.py | 13 - digi/xbee/models/accesspoint.py | 224 -- digi/xbee/models/address.py | 442 --- digi/xbee/models/atcomm.py | 273 -- digi/xbee/models/hw.py | 145 - digi/xbee/models/message.py | 457 --- digi/xbee/models/mode.py | 204 - digi/xbee/models/options.py | 470 --- digi/xbee/models/protocol.py | 331 -- digi/xbee/models/status.py | 805 ---- digi/xbee/packets/__init__.py | 13 - digi/xbee/packets/aft.py | 114 - digi/xbee/packets/base.py | 648 --- digi/xbee/packets/cellular.py | 353 -- digi/xbee/packets/common.py | 2894 -------------- digi/xbee/packets/devicecloud.py | 996 ----- digi/xbee/packets/factory.py | 218 - digi/xbee/packets/network.py | 528 --- digi/xbee/packets/raw.py | 1471 ------- digi/xbee/packets/relay.py | 343 -- digi/xbee/packets/wifi.py | 746 ---- digi/xbee/reader.py | 1245 ------ digi/xbee/util/__init__.py | 13 - digi/xbee/util/utils.py | 307 -- digi/xbee/xbeeserial.py | 138 - 30 files changed, 20577 deletions(-) delete mode 100644 digi/__init__.py delete mode 100644 digi/xbee/__init__.py delete mode 100644 digi/xbee/devices.py delete mode 100644 digi/xbee/exception.py delete mode 100644 digi/xbee/io.py delete mode 100644 digi/xbee/models/__init__.py delete mode 100644 digi/xbee/models/accesspoint.py delete mode 100644 digi/xbee/models/address.py delete mode 100644 digi/xbee/models/atcomm.py delete mode 100644 digi/xbee/models/hw.py delete mode 100644 digi/xbee/models/message.py delete mode 100644 digi/xbee/models/mode.py delete mode 100644 digi/xbee/models/options.py delete mode 100644 digi/xbee/models/protocol.py delete mode 100644 digi/xbee/models/status.py delete mode 100644 digi/xbee/packets/__init__.py delete mode 100644 digi/xbee/packets/aft.py delete mode 100644 digi/xbee/packets/base.py delete mode 100644 digi/xbee/packets/cellular.py delete mode 100644 digi/xbee/packets/common.py delete mode 100644 digi/xbee/packets/devicecloud.py delete mode 100644 digi/xbee/packets/factory.py delete mode 100644 digi/xbee/packets/network.py delete mode 100644 digi/xbee/packets/raw.py delete mode 100644 digi/xbee/packets/relay.py delete mode 100644 digi/xbee/packets/wifi.py delete mode 100644 digi/xbee/reader.py delete mode 100644 digi/xbee/util/__init__.py delete mode 100644 digi/xbee/util/utils.py delete mode 100644 digi/xbee/xbeeserial.py diff --git a/digi/__init__.py b/digi/__init__.py deleted file mode 100644 index 8ea10d3..0000000 --- a/digi/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -# 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. diff --git a/digi/xbee/__init__.py b/digi/xbee/__init__.py deleted file mode 100644 index 8ea10d3..0000000 --- a/digi/xbee/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -# 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. diff --git a/digi/xbee/devices.py b/digi/xbee/devices.py deleted file mode 100644 index 94f727c..0000000 --- a/digi/xbee/devices.py +++ /dev/null @@ -1,6351 +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. - -from abc import ABCMeta, abstractmethod -import logging -from ipaddress import IPv4Address -import threading -import time - -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 -from digi.xbee.models.hw import HardwareVersion -from digi.xbee.models.mode import OperatingMode, APIOutputMode, IPAddressingMode -from digi.xbee.models.address import XBee64BitAddress, XBee16BitAddress, XBeeIMEIAddress -from digi.xbee.models.message import XBeeMessage, ExplicitXBeeMessage, IPMessage -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 -from digi.xbee.packets.aft import ApiFrameType -from digi.xbee.packets.base import XBeeAPIPacket -from digi.xbee.packets.common import ATCommPacket, TransmitPacket, RemoteATCommandPacket, ExplicitAddressingPacket, \ - ATCommQueuePacket, ATCommResponsePacket, RemoteATCommandResponsePacket -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.util import utils -from digi.xbee.exception import XBeeException, TimeoutException, InvalidOperatingModeException, \ - ATCommandException, OperationNotSupportedException -from digi.xbee.io import IOSample, IOMode -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 - - -class AbstractXBeeDevice(object): - """ - This class provides common functionality for all XBee devices. - """ - __metaclass__ = ABCMeta - - _DEFAULT_TIMEOUT_SYNC_OPERATIONS = 4 - """ - The default timeout for all synchronous operations, in seconds. - """ - - _BLE_API_USERNAME = "apiservice" - """ - The Bluetooth Low Energy API username. - """ - - LOG_PATTERN = "{port:<6s}{event:<12s}{opmode:<20s}{content:<50s}" - """ - Pattern used to log packet events. - """ - - _log = logging.getLogger(__name__) - """ - Logger. - """ - - 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. - - Args: - local_xbee_device (:class:`.XBeeDevice`, optional): only necessary if XBee device is remote. The local - XBee device that will behave as connection interface to communicate with the remote XBee one. - serial_port (:class:`.XBeeSerialPort`, optional): only necessary if the XBee device is local. The serial - 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. - - .. seealso:: - | :class:`.XBeeDevice` - | :class:`.XBeeSerialPort` - """ - self.__current_frame_id = 0x00 - - self._16bit_addr = None - self._64bit_addr = None - self._apply_changes_flag = True - - self._is_open = False - self._operating_mode = None - - self._local_xbee_device = local_xbee_device - self._serial_port = serial_port - self._timeout = sync_ops_timeout - - self.__io_packet_received = False - self.__io_packet_payload = None - - self._hardware_version = None - self._firmware_version = None - self._protocol = None - self._node_id = None - - self._packet_listener = None - - 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. - - Returns: - If at least one XBee device has 64 bit address (not ``None``), this method returns ``True`` if both - XBee device's addresses are equal, ``False`` otherwise. - - If at least one XBee device has 16 bit address (not ``None``), this method returns ``True`` if both - XBee device addresses are equal, ``False`` otherwise. - - If at least one XBee device has node id (not ``None``), this method returns ``True`` if both - XBee device IDs are equal, ``False`` otherwise. - - Else (all parameters of both devices are ``None``) returns ``True``. - """ - if other is None: - return False - if not isinstance(self, AbstractXBeeDevice) or not isinstance(other, AbstractXBeeDevice): - 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 update_device_data_from(self, device): - """ - Updates the current device reference with the data provided for the given device. - - This is only for internal use. - - 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() - - 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. - - 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. - """ - value = self.__send_parameter(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("Could not get the %s value." % parameter) - - return value - - def set_parameter(self, parameter, value): - """ - Sets the value of a parameter via an AT Command. - - If you send parameter to a local XBee device, all changes - will be applied automatically, except for non-volatile memory, - in which case you will need to execute the parameter "WR" via - :meth:`.AbstractXBeeDevice.execute_command` method, or - :meth:`.AbstractXBeeDevice.apply_changes` method. - - If you are sending parameters to a remote XBee device, - the changes will be not applied automatically, unless the "apply_changes" - flag is activated. - - You can set this flag via the method :meth:`.AbstractXBeeDevice.enable_apply_changes`. - - This flag only works for volatile memory, if you want to save - changed parameters in non-volatile memory, even for remote devices, - you must execute "WR" command by one of the 2 ways mentioned above. - - Args: - parameter (String): parameter to set. - value (Bytearray): value of the parameter. - - 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 ``parameter`` is ``None`` or ``value`` is ``None``. - """ - if value is None: - raise ValueError("Value of the parameter cannot be None.") - - self.__send_parameter(parameter, value) - - # Refresh cached parameters if this method modifies some of them. - self._refresh_if_cached(parameter, value) - - def execute_command(self, parameter): - """ - Executes the provided command. - - 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. - """ - self.__send_parameter(parameter, None) - - def __send_parameter(self, parameter, parameter_value=None): - """ - Sends the given AT parameter to this XBee device with an optional - argument or value and returns the response (likely the value) of that - parameter in a byte array format. - - Args: - parameter (String): The name of the AT command to be executed. - parameter_value (bytearray, optional): The value of the parameter to set (if any). - - Returns: - Bytearray: A byte array containing the value of the parameter. - - Raises: - ValueError: if ``parameter`` is ``None`` or if ``len(parameter) != 2``. - """ - if parameter is None: - raise ValueError("Parameter cannot be None.") - if len(parameter) != 2: - raise ValueError("Parameter must contain exactly 2 characters.") - - at_command = ATCommand(parameter, parameter_value) - - # Send the AT command. - response = self._send_at_command(at_command) - - self._check_at_cmd_response_is_valid(response) - - return response.response - - def _check_at_cmd_response_is_valid(self, response): - """ - Checks if the provided ``ATCommandResponse`` is valid throwing an - :class:`.ATCommandException` in case it is not. - - Args: - response: The AT command response to check. - - Raises: - ATCommandException: if ``response`` is ``None`` or - if ``response.response != OK``. - """ - if response is None or not isinstance(response, ATCommandResponse) or response.status is None: - raise ATCommandException(None) - elif response.status != ATCommandStatus.OK: - raise ATCommandException(response.status) - - def _send_at_command(self, command): - """ - Sends the given AT command and waits for answer or until the configured - receive timeout expires. - - Args: - command (:class:`.ATCommand`): AT command to be sent. - - Returns: - :class:`.ATCommandResponse`: object containing the response of the command - or ``None`` if there is no response. - - Raises: - ValueError: if ``command`` is ``None``. - InvalidOperatingModeException: if the operating mode is different than ``API`` or ``ESCAPED_API_MODE``. - - """ - if command is None: - raise ValueError("AT command cannot be None.") - - operating_mode = self._get_operating_mode() - if operating_mode != OperatingMode.API_MODE and operating_mode != OperatingMode.ESCAPED_API_MODE: - raise InvalidOperatingModeException.from_operating_mode(operating_mode) - - if self.is_remote(): - remote_at_cmd_opts = RemoteATCmdOptions.NONE.value - if self.is_apply_changes_enabled(): - remote_at_cmd_opts |= RemoteATCmdOptions.APPLY_CHANGES.value - - remote_16bit_addr = self.get_16bit_addr() - if remote_16bit_addr is None: - 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, command.parameter) - else: - if self.is_apply_changes_enabled(): - packet = ATCommPacket(self._get_next_frame_id(), command.command, command.parameter) - else: - 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) - else: - answer_packet = self._send_packet_sync_and_get_response(packet) - - response = None - - if isinstance(answer_packet, ATCommResponsePacket) or isinstance(answer_packet, RemoteATCommandResponsePacket): - response = ATCommandResponse(command, answer_packet.command_value, answer_packet.status) - - return response - - def apply_changes(self): - """ - Applies changes via ``AC`` command. - - 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. - """ - self.execute_command("AC") - - def write_changes(self): - """ - Writes configurable parameter values to the non-volatile memory of the - XBee device so that parameter modifications persist through subsequent - resets. - - Parameters values remain in this device's memory until overwritten by - subsequent use of this method. - - If changes are made without writing them to non-volatile memory, the - module reverts back to previously saved parameters the next time the - module is powered-on. - - Writing the parameter modifications does not mean those values are - immediately applied, this depends on the status of the 'apply - configuration changes' option. Use method - :meth:`is_apply_configuration_changes_enabled` to get its status and - :meth:`enable_apply_configuration_changes` to enable/disable the - option. If it is disabled, method :meth:`apply_changes` can be used in - order to manually apply the changes. - - 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. - """ - self.execute_command("WR") - - @abstractmethod - def reset(self): - """ - Performs a software reset on this XBee device and blocks until the process is completed. - - 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. - """ - pass - - def read_device_info(self): - """ - Updates all instance parameters reading them from the XBee device. - - 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.is_remote(): - 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("Not supported operating mode: " + str(self._operating_mode)) - - if not self._serial_port.is_open: - raise XBeeException("XBee device's serial port closed") - - # Hardware version: - self._hardware_version = HardwareVersion.get(self.get_parameter("HV")[0]) - # Firmware version: - 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: - sh = self.get_parameter("SH") - sl = self.get_parameter("SL") - self._64bit_addr = XBee64BitAddress(sh + sl) - # Node ID: - 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]: - r = self.get_parameter("MY") - self._16bit_addr = XBee16BitAddress(r) - - def get_node_id(self): - """ - Returns the Node Identifier (``NI``) value of the XBee device. - - Returns: - String: the Node Identifier (``NI``) of the XBee device. - """ - return self._node_id - - def set_node_id(self, node_id): - """ - Sets the Node Identifier (``NI``) value of the XBee device.. - - Args: - node_id (String): the new Node Identifier (``NI``) of the XBee device. - - Raises: - ValueError: if ``node_id`` is ``None`` or its length is greater than 20. - TimeoutException: if the response is not received before the read timeout expires. - """ - if node_id is None: - raise ValueError("Node ID cannot be None") - if len(node_id) > 20: - raise ValueError("Node ID length must be less than 21") - - self.set_parameter("NI", bytearray(node_id, 'utf8')) - self._node_id = node_id - - def get_hardware_version(self): - """ - Returns the hardware version of the XBee device. - - Returns: - :class:`.HardwareVersion`: the hardware version of the XBee device. - - .. seealso:: - | :class:`.HardwareVersion` - """ - return self._hardware_version - - def get_firmware_version(self): - """ - Returns the firmware version of the XBee device. - - Returns: - Bytearray: the hardware version of the XBee device. - """ - return self._firmware_version - - def get_protocol(self): - """ - Returns the current protocol of the XBee device. - - Returns: - :class:`.XBeeProtocol`: the current protocol of the XBee device. - - .. seealso:: - | :class:`.XBeeProtocol` - """ - return self._protocol - - def get_16bit_addr(self): - """ - Returns the 16-bit address of the XBee device. - - Returns: - :class:`.XBee16BitAddress`: the 16-bit address of the XBee device. - - .. seealso:: - | :class:`.XBee16BitAddress` - """ - return self._16bit_addr - - def set_16bit_addr(self, value): - """ - Sets the 16-bit address of the XBee device. - - Args: - value (:class:`.XBee16BitAddress`): the new 16-bit address of the XBee device. - - 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 the current protocol is not 802.15.4. - """ - if self.get_protocol() != XBeeProtocol.RAW_802_15_4: - raise OperationNotSupportedException("16-bit address can only be set in 802.15.4 protocol") - - self.set_parameter("MY", value.address) - self._16bit_addr = value - - def get_64bit_addr(self): - """ - Returns the 64-bit address of the XBee device. - - Returns: - :class:`.XBee64BitAddress`: the 64-bit address of the XBee device. - - .. seealso:: - | :class:`.XBee64BitAddress` - """ - return self._64bit_addr - - def get_current_frame_id(self): - """ - Returns the last used frame ID. - - Returns: - Integer: the last used frame ID. - """ - return self.__current_frame_id - - def enable_apply_changes(self, value): - """ - Sets the apply_changes flag. - - Args: - value (Boolean): ``True`` to enable the apply changes flag, ``False`` to disable it. - """ - self._apply_changes_flag = value - - def is_apply_changes_enabled(self): - """ - Returns whether the apply_changes flag is enabled or not. - - Returns: - Boolean: ``True`` if the apply_changes flag is enabled, ``False`` otherwise. - """ - return self._apply_changes_flag - - @abstractmethod - def is_remote(self): - """ - Determines whether the XBee device is remote or not. - - Returns: - Boolean: ``True`` if the XBee device is remote, ``False`` otherwise. - """ - pass - - def set_sync_ops_timeout(self, sync_ops_timeout): - """ - Sets the serial port read timeout. - - Args: - sync_ops_timeout (Integer): the read timeout, expressed in seconds. - """ - self._timeout = sync_ops_timeout - if self.is_remote(): - self._local_xbee_device.serial_port.timeout = self._timeout - else: - self._serial_port.timeout = self._timeout - - def get_sync_ops_timeout(self): - """ - Returns the serial port read timeout. - - Returns: - Integer: the serial port read timeout in seconds. - """ - return self._timeout - - def get_dest_address(self): - """ - Returns the 64-bit address of the XBee device that data will be reported to. - - Returns: - :class:`.XBee64BitAddress`: the address. - - Raises: - TimeoutException: if the response is not received before the read timeout expires. - - .. seealso:: - | :class:`.XBee64BitAddress` - """ - dh = self.get_parameter("DH") - dl = self.get_parameter("DL") - return XBee64BitAddress(dh + dl) - - 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. - - Raises: - 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() - - apply_changes = None - with self.__generic_lock: - try: - apply_changes = self.is_apply_changes_enabled() - self.enable_apply_changes(False) - 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 - finally: - if apply_changes: - self.enable_apply_changes(True) - self.apply_changes() - - def get_pan_id(self): - """ - Returns the operating PAN ID of the XBee device. - - Returns: - Bytearray: operating PAN ID of the XBee device. - - Raises: - TimeoutException: if the response is not received before the read timeout expires. - """ - if self.get_protocol() == XBeeProtocol.ZIGBEE: - return self.get_parameter("OP") - return self.get_parameter("ID") - - def set_pan_id(self, value): - """ - Sets the operating PAN ID of the XBee device. - - Args: - value (Bytearray): the new operating PAN ID of the XBee device.. Must have only 1 or 2 bytes. - - Raises: - TimeoutException: if the response is not received before the read timeout expires. - """ - self.set_parameter("ID", value) - - def get_power_level(self): - """ - Returns the power level of the XBee device. - - Returns: - :class:`.PowerLevel`: the power level of the XBee device. - - Raises: - TimeoutException: if the response is not received before the read timeout expires. - - .. seealso:: - | :class:`.PowerLevel` - """ - return PowerLevel.get(self.get_parameter("PL")[0]) - - def set_power_level(self, power_level): - """ - Sets the power level of the XBee device. - - Args: - power_level (:class:`.PowerLevel`): the new power level of the XBee device. - - Raises: - TimeoutException: if the response is not received before the read timeout expires. - - .. seealso:: - | :class:`.PowerLevel` - """ - self.set_parameter("PL", bytearray([power_level.code])) - - def set_io_configuration(self, io_line, io_mode): - """ - Sets the configuration of the provided IO line. - - Args: - io_line (:class:`.IOLine`): the IO line to configure. - io_mode (:class:`.IOMode`): the IO mode to set to the IO line. - - 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. - - .. seealso:: - | :class:`.IOLine` - | :class:`.IOMode` - """ - self.set_parameter(io_line.at_command, bytearray([io_mode.value])) - - def get_io_configuration(self, io_line): - """ - Returns the configuration of the provided IO line. - - Args: - io_line (:class:`.IOLine`): the io line to configure. - - Returns: - :class:`.IOMode`: the IO mode of the IO line provided. - - 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 the received data is not an IO mode. - """ - try: - mode = IOMode.get(self.get_parameter(io_line.at_command)[0]) - except ValueError: - raise OperationNotSupportedException("The received value is not an IO mode.") - return mode - - def get_io_sampling_rate(self): - """ - Returns the IO sampling rate of the XBee device. - - Returns: - Integer: the IO sampling rate of XBee device. - - 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. - """ - resp = self.get_parameter("IR") - return utils.bytes_to_int(resp) / 1000.00 - - def set_io_sampling_rate(self, rate): - """ - Sets the IO sampling rate of the XBee device in seconds. A sample rate of 0 means the IO sampling feature is - disabled. - - Args: - rate (Integer): the new IO sampling rate of the XBee device in seconds. - - 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. - """ - self.set_parameter("IR", utils.int_to_bytes(int(rate * 1000))) - - def read_io_sample(self): - """ - Returns an IO sample from the XBee device containing the value of all enabled digital IO and - analog input channels. - - Returns: - :class:`.IOSample`: the IO sample read from the XBee device. - - 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. - - .. seealso:: - | :class:`.IOSample` - """ - # The response to the IS command in local 802.15.4 devices is empty, - # so we have to use callbacks to read the packet. - if not self.is_remote() and self.get_protocol() == XBeeProtocol.RAW_802_15_4: - lock = threading.Condition() - self.__io_packet_received = False - self.__io_packet_payload = None - - def io_sample_callback(received_packet): - # Discard non API packets. - if not isinstance(received_packet, XBeeAPIPacket): - return - # If we already have received an IO packet, ignore this packet. - if self.__io_packet_received: - return - frame_type = received_packet.get_frame_type() - # Save the packet value (IO sample payload). - if (frame_type == ApiFrameType.IO_DATA_SAMPLE_RX_INDICATOR - or frame_type == ApiFrameType.RX_IO_16 - or frame_type == ApiFrameType.RX_IO_64): - self.__io_packet_payload = received_packet.rf_data - else: - return - # Set the IO packet received flag. - self.__io_packet_received = True - # Continue execution by notifying the lock object. - lock.acquire() - lock.notify() - lock.release() - - self._add_packet_received_callback(io_sample_callback) - - try: - # Execute 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("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("IS") - - try: - return IOSample(sample_payload) - except Exception as e: - raise XBeeException("Could not create the IO sample.", e) - - def get_adc_value(self, io_line): - """ - Returns the analog value of the provided IO line. - - The provided IO line must be previously configured as ADC. To do so, - use :meth:`.AbstractXBeeDevice.set_io_configuration` and :attr:`.IOMode.ADC`. - - Args: - io_line (:class:`.IOLine`): the IO line to get its ADC value. - - Returns: - Integer: the analog value corresponding to the provided IO line. - - 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 the response does not contain the value for the given IO line. - - .. seealso:: - | :class:`.IOLine` - """ - 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 values for the given IO line.") - return io_sample.analog_values[io_line] - - def set_pwm_duty_cycle(self, io_line, cycle): - """ - Sets the duty cycle in % of the provided IO line. - - The provided IO line must be PWM-capable, previously configured as PWM output. - - Args: - io_line (:class:`.IOLine`): the IO Line to be assigned. - cycle (Integer): duty cycle in % to be assigned. Must be between 0 and 100. - - 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 the given IO line does not have PWM capability or ``cycle`` is not between 0 and 100. - - .. seealso:: - | :class:`.IOLine` - | :attr:`.IOMode.PWM` - """ - if not io_line.has_pwm_capability(): - raise ValueError("%s has no PWM capability." % io_line) - if cycle < 0 or cycle > 100: - raise ValueError("Cycle must be between 0% and 100%.") - - duty_cycle = int(round(cycle * 1023.00 / 100.00)) - - self.set_parameter(io_line.pwm_at_command, bytearray(utils.int_to_bytes(duty_cycle))) - - def get_pwm_duty_cycle(self, io_line): - """ - Returns the PWM duty cycle in % corresponding to the provided IO line. - - Args: - io_line (:class:`.IOLine`): the IO line to get its PWM duty cycle. - - Returns: - Integer: the PWM duty cycle of the given IO line or ``None`` if the response is empty. - - 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 the passed :class:`.IO_LINE` has no PWM capability. - - .. seealso:: - | :class:`.IOLine` - """ - if not io_line.has_pwm_capability(): - raise ValueError("%s has no PWM capability." % io_line) - - value = utils.bytes_to_int(self.get_parameter(io_line.pwm_at_command)) - return round(((value * 100.0 / 1023.0) * 100.0) / 100.0) - - def get_dio_value(self, io_line): - """ - Returns the digital value of the provided IO line. - - The provided IO line must be previously configured as digital I/O. - To do so, use :meth:`.AbstractXBeeDevice.set_io_configuration`. - - Args: - io_line (:class:`.IOLine`): the DIO line to gets its digital value. - - Returns: - :class:`.IOValue`: current value of the provided IO line. - - 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 the response does not contain the value for the given IO line. - - .. seealso:: - | :class:`.IOLine` - | :class:`.IOValue` - """ - 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 values for the given IO_LINE") - return sample.digital_values[io_line] - - def set_dio_value(self, io_line, io_value): - """ - Sets the digital value (high or low) to the provided IO line. - - Args: - io_line (:class:`.IOLine`): the digital IO line to sets its value. - io_value (:class:`.IOValue`): the IO value to set to the IO line. - - 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. - - .. seealso:: - | :class:`.IOLine` - | :class:`.IOValue` - """ - self.set_parameter(io_line.at_command, bytearray([io_value.value])) - - def set_dio_change_detection(self, io_lines_set): - """ - Sets the digital IO lines to be monitored and sampled whenever their status changes. - - A ``None`` set of lines disables this feature. - - Args: - io_lines_set: set of :class:`.IOLine`. - - 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. - - .. seealso:: - | :class:`.IOLine` - """ - flags = bytearray(2) - if io_lines_set is not None: - for io_line in io_lines_set: - i = io_line.index - if i < 8: - flags[1] = flags[1] | (1 << i) - else: - flags[0] = flags[0] | ((1 << i) - 8) - self.set_parameter("IC", flags) - - def get_api_output_mode(self): - """ - Returns the API output mode of the XBee device. - - The API output mode determines the format that the received data is - output through the serial interface of the XBee device. - - Returns: - :class:`.APIOutputMode`: the API output mode of the XBee device. - - 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. - - .. seealso:: - | :class:`.APIOutputMode` - """ - return APIOutputMode.get(self.get_parameter("AO")[0]) - - def set_api_output_mode(self, api_output_mode): - """ - Sets the API output mode of the XBee device. - - Args: - api_output_mode (:class:`.APIOutputMode`): the new API output mode of the XBee device. - - 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 the current protocol is ZigBee - - .. seealso:: - | :class:`.APIOutputMode` - """ - self.set_parameter("AO", bytearray([api_output_mode.code])) - - def enable_bluetooth(self): - """ - Enables the Bluetooth interface of this XBee device. - - To work with this interface, you must also configure the Bluetooth password if not done previously. - You can use the :meth:`.AbstractXBeeDevice.update_bluetooth_password` method for that purpose. - - Note that your device must have Bluetooth Low Energy support to use this method. - - 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. - """ - self._enable_bluetooth(True) - - def disable_bluetooth(self): - """ - Disables the Bluetooth interface of this XBee device. - - Note that your device must have Bluetooth Low Energy support to use this method. - - 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. - """ - self._enable_bluetooth(False) - - def _enable_bluetooth(self, enable): - """ - Enables or disables the Bluetooth interface of this XBee device. - - Args: - enable (Boolean): ``True`` to enable the Bluetooth interface, ``False`` to disable it. - - 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. - """ - self.set_parameter("BT", b'\x01' if enable else b'\x00') - self.write_changes() - self.apply_changes() - - def get_bluetooth_mac_addr(self): - """ - Reads and returns the EUI-48 Bluetooth MAC address of this XBee device in a format such as ``00112233AABB``. - - Note that your device must have Bluetooth Low Energy support to use this method. - - Returns: - String: The Bluetooth MAC 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. - """ - return utils.hex_to_string(self.get_parameter("BL"), False) - - def update_bluetooth_password(self, new_password): - """ - Changes the password of this Bluetooth device with the new one provided. - - Note that your device must have Bluetooth Low Energy support to use this method. - - Args: - new_password (String): New Bluetooth password. - - 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. - """ - # 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) - - # Ensure the verifier is 128 bytes. - verifier = (128 - len(verifier)) * b'\x00' + verifier - - # Set the salt. - self.set_parameter("$S", salt) - - # Set the verifier (split in 4 settings) - index = 0 - at_length = int(len(verifier) / 4) - - self.set_parameter("$V", verifier[index:(index + at_length)]) - index += at_length - self.set_parameter("$W", verifier[index:(index + at_length)]) - index += at_length - self.set_parameter("$X", verifier[index:(index + at_length)]) - index += at_length - self.set_parameter("$Y", verifier[index:(index + at_length)]) - - # Write and apply changes. - self.write_changes() - self.apply_changes() - - def _get_ai_status(self): - """ - Returns the current association status of this XBee device. - - It indicates occurrences of errors during the modem initialization - and connection. - - Returns: - :class:`.AssociationIndicationStatus`: The association indication status of the XBee device. - - 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. - """ - value = self.get_parameter("AI") - return AssociationIndicationStatus.get(utils.bytes_to_int(value)) - - def _force_disassociate(self): - """ - Forces this XBee device to immediately disassociate from the network and - re-attempt to associate. - - Only valid for End Devices. - - 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. - """ - self.execute_command("DA") - - def _refresh_if_cached(self, parameter, value): - """ - Refreshes the proper cached parameter depending on ``parameter`` value. - - If ``parameter`` is not a cached parameter, this method does nothing. - - Args: - parameter (String): the parameter to refresh its value. - value (Bytearray): the new value of the parameter. - """ - if parameter == "NI": - self._node_id = value.decode() - elif parameter == "MY": - self._16bit_addr = XBee16BitAddress(value) - elif parameter == "AP": - self._operating_mode = OperatingMode.get(utils.bytes_to_int(value)) - - def _get_next_frame_id(self): - """ - Returns the next frame ID of the XBee device. - - Returns: - Integer: The next frame ID of the XBee device. - """ - if self.__current_frame_id == 0xFF: - self.__current_frame_id = 1 - else: - self.__current_frame_id += 1 - return self.__current_frame_id - - def _get_operating_mode(self): - """ - Returns the Operating mode (AT, API or API escaped) of this XBee device - for a local device, and the operating mode of the local device used as - communication interface for a remote device. - - Returns: - :class:`.OperatingMode`: The operating mode of the local XBee device. - """ - if self.is_remote(): - return self._local_xbee_device.operating_mode - return self._operating_mode - - @staticmethod - def _before_send_method(func): - """ - Decorator. Used to check the operating mode and the COM port's state before a sending operation. - """ - @wraps(func) - def dec_function(self, *args, **kwargs): - 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("Not supported operating mode: " + - str(args[0].operating_mode.description)) - return func(self, *args, **kwargs) - return dec_function - - @staticmethod - def _after_send_method(func): - """ - Decorator. Used to check if the response's transmit status is success after a sending operation. - """ - @wraps(func) - def dec_function(*args, **kwargs): - response = func(*args, **kwargs) - if response.transmit_status != TransmitStatus.SUCCESS: - raise XBeeException("Transmit status: %s" % response.transmit_status.description) - return response - return dec_function - - def _get_packet_by_id(self, frame_id): - """ - Reads packets until there is one packet found with the provided frame ID. - - Args: - frame_id (Integer): frame ID to use for. Must be between 0 and 255. - - Returns: - :class:XBeePacket: the first XBee packet read whose frame ID matches the provided one. - - Raises: - ValueError: if ``frame_id`` is less than 0 or greater than 255. - TimeoutException: if there was not any XBee packet matching the provided frame ID that could be read. - """ - if not (0 <= frame_id <= 255): - raise ValueError("Frame ID must be between 0 and 255.") - - queue = self._packet_listener.get_queue() - - packet = queue.get_by_id(frame_id, XBeeDevice.TIMEOUT_READ_PACKET) - - return packet - - @staticmethod - def __is_api_packet(xbee_packet): - """ - Determines whether the provided XBee packet is an API packet or not. - - Returns: - Boolean: ``True`` if the provided XBee packet is an API packet (its frame type is inside - :class:`.ApiFrameType` enum), ``False`` otherwise. - """ - aft = xbee_packet.get_frame_type() - try: - ApiFrameType.get(aft) - except ValueError: - return False - return True - - def _add_packet_received_callback(self, callback): - """ - Adds a callback for the event :class:`.PacketReceived`. - - Args: - callback (Function): the callback. Receives two arguments. - - * 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:`.PacketReceived` event. - - Args: - callback (Function): the callback to delete. - - Raises: - ValueError: if ``callback`` is not in the callback list of :class:`.PacketReceived` event. - """ - self._packet_listener.del_packet_received_callback(callback) - - 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: - - 1. Puts "_sync_packet" to ``None``, to discard the last sync. packet read. - 2. Refresh "_sync_packet" to be used by the thread in charge of the synchronous read. - 3. Tells the packet listener that this XBee device is waiting for a packet with a determined frame ID. - 4. Sends the ``packet_to_send``. - 5. Waits the configured timeout for synchronous operations. - 6. Returns all attributes to a consistent state (except _sync_packet) - | 6.1. _sync_packet to ``None``. - | 6.2. notify the listener that we are no longer waiting for any packet. - 7. Returns the received packet if it has arrived, ``None`` otherwise. - - This method must be only used when the packet listener is online. - - At the end of this method, the class attribute ``_sync_packet`` will be - the packet read by this method, or ``None`` if the previous was not possible. - Note that ``_sync_packet`` will remain being "the last packet read in a - synchronous operation" until you call this method again. - Then, ``_sync_packet`` will be refreshed. - - Args: - packet_to_send (:class:`.XBeePacket`): the packet to send. - - Returns: - :class:`.XBeePacket`: the response packet obtained after sending the provided one. - - Raises: - TimeoutException: if the response is not received in the configured timeout. - - .. seealso:: - | :class:`.XBeePacket` - """ - lock = threading.Condition() - response_list = list() - - # Create a packet received callback for the packet to be sent. - def packet_received_callback(received_packet): - # Check if it is the packet we are waiting for. - if received_packet.needs_id() and received_packet.frame_id == packet_to_send.frame_id: - if not isinstance(packet_to_send, XBeeAPIPacket) or not isinstance(received_packet, XBeeAPIPacket): - 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: - 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: - 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 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) - - try: - # Send the packet. - self._send_packet(packet_to_send) - # Wait for response or timeout. - lock.acquire() - 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("Response not received in the configured timeout.") - # Return the received packet. - return response_list[0] - finally: - # Always remove the packet listener from the list. - self._del_packet_received_callback(packet_received_callback) - - def _send_packet(self, packet, sync=False): - """ - Sends a packet to the XBee device and waits for the response. - The packet to send will be escaped or not depending on the current - operating mode. - - This method can be synchronous or asynchronous. - - If is synchronous, this method will discard all response - packets until it finds the one that has the appropriate frame ID, - that is, the sent packet's frame ID. - - If is asynchronous, this method does not wait for any packet. Returns ``None``. - - Args: - packet (:class:`.XBeePacket`): The packet to send. - sync (Boolean): ``True`` to wait for the response of the sent packet and return it, ``False`` otherwise. - - Returns: - :class:`.XBeePacket`: The response packet if ``sync`` is ``True``, ``None`` otherwise. - - Raises: - TimeoutException: if ``sync`` is ``True`` and the response packet for the sent one cannot be read. - 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 packet listener is not running. - XBeeException: if the XBee device's serial port is closed. - - .. seealso:: - | :class:`.XBeePacket` - """ - if not self._packet_listener.is_running(): - raise XBeeException("Packet listener is not running.") - - escape = self._operating_mode == OperatingMode.ESCAPED_API_MODE - 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_log(self): - """ - Returns the XBee device log. - - Returns: - :class:`.Logger`: the XBee device logger. - """ - return self._log - - log = property(__get_log) - """:class:`.Logger`. The XBee device logger.""" - - -class XBeeDevice(AbstractXBeeDevice): - """ - This class represents a non-remote generic XBee device. - - This class has fields that are events. Its recommended to use only the - append() and remove() method on them, or -= and += operators. - If you do something more with them, it's for your own risk. - """ - - __TIMEOUT_BEFORE_COMMAND_MODE = 1.2 # 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 transparent mode). - """ - - __TIMEOUT_ENTER_COMMAND_MODE = 1.5 # 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 transparent mode). - """ - - __TIMEOUT_RESET = 5 # seconds - """ - Timeout to wait when resetting the module. - """ - - TIMEOUT_READ_PACKET = 3 # seconds - """ - Timeout to read packets. - """ - - __COMMAND_MODE_CHAR = "+" - """ - Character you have to send to enter AT command mode - """ - - __COMMAND_MODE_OK = "OK\r" - """ - Response that will be receive if the attempt to enter in at command mode goes well. - """ - - 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): - """ - Class constructor. Instantiates a new :class:`.XBeeDevice` 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. - 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): comm port read timeout. - - Raises: - All exceptions raised by PySerial's Serial class constructor. - - .. seealso:: - | PySerial documentation: http://pyserial.sourceforge.net - """ - 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), - sync_ops_timeout=_sync_ops_timeout - ) - 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 - self.__explicit_queue = None - - self.__modem_status_received = False - - @classmethod - def create_xbee_device(cls, comm_port_data): - """ - Creates and returns an :class:`.XBeeDevice` from data of the port to which is connected. - - Args: - comm_port_data (Dictionary): dictionary with all comm port data needed. - The dictionary keys are: - | "baudRate" --> Baud rate. - | "port" --> Port number. - | "bitSize" --> Bit size. - | "stopBits" --> Stop bits. - | "parity" --> Parity. - | "flowControl" --> Flow control. - | "timeout" for --> Timeout for synchronous operations (in seconds). - - Returns: - :class:`.XBeeDevice`: the XBee device created. - - Raises: - SerialException: if the port you want to open does not exist or is already opened. - - .. seealso:: - | :class:`.XBeeDevice` - """ - return XBeeDevice(comm_port_data["port"], - comm_port_data["baudRate"], - comm_port_data["bitSize"], - comm_port_data["stopBits"], - comm_port_data["parity"], - comm_port_data["flowControl"], - comm_port_data["timeout"]) - - def 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. - """ - - if self._is_open: - raise XBeeException("XBee device already open.") - - 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 = 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() - self._packet_listener.start() - - # 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("Could not determine operating mode") - if self._operating_mode == OperatingMode.AT_MODE: - self.close() - raise InvalidOperatingModeException.from_operating_mode(self._operating_mode) - - # Read the device info (obtain its parameters and protocol). - self.read_device_info() - - self._is_open = True - - def close(self): - """ - Closes the communication with the XBee device. - - This method guarantees that all threads running are stopped and - the serial port is closed. - """ - if self._network is not None: - self._network.stop_discovery_process() - - if self._packet_listener is not None: - self._packet_listener.stop() - - 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. - - Returns: - :class:`.XBeeSerialPort`: the serial port associated to the XBee device. - - .. seealso:: - | :class:`.XBeeSerialPort` - """ - return self._serial_port - - @AbstractXBeeDevice._before_send_method - def get_parameter(self, param): - """ - Override. - - .. seealso:: - | :meth:`.AbstractXBeeDevice.get_parameter` - """ - return super(XBeeDevice, self).get_parameter(param) - - @AbstractXBeeDevice._before_send_method - def set_parameter(self, param, value): - """ - Override. - - See: - :meth:`.AbstractXBeeDevice.set_parameter` - """ - super(XBeeDevice, self).set_parameter(param, value) - - @AbstractXBeeDevice._before_send_method - @AbstractXBeeDevice._after_send_method - def _send_data_64_16(self, x64addr, x16addr, data, transmit_options=TransmitOptions.NONE.value): - """ - Blocking method. This method sends data to a remote XBee device corresponding to the given - 64-bit/16-bit address. - - This method will wait for the packet response. - - The default timeout for this method is :attr:`.XBeeDevice._DEFAULT_TIMEOUT_SYNC_OPERATIONS`. - - Args: - x64addr (:class:`.XBee64BitAddress`): The 64-bit address of the XBee that will receive the data. - x16addr (:class:`.XBee16BitAddress`): The 16-bit address of the XBee that will receive the data, - :attr:`.XBee16BitAddress.UNKNOWN_ADDRESS` if it is unknown. - data (String or Bytearray): the raw data to send. - transmit_options (Integer, optional): transmit options, bitfield of :class:`.TransmitOptions`. Default to - ``TransmitOptions.NONE.value``. - - Returns: - :class:`.XBeePacket` the response. - - Raises: - ValueError: if ``x64addr`` is ``None`` - ValueError: if ``x16addr`` is ``None`` - ValueError: if ``data`` is ``None``. - TimeoutException: if this method can't read a response packet in - :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. - XBeeException: if the XBee device's serial port is closed. - XBeeException: if the status of the response received is not OK. - - .. seealso:: - | :class:`.XBee64BitAddress` - | :class:`.XBee16BitAddress` - | :class:`.XBeePacket` - """ - if x64addr is None: - raise ValueError("64-bit address cannot be None") - if x16addr is None: - raise ValueError("16-bit address cannot be None") - if data is None: - raise ValueError("Data cannot be None") - - if self.is_remote(): - raise OperationNotSupportedException("Cannot send data to a remote device from a remote device") - - if isinstance(data, str): - data = data.encode("utf8") - - packet = TransmitPacket(self.get_next_frame_id(), - x64addr, - x16addr, - 0, - transmit_options, - data) - return self.send_packet_sync_and_get_response(packet) - - @AbstractXBeeDevice._before_send_method - @AbstractXBeeDevice._after_send_method - def _send_data_64(self, x64addr, data, transmit_options=TransmitOptions.NONE.value): - """ - Blocking method. This method sends data to a remote XBee device corresponding to the given - 64-bit address. - - This method will wait for the packet response. - - The default timeout for this method is :attr:`.XBeeDevice._DEFAULT_TIMEOUT_SYNC_OPERATIONS`. - - Args: - x64addr (:class:`.XBee64BitAddress`): The 64-bit address of the XBee that will receive the data. - data (String or Bytearray): the raw data to send. - transmit_options (Integer, optional): transmit options, bitfield of :class:`.TransmitOptions`. Default to - ``TransmitOptions.NONE.value``. - - Returns: - :class:`.XBeePacket` the response. - - Raises: - ValueError: if ``x64addr`` is ``None`` - ValueError: if ``data`` is ``None``. - TimeoutException: if this method can't read a response packet in - :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. - XBeeException: if the XBee device's serial port is closed. - XBeeException: if the status of the response received is not OK. - - .. seealso:: - | :class:`.XBee64BitAddress` - | :class:`.XBeePacket` - """ - if x64addr is None: - raise ValueError("64-bit address cannot be None") - if data is None: - raise ValueError("Data cannot be None") - - if self.is_remote(): - raise OperationNotSupportedException("Cannot send data to a remote device from a remote device") - - if isinstance(data, str): - data = data.encode("utf8") - - if self.get_protocol() == XBeeProtocol.RAW_802_15_4: - packet = TX64Packet(self.get_next_frame_id(), - x64addr, - transmit_options, - data) - else: - packet = TransmitPacket(self.get_next_frame_id(), - x64addr, - XBee16BitAddress.UNKNOWN_ADDRESS, - 0, - transmit_options, - data) - return self.send_packet_sync_and_get_response(packet) - - @AbstractXBeeDevice._before_send_method - @AbstractXBeeDevice._after_send_method - def _send_data_16(self, x16addr, data, transmit_options=TransmitOptions.NONE.value): - """ - Blocking method. This method sends data to a remote XBee device corresponding to the given - 16-bit address. - - This method will wait for the packet response. - - The default timeout for this method is :attr:`.XBeeDevice._DEFAULT_TIMEOUT_SYNC_OPERATIONS`. - - Args: - x16addr (:class:`.XBee16BitAddress`): The 16-bit address of the XBee that will receive the data. - data (String or Bytearray): the raw data to send. - transmit_options (Integer, optional): transmit options, bitfield of :class:`.TransmitOptions`. Default to - ``TransmitOptions.NONE.value``. - - Returns: - :class:`.XBeePacket` the response. - - Raises: - ValueError: if ``x16addr`` is ``None`` - ValueError: if ``data`` is ``None``. - TimeoutException: if this method can't read a response packet in - :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. - XBeeException: if the XBee device's serial port is closed. - XBeeException: if the status of the response received is not OK. - - .. seealso:: - | :class:`.XBee16BitAddress` - | :class:`.XBeePacket` - """ - if x16addr is None: - raise ValueError("16-bit address cannot be None") - if data is None: - raise ValueError("Data cannot be None") - - if self.is_remote(): - raise OperationNotSupportedException("Cannot send data to a remote device from a remote device") - - if isinstance(data, str): - data = data.encode("utf8") - - packet = TX16Packet(self.get_next_frame_id(), - x16addr, - transmit_options, - data) - return self.send_packet_sync_and_get_response(packet) - - def send_data(self, remote_xbee_device, data, transmit_options=TransmitOptions.NONE.value): - """ - Blocking method. This method sends data to a remote XBee device synchronously. - - This method will wait for the packet response. - - The default timeout for this method is :attr:`.XBeeDevice._DEFAULT_TIMEOUT_SYNC_OPERATIONS`. - - Args: - remote_xbee_device (:class:`.RemoteXBeeDevice`): the remote XBee device to send data to. - data (String or Bytearray): the raw data to send. - transmit_options (Integer, optional): transmit options, bitfield of :class:`.TransmitOptions`. Default to - ``TransmitOptions.NONE.value``. - - Returns: - :class:`.XBeePacket` the response. - - Raises: - ValueError: if ``remote_xbee_device`` is ``None``. - TimeoutException: if this method can't read a response packet in - :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. - XBeeException: if the XBee device's serial port is closed. - XBeeException: if the status of the response received is not OK. - - .. seealso:: - | :class:`.RemoteXBeeDevice` - | :class:`.XBeePacket` - """ - if remote_xbee_device is None: - raise ValueError("Remote XBee device cannot be None") - - protocol = self.get_protocol() - 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) - elif remote_xbee_device.get_64bit_addr() is not None: - 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) - 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) - else: - 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) - - @AbstractXBeeDevice._before_send_method - def _send_data_async_64_16(self, x64addr, x16addr, data, transmit_options=TransmitOptions.NONE.value): - """ - Non-blocking method. This method sends data to a remote XBee device corresponding to the given - 64-bit/16-bit address. - - This method won't wait for the response. - - Args: - x64addr (:class:`.XBee64BitAddress`): The 64-bit address of the XBee that will receive the data. - x16addr (:class:`.XBee16BitAddress`): The 16-bit address of the XBee that will receive the data, - :attr:`.XBee16BitAddress.UNKNOWN_ADDRESS` if it is unknown. - data (String or Bytearray): the raw data to send. - transmit_options (Integer, optional): transmit options, bitfield of :class:`.TransmitOptions`. Default to - ``TransmitOptions.NONE.value``. - - Returns: - :class:`.XBeePacket` the response. - - Raises: - ValueError: if ``x64addr`` is ``None`` - ValueError: if ``x16addr`` is ``None`` - ValueError: if ``data`` is ``None``. - 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. - - .. seealso:: - | :class:`.XBee64BitAddress` - | :class:`.XBee16BitAddress` - | :class:`.XBeePacket` - """ - if x64addr is None: - raise ValueError("64-bit address cannot be None") - if x16addr is None: - raise ValueError("16-bit address cannot be None") - if data is None: - raise ValueError("Data cannot be None") - - if self.is_remote(): - raise OperationNotSupportedException("Cannot send data to a remote device from a remote device") - - if isinstance(data, str): - data = data.encode("utf8") - - packet = TransmitPacket(self.get_next_frame_id(), - x64addr, - x16addr, - 0, - transmit_options, - data) - self.send_packet(packet) - - @AbstractXBeeDevice._before_send_method - def _send_data_async_64(self, x64addr, data, transmit_options=TransmitOptions.NONE.value): - """ - Non-blocking method. This method sends data to a remote XBee device corresponding to the given - 64-bit address. - - This method won't wait for the response. - - Args: - x64addr (:class:`.XBee64BitAddress`): The 64-bit address of the XBee that will receive the data. - data (String or Bytearray): the raw data to send. - transmit_options (Integer, optional): transmit options, bitfield of :class:`.TransmitOptions`. Default to - ``TransmitOptions.NONE.value``. - - Returns: - :class:`.XBeePacket` the response. - - Raises: - ValueError: if ``x64addr`` is ``None`` - ValueError: if ``data`` is ``None``. - 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. - - .. seealso:: - | :class:`.XBee64BitAddress` - | :class:`.XBeePacket` - """ - if x64addr is None: - raise ValueError("64-bit address cannot be None") - if data is None: - raise ValueError("Data cannot be None") - - if self.is_remote(): - raise OperationNotSupportedException("Cannot send data to a remote device from a remote device") - - if isinstance(data, str): - data = data.encode("utf8") - - if self.get_protocol() == XBeeProtocol.RAW_802_15_4: - packet = TX64Packet(self.get_next_frame_id(), - x64addr, - transmit_options, - data) - else: - packet = TransmitPacket(self.get_next_frame_id(), - x64addr, - XBee16BitAddress.UNKNOWN_ADDRESS, - 0, - transmit_options, - data) - self.send_packet(packet) - - @AbstractXBeeDevice._before_send_method - def _send_data_async_16(self, x16addr, data, transmit_options=TransmitOptions.NONE.value): - """ - Non-blocking method. This method sends data to a remote XBee device corresponding to the given - 16-bit address. - - This method won't wait for the response. - - Args: - x16addr (:class:`.XBee16BitAddress`): The 16-bit address of the XBee that will receive the data. - data (String or Bytearray): the raw data to send. - transmit_options (Integer, optional): transmit options, bitfield of :class:`.TransmitOptions`. Default to - ``TransmitOptions.NONE.value``. - - Returns: - :class:`.XBeePacket` the response. - - Raises: - ValueError: if ``x16addr`` is ``None`` - ValueError: if ``data`` is ``None``. - 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. - - .. seealso:: - | :class:`.XBee16BitAddress` - | :class:`.XBeePacket` - """ - if x16addr is None: - raise ValueError("16-bit address cannot be None") - if data is None: - raise ValueError("Data cannot be None") - - if self.is_remote(): - raise OperationNotSupportedException("Cannot send data to a remote device from a remote device") - - if isinstance(data, str): - data = data.encode("utf8") - - packet = TX16Packet(self.get_next_frame_id(), - x16addr, - transmit_options, - data) - self.send_packet(packet) - - def send_data_async(self, remote_xbee_device, data, transmit_options=TransmitOptions.NONE.value): - """ - Non-blocking method. This method sends data to a remote XBee device. - - This method won't wait for the response. - - Args: - remote_xbee_device (:class:`.RemoteXBeeDevice`): the remote XBee device to send data to. - data (String or Bytearray): the raw data to send. - transmit_options (Integer, optional): transmit options, bitfield of :class:`.TransmitOptions`. Default to - ``TransmitOptions.NONE.value``. - - 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. - - .. seealso:: - | :class:`.RemoteXBeeDevice` - """ - if remote_xbee_device is None: - raise ValueError("Remote XBee device cannot be None") - - protocol = self.get_protocol() - 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) - elif remote_xbee_device.get_64bit_addr() is not None: - 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) - 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) - else: - 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) - - def send_data_broadcast(self, data, transmit_options=TransmitOptions.NONE.value): - """ - Sends the provided data to all the XBee nodes of the network (broadcast). - - This method blocks till a success or error transmit status arrives or - the configured receive timeout expires. - - The received timeout is configured using the :meth:`.AbstractXBeeDevice.set_sync_ops_timeout` - method and can be consulted with :meth:`.AbstractXBeeDevice.get_sync_ops_timeout` method. - - Args: - data (String or Bytearray): data to send. - transmit_options (Integer, optional): transmit options, bitfield of :class:`.TransmitOptions`. Default to - ``TransmitOptions.NONE.value``. - - Raises: - TimeoutException: if this method can't read a response packet in - :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. - 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) - - @AbstractXBeeDevice._before_send_method - def send_user_data_relay(self, local_interface, data): - """ - Sends the given data to the given XBee local interface. - - Args: - local_interface (:class:`.XBeeLocalInterface`): Destination XBee local interface. - data (Bytearray): Data to send. - - 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. - ValueError: if ``local_interface`` is ``None``. - XBeeException: if there is any problem sending the User Data Relay. - - .. seealso:: - | :class:`.XBeeLocalInterface` - """ - if local_interface is None: - 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)) - - def send_bluetooth_data(self, data): - """ - Sends the given data to the Bluetooth interface using a User Data Relay frame. - - Args: - data (Bytearray): Data to send. - - 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 there is any problem sending the data. - - .. seealso:: - | :meth:`.XBeeDevice.send_micropython_data` - | :meth:`.XBeeDevice.send_user_data_relay` - """ - self.send_user_data_relay(XBeeLocalInterface.BLUETOOTH, data) - - def send_micropython_data(self, data): - """ - Sends the given data to the MicroPython interface using a User Data Relay frame. - - Args: - data (Bytearray): Data to send. - - 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 there is any problem sending the data. - - .. seealso:: - | :meth:`.XBeeDevice.send_bluetooth_data` - | :meth:`.XBeeDevice.send_user_data_relay` - """ - self.send_user_data_relay(XBeeLocalInterface.MICROPYTHON, data) - - def read_data(self, timeout=None): - """ - Reads new data received by this XBee device. - - If a ``timeout`` is specified, this method blocks until new data is received or the timeout expires, - throwing in that case a :class:`.TimeoutException`. - - Args: - timeout (Integer, optional): read timeout in seconds. If it's ``None``, this method is non-blocking - and will return ``None`` if there is no data available. - - Returns: - :class:`.XBeeMessage`: the read message or ``None`` if this XBee did not receive new data. - - Raises: - ValueError: if a timeout is specified and is less than 0. - TimeoutException: if a timeout is specified and no data was received during that time. - 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. - - .. seealso:: - | :class:`.XBeeMessage` - """ - return self.__read_data_packet(None, timeout, False) - - def read_data_from(self, remote_xbee_device, timeout=None): - """ - Reads new data received from the given remote XBee device. - - If a ``timeout`` is specified, this method blocks until new data is received or the timeout expires, - throwing in that case a :class:`.TimeoutException`. - - Args: - remote_xbee_device (:class:`.RemoteXBeeDevice`): the remote XBee device that sent the data. - timeout (Integer, optional): read timeout in seconds. If it's ``None``, this method is non-blocking - and will return ``None`` if there is no data available. - - Returns: - :class:`.XBeeMessage`: the read message sent by ``remote_xbee_device`` or ``None`` if this XBee did - not receive new data. - - Raises: - ValueError: if a timeout is specified and is less than 0. - TimeoutException: if a timeout is specified and no data was received during that time. - 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. - - .. seealso:: - | :class:`.XBeeMessage` - | :class:`.RemoteXBeeDevice` - """ - return self.__read_data_packet(remote_xbee_device, timeout, False) - - def has_packets(self): - """ - Returns whether the XBee device's queue has packets or not. - This do not include explicit packets. - - Return: - Boolean: ``True`` if this XBee device's queue has packets, ``False`` otherwise. - - .. seealso:: - | :meth:`.XBeeDevice.has_explicit_packets` - """ - return not self.__packet_queue.empty() - - def has_explicit_packets(self): - """ - Returns whether the XBee device's queue has explicit packets or not. - This do not include non-explicit packets. - - Return: - Boolean: ``True`` if this XBee device's queue has explicit packets, ``False`` otherwise. - - .. seealso:: - | :meth:`.XBeeDevice.has_packets` - """ - return not self.__explicit_queue.empty() - - def flush_queues(self): - """ - Flushes the packets queue. - """ - self.__packet_queue.flush() - self.__data_queue.flush() - self.__explicit_queue.flush() - - def reset(self): - """ - Override method. - - .. seealso:: - | :meth:`.AbstractXBeeDevice.reset` - """ - # Send reset command. - response = self._send_at_command(ATCommand("FR")) - - # Check if AT Command response is valid. - self._check_at_cmd_response_is_valid(response) - - lock = threading.Condition() - self.__modem_status_received = False - - def ms_callback(modem_status): - if modem_status == ModemStatus.HARDWARE_RESET or modem_status == ModemStatus.WATCHDOG_TIMER_RESET: - self.__modem_status_received = True - lock.acquire() - lock.notify() - lock.release() - - self.add_modem_status_received_callback(ms_callback) - lock.acquire() - lock.wait(self.__TIMEOUT_RESET) - lock.release() - self.del_modem_status_received_callback(ms_callback) - - if self.__modem_status_received is False: - raise XBeeException("Invalid modem status.") - - def add_packet_received_callback(self, callback): - """ - Override. - """ - super(XBeeDevice, self)._add_packet_received_callback(callback) - - def add_data_received_callback(self, callback): - """ - Override. - """ - super(XBeeDevice, self)._add_data_received_callback(callback) - - def add_modem_status_received_callback(self, callback): - """ - Override. - """ - super(XBeeDevice, self)._add_modem_status_received_callback(callback) - - def add_io_sample_received_callback(self, callback): - """ - Override. - """ - super(XBeeDevice, self)._add_io_sample_received_callback(callback) - - def add_expl_data_received_callback(self, callback): - """ - Override. - """ - super(XBeeDevice, self)._add_expl_data_received_callback(callback) - - def add_user_data_relay_received_callback(self, callback): - """ - Override. - """ - super(XBeeDevice, self)._add_user_data_relay_received_callback(callback) - - def add_bluetooth_data_received_callback(self, callback): - """ - Override. - """ - super(XBeeDevice, self)._add_bluetooth_data_received_callback(callback) - - def add_micropython_data_received_callback(self, callback): - """ - Override. - """ - super(XBeeDevice, self)._add_micropython_data_received_callback(callback) - - def del_packet_received_callback(self, callback): - """ - Override. - """ - super(XBeeDevice, self)._del_packet_received_callback(callback) - - def del_data_received_callback(self, callback): - """ - Override. - """ - super(XBeeDevice, self)._del_data_received_callback(callback) - - def del_modem_status_received_callback(self, callback): - """ - Override. - """ - super(XBeeDevice, self)._del_modem_status_received_callback(callback) - - def del_io_sample_received_callback(self, callback): - """ - Override. - """ - super(XBeeDevice, self)._del_io_sample_received_callback(callback) - - def del_expl_data_received_callback(self, callback): - """ - Override. - """ - super(XBeeDevice, self)._del_expl_data_received_callback(callback) - - def del_user_data_relay_received_callback(self, callback): - """ - Override. - """ - super(XBeeDevice, self)._del_user_data_relay_received_callback(callback) - - def del_bluetooth_data_received_callback(self, callback): - """ - Override. - """ - super(XBeeDevice, self)._del_bluetooth_data_received_callback(callback) - - def del_micropython_data_received_callback(self, callback): - """ - Override. - """ - 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() - - for i in self._network.get_discovery_callbacks(): - api_callbacks.append(i) - return api_callbacks - - def __get_operating_mode(self): - """ - Returns this XBee device's operating mode. - - Returns: - :class:`.OperatingMode`. This XBee device's operating mode. - """ - return super(XBeeDevice, self)._get_operating_mode() - - def is_open(self): - """ - Returns whether this XBee device is open or not. - - Returns: - Boolean. ``True`` if this XBee device is open, ``False`` otherwise. - """ - return self._is_open - - def is_remote(self): - """ - Override method. - - .. seealso:: - | :meth:`.AbstractXBeeDevice.is_remote` - """ - return False - - def get_network(self): - """ - Returns this XBee device's current network. - - Returns: - :class:`.XBeeDevice.XBeeNetwork` - """ - return self._network - - @AbstractXBeeDevice._before_send_method - @AbstractXBeeDevice._after_send_method - def _send_expl_data(self, remote_xbee_device, data, src_endpoint, dest_endpoint, - cluster_id, profile_id, transmit_options=TransmitOptions.NONE.value): - """ - Blocking method. Sends the provided data to the given XBee device in - application layer mode. Application layer mode means that you need to - specify the application layer fields to be sent with the data. - - This method blocks till a success or error response arrives or the - configured receive timeout expires. - - The default timeout for this method is :attr:`.XBeeDevice._DEFAULT_TIMEOUT_SYNC_OPERATIONS`. - - Args: - remote_xbee_device (:class:`.RemoteXBeeDevice`): the remote XBee device to send data to. - data (String or Bytearray): the raw data to send. - src_endpoint (Integer): source endpoint of the transmission. 1 byte. - dest_endpoint (Integer): destination endpoint of the transmission. 1 byte. - cluster_id (Integer): Cluster ID of the transmission. Must be between 0x0 and 0xFFFF. - profile_id (Integer): Profile ID of the transmission. Must be between 0x0 and 0xFFFF. - transmit_options (Integer, optional): transmit options, bitfield of :class:`.TransmitOptions`. Default to - ``TransmitOptions.NONE.value``. - - Returns: - :class:`.XBeePacket`: the response packet obtained after sending the provided one. - - Raises: - TimeoutException: if this method can't read a response packet in - :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. - 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. - - .. seealso:: - | :class:`.RemoteXBeeDevice` - | :class:`.XBeePacket` - """ - return self.send_packet_sync_and_get_response(self.__build_expldata_packet(remote_xbee_device, data, - src_endpoint, dest_endpoint, - cluster_id, profile_id, - False, transmit_options)) - - @AbstractXBeeDevice._before_send_method - def _send_expl_data_async(self, remote_xbee_device, data, src_endpoint, dest_endpoint, - cluster_id, profile_id, transmit_options=TransmitOptions.NONE.value): - """ - Non-blocking method. Sends the provided data to the given XBee device in - application layer mode. Application layer mode means that you need to - specify the application layer fields to be sent with the data. - - Args: - remote_xbee_device (:class:`.RemoteXBeeDevice`): the remote XBee device to send data to. - data (String or Bytearray): the raw data to send. - src_endpoint (Integer): source endpoint of the transmission. 1 byte. - dest_endpoint (Integer): destination endpoint of the transmission. 1 byte. - cluster_id (Integer): Cluster ID of the transmission. Must be between 0x0 and 0xFFFF. - profile_id (Integer): Profile ID of the transmission. Must be between 0x0 and 0xFFFF. - transmit_options (Integer, optional): transmit options, bitfield of :class:`.TransmitOptions`. Default to - ``TransmitOptions.NONE.value``. - - 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 ``cluster_id`` is less than 0x0 or greater than 0xFFFF. - ValueError: if ``profile_id`` is less than 0x0 or greater than 0xFFFF. - - .. seealso:: - | :class:`.RemoteXBeeDevice` - """ - self.send_packet(self.__build_expldata_packet(remote_xbee_device, data, src_endpoint, - dest_endpoint, cluster_id, - 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): - """ - Sends the provided data to all the XBee nodes of the network (broadcast) - in application layer mode. Application layer mode means that you need to - specify the application layer fields to be sent with the data. - - This method blocks till a success or error transmit status arrives or - the configured receive timeout expires. - - The received timeout is configured using the :meth:`.AbstractXBeeDevice.set_sync_ops_timeout` - method and can be consulted with :meth:`.AbstractXBeeDevice.get_sync_ops_timeout` method. - - Args: - data (String or Bytearray): the raw data to send. - src_endpoint (Integer): source endpoint of the transmission. 1 byte. - dest_endpoint (Integer): destination endpoint of the transmission. 1 byte. - cluster_id (Integer): Cluster ID of the transmission. Must be between 0x0 and 0xFFFF. - profile_id (Integer): Profile ID of the transmission. Must be between 0x0 and 0xFFFF. - transmit_options (Integer, optional): transmit options, bitfield of :class:`.TransmitOptions`. Default to - ``TransmitOptions.NONE.value``. - - .. seealso:: - | :meth:`.XBeeDevice._send_expl_data` - """ - return self.send_packet_sync_and_get_response(self.__build_expldata_packet(None, data, src_endpoint, - dest_endpoint, cluster_id, - profile_id, True, transmit_options)) - - def _read_expl_data(self, timeout=None): - """ - Reads new explicit data received by this XBee device. - - If a ``timeout`` is specified, this method blocks until new data is received or the timeout expires, - throwing in that case a :class:`.TimeoutException`. - - Args: - timeout (Integer, optional): read timeout in seconds. If it's ``None``, this method is non-blocking - and will return ``None`` if there is no explicit data available. - - Returns: - :class:`.ExplicitXBeeMessage`: the read message or ``None`` if this XBee did not receive new data. - - Raises: - ValueError: if a timeout is specified and is less than 0. - TimeoutException: if a timeout is specified and no explicit data was received during that time. - InvalidOperatingModeException: if the XBee 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. - - .. seealso:: - | :class:`.ExplicitXBeeMessage` - """ - return self.__read_data_packet(None, timeout, True) - - def _read_expl_data_from(self, remote_xbee_device, timeout=None): - """ - Reads new explicit data received from the given remote XBee device. - - If a ``timeout`` is specified, this method blocks until new data is received or the timeout expires, - throwing in that case a :class:`.TimeoutException`. - - Args: - remote_xbee_device (:class:`.RemoteXBeeDevice`): the remote XBee device that sent the explicit data. - timeout (Integer, optional): read timeout in seconds. If it's ``None``, this method is non-blocking - and will return ``None`` if there is no data available. - - Returns: - :class:`.ExplicitXBeeMessage`: the read message sent by ``remote_xbee_device`` or ``None`` if this - XBee did not receive new data. - - Raises: - ValueError: if a timeout is specified and is less than 0. - TimeoutException: if a timeout is specified and no explicit data was received during that time. - InvalidOperatingModeException: if the XBee 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. - - .. seealso:: - | :class:`.ExplicitXBeeMessage` - | :class:`.RemoteXBeeDevice` - """ - return self.__read_data_packet(remote_xbee_device, timeout, True) - - @AbstractXBeeDevice._before_send_method - def __read_data_packet(self, remote, timeout, explicit): - """ - Reads a new data packet received by this XBee device during the provided timeout. - - If a ``timeout`` is specified, this method blocks until new data is received or the timeout expires, - throwing in that case a :class:`.TimeoutException`. - - Args: - remote (:class:`.RemoteXBeeDevice`): The remote device to get a data packet from. ``None`` to read a - data packet sent by any device. - timeout (Integer): The time to wait for a data packet in seconds. - explicit (Boolean): ``True`` to read an explicit data packet, ``False`` to read an standard data packet. - - Returns: - :class:`.XBeeMessage` or :class:`.ExplicitXBeeMessage`: the XBee message received by this device. - - Raises: - ValueError: if a timeout is specified and is less than 0. - TimeoutException: if a timeout is specified and no data was received during that time. - 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. - - .. seealso:: - | :class:`.XBeeMessage` - | :class:`.ExplicitXBeeMessage` - | :class:`.RemoteXBeeDevice` - """ - if timeout is not None and timeout < 0: - raise ValueError("Read timeout must be 0 or greater") - - if not explicit: - if remote is None: - packet = self.__data_queue.get(timeout=timeout) - else: - 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) - - 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, False) - elif frame_type == ApiFrameType.EXPLICIT_RX_INDICATOR: - return self.__build_xbee_message(packet, True) - else: - return None - - def _enter_at_command_mode(self): - """ - Attempts to put this device in AT Command mode. Only valid if device is - working in AT mode. - - Returns: - Boolean: ``True`` if the XBee device has entered in AT command mode, ``False`` otherwise. - - Raises: - SerialTimeoutException: if there is any error trying to write within the serial port. - """ - 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.__TIMEOUT_BEFORE_COMMAND_MODE) - # Send the command mode sequence. - b = bytearray(self.__COMMAND_MODE_CHAR, "utf8") - self._serial_port.write(b) - self._serial_port.write(b) - self._serial_port.write(b) - # Wait some time to let the module generate a response. - time.sleep(self.__TIMEOUT_ENTER_COMMAND_MODE) - # Read data from the device (it should answer with 'OK\r'). - data = self._serial_port.read_existing().decode() - - return data and data in self.__COMMAND_MODE_OK - - def _determine_operating_mode(self): - """ - Determines and returns the operating mode of the XBee device. - - If the XBee device is not in AT command mode, this method attempts - to enter on it. - - Returns: - :class:`.OperatingMode` - - .. seealso:: - | :class:`.OperatingMode` - """ - try: - self._operating_mode = OperatingMode.API_MODE - response = self.get_parameter("AP") - return OperatingMode.get(response[0]) - except TimeoutException: - self._operating_mode = OperatingMode.AT_MODE - try: - # If there is timeout exception and is possible to enter - # in AT command mode, the current operating mode is AT. - if self._enter_at_command_mode(): - return OperatingMode.AT_MODE - except SerialTimeoutException as ste: - self._log.exception(ste) - return OperatingMode.UNKNOWN - - def send_packet_sync_and_get_response(self, packet_to_send): - """ - Override method. - - .. seealso:: - | :meth:`.AbstractXBeeDevice._send_packet_sync_and_get_response` - """ - return super(XBeeDevice, self)._send_packet_sync_and_get_response(packet_to_send) - - def send_packet(self, packet, sync=False): - """ - Override method. - - .. seealso:: - | :meth:`.AbstractXBeeDevice._send_packet` - """ - return super(XBeeDevice, self)._send_packet(packet, sync) - - def __build_xbee_message(self, packet, explicit=False): - """ - Builds and returns the XBee message corresponding to the provided ``packet``. The result is an - :class:`.XBeeMessage` or :class:`.ExplicitXBeeMessage` depending on the packet. - - Args: - packet (:class:`.XBeePacket`): the packet to get its corresponding XBee message. - explicit (Boolean): ``True`` if the packet is an explicit packet, ``False`` otherwise. - - Returns: - :class:`.XBeeMessage` or :class:`.ExplicitXBeeMessage`: the resulting XBee message. - - .. seealso:: - | :class:`.ExplicitXBeeMessage` - | :class:`.XBeeMessage` - | :class:`.XBeePacket` - """ - x64addr = None - x16addr = None - remote = None - - if hasattr(packet, "x16bit_source_addr"): - x16addr = packet.x16bit_source_addr - if hasattr(packet, "x64bit_source_addr"): - x64addr = packet.x64bit_source_addr - if x64addr is not None or x16addr is not None: - 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, packet.is_broadcast()) - else: - msg = XBeeMessage(packet.rf_data, remote, time.time(), packet.is_broadcast()) - - return msg - - def __build_expldata_packet(self, remote_xbee_device, data, src_endpoint, dest_endpoint, - cluster_id, profile_id, broadcast=False, transmit_options=TransmitOptions.NONE.value): - """ - Builds and returns an explicit data packet with the provided parameters. - - Args: - remote_xbee_device (:class:`.RemoteXBeeDevice`): the remote XBee device to send data to. - data (String or Bytearray): the raw data to send. - src_endpoint (Integer): source endpoint of the transmission. 1 byte. - dest_endpoint (Integer): destination endpoint of the transmission. 1 byte. - cluster_id (Integer): Cluster ID of the transmission. Must be between 0x0 and 0xFFFF. - profile_id (Integer): Profile ID of the transmission. Must be between 0x0 and 0xFFFF. - broadcast (Boolean, optional): ``True`` to send data in broadcast mode (``remote_xbee_device`` is ignored), - ``False`` to send data to the specified ``remote_xbee_device``. - transmit_options (Integer, optional): transmit options, bitfield of :class:`.TransmitOptions`. Default to - ``TransmitOptions.NONE.value``. - - Returns: - :class:`.ExplicitAddressingPacket`: the explicit packet generated with the provided parameters. - - Raises: - All exceptions raised by :meth:`.ExplicitAddressingPacket.__init__` - - .. seealso:: - | :class:`.ExplicitAddressingPacket` - | :meth:`.ExplicitAddressingPacket.__init__` - | :class:`.RemoteXBeeDevice` - """ - if broadcast: - x64addr = XBee64BitAddress.BROADCAST_ADDRESS - x16addr = XBee16BitAddress.UNKNOWN_ADDRESS - else: - x64addr = remote_xbee_device.get_64bit_addr() - x16addr = remote_xbee_device.get_16bit_addr() - - # If the device does not have 16-bit address, set it to Unknown. - if x16addr is None: - x16addr = XBee16BitAddress.UNKNOWN_ADDRESS - - if isinstance(data, str): - data = data.encode("utf8") - - return ExplicitAddressingPacket(self._get_next_frame_id(), x64addr, - x16addr, src_endpoint, dest_endpoint, - cluster_id, profile_id, 0, transmit_options, data) - - def get_next_frame_id(self): - """ - Returns the next frame ID of the XBee device. - - Returns: - Integer: The next frame ID of the XBee device. - """ - return self._get_next_frame_id() - - serial_port = property(__get_serial_port) - """:class:`.XBeeSerialPort`. The serial port associated to the XBee device.""" - - operating_mode = property(__get_operating_mode) - """:class:`.OperatingMode`. The operating mode of the XBee device.""" - - -class Raw802Device(XBeeDevice): - """ - This class represents a local 802.15.4 XBee device. - """ - - def __init__(self, port, baud_rate): - """ - 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. - baud_rate (Integer): the serial port baud rate. - - Raises: - All exceptions raised by :meth:`.XBeeDevice.__init__` constructor. - - .. seealso:: - | :class:`.XBeeDevice` - | :meth:`.XBeeDevice.__init__` - """ - super(Raw802Device, self).__init__(port, baud_rate) - - def open(self): - """ - Override. - - Raises: - XBeeException: if the protocol is invalid. - All exceptions raised by :meth:`.XBeeDevice.open`. - - .. seealso:: - | :meth:`.XBeeDevice.open` - """ - super(Raw802Device, self).open() - if not self.is_remote() and self.get_protocol() != XBeeProtocol.RAW_802_15_4: - raise XBeeException("Invalid protocol.") - - def get_network(self): - """ - Override. - - .. seealso:: - | :meth:`.XBeeDevice.get_network` - """ - if self._network is None: - self._network = Raw802Network(self) - return self._network - - def get_protocol(self): - """ - Override. - - .. seealso:: - | :meth:`.XBeeDevice.get_protocol` - """ - return XBeeProtocol.RAW_802_15_4 - - def get_ai_status(self): - """ - Override. - - .. seealso:: - | :meth:`.AbstractXBeeDevice._get_ai_status` - """ - return super(Raw802Device, self)._get_ai_status() - - def send_data_64(self, x64addr, data, transmit_options=TransmitOptions.NONE.value): - """ - Override. - - .. seealso:: - | :meth:`.XBeeDevice.send_data_64` - """ - return super(Raw802Device, self)._send_data_64(x64addr, data, transmit_options) - - def send_data_async_64(self, x64addr, data, transmit_options=TransmitOptions.NONE.value): - """ - Override. - - .. seealso:: - | :meth:`.XBeeDevice.send_data_async_64` - """ - super(Raw802Device, self)._send_data_async_64(x64addr, data, transmit_options) - - def send_data_16(self, x16addr, data, transmit_options=TransmitOptions.NONE.value): - """ - Override. - - .. seealso:: - | :meth:`.XBeeDevice._send_data_16` - """ - return super(Raw802Device, self)._send_data_16(x16addr, data, transmit_options) - - def send_data_async_16(self, x16addr, data, transmit_options=TransmitOptions.NONE.value): - """ - Override. - - .. seealso:: - | :meth:`.XBeeDevice._send_data_async_16` - """ - super(Raw802Device, self)._send_data_async_16(x16addr, data, transmit_options) - - -class DigiMeshDevice(XBeeDevice): - """ - This class represents a local DigiMesh XBee device. - """ - - def __init__(self, port, baud_rate): - """ - 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. - baud_rate (Integer): the serial port baud rate. - - Raises: - All exceptions raised by :meth:`.XBeeDevice.__init__` constructor. - - .. seealso:: - | :class:`.XBeeDevice` - | :meth:`.XBeeDevice.__init__` - """ - super(DigiMeshDevice, self).__init__(port, baud_rate) - - def open(self): - """ - Override. - - Raises: - XBeeException: if the protocol is invalid. - All exceptions raised by :meth:`.XBeeDevice.open`. - - .. seealso:: - | :meth:`.XBeeDevice.open` - """ - super(DigiMeshDevice, self).open() - if self.get_protocol() != XBeeProtocol.DIGI_MESH: - raise XBeeException("Invalid protocol.") - - def get_network(self): - """ - Override. - - .. seealso:: - | :meth:`.XBeeDevice.get_network` - """ - if self._network is None: - self._network = DigiMeshNetwork(self) - return self._network - - def get_protocol(self): - """ - Override. - - .. seealso:: - | :meth:`.XBeeDevice.get_protocol` - """ - return XBeeProtocol.DIGI_MESH - - def send_data_64(self, x64addr, data, transmit_options=TransmitOptions.NONE.value): - """ - Override. - - .. seealso:: - | :meth:`.XBeeDevice.send_data_64` - """ - return super(DigiMeshDevice, self)._send_data_64(x64addr, data, transmit_options) - - def send_data_async_64(self, x64addr, data, transmit_options=TransmitOptions.NONE.value): - """ - Override. - - .. seealso:: - | :meth:`.XBeeDevice.send_data_async_64` - """ - super(DigiMeshDevice, self)._send_data_async_64(x64addr, data, transmit_options) - - def read_expl_data(self, timeout=None): - """ - Override. - - .. seealso:: - | :meth:`.XBeeDevice.read_expl_data` - """ - return super(DigiMeshDevice, self)._read_expl_data(timeout=timeout) - - def read_expl_data_from(self, remote_xbee_device, timeout=None): - """ - Override. - - .. seealso:: - | :meth:`.XBeeDevice.read_expl_data_from` - """ - 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): - """ - Override. - - .. seealso:: - | :meth:`.XBeeDevice.send_expl_data` - """ - 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): - """ - Override. - - .. seealso:: - | :meth:`.XBeeDevice._send_expl_data_broadcast` - """ - 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): - """ - Override. - - .. seealso:: - | :meth:`.XBeeDevice.send_expl_data_async` - """ - super(DigiMeshDevice, self)._send_expl_data_async(remote_xbee_device, data, src_endpoint, - dest_endpoint, cluster_id, profile_id, transmit_options) - - -class DigiPointDevice(XBeeDevice): - """ - This class represents a local DigiPoint XBee device. - """ - - def __init__(self, port, baud_rate): - """ - 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. - baud_rate (Integer): the serial port baud rate. - - Raises: - All exceptions raised by :meth:`XBeeDevice.__init__` constructor. - - .. seealso:: - | :class:`.XBeeDevice` - | :meth:`.XBeeDevice.__init__` - """ - super(DigiPointDevice, self).__init__(port, baud_rate) - - def open(self): - """ - Override. - - Raises: - XBeeException: if the protocol is invalid. - All exceptions raised by :meth:`.XBeeDevice.open`. - - .. seealso:: - | :meth:`.XBeeDevice.open` - """ - super(DigiPointDevice, self).open() - if self.get_protocol() != XBeeProtocol.DIGI_POINT: - raise XBeeException("Invalid protocol.") - - def get_network(self): - """ - Override. - - .. seealso:: - | :meth:`.XBeeDevice.get_network` - """ - if self._network is None: - self._network = DigiPointNetwork(self) - return self._network - - def get_protocol(self): - """ - Override. - - .. seealso:: - | :meth:`.XBeeDevice.get_protocol` - """ - return XBeeProtocol.DIGI_POINT - - def send_data_64_16(self, x64addr, x16addr, data, transmit_options=TransmitOptions.NONE.value): - """ - Override. - - .. seealso:: - | :meth:`.XBeeDevice.send_data_64_16` - """ - 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): - """ - Override. - - .. seealso:: - | :meth:`.XBeeDevice.send_data_async_64_16` - """ - super(DigiPointDevice, self)._send_data_async_64_16(x64addr, x16addr, data, transmit_options) - - def read_expl_data(self, timeout=None): - """ - Override. - - .. seealso:: - | :meth:`.XBeeDevice.read_expl_data` - """ - return super(DigiPointDevice, self)._read_expl_data(timeout=timeout) - - def read_expl_data_from(self, remote_xbee_device, timeout=None): - """ - Override. - - .. seealso:: - | :meth:`.XBeeDevice.read_expl_data_from` - """ - 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): - """ - Override. - - .. seealso:: - | :meth:`.XBeeDevice.send_expl_data` - """ - 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): - """ - Override. - - .. seealso:: - | :meth:`.XBeeDevice._send_expl_data_broadcast` - """ - 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): - """ - Override. - - .. seealso:: - | :meth:`.XBeeDevice.send_expl_data_async` - """ - super(DigiPointDevice, self)._send_expl_data_async(remote_xbee_device, data, src_endpoint, - dest_endpoint, cluster_id, profile_id, transmit_options) - - -class ZigBeeDevice(XBeeDevice): - """ - This class represents a local ZigBee XBee device. - """ - - def __init__(self, port, baud_rate): - """ - 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. - baud_rate (Integer): the serial port baud rate. - - Raises: - All exceptions raised by :func:`.XBeeDevice.__init__` constructor. - - .. seealso:: - | :class:`.XBeeDevice` - | :meth:`XBeeDevice.__init__` - """ - super(ZigBeeDevice, self).__init__(port, baud_rate) - - def open(self): - """ - Override. - - Raises: - XBeeException: if the protocol is invalid. - All exceptions raised by :meth:`.XBeeDevice.open`. - - .. seealso:: - | :meth:`.XBeeDevice.open` - """ - super(ZigBeeDevice, self).open() - if self.get_protocol() != XBeeProtocol.ZIGBEE: - raise XBeeException("Invalid protocol.") - - def get_network(self): - """ - Override. - - .. seealso:: - | :meth:`.XBeeDevice.get_network` - """ - if self._network is None: - self._network = ZigBeeNetwork(self) - return self._network - - def get_protocol(self): - """ - Override. - - .. seealso:: - | :meth:`.XBeeDevice.get_protocol` - """ - return XBeeProtocol.ZIGBEE - - def get_ai_status(self): - """ - Override. - - .. seealso:: - | :meth:`.AbstractXBeeDevice._get_ai_status` - """ - return super(ZigBeeDevice, self)._get_ai_status() - - def force_disassociate(self): - """ - Override. - - .. seealso:: - | :meth:`.AbstractXBeeDevice._force_disassociate` - """ - super(ZigBeeDevice, self)._force_disassociate() - - def send_data_64_16(self, x64addr, x16addr, data, transmit_options=TransmitOptions.NONE.value): - """ - Override. - - .. seealso:: - | :meth:`.XBeeDevice.send_data_64_16` - """ - 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): - """ - Override. - - .. seealso:: - | :meth:`.XBeeDevice.send_data_async_64_16` - """ - super(ZigBeeDevice, self)._send_data_async_64_16(x64addr, x16addr, data, transmit_options) - - def read_expl_data(self, timeout=None): - """ - Override. - - .. seealso:: - | :meth:`.XBeeDevice._read_expl_data` - """ - return super(ZigBeeDevice, self)._read_expl_data(timeout=timeout) - - def read_expl_data_from(self, remote_xbee_device, timeout=None): - """ - Override. - - .. seealso:: - | :meth:`.XBeeDevice._read_expl_data_from` - """ - 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): - """ - Override. - - .. seealso:: - | :meth:`.XBeeDevice._send_expl_data` - """ - 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): - """ - Override. - - .. seealso:: - | :meth:`.XBeeDevice._send_expl_data_broadcast` - """ - 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): - """ - Override. - - .. seealso:: - | :meth:`.XBeeDevice.send_expl_data_async` - """ - 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 - def send_multicast_data(self, group_id, data, src_endpoint, dest_endpoint, - cluster_id, profile_id): - """ - Blocking method. This method sends multicast data to the provided group ID - synchronously. - - This method will wait for the packet response. - - The default timeout for this method is :attr:`.XBeeDevice._DEFAULT_TIMEOUT_SYNC_OPERATIONS`. - - Args: - group_id (:class:`.XBee16BitAddress`): the 16 bit address of the multicast group. - data (Bytearray): the raw data to send. - src_endpoint (Integer): source endpoint of the transmission. 1 byte. - dest_endpoint (Integer): destination endpoint of the transmission. 1 byte. - cluster_id (Integer): Cluster ID of the transmission. Must be between 0x0 and 0xFFFF. - profile_id (Integer): Profile ID of the transmission. Must be between 0x0 and 0xFFFF. - - Returns: - :class:`.XBeePacket`: the response packet. - - Raises: - TimeoutException: if this method can't read a response packet in - :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. - XBeeException: if the XBee device's serial port is closed. - XBeeException: if the status of the response received is not OK. - - .. seealso:: - | :class:`XBee16BitAddress` - | :class:`XBeePacket` - """ - 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, data) - - return self.send_packet_sync_and_get_response(packet_to_send) - - @AbstractXBeeDevice._before_send_method - def send_multicast_data_async(self, group_id, data, src_endpoint, dest_endpoint, cluster_id, profile_id): - """ - Non-blocking method. This method sends multicast data to the provided group ID. - - This method won't wait for the response. - - Args: - group_id (:class:`.XBee16BitAddress`): the 16 bit address of the multicast group. - data (Bytearray): the raw data to send. - src_endpoint (Integer): source endpoint of the transmission. 1 byte. - dest_endpoint (Integer): destination endpoint of the transmission. 1 byte. - cluster_id (Integer): Cluster ID of the transmission. Must be between 0x0 and 0xFFFF. - profile_id (Integer): Profile ID of the transmission. Must be between 0x0 and 0xFFFF. - - Raises: - TimeoutException: if this method can't read a response packet in - :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. - XBeeException: if the XBee device's serial port is closed. - - .. seealso:: - | :class:`XBee16BitAddress` - """ - 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, data) - - self.send_packet(packet_to_send) - - -class IPDevice(XBeeDevice): - """ - This class provides common functionality for XBee IP devices. - """ - - BROADCAST_IP = "255.255.255.255" - - __DEFAULT_SOURCE_PORT = 9750 - - __DEFAULT_PROTOCOL = IPProtocol.TCP - - __OPERATION_EXCEPTION = "Operation not supported in this module." - - def __init__(self, port, baud_rate): - """ - 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. - baud_rate (Integer): the serial port baud rate. - - Raises: - All exceptions raised by :func:`.XBeeDevice.__init__` constructor. - - .. seealso:: - | :class:`.XBeeDevice` - | :meth:`XBeeDevice.__init__` - """ - super(IPDevice, self).__init__(port, baud_rate) - - self._ip_addr = None - self._source_port = self.__DEFAULT_SOURCE_PORT - - def read_device_info(self): - """ - Override. - - .. seealso:: - | :meth:`.AbstractXBeeDevice.read_device_info` - """ - super(IPDevice, self).read_device_info() - - # Read the module's IP address. - resp = self.get_parameter("MY") - self._ip_addr = IPv4Address(utils.bytes_to_int(resp)) - - # Read the source port. - 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): - """ - Returns the IP address of this IP device. - - To refresh this value use the method :meth:`.IPDevice.read_device_info`. - - Returns: - :class:`ipaddress.IPv4Address`: The IP address of this IP device. - - .. seealso:: - | :class:`ipaddress.IPv4Address` - """ - return self._ip_addr - - def set_dest_ip_addr(self, address): - """ - Sets the destination IP address. - - Args: - address (:class:`ipaddress.IPv4Address`): Destination IP address. - - Raises: - ValueError: if ``address`` is ``None``. - TimeoutException: if there is a timeout setting the destination IP address. - XBeeException: if there is any other XBee related exception. - - .. seealso:: - | :class:`ipaddress.IPv4Address` - """ - if address is None: - raise ValueError("Destination IP address cannot be None") - - self.set_parameter("DL", bytearray(address.exploded, "utf8")) - - def get_dest_ip_addr(self): - """ - Returns the destination IP address. - - Returns: - :class:`ipaddress.IPv4Address`: The configured destination IP address. - - Raises: - TimeoutException: if there is a timeout getting the destination IP address. - XBeeException: if there is any other XBee related exception. - - .. seealso:: - | :class:`ipaddress.IPv4Address` - """ - 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:`.IPDataReceived`. - - Args: - callback (Function): the callback. Receives one argument. - - * 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:`.IPDataReceived` - event. - - Args: - callback (Function): the callback to delete. - - Raises: - ValueError: if ``callback`` is not in the callback list of :class:`.IPDataReceived` event. - """ - self._packet_listener.del_ip_data_received_callback(callback) - - def start_listening(self, source_port): - """ - Starts listening for incoming IP transmissions in the provided port. - - Args: - source_port (Integer): Port to listen for incoming transmissions. - - Raises: - ValueError: if ``source_port`` is less than 0 or greater than 65535. - TimeoutException: if there is a timeout setting the source port. - XBeeException: if there is any other XBee related exception. - """ - if not 0 <= source_port <= 65535: - raise ValueError("Source port must be between 0 and 65535") - - self.set_parameter("C0", utils.int_to_bytes(source_port)) - self._source_port = source_port - - def stop_listening(self): - """ - Stops listening for incoming IP transmissions. - - Raises: - TimeoutException: if there is a timeout processing the operation. - XBeeException: if there is any other XBee related exception. - """ - self.set_parameter("C0", utils.int_to_bytes(0)) - self._source_port = 0 - - @AbstractXBeeDevice._before_send_method - @AbstractXBeeDevice._after_send_method - def send_ip_data(self, ip_addr, dest_port, protocol, data, close_socket=False): - """ - Sends the provided IP data to the given IP address and port using - the specified IP protocol. For TCP and TCP SSL protocols, you can - also indicate if the socket should be closed when data is sent. - - This method blocks till a success or error response arrives or the - configured receive timeout expires. - - Args: - ip_addr (:class:`ipaddress.IPv4Address`): The IP address to send IP data to. - dest_port (Integer): The destination port of the transmission. - protocol (:class:`.IPProtocol`): The IP protocol used for the transmission. - data (String or Bytearray): The IP data to be sent. - close_socket (Boolean, optional): ``True`` to close the socket just after the - transmission. ``False`` to keep it open. Default to ``False``. - - Raises: - ValueError: if ``ip_addr`` is ``None``. - ValueError: if ``protocol`` is ``None``. - ValueError: if ``data`` is ``None``. - ValueError: if ``dest_port`` is less than 0 or greater than 65535. - OperationNotSupportedException: if the device is remote. - TimeoutException: if there is a timeout sending the data. - XBeeException: if there is any other XBee related exception. - """ - if ip_addr is None: - raise ValueError("IP address cannot be None") - if protocol is None: - raise ValueError("Protocol cannot be None") - if data is None: - raise ValueError("Data cannot be None") - - if not 0 <= dest_port <= 65535: - raise ValueError("Destination port must be between 0 and 65535") - - # Check if device is remote. - if self.is_remote(): - 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. - source_port = self._source_port - if protocol is not IPProtocol.UDP: - source_port = 0 - - if isinstance(data, str): - data = data.encode("utf8") - - 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) - - return self.send_packet_sync_and_get_response(packet) - - @AbstractXBeeDevice._before_send_method - def send_ip_data_async(self, ip_addr, dest_port, protocol, data, close_socket=False): - """ - Sends the provided IP data to the given IP address and port - asynchronously using the specified IP protocol. For TCP and TCP SSL - protocols, you can also indicate if the socket should be closed when - data is sent. - - Asynchronous transmissions do not wait for answer from the remote - device or for transmit status packet. - - Args: - ip_addr (:class:`ipaddress.IPv4Address`): The IP address to send IP data to. - dest_port (Integer): The destination port of the transmission. - protocol (:class:`.IPProtocol`): The IP protocol used for the transmission. - data (String or Bytearray): The IP data to be sent. - close_socket (Boolean, optional): ``True`` to close the socket just after the - transmission. ``False`` to keep it open. Default to ``False``. - - Raises: - ValueError: if ``ip_addr`` is ``None``. - ValueError: if ``protocol`` is ``None``. - ValueError: if ``data`` is ``None``. - ValueError: if ``dest_port`` is less than 0 or greater than 65535. - OperationNotSupportedException: if the device is remote. - XBeeException: if there is any other XBee related exception. - """ - if ip_addr is None: - raise ValueError("IP address cannot be None") - if protocol is None: - raise ValueError("Protocol cannot be None") - if data is None: - raise ValueError("Data cannot be None") - - if not 0 <= dest_port <= 65535: - raise ValueError("Destination port must be between 0 and 65535") - - # Check if device is remote. - if self.is_remote(): - 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. - source_port = self._source_port - if protocol is IPProtocol.UDP: - source_port = 0 - - if isinstance(data, str): - data = data.encode("utf8") - - 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) - - self.send_packet(packet) - - def send_ip_data_broadcast(self, dest_port, data): - """ - Sends the provided IP data to all clients. - - This method blocks till a success or error transmit status arrives or - the configured receive timeout expires. - - Args: - dest_port (Integer): The destination port of the transmission. - data (String or Bytearray): The IP data to be sent. - - Raises: - ValueError: if ``data`` is ``None``. - ValueError: if ``dest_port`` is less than 0 or greater than 65535. - TimeoutException: if there is a timeout sending the data. - XBeeException: if there is any other XBee related exception. - """ - return self.send_ip_data(IPv4Address(self.BROADCAST_IP), dest_port, IPProtocol.UDP, data) - - @AbstractXBeeDevice._before_send_method - def read_ip_data(self, timeout=XBeeDevice.TIMEOUT_READ_PACKET): - """ - Reads new IP data received by this XBee device during the - provided timeout. - - This method blocks until new IP data is received or the provided - timeout expires. - - For non-blocking operations, register a callback and use the method - :meth:`IPDevice.add_ip_data_received_callback`. - - Before reading IP data you need to start listening for incoming - IP data at a specific port. Use the method :meth:`IPDevice.start_listening` - for that purpose. When finished, you can use the method - :meth:`IPDevice.stop_listening` to stop listening for incoming IP data. - - Args: - timeout (Integer, optional): The time to wait for new IP data in seconds. - - Returns: - :class:`.IPMessage`: IP message, ``None`` if this device did not receive new data. - - Raises: - ValueError: if ``timeout`` is less than 0. - """ - if timeout < 0: - raise ValueError("Read timeout must be 0 or greater.") - - return self.__read_ip_data_packet(timeout) - - @AbstractXBeeDevice._before_send_method - def read_ip_data_from(self, ip_addr, timeout=XBeeDevice.TIMEOUT_READ_PACKET): - """ - Reads new IP data received from the given IP address during the - provided timeout. - - This method blocks until new IP data from the provided IP - address is received or the given timeout expires. - - For non-blocking operations, register a callback and use the method - :meth:`IPDevice.add_ip_data_received_callback`. - - Before reading IP data you need to start listening for incoming - IP data at a specific port. Use the method :meth:`IPDevice.start_listening` - for that purpose. When finished, you can use the method - :meth:`IPDevice.stop_listening` to stop listening for incoming IP data. - - Args: - ip_addr (:class:`ipaddress.IPv4Address`): The IP address to read data from. - timeout (Integer, optional): The time to wait for new IP data in seconds. - - Returns: - :class:`.IPMessage`: IP message, ``None`` if this device did not - receive new data from the provided IP address. - - Raises: - ValueError: if ``timeout`` is less than 0. - """ - if timeout < 0: - raise ValueError("Read timeout must be 0 or greater.") - - return self.__read_ip_data_packet(timeout, ip_addr) - - def __read_ip_data_packet(self, timeout, ip_addr=None): - """ - Reads a new IP data packet received by this IP XBee device during - the provided timeout. - - This method blocks until new IP data is received or the given - timeout expires. - - If the provided IP address is ``None`` the method returns - the first IP data packet read from any IP address. If the IP address is - not ``None`` the method returns the first data package read from - the provided IP address. - - Args: - timeout (Integer, optional): The time to wait for new IP data in seconds. Optional. - ip_addr (:class:`ipaddress.IPv4Address`, optional): The IP address to read data from. - ``None`` to read an IP data packet from any IP address. - - Returns: - :class:`.IPMessage`: IP message, ``None`` if this device did not - receive new data from the provided IP address. - """ - queue = self._packet_listener.get_ip_queue() - - if ip_addr is None: - packet = queue.get(timeout=timeout) - else: - packet = queue.get_by_ip(ip_addr, timeout) - - if packet is None: - return None - - if packet.get_frame_type() == ApiFrameType.RX_IPV4: - return IPMessage(packet.source_address, packet.source_port, - packet.dest_port, packet.ip_protocol, - packet.data) - - return None - - def get_network(self): - """ - Deprecated. - - This protocol does not support the network functionality. - """ - return None - - def get_16bit_addr(self): - """ - Deprecated. - - This protocol does not have an associated 16-bit address. - """ - return None - - def get_dest_address(self): - """ - Deprecated. - - Operation not supported in this protocol. Use :meth:`.IPDevice.get_dest_ip_addr` instead. - This method will raise an :class:`.AttributeError`. - """ - raise AttributeError(self.__OPERATION_EXCEPTION) - - def set_dest_address(self, addr): - """ - Deprecated. - - Operation not supported in this protocol. Use :meth:`.IPDevice.set_dest_ip_addr` instead. - This method will raise an :class:`.AttributeError`. - """ - raise AttributeError(self.__OPERATION_EXCEPTION) - - def get_pan_id(self): - """ - Deprecated. - - Operation not supported in this protocol. - This method will raise an :class:`.AttributeError`. - """ - raise AttributeError(self.__OPERATION_EXCEPTION) - - def set_pan_id(self, value): - """ - Deprecated. - - Operation not supported in this protocol. - This method will raise an :class:`.AttributeError`. - """ - raise AttributeError(self.__OPERATION_EXCEPTION) - - def add_data_received_callback(self, callback): - """ - Deprecated. - - Operation not supported in this protocol. - This method will raise an :class:`.AttributeError`. - """ - raise AttributeError(self.__OPERATION_EXCEPTION) - - def del_data_received_callback(self, callback): - """ - Deprecated. - - Operation not supported in this protocol. - This method will raise an :class:`.AttributeError`. - """ - raise AttributeError(self.__OPERATION_EXCEPTION) - - def add_expl_data_received_callback(self, callback): - """ - Deprecated. - - Operation not supported in this protocol. - This method will raise an :class:`.AttributeError`. - """ - raise AttributeError(self.__OPERATION_EXCEPTION) - - def del_expl_data_received_callback(self, callback): - """ - Deprecated. - - Operation not supported in this protocol. - This method will raise an :class:`.AttributeError`. - """ - raise AttributeError(self.__OPERATION_EXCEPTION) - - def read_data(self, timeout=None, explicit=False): - """ - Deprecated. - - Operation not supported in this protocol. - This method will raise an :class:`.AttributeError`. - """ - raise AttributeError(self.__OPERATION_EXCEPTION) - - def read_data_from(self, remote_xbee_device, timeout=None, explicit=False): - """ - Deprecated. - - Operation not supported in this protocol. - This method will raise an :class:`.AttributeError`. - """ - raise AttributeError(self.__OPERATION_EXCEPTION) - - def send_data_broadcast(self, data, transmit_options=TransmitOptions.NONE.value): - """ - Deprecated. - - Operation not supported in this protocol. - This method will raise an :class:`.AttributeError`. - """ - raise AttributeError(self.__OPERATION_EXCEPTION) - - def send_data(self, remote_xbee_device, data, transmit_options=TransmitOptions.NONE.value): - """ - Deprecated. - - Operation not supported in this protocol. - This method will raise an :class:`.AttributeError`. - """ - raise AttributeError(self.__OPERATION_EXCEPTION) - - def send_data_async(self, remote_xbee_device, data, transmit_options=TransmitOptions.NONE.value): - """ - Deprecated. - - Operation not supported in this protocol. - This method will raise an :class:`.AttributeError`. - """ - raise AttributeError(self.__OPERATION_EXCEPTION) - - -class CellularDevice(IPDevice): - """ - This class represents a local Cellular device. - """ - - def __init__(self, port, baud_rate): - """ - 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. - baud_rate (Integer): the serial port baud rate. - - Raises: - All exceptions raised by :func:`.XBeeDevice.__init__` constructor. - - .. seealso:: - | :class:`.XBeeDevice` - | :meth:`XBeeDevice.__init__` - """ - super(CellularDevice, self).__init__(port, baud_rate) - - self._imei_addr = None - - def open(self): - """ - Override. - - Raises: - XBeeException: if the protocol is invalid. - All exceptions raised by :meth:`.XBeeDevice.open`. - - .. seealso:: - | :meth:`.XBeeDevice.open` - """ - super(CellularDevice, self).open() - if self.get_protocol() not in [XBeeProtocol.CELLULAR, XBeeProtocol.CELLULAR_NBIOT]: - raise XBeeException("Invalid protocol.") - - def get_protocol(self): - """ - Override. - - .. seealso:: - | :meth:`.XBeeDevice.get_protocol` - """ - return XBeeProtocol.CELLULAR - - def read_device_info(self): - """ - Override. - - .. seealso:: - | :meth:`.XBeeDevice.read_device _info` - """ - super(CellularDevice, self).read_device_info() - - # Generate the IMEI address. - self._imei_addr = XBeeIMEIAddress(self._64bit_addr.address) - - def is_connected(self): - """ - Returns whether the device is connected to the Internet or not. - - Returns: - Boolean: ``True`` if the device is connected to the Internet, ``False`` otherwise. - - Raises: - TimeoutException: if there is a timeout getting the association indication status. - XBeeException: if there is any other XBee related exception. - """ - status = self.get_cellular_ai_status() - return status == CellularAssociationIndicationStatus.SUCCESSFULLY_CONNECTED - - def get_cellular_ai_status(self): - """ - Returns the current association status of this Cellular device. - - It indicates occurrences of errors during the modem initialization - and connection. - - Returns: - :class:`.CellularAssociationIndicationStatus`: The association indication status of the Cellular device. - - Raises: - TimeoutException: if there is a timeout getting the association indication status. - XBeeException: if there is any other XBee related exception. - """ - 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:`.SMSReceived`. - - Args: - callback (Function): the callback. Receives one argument. - - * 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:`.SMSReceived` - event. - - Args: - callback (Function): the callback to delete. - - Raises: - ValueError: if ``callback`` is not in the callback list of :class:`.SMSReceived` event. - """ - self._packet_listener.del_sms_received_callback(callback) - - def get_imei_addr(self): - """ - Returns the IMEI address of this Cellular device. - - To refresh this value use the method :meth:`.CellularDevice.read_device_info`. - - Returns: - :class:`.XBeeIMEIAddress`: The IMEI address of this Cellular device. - """ - return self._imei_addr - - @AbstractXBeeDevice._before_send_method - @AbstractXBeeDevice._after_send_method - def send_sms(self, phone_number, data): - """ - Sends the provided SMS message to the given phone number. - - This method blocks till a success or error response arrives or the - configured receive timeout expires. - - For non-blocking operations use the method :meth:`.CellularDevice.send_sms_async`. - - Args: - phone_number (String): The phone number to send the SMS to. - data (String): Text of the SMS. - - Raises: - ValueError: if ``phone_number`` is ``None``. - ValueError: if ``data`` is ``None``. - OperationNotSupportedException: if the device is remote. - TimeoutException: if there is a timeout sending the SMS. - XBeeException: if there is any other XBee related exception. - """ - if phone_number is None: - raise ValueError("Phone number cannot be None") - if data is None: - raise ValueError("Data cannot be None") - - # Check if device is remote. - if self.is_remote(): - raise OperationNotSupportedException("Cannot send SMS from a remote device") - - xbee_packet = TXSMSPacket(self.get_next_frame_id(), phone_number, data) - - return self.send_packet_sync_and_get_response(xbee_packet) - - @AbstractXBeeDevice._before_send_method - def send_sms_async(self, phone_number, data): - """ - Sends asynchronously the provided SMS to the given phone number. - - Asynchronous transmissions do not wait for answer or for transmit - status packet. - - Args: - phone_number (String): The phone number to send the SMS to. - data (String): Text of the SMS. - - Raises: - ValueError: if ``phone_number`` is ``None``. - ValueError: if ``data`` is ``None``. - OperationNotSupportedException: if the device is remote. - XBeeException: if there is any other XBee related exception. - """ - if phone_number is None: - raise ValueError("Phone number cannot be None") - if data is None: - raise ValueError("Data cannot be None") - - # Check if device is remote. - if self.is_remote(): - 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_64bit_addr(self): - """ - Deprecated. - - Cellular protocol does not have an associated 64-bit address. - """ - return None - - def add_io_sample_received_callback(self, callback): - """ - Deprecated. - - Operation not supported in this protocol. - This method will raise an :class:`.AttributeError`. - """ - raise AttributeError(self.__OPERATION_EXCEPTION) - - def del_io_sample_received_callback(self, callback): - """ - Deprecated. - - Operation not supported in this protocol. - This method will raise an :class:`.AttributeError`. - """ - raise AttributeError(self.__OPERATION_EXCEPTION) - - def set_dio_change_detection(self, io_lines_set): - """ - Deprecated. - - Operation not supported in this protocol. - This method will raise an :class:`.AttributeError`. - """ - raise AttributeError(self.__OPERATION_EXCEPTION) - - def get_io_sampling_rate(self): - """ - Deprecated. - - Operation not supported in this protocol. - This method will raise an :class:`.AttributeError`. - """ - raise AttributeError(self.__OPERATION_EXCEPTION) - - def set_io_sampling_rate(self, rate): - """ - Deprecated. - - Operation not supported in this protocol. - This method will raise an :class:`.AttributeError`. - """ - raise AttributeError(self.__OPERATION_EXCEPTION) - - def get_node_id(self): - """ - Deprecated. - - Operation not supported in this protocol. - This method will raise an :class:`.AttributeError`. - """ - raise AttributeError(self.__OPERATION_EXCEPTION) - - def set_node_id(self, node_id): - """ - Deprecated. - - Operation not supported in this protocol. - This method will raise an :class:`.AttributeError`. - """ - raise AttributeError(self.__OPERATION_EXCEPTION) - - def get_power_level(self): - """ - Deprecated. - - Operation not supported in this protocol. - This method will raise an :class:`.AttributeError`. - """ - raise AttributeError(self.__OPERATION_EXCEPTION) - - def set_power_level(self, power_level): - """ - Deprecated. - - Operation not supported in this protocol. - This method will raise an :class:`.AttributeError`. - """ - raise AttributeError(self.__OPERATION_EXCEPTION) - - -class LPWANDevice(CellularDevice): - """ - This class provides common functionality for XBee Low-Power Wide-Area Network - devices. - """ - - def __init__(self, port, baud_rate): - """ - 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. - baud_rate (Integer): the serial port baud rate. - - Raises: - All exceptions raised by :func:`.XBeeDevice.__init__` constructor. - - .. seealso:: - | :class:`.XBeeDevice` - | :meth:`XBeeDevice.__init__` - """ - super(LPWANDevice, self).__init__(port, baud_rate) - - def send_ip_data(self, ip_addr, dest_port, protocol, data, close_socket=False): - """ - Sends the provided IP data to the given IP address and port using - the specified IP protocol. - - This method blocks till a success or error response arrives or the - configured receive timeout expires. - - Args: - ip_addr (:class:`ipaddress.IPv4Address`): The IP address to send IP data to. - dest_port (Integer): The destination port of the transmission. - protocol (:class:`.IPProtocol`): The IP protocol used for the transmission. - data (String or Bytearray): The IP data to be sent. - close_socket (Boolean, optional): Must be ``False``. - - Raises: - ValueError: if ``protocol`` is not UDP. - """ - if protocol != IPProtocol.UDP: - raise ValueError("This protocol only supports UDP transmissions") - - 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): - """ - Sends the provided IP data to the given IP address and port - asynchronously using the specified IP protocol. - - Asynchronous transmissions do not wait for answer from the remote - device or for transmit status packet. - - Args: - ip_addr (:class:`ipaddress.IPv4Address`): The IP address to send IP data to. - dest_port (Integer): The destination port of the transmission. - protocol (:class:`.IPProtocol`): The IP protocol used for the transmission. - data (String or Bytearray): The IP data to be sent. - close_socket (Boolean, optional): Must be ``False``. - - Raises: - ValueError: if ``protocol`` is not UDP. - """ - if protocol != IPProtocol.UDP: - raise ValueError("This protocol only supports UDP transmissions") - - super(LPWANDevice, self).send_ip_data_async(ip_addr, dest_port, protocol, data) - - def add_sms_callback(self, callback): - """ - Deprecated. - - Operation not supported in this protocol. - This method will raise an :class:`.AttributeError`. - """ - raise AttributeError(self.__OPERATION_EXCEPTION) - - def del_sms_callback(self, callback): - """ - Deprecated. - - Operation not supported in this protocol. - This method will raise an :class:`.AttributeError`. - """ - raise AttributeError(self.__OPERATION_EXCEPTION) - - def send_sms(self, phone_number, data): - """ - Deprecated. - - Operation not supported in this protocol. - This method will raise an :class:`.AttributeError`. - """ - raise AttributeError(self.__OPERATION_EXCEPTION) - - def send_sms_async(self, phone_number, data): - """ - Deprecated. - - Operation not supported in this protocol. - This method will raise an :class:`.AttributeError`. - """ - raise AttributeError(self.__OPERATION_EXCEPTION) - - -class NBIoTDevice(LPWANDevice): - """ - This class represents a local NB-IoT device. - """ - - def __init__(self, port, baud_rate): - """ - 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. - baud_rate (Integer): the serial port baud rate. - - Raises: - All exceptions raised by :func:`.XBeeDevice.__init__` constructor. - - .. seealso:: - | :class:`.XBeeDevice` - | :meth:`XBeeDevice.__init__` - """ - super(NBIoTDevice, self).__init__(port, baud_rate) - - self._imei_addr = None - - def open(self): - """ - Override. - - Raises: - XBeeException: if the protocol is invalid. - All exceptions raised by :meth:`.XBeeDevice.open`. - - .. seealso:: - | :meth:`.XBeeDevice.open` - """ - super(NBIoTDevice, self).open() - if self.get_protocol() != XBeeProtocol.CELLULAR_NBIOT: - raise XBeeException("Invalid protocol.") - - def get_protocol(self): - """ - Override. - - .. seealso:: - | :meth:`.XBeeDevice.get_protocol` - """ - return XBeeProtocol.CELLULAR_NBIOT - - -class WiFiDevice(IPDevice): - """ - This class represents a local Wi-Fi XBee device. - """ - - __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, baud_rate): - """ - 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. - baud_rate (Integer): the serial port baud rate. - - Raises: - All exceptions raised by :func:`.XBeeDevice.__init__` constructor. - - .. seealso:: - | :class:`.XBeeDevice` - | :meth:`XBeeDevice.__init__` - """ - 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): - """ - Override. - - Raises: - XBeeException: if the protocol is invalid. - All exceptions raised by :meth:`.XBeeDevice.open`. - - .. seealso:: - | :meth:`.XBeeDevice.open` - """ - super(WiFiDevice, self).open() - if self.get_protocol() != XBeeProtocol.XBEE_WIFI: - raise XBeeException("Invalid protocol.") - - def get_protocol(self): - """ - Override. - - .. seealso:: - | :meth:`.XBeeDevice.get_protocol` - """ - return XBeeProtocol.XBEE_WIFI - - def get_wifi_ai_status(self): - """ - Returns the current association status of the device. - - Returns: - :class:`.WiFiAssociationIndicationStatus`: the current association status of the device. - - Raises: - TimeoutException: if there is a timeout getting the association indication status. - XBeeException: if there is any other XBee related exception. - - .. seealso:: - | :class:`.WiFiAssociationIndicationStatus` - """ - return WiFiAssociationIndicationStatus.get(utils.bytes_to_int(self.get_parameter("AI"))) - - def get_access_point(self, ssid): - """ - Finds and returns the access point that matches the supplied SSID. - - Args: - ssid (String): the SSID of the access point to get. - - Returns: - :class:`.AccessPoint`: the discovered access point with the provided SSID, or ``None`` - if the timeout expires and the access point was not found. - - Raises: - TimeoutException: if there is a timeout getting the access point. - XBeeException: if there is an error sending the discovery command. - - .. seealso:: - | :class:`.AccessPoint` - """ - ap_list = self.scan_access_points() - - for access_point in ap_list: - if access_point.ssid == ssid: - return access_point - - return None - - @AbstractXBeeDevice._before_send_method - def scan_access_points(self): - """ - Performs a scan to search for access points in the vicinity. - - This method blocks until all the access points are discovered or the - configured access point timeout expires. - - The access point timeout is configured using the :meth:`.WiFiDevice.set_access_point_timeout` - method and can be consulted with :meth:`.WiFiDevice.get_access_point_timeout` method. - - Returns: - List: the list of :class:`.AccessPoint` objects discovered. - - Raises: - TimeoutException: if there is a timeout scanning the access points. - XBeeException: if there is any other XBee related exception. - - .. seealso:: - | :class:`.AccessPoint` - """ - access_points_list = [] - - 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 != "AS": - return - - # Check for error. - if xbee_packet.status == ATCommandStatus.ERROR: - self.__scanning_aps = False - self.__scanning_aps_error = True - # Check for end of discovery. - elif xbee_packet.command_value is None or len(xbee_packet.command_value) == 0: - self.__scanning_aps = False - # Get the access point from the command value. - else: - access_point = self.__parse_access_point(xbee_packet.command_value) - if access_point is not None: - access_points_list.append(access_point) - - self.add_packet_received_callback(packet_receive_callback) - self.__scanning_aps = True - - try: - 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: - time.sleep(0.1) - - # Check if we exited because of a timeout. - if self.__scanning_aps: - raise TimeoutException - # Check if there was an error in the active scan command (device is already connected). - if self.__scanning_aps_error: - raise XBeeException("There is an SSID already configured.") - finally: - self.__scanning_aps = False - self.__scanning_aps_error = False - self.del_packet_received_callback(packet_receive_callback) - - return access_points_list - - def connect_by_ap(self, access_point, password=None): - """ - Connects to the provided access point. - - This method blocks until the connection with the access point is - established or the configured access point timeout expires. - - The access point timeout is configured using the - :meth:`.WiFiDevice.set_access_point_timeout` method and can be consulted with - :meth:`.WiFiDevice.get_access_point_timeout` method. - - Once the module is connected to the access point, you can issue - the :meth:`.WiFiDevice.write_changes` method to save the connection settings. This - way the module will try to connect to the access point every time it - is powered on. - - Args: - access_point (:class:`.AccessPoint`): The access point to connect to. - password (String, optional): The password for the access point, ``None`` if it does not have - any encryption enabled. Optional. - - Returns: - Boolean: ``True`` if the module connected to the access point successfully, ``False`` otherwise. - - Raises: - ValueError:if ``access_point`` is ``None``. - TimeoutException: if there is a timeout sending the connect commands. - XBeeException: if there is any other XBee related exception. - - .. seealso:: - | :meth:`.WiFiDevice.connect_by_ssid` - | :meth:`.WiFiDevice.disconnect` - | :meth:`.WiFiDevice.get_access_point` - | :meth:`.WiFiDevice.get_access_point_timeout` - | :meth:`.WiFiDevice.scan_access_points` - | :meth:`.WiFiDevice.set_access_point_timeout` - """ - if access_point is None: - raise ValueError("The access point to connect to cannot be None.") - - # Set connection parameters. - 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("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("AI") - if status is None or len(status) < 1: - continue - if status[0] == 0: - return True - return False - - def connect_by_ssid(self, ssid, password=None): - """ - Connects to the access point with provided SSID. - - This method blocks until the connection with the access point is - established or the configured access point timeout expires. - - The access point timeout is configured using the - :meth:`.WiFiDevice.set_access_point_timeout` method and can be consulted with - :meth:`.WiFiDevice.get_access_point_timeout` method. - - Once the module is connected to the access point, you can issue - the :meth:`.WiFiDevice.write_changes` method to save the connection settings. This - way the module will try to connect to the access point every time it - is powered on. - - Args: - ssid (String): the SSID of the access point to connect to. - password (String, optional): The password for the access point, ``None`` if it does not have - any encryption enabled. Optional. - - Returns: - Boolean: ``True`` if the module connected to the access point successfully, ``False`` otherwise. - - Raises: - ValueError: if ``ssid`` is ``None``. - TimeoutException: if there is a timeout sending the connect commands. - XBeeException: if the access point with the provided SSID cannot be found. - XBeeException: if there is any other XBee related exception. - - .. seealso:: - | :meth:`.WiFiDevice.connect_by_ap` - | :meth:`.WiFiDevice.disconnect` - | :meth:`.WiFiDevice.get_access_point` - | :meth:`.WiFiDevice.get_access_point_timeout` - | :meth:`.WiFiDevice.scan_access_points` - | :meth:`.WiFiDevice.set_access_point_timeout` - """ - if ssid is None: - raise ValueError("SSID of access point cannot be None.") - - access_point = self.get_access_point(ssid) - 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) - - def disconnect(self): - """ - Disconnects from the access point that the device is connected to. - - This method blocks until the device disconnects totally from the - access point or the configured access point timeout expires. - - The access point timeout is configured using the - :meth:`.WiFiDevice.set_access_point_timeout` method and can be consulted with - :meth:`.WiFiDevice.get_access_point_timeout` method. - - Returns: - Boolean: ``True`` if the module disconnected from the access point successfully, ``False`` otherwise. - - Raises: - TimeoutException: if there is a timeout sending the disconnect command. - XBeeException: if there is any other XBee related exception. - - .. seealso:: - | :meth:`.WiFiDevice.connect_by_ap` - | :meth:`.WiFiDevice.connect_by_ssid` - | :meth:`.WiFiDevice.get_access_point_timeout` - | :meth:`.WiFiDevice.set_access_point_timeout` - """ - 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("AI") - if status is None or len(status) < 1: - continue - if status[0] == 0x23: - return True - return False - - def is_connected(self): - """ - Returns whether the device is connected to an access point or not. - - Returns: - Boolean: ``True`` if the device is connected to an access point, ``False`` otherwise. - - Raises: - TimeoutException: if there is a timeout getting the association indication status. - - .. seealso:: - | :meth:`.WiFiDevice.get_wifi_ai_status` - | :class:`.WiFiAssociationIndicationStatus` - """ - status = self.get_wifi_ai_status() - - return status == WiFiAssociationIndicationStatus.SUCCESSFULLY_JOINED - - def __parse_access_point(self, ap_data): - """ - Parses the given active scan API data and returns an :class:`.AccessPoint`: object. - - Args: - ap_data (Bytearray): access point data to parse. - - Returns: - :class:`.AccessPoint`: access point parsed from the provided data. ``None`` if the provided data - does not correspond to an access point. - - .. seealso:: - | :class:`.AccessPoint` - """ - index = 0 - - if len(ap_data) == 0: - return None - - # Get the version. - version = ap_data[index] - index += 1 - if len(ap_data[index:]) == 0: - return None - # Get the channel. - channel = ap_data[index] - index += 1 - if len(ap_data[index:]) == 0: - return None - # Get the encryption type. - encryption_type = ap_data[index] - index += 1 - if len(ap_data[index:]) == 0: - return None - # Get the signal strength. - signal_strength = ap_data[index] - index += 1 - if len(ap_data[index:]) == 0: - return None - - signal_quality = self.__get_signal_quality(version, signal_strength) - ssid = (ap_data[index:]).decode("utf8") - - return AccessPoint(ssid, WiFiEncryptionType.get(encryption_type), channel, signal_quality) - - @staticmethod - def __get_signal_quality(wifi_version, signal_strength): - """ - Converts the signal strength value in signal quality (%) based on the - provided Wi-Fi version. - - Args: - wifi_version (Integer): Wi-Fi protocol version of the Wi-Fi XBee device. - signal_strength (Integer): signal strength value to convert to %. - - Returns: - Integer: the signal quality in %. - """ - if wifi_version == 1: - if signal_strength <= -100: - quality = 0 - elif signal_strength >= -50: - quality = 100 - else: - quality = (2 * (signal_strength + 100)) - else: - quality = 2 * signal_strength - - # Check limits. - if quality > 100: - quality = 100 - if quality < 0: - quality = 0 - - return quality - - def get_access_point_timeout(self): - """ - Returns the configured access point timeout for connecting, - disconnecting and scanning access points. - - Returns: - Integer: the current access point timeout in milliseconds. - - .. seealso:: - | :meth:`.WiFiDevice.set_access_point_timeout` - """ - return self.__ap_timeout - - def set_access_point_timeout(self, ap_timeout): - """ - Configures the access point timeout in milliseconds for connecting, - disconnecting and scanning access points. - - Args: - ap_timeout (Integer): the new access point timeout in milliseconds. - - Raises: - ValueError: if ``ap_timeout`` is less than 0. - - .. seealso:: - | :meth:`.WiFiDevice.get_access_point_timeout` - """ - if ap_timeout < 0: - raise ValueError("Access point timeout cannot be less than 0.") - self.__ap_timeout = ap_timeout - - def get_ip_addressing_mode(self): - """ - Returns the IP addressing mode of the device. - - Returns: - :class:`.IPAddressingMode`: the IP addressing mode. - - Raises: - TimeoutException: if there is a timeout reading the IP addressing mode. - - .. seealso:: - | :meth:`.WiFiDevice.set_ip_addressing_mode` - | :class:`.IPAddressingMode` - """ - return IPAddressingMode.get(utils.bytes_to_int(self.get_parameter("MA"))) - - def set_ip_addressing_mode(self, mode): - """ - Sets the IP addressing mode of the device. - - Args: - mode (:class:`.IPAddressingMode`): the new IP addressing mode to set. - - Raises: - TimeoutException: if there is a timeout setting the IP addressing mode. - - .. seealso:: - | :meth:`.WiFiDevice.get_ip_addressing_mode` - | :class:`.IPAddressingMode` - """ - self.set_parameter("MA", utils.int_to_bytes(mode.code, num_bytes=1)) - - def set_ip_address(self, ip_address): - """ - Sets the IP address of the module. - - This method can only be called if the module is configured - in :attr:`.IPAddressingMode.STATIC` mode. Otherwise an ``XBeeException`` - will be thrown. - - Args: - ip_address (:class:`ipaddress.IPv4Address`): the new IP address to set. - - Raises: - TimeoutException: if there is a timeout setting the IP address. - - .. seealso:: - | :meth:`.WiFiDevice.get_mask_address` - | :class:`ipaddress.IPv4Address` - """ - self.set_parameter("MY", ip_address.packed) - - def get_mask_address(self): - """ - Returns the subnet mask IP address. - - Returns: - :class:`ipaddress.IPv4Address`: the subnet mask IP address. - - Raises: - TimeoutException: if there is a timeout reading the subnet mask address. - - .. seealso:: - | :meth:`.WiFiDevice.set_mask_address` - | :class:`ipaddress.IPv4Address` - """ - return IPv4Address(bytes(self.get_parameter("MK"))) - - def set_mask_address(self, mask_address): - """ - Sets the subnet mask IP address. - - This method can only be called if the module is configured - in :attr:`.IPAddressingMode.STATIC` mode. Otherwise an ``XBeeException`` - will be thrown. - - Args: - mask_address (:class:`ipaddress.IPv4Address`): the new subnet mask address to set. - - Raises: - TimeoutException: if there is a timeout setting the subnet mask address. - - .. seealso:: - | :meth:`.WiFiDevice.get_mask_address` - | :class:`ipaddress.IPv4Address` - """ - self.set_parameter("MK", mask_address.packed) - - def get_gateway_address(self): - """ - Returns the IP address of the gateway. - - Returns: - :class:`ipaddress.IPv4Address`: the IP address of the gateway. - - Raises: - TimeoutException: if there is a timeout reading the gateway address. - - .. seealso:: - | :meth:`.WiFiDevice.set_dns_address` - | :class:`ipaddress.IPv4Address` - """ - return IPv4Address(bytes(self.get_parameter("GW"))) - - def set_gateway_address(self, gateway_address): - """ - Sets the IP address of the gateway. - - This method can only be called if the module is configured - in :attr:`.IPAddressingMode.STATIC` mode. Otherwise an ``XBeeException`` - will be thrown. - - Args: - gateway_address (:class:`ipaddress.IPv4Address`): the new gateway address to set. - - Raises: - TimeoutException: if there is a timeout setting the gateway address. - - .. seealso:: - | :meth:`.WiFiDevice.get_gateway_address` - | :class:`ipaddress.IPv4Address` - """ - self.set_parameter("GW", gateway_address.packed) - - def get_dns_address(self): - """ - Returns the IP address of Domain Name Server (DNS). - - Returns: - :class:`ipaddress.IPv4Address`: the DNS address configured. - - Raises: - TimeoutException: if there is a timeout reading the DNS address. - - .. seealso:: - | :meth:`.WiFiDevice.set_dns_address` - | :class:`ipaddress.IPv4Address` - """ - return IPv4Address(bytes(self.get_parameter("NS"))) - - def set_dns_address(self, dns_address): - """ - Sets the IP address of Domain Name Server (DNS). - - Args: - dns_address (:class:`ipaddress.IPv4Address`): the new DNS address to set. - - Raises: - TimeoutException: if there is a timeout setting the DNS address. - - .. seealso:: - | :meth:`.WiFiDevice.get_dns_address` - | :class:`ipaddress.IPv4Address` - """ - self.set_parameter("NS", dns_address.packed) - - -class RemoteXBeeDevice(AbstractXBeeDevice): - """ - This class represents a remote XBee device. - """ - - def __init__(self, local_xbee_device, x64bit_addr=XBee64BitAddress.UNKNOWN_ADDRESS, - x16bit_addr=XBee16BitAddress.UNKNOWN_ADDRESS, node_id=None): - """ - Class constructor. Instantiates a new :class:`.RemoteXBeeDevice` with the provided parameters. - - Args: - local_xbee_device (:class:`.XBeeDevice`): the local XBee device associated with the remote one. - x64bit_addr (:class:`.XBee64BitAddress`): the 64-bit address of the remote XBee device. - x16bit_addr (:class:`.XBee16BitAddress`): the 16-bit address of the remote XBee device. - node_id (String, optional): the node identifier of the remote XBee device. Optional. - - .. seealso:: - | :class:`XBee16BitAddress` - | :class:`XBee64BitAddress` - | :class:`XBeeDevice` - """ - 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 - self._16bit_addr = x16bit_addr - self._node_id = node_id - - def get_parameter(self, parameter): - """ - Override. - - .. seealso:: - | :meth:`.AbstractXBeeDevice.get_parameter` - """ - return super(RemoteXBeeDevice, self).get_parameter(parameter) - - def set_parameter(self, parameter, value): - """ - Override. - - .. seealso:: - | :meth:`.AbstractXBeeDevice.set_parameter` - """ - super(RemoteXBeeDevice, self).set_parameter(parameter, value) - - def is_remote(self): - """ - Override method. - - .. seealso:: - | :meth:`.AbstractXBeeDevice.is_remote` - """ - return True - - def reset(self): - """ - Override method. - - .. seealso:: - | :meth:`.AbstractXBeeDevice.reset` - """ - # Send reset command. - try: - 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: - return - else: - raise te - - # Check if AT Command response is valid. - self._check_at_cmd_response_is_valid(response) - - def get_local_xbee_device(self): - """ - Returns the local XBee device associated to the remote one. - - Returns: - :class:`.XBeeDevice` - - """ - return self._local_xbee_device - - def set_local_xbee_device(self, local_xbee_device): - """ - This methods associates a :class:`.XBeeDevice` to the remote XBee device. - - Args: - local_xbee_device (:class:`.XBeeDevice`): the new local XBee device associated to the remote one. - - .. seealso:: - | :class:`.XBeeDevice` - """ - self._local_xbee_device = local_xbee_device - - def get_serial_port(self): - """ - Returns the serial port of the local XBee device associated to the remote one. - - Returns: - :class:`XBeeSerialPort`: the serial port of the local XBee device associated to the remote one. - - .. seealso:: - | :class:`XBeeSerialPort` - """ - return self._local_xbee_device.serial_port - - 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): - """ - This class represents a remote 802.15.4 XBee device. - """ - - def __init__(self, local_xbee_device, x64bit_addr=None, x16bit_addr=None, node_id=None): - """ - Class constructor. Instantiates a new :class:`.RemoteXBeeDevice` with the provided parameters. - - Args: - local_xbee_device (:class:`.XBeeDevice`): the local XBee device associated with the remote one. - x64bit_addr (:class:`.XBee64BitAddress`): the 64-bit address of the remote XBee device. - x16bit_addr (:class:`.XBee16BitAddress`): the 16-bit address of the remote XBee device. - node_id (String, optional): the node identifier of the remote XBee device. Optional. - - Raises: - XBeeException: if the protocol of ``local_xbee_device`` is invalid. - All exceptions raised by :class:`.RemoteXBeeDevice` constructor. - - .. seealso:: - | :class:`RemoteXBeeDevice` - | :class:`XBee16BitAddress` - | :class:`XBee64BitAddress` - | :class:`XBeeDevice` - """ - if local_xbee_device.get_protocol() != XBeeProtocol.RAW_802_15_4: - raise XBeeException("Invalid protocol.") - - super(RemoteRaw802Device, self).__init__(local_xbee_device, x64bit_addr, x16bit_addr, node_id=node_id) - - def get_protocol(self): - """ - Override. - - .. seealso:: - | :meth:`.RemoteXBeeDevice.get_protocol` - """ - return XBeeProtocol.RAW_802_15_4 - - def set_64bit_addr(self, address): - """ - Sets the 64-bit address of this remote 802.15.4 device. - - Args: - address (:class:`.XBee64BitAddress`): The 64-bit address to be set to the device. - - Raises: - ValueError: if ``address`` is ``None``. - """ - if address is None: - raise ValueError("64-bit address cannot be None") - - self._64bit_addr = address - - def get_ai_status(self): - """ - Override. - - .. seealso:: - | :meth:`.AbstractXBeeDevice._get_ai_status` - """ - return super(RemoteRaw802Device, self)._get_ai_status() - - -class RemoteDigiMeshDevice(RemoteXBeeDevice): - """ - This class represents a remote DigiMesh XBee device. - """ - - def __init__(self, local_xbee_device, x64bit_addr=None, node_id=None): - """ - Class constructor. Instantiates a new :class:`.RemoteDigiMeshDevice` with the provided parameters. - - Args: - local_xbee_device (:class:`.XBeeDevice`): the local XBee device associated with the remote one. - x64bit_addr (:class:`.XBee64BitAddress`): the 64-bit address of the remote XBee device. - node_id (String, optional): the node identifier of the remote XBee device. Optional. - - Raises: - XBeeException: if the protocol of ``local_xbee_device`` is invalid. - All exceptions raised by :class:`.RemoteXBeeDevice` constructor. - - .. seealso:: - | :class:`RemoteXBeeDevice` - | :class:`XBee64BitAddress` - | :class:`XBeeDevice` - """ - if local_xbee_device.get_protocol() != XBeeProtocol.DIGI_MESH: - raise XBeeException("Invalid protocol.") - - super(RemoteDigiMeshDevice, self).__init__(local_xbee_device, x64bit_addr, None, node_id) - - def get_protocol(self): - """ - Override. - - .. seealso:: - | :meth:`.RemoteXBeeDevice.get_protocol` - """ - return XBeeProtocol.DIGI_MESH - - -class RemoteDigiPointDevice(RemoteXBeeDevice): - """ - This class represents a remote DigiPoint XBee device. - """ - - def __init__(self, local_xbee_device, x64bit_addr=None, node_id=None): - """ - Class constructor. Instantiates a new :class:`.RemoteDigiMeshDevice` with the provided parameters. - - Args: - local_xbee_device (:class:`.XBeeDevice`): the local XBee device associated with the remote one. - x64bit_addr (:class:`.XBee64BitAddress`): the 64-bit address of the remote XBee device. - node_id (String, optional): the node identifier of the remote XBee device. Optional. - - Raises: - XBeeException: if the protocol of ``local_xbee_device`` is invalid. - All exceptions raised by :class:`.RemoteXBeeDevice` constructor. - - .. seealso:: - | :class:`RemoteXBeeDevice` - | :class:`XBee64BitAddress` - | :class:`XBeeDevice` - """ - if local_xbee_device.get_protocol() != XBeeProtocol.DIGI_POINT: - raise XBeeException("Invalid protocol.") - - super(RemoteDigiPointDevice, self).__init__(local_xbee_device, x64bit_addr, None, node_id) - - def get_protocol(self): - """ - Override. - - .. seealso:: - | :meth:`.RemoteXBeeDevice.get_protocol` - """ - return XBeeProtocol.DIGI_POINT - - -class RemoteZigBeeDevice(RemoteXBeeDevice): - """ - This class represents a remote ZigBee XBee device. - """ - - def __init__(self, local_xbee_device, x64bit_addr=None, x16bit_addr=None, node_id=None): - """ - Class constructor. Instantiates a new :class:`.RemoteDigiMeshDevice` with the provided parameters. - - Args: - local_xbee_device (:class:`.XBeeDevice`): the local XBee device associated with the remote one. - x64bit_addr (:class:`.XBee64BitAddress`): the 64-bit address of the remote XBee device. - x16bit_addr (:class:`.XBee16BitAddress`): the 16-bit address of the remote XBee device. - node_id (String, optional): the node identifier of the remote XBee device. Optional. - - Raises: - XBeeException: if the protocol of ``local_xbee_device`` is invalid. - All exceptions raised by :class:`.RemoteXBeeDevice` constructor. - - .. seealso:: - | :class:`RemoteXBeeDevice` - | :class:`XBee16BitAddress` - | :class:`XBee64BitAddress` - | :class:`XBeeDevice` - """ - if local_xbee_device.get_protocol() != XBeeProtocol.ZIGBEE: - raise XBeeException("Invalid protocol.") - - super(RemoteZigBeeDevice, self).__init__(local_xbee_device, x64bit_addr, x16bit_addr, node_id) - - def get_protocol(self): - """ - Override. - - .. seealso:: - | :meth:`.RemoteXBeeDevice.get_protocol` - """ - return XBeeProtocol.ZIGBEE - - def get_ai_status(self): - """ - Override. - - .. seealso:: - | :meth:`.AbstractXBeeDevice._get_ai_status` - """ - return super(RemoteZigBeeDevice, self)._get_ai_status() - - def force_disassociate(self): - """ - Override. - - .. seealso:: - | :meth:`.AbstractXBeeDevice._force_disassociate` - """ - super(RemoteZigBeeDevice, self)._force_disassociate() - - -class XBeeNetwork(object): - """ - This class represents an XBee Network. - - The network allows the discovery of remote devices in the same network - as the local one and stores them. - """ - - ND_PACKET_FINISH = 0x01 - """ - Flag that indicates a "discovery process finish" packet. - """ - - ND_PACKET_REMOTE = 0x02 - """ - Flag that indicates a discovery process packet with info about a remote XBee device. - """ - - # Default timeout for discovering process in case of - # the real timeout can't be determined. - __DEFAULT_DISCOVERY_TIMEOUT = 20 - - # Correction values for the timeout for determined devices. - # It has been tested and work 'fine' - __DIGI_MESH_TIMEOUT_CORRECTION = 3 - __DIGI_MESH_SLEEP_TIMEOUT_CORRECTION = 0.1 # DigiMesh with sleep support. - __DIGI_POINT_TIMEOUT_CORRECTION = 8 - - __NODE_DISCOVERY_COMMAND = "ND" - - def __init__(self, xbee_device): - """ - Class constructor. Instantiates a new ``XBeeNetwork``. - - Args: - xbee_device (:class:`.XBeeDevice`): the local XBee device to get the network from. - - Raises: - ValueError: if ``xbee_device`` is ``None``. - """ - if xbee_device is None: - raise ValueError("Local XBee device cannot be None") - - self.__xbee_device = xbee_device - self.__devices_list = [] - self.__last_search_dev_list = [] - self.__lock = threading.Lock() - self.__discovering = False - self.__device_discovered = DeviceDiscovered() - self.__device_discovery_finished = DiscoveryProcessFinished() - self.__discovery_thread = None - self.__sought_device_id = None - self.__discovered_device = None - - def start_discovery_process(self): - """ - Starts the discovery process. This method is not blocking. - - 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` - """ - with self.__lock: - if self.__discovering: - return - - self.__discovery_thread = threading.Thread(target=self.__discover_devices_and_notify_callbacks) - self.__discovering = True - self.__discovery_thread.start() - - def stop_discovery_process(self): - """ - Stops the discovery process if it is running. - - Note that DigiMesh/DigiPoint devices are blocked until the discovery - time configured (NT parameter) has elapsed, so if you try to get/set - any parameter during the discovery process you will receive a timeout - exception. - """ - if self.__discovering: - with self.__lock: - self.__discovering = False - - def discover_device(self, node_id): - """ - Blocking method. Discovers and reports the first remote XBee device that matches the - supplied identifier. - - Args: - node_id (String): the node identifier of the device to be discovered. - - Returns: - :class:`.RemoteXBeeDevice`: the discovered remote XBee device with the given identifier, - ``None`` if the timeout expires and the device was not found. - """ - try: - with self.__lock: - self.__sought_device_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) - return remote - - def discover_devices(self, device_id_list): - """ - Blocking method. Attempts to discover a list of devices and add them to the - current network. - - This method does not guarantee that all devices of ``device_id_list`` - will be found, even if they exist physically. This will depend on the node - discovery operation (``ND``) and timeout. - - Args: - device_id_list (List): list of device IDs to discover. - - Returns: - List: a list with the discovered devices. It may not contain all devices specified in ``device_id_list`` - """ - self.start_discovery_process() - while self.is_discovery_running(): - time.sleep(0.1) - return list(filter(lambda x: x.get_node_id() in device_id_list, self.__last_search_dev_list)) - - def is_discovery_running(self): - """ - Returns whether the discovery process is running or not. - - Returns: - Boolean: ``True`` if the discovery process is running, ``False`` otherwise. - """ - return self.__discovering - - def get_devices(self): - """ - Returns a copy of the XBee devices list of the network. - - If another XBee device is added to the list before the execution - of this method, this XBee device will not be added to the list returned - by this method. - - Returns: - List: a copy of the XBee devices list of the network. - """ - with self.__lock: - dl_copy = [len(self.__devices_list)] - dl_copy[:] = self.__devices_list[:] - return dl_copy - - def has_devices(self): - """ - Returns whether there is any device in the network or not. - - Returns: - Boolean: ``True`` if there is at least one device in the network, ``False`` otherwise. - """ - return len(self.__devices_list) > 0 - - def get_number_devices(self): - """ - Returns the number of devices in the network. - - Returns: - Integer: the number of devices in the network. - """ - return len(self.__devices_list) - - def add_device_discovered_callback(self, callback): - """ - Adds a callback for the event :class:`.DeviceDiscovered`. - - Args: - callback (Function): the callback. Receives one argument. - - * The discovered remote XBee device as a :class:`.RemoteXBeeDevice` - - .. seealso:: - | :meth:`.XBeeNetwork.del_device_discovered_callback` - | :meth:`.XBeeNetwork.add_discovery_process_finished_callback` - | :meth:`.XBeeNetwork.del_discovery_process_finished_callback` - """ - self.__device_discovered += callback - - def add_discovery_process_finished_callback(self, callback): - """ - Adds a callback for the event :class:`.DiscoveryProcessFinished`. - - Args: - callback (Function): the callback. Receives one argument. - - * The event code as an :class:`.Integer` - - .. seealso:: - | :meth:`.XBeeNetwork.del_discovery_process_finished_callback` - | :meth:`.XBeeNetwork.add_device_discovered_callback` - | :meth:`.XBeeNetwork.del_device_discovered_callback` - """ - self.__device_discovery_finished += callback - - def del_device_discovered_callback(self, callback): - """ - Deletes a callback for the callback list of :class:`.DeviceDiscovered` event. - - Args: - callback (Function): the callback to delete. - - Raises: - ValueError: if ``callback`` is not in the callback list of :class:`.DeviceDiscovered` event. - - .. seealso:: - | :meth:`.XBeeNetwork.add_device_discovered_callback` - | :meth:`.XBeeNetwork.add_discovery_process_finished_callback` - | :meth:`.XBeeNetwork.del_discovery_process_finished_callback` - """ - self.__device_discovered -= callback - - def del_discovery_process_finished_callback(self, callback): - """ - Deletes a callback for the callback list of :class:`.DiscoveryProcessFinished` event. - - Args: - callback (Function): the callback to delete. - - Raises: - ValueError: if ``callback`` is not in the callback list of :class:`.DiscoveryProcessFinished` event. - - .. seealso:: - | :meth:`.XBeeNetwork.add_discovery_process_finished_callback` - | :meth:`.XBeeNetwork.add_device_discovered_callback` - | :meth:`.XBeeNetwork.del_device_discovered_callback` - """ - self.__device_discovery_finished -= callback - - def clear(self): - """ - Removes all the remote XBee devices from the network. - """ - with self.__lock: - self.__devices_list = [] - - def get_discovery_options(self): - """ - Returns the network discovery process options. - - 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. - """ - return self.__xbee_device.get_parameter("NO") - - def set_discovery_options(self, options): - """ - Configures the discovery options (``NO`` parameter) with the given value. - - Args: - options (Set of :class:`.DiscoveryOptions`): new discovery options, empty set to clear the options. - - Raises: - ValueError: if ``options`` is ``None``. - 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. - - .. seealso:: - | :class:`.DiscoveryOptions` - """ - if options is None: - raise ValueError("Options cannot be None") - - 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): - """ - Returns the network discovery timeout. - - Returns: - Float: the network discovery timeout. - - 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. - """ - 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. - 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 ``discovery_timeout`` is not between 0x20 and 0xFF - """ - 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.__xbee_device.set_parameter("NT", timeout) - - def get_device_by_64(self, x64bit_addr): - """ - 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:`.RemoteXBeeDevice`: the remote XBee device in the network or ``None`` if it is not found. - - Raises: - ValueError: if ``x64bit_addr`` is ``None`` or unknown. - """ - if x64bit_addr is None: - raise ValueError("64-bit address cannot be None") - if x64bit_addr == XBee64BitAddress.UNKNOWN_ADDRESS: - raise ValueError("64-bit address cannot be unknown") - - with self.__lock: - for device in self.__devices_list: - if device.get_64bit_addr() is not None and device.get_64bit_addr() == x64bit_addr: - return device - - return None - - def get_device_by_16(self, x16bit_addr): - """ - 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:`.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.__xbee_device.get_protocol() == XBeeProtocol.DIGI_MESH: - raise ValueError("DigiMesh protocol does not support 16-bit addressing") - 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") - - with self.__lock: - for device in self.__devices_list: - if device.get_16bit_addr() is not None and device.get_16bit_addr() == x16bit_addr: - return device - - return None - - def get_device_by_node_id(self, node_id): - """ - 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:`.RemoteXBeeDevice`: the remote XBee device in the network or ``None`` if it is not found. - - Raises: - ValueError: if ``node_id`` is ``None``. - """ - if node_id is None: - raise ValueError("Node ID cannot be None") - - with self.__lock: - for device in self.__devices_list: - if device.get_node_id() is not None and device.get_node_id() == node_id: - return device - - return None - - def add_if_not_exist(self, x64bit_addr=None, x16bit_addr=None, node_id=None): - """ - Adds an XBee device with the provided parameters if it does not exist in the current network. - - If the XBee device already exists, its data will be updated with the provided parameters that are not ``None``. - - Args: - x64bit_addr (:class:`XBee64BitAddress`, optional): XBee device's 64bit address. Optional. - x16bit_addr (:class:`XBee16BitAddress`, optional): XBee device's 16bit address. Optional. - node_id (String, optional): the node identifier of the XBee device. Optional. - - Returns: - :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. - """ - remote = RemoteXBeeDevice(self.__xbee_device, x64bit_addr, x16bit_addr, node_id) - return self.add_remote(remote) - - def add_remote(self, remote_xbee_device): - """ - 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_device (:class:`.RemoteXBeeDevice`): the remote XBee device to add to the network. - - Returns: - :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. - """ - 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): - """ - Adds a list of remote XBee devices to the network. - - If any XBee device of the list 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_devices (List): the list of :class:`.RemoteXBeeDevice` to add to the network. - """ - for rem in remote_xbee_devices: - self.add_remote(rem) - - 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. - - Raises: - ValueError: if the provided :class:`.RemoteXBeeDevice` is not in the network. - """ - self.__devices_list.remove(remote_xbee_device) - - def get_discovery_callbacks(self): - """ - Returns the API callbacks that are used in the device discovery process. - - This callbacks notify the user callbacks for each XBee device discovered. - - Returns: - Tuple (Function, Function): callback for generic devices discovery process, - callback for discovery specific XBee device ops. - """ - def discovery_gen_callback(xbee_packet): - """ - Callback for generic devices discovery process. - """ - # if the discovering process is not running, stop. - if not self.__discovering: - return - # Check the 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 - with self.__lock: - self.__discovering = xbee_packet.status != ATCommandStatus.OK - elif nd_id == XBeeNetwork.ND_PACKET_REMOTE: - 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 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): - """ - This callback is used for discovery specific XBee device ops. - """ - # if __sought_device_id is None, exit (not searching XBee device). - if self.__sought_device_id is None: - return - # Check the 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 - if xbee_packet.status == ATCommandStatus.OK: - with self.__lock: - self.__sought_device_id = None - elif nd_id == XBeeNetwork.ND_PACKET_REMOTE: - # if it is not a finish signal, it contains info about a remote XBee device. - 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 - - return discovery_gen_callback, discovery_spec_callback - - def _get_discovery_thread(self): - """ - Returns the network discovery thread. - - Used to determine whether the discovery thread is alive or not. - - Returns: - :class:`.Thread`: the network discovery thread. - """ - return self.__discovery_thread - - @staticmethod - def __check_nd_packet(xbee_packet): - """ - Checks if the provided XBee packet is an ND response or not. If so, checks if is the 'end' signal - of the discovery process or if it has information about a remote XBee device. - - Returns: - Integer: the ID that indicates if the packet is a finish discovery signal or if it contains information - about a remote XBee device, or ``None`` if the ``xbee_packet`` is not a response for an ``ND`` command. - - * :attr:`.XBeeNetwork.ND_PACKET_FINISH`: if ``xbee_packet`` is an end signal. - * :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 == XBeeNetwork.__NODE_DISCOVERY_COMMAND): - if xbee_packet.command_value is None or len(xbee_packet.command_value) == 0: - return XBeeNetwork.ND_PACKET_FINISH - else: - return XBeeNetwork.ND_PACKET_REMOTE - else: - return None - - 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. - """ - self.__discover_devices() - self.__device_discovery_finished(NetworkDiscoveryStatus.SUCCESS) - - 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. Optional. - """ - try: - 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.__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) - - else: - # 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) - except Exception as e: - self.__xbee_device.log.exception(e) - finally: - with self.__lock: - self.__discovering = False - - def __is_802_compatible(self): - """ - Checks if the device performing the node discovery is a legacy - 802.15.4 device or a S1B device working in compatibility mode. - - Returns: - Boolean: ``True`` if the device performing the node discovery is a legacy - 802.15.4 device or S1B in compatibility mode, ``False`` otherwise. - - """ - if self.__xbee_device.get_protocol() != XBeeProtocol.RAW_802_15_4: - return False - param = None - try: - param = self.__xbee_device.get_parameter("C8") - except ATCommandException: - pass - if param is None or param[0] & 0x2 == 2: - return True - return False - - def __calculate_timeout(self): - """ - Determines the discovery timeout. - - Gets timeout information from the device and applies the proper - corrections to it. - - If the timeout cannot be determined getting it from the device, this - method returns the default timeout for discovery operations. - - Returns: - Float: discovery timeout in seconds. - """ - # Read the maximum discovery timeout (N?) - try: - discovery_timeout = utils.bytes_to_int(self.__xbee_device.get_parameter("N?")) / 1000 - except XBeeException: - discovery_timeout = None - - # If N? does not exist, read the NT parameter. - if discovery_timeout is None: - # Read the XBee device timeout (NT). - try: - discovery_timeout = utils.bytes_to_int(self.__xbee_device.get_parameter("NT")) / 10 - except XBeeException as xe: - discovery_timeout = XBeeNetwork.__DEFAULT_DISCOVERY_TIMEOUT - 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.__xbee_device.get_protocol() == XBeeProtocol.DIGI_MESH: - discovery_timeout += XBeeNetwork.__DIGI_MESH_TIMEOUT_CORRECTION - elif self.__xbee_device.get_protocol() == XBeeProtocol.DIGI_POINT: - discovery_timeout += XBeeNetwork.__DIGI_POINT_TIMEOUT_CORRECTION - - 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.__xbee_device.get_parameter("SM")) == 7: - discovery_timeout += discovery_timeout + \ - (discovery_timeout * XBeeNetwork.__DIGI_MESH_SLEEP_TIMEOUT_CORRECTION) - except XBeeException as xe: - self.__xbee_device.log.exception(xe) - - return discovery_timeout - - 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. - - 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:: - | :meth:`.XBeeNetwork.__get_data_for_remote` - """ - if discovery_data is None: - return None - p = self.__xbee_device.get_protocol() - x16bit_addr, x64bit_addr, node_id = self.__get_data_for_remote(discovery_data) - - if p == XBeeProtocol.ZIGBEE: - return RemoteZigBeeDevice(self.__xbee_device, x64bit_addr, x16bit_addr, node_id) - elif p == XBeeProtocol.DIGI_MESH: - return RemoteDigiMeshDevice(self.__xbee_device, x64bit_addr, node_id) - elif p == XBeeProtocol.DIGI_POINT: - return RemoteDigiPointDevice(self.__xbee_device, x64bit_addr, node_id) - elif p == XBeeProtocol.RAW_802_15_4: - return RemoteRaw802Device(self.__xbee_device, x64bit_addr, x16bit_addr, node_id) - else: - return RemoteXBeeDevice(self.__xbee_device, x64bit_addr, x16bit_addr, node_id) - - def __get_data_for_remote(self, data): - """ - Extracts the :class:`.XBee16BitAddress` (bytes 0 and 1), the - :class:`.XBee64BitAddress` (bytes 2 to 9) and the node identifier - from the provided data. - - Args: - data (Bytearray): the data to extract information from. - - Returns: - Tuple (:class:`.XBee16BitAddress`, :class:`.XBee64BitAddress`, Bytearray): remote device information - """ - 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 - # node ID goes from 11 to the next 0x00. - while data[i] != 0x00: - i += 1 - node_id = data[11:i] - else: - # node ID starts at 10 if protocol is not 802.15.4 - i = 10 - # node id goes from 'i' to the next 0x00. - while data[i] != 0x00: - i += 1 - node_id = data[10:i] - return XBee16BitAddress(data[0:2]), XBee64BitAddress(data[2:10]), node_id.decode() - - -class ZigBeeNetwork(XBeeNetwork): - """ - This class represents a ZigBee 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 ``ZigBeeNetwork``. - - Args: - device (:class:`.ZigBeeDevice`): the local ZigBee device to get the network from. - - Raises: - ValueError: if ``device`` is ``None``. - """ - super(ZigBeeNetwork, self).__init__(device) - - -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(Raw802Network, self).__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(DigiMeshNetwork, self).__init__(device) - - -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``. - - Args: - device (:class:`.DigiPointDevice`): the local DigiPoint device to get the network from. - - Raises: - ValueError: if ``device`` is ``None``. - """ - super(DigiPointNetwork, self).__init__(device) diff --git a/digi/xbee/exception.py b/digi/xbee/exception.py deleted file mode 100644 index 1130d25..0000000 --- a/digi/xbee/exception.py +++ /dev/null @@ -1,162 +0,0 @@ -# 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. - - -class XBeeException(Exception): - """ - Generic XBee API exception. This class and its subclasses indicate - conditions that an application might want to catch. - - All functionality of this class is the inherited of `Exception - `_. - """ - pass - - -class CommunicationException(XBeeException): - """ - This exception will be thrown when any problem related to the communication - with the XBee device occurs. - - All functionality of this class is the inherited of `Exception - `_. - """ - pass - - -class ATCommandException(CommunicationException): - """ - This exception will be thrown when a response of a packet is not success or OK. - - All functionality of this class is the inherited of `Exception - `_. - """ - pass - - -class ConnectionException(XBeeException): - """ - This exception will be thrown when any problem related to the connection - with the XBee device occurs. - - All functionality of this class is the inherited of `Exception - `_. - """ - pass - - -class XBeeDeviceException(XBeeException): - """ - This exception will be thrown when any problem related to the XBee device - occurs. - - All functionality of this class is the inherited of `Exception - `_. - """ - pass - - -class InvalidConfigurationException(ConnectionException): - """ - 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 - `_. - """ - __DEFAULT_MESSAGE = "The configuration used to open the interface is invalid." - - def __init__(self, message=__DEFAULT_MESSAGE): - ConnectionException.__init__(self, message) - - -class InvalidOperatingModeException(ConnectionException): - """ - This exception will be thrown if the operating mode is different than - *OperatingMode.API_MODE* and *OperatingMode.API_MODE* - - All functionality of this class is the inherited of `Exception - `_. - """ - __DEFAULT_MESSAGE = "The operating mode of the XBee device is not supported by the library." - - def __init__(self, message=__DEFAULT_MESSAGE): - ConnectionException.__init__(self, message) - - @classmethod - def from_operating_mode(cls, operating_mode): - """ - Class constructor. - - Args: - operating_mode (:class:`.OperatingMode`): the operating mode that generates the exceptions. - """ - return cls("Unsupported operating mode: " + operating_mode.description) - - -class InvalidPacketException(CommunicationException): - """ - This exception will be thrown when there is an error parsing an API packet - from the input stream. - - All functionality of this class is the inherited of `Exception - `_. - """ - __DEFAULT_MESSAGE = "The XBee API packet is not properly formed." - - def __init__(self, message=__DEFAULT_MESSAGE): - CommunicationException.__init__(self, message) - - -class OperationNotSupportedException(XBeeDeviceException): - """ - This exception will be thrown when the operation performed is not supported - by the XBee device. - - 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." - - def __init__(self, message=__DEFAULT_MESSAGE): - XBeeDeviceException.__init__(self, message) - - -class TimeoutException(CommunicationException): - """ - This exception will be thrown when performing synchronous operations and - the configured time expires. - - All functionality of this class is the inherited of `Exception - `_. - """ - __DEFAULT_MESSAGE = "There was a timeout while executing the requested operation." - - def __init__(self, _message=__DEFAULT_MESSAGE): - CommunicationException.__init__(self) - - -class TransmitException(CommunicationException): - """ - This exception will be thrown when receiving a transmit status different - than *TransmitStatus.SUCCESS* after sending an XBee API packet. - - All functionality of this class is the inherited of `Exception - `_. - """ - __DEFAULT_MESSAGE = "There was a problem with a transmitted packet response (status not ok)" - - def __init__(self, _message=__DEFAULT_MESSAGE): - CommunicationException.__init__(self, _message) diff --git a/digi/xbee/io.py b/digi/xbee/io.py deleted file mode 100644 index 966d0c3..0000000 --- a/digi/xbee/io.py +++ /dev/null @@ -1,647 +0,0 @@ -# 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 -# 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.util import utils -from enum import Enum, unique -from digi.xbee.exception import OperationNotSupportedException - - -@unique -class IOLine(Enum): - """ - Enumerates the different IO lines that can be found in the XBee devices. - - Depending on the hardware and firmware of the device, the number of lines - that can be used as well as their functionality may vary. Refer to the - product manual to learn more about the IO lines of your XBee device. - """ - - DIO0_AD0 = ("DIO0/AD0", 0, "D0") - DIO1_AD1 = ("DIO1/AD1", 1, "D1") - DIO2_AD2 = ("DIO2/AD2", 2, "D2") - DIO3_AD3 = ("DIO3/AD3", 3, "D3") - DIO4_AD4 = ("DIO4/AD4", 4, "D4") - DIO5_AD5 = ("DIO5/AD5", 5, "D5") - DIO6 = ("DIO6", 6, "D6") - DIO7 = ("DIO7", 7, "D7") - DIO8 = ("DIO8", 8, "D8") - DIO9 = ("DIO9", 9, "D9") - DIO10_PWM0 = ("DIO10/PWM0", 10, "P0", "M0") - DIO11_PWM1 = ("DIO11/PWM1", 11, "P1", "M1") - DIO12 = ("DIO12", 12, "P2") - DIO13 = ("DIO13", 13, "P3") - DIO14 = ("DIO14", 14, "P4") - DIO15 = ("DIO15", 15, "P5") - DIO16 = ("DIO16", 16, "P6") - DIO17 = ("DIO17", 17, "P7") - DIO18 = ("DIO18", 18, "P8") - DIO19 = ("DIO19", 19, "P9") - - def __init__(self, description, index, at_command, pwm_command=None): - self.__description = description - self.__index = index - self.__at_command = at_command - self.__pwm_command = pwm_command - - def __get_description(self): - """ - Returns the description of the IOLine element. - - Returns: - String: the description of the IOLine element. - """ - return self.__description - - def __get_index(self): - """ - Returns the index of the IOLine element. - - Returns: - Integer: the index of the IOLine element. - """ - return self.__index - - def __get_at_command(self): - """ - Returns the AT command of the IOLine element. - - Returns: - String: the AT command of the IOLine element. - """ - return self.__at_command - - def __get_pwm_command(self): - """ - Returns the PWM AT command associated to the IOLine element. - - Returns: - String: the PWM AT command associated to the IO line, ``None`` if the IO line does not have a PWM - AT command associated. - """ - return self.__pwm_command - - def has_pwm_capability(self): - """ - Returns whether the IO line has PWM capability or not. - - Returns: - Boolean: ``True`` if the IO line has PWM capability, ``False`` otherwise. - """ - return self.__pwm_command is not None - - @classmethod - def get(cls, index): - """ - Returns the :class:`.IOLine` for the given index. - - Args: - index (Integer): Returns the :class:`.IOLine` for the given index. - - Returns: - :class:`.IOLine`: :class:`.IOLine` with the given code, ``None`` if there is not any line with that index. - """ - try: - return cls.lookupTable[index] - except KeyError: - return None - - description = property(__get_description) - """String. The IO line description.""" - - index = property(__get_index) - """Integer. The IO line index.""" - - at_command = property(__get_at_command) - """String. The IO line AT command.""" - - pwm_at_command = property(__get_pwm_command) - """String. The IO line PWM AT command.""" - - -IOLine.lookupTable = {x.index: x for x in IOLine} -IOLine.__doc__ += utils.doc_enum(IOLine) - - -@unique -class IOValue(Enum): - """ - Enumerates the possible values of a :class:`.IOLine` configured as digital I/O. - """ - - LOW = 4 - HIGH = 5 - - def __init__(self, code): - self.__code = code - - def __get_code(self): - """ - Returns the code of the IOValue element. - - Returns: - String: the code of the IOValue element. - """ - return self.__code - - @classmethod - def get(cls, code): - """ - Returns the IOValue for the given code. - - Args: - code (Integer): the code corresponding to the IOValue to get. - - Returns: - :class:`.IOValue`: the IOValue with the given code, ``None`` if there is not any IOValue with that code. - """ - try: - return cls.lookupTable[code] - except KeyError: - return None - - code = property(__get_code) - """Integer. The IO value code.""" - - -IOValue.lookupTable = {x.code: x for x in IOValue} - - -class IOSample(object): - """ - This class represents an IO Data Sample. The sample is built using the - the constructor. The sample contains an analog and digital mask indicating - which IO lines are configured with that functionality. - - Depending on the protocol the XBee device is executing, the digital and - analog masks are retrieved in separated bytes (2 bytes for the digital mask - and 1 for the analog mask) or merged contained (digital and analog masks - are contained in 2 bytes). - - Digital and analog channels masks - Indicates which digital and ADC IO lines are configured in the module. Each - bit corresponds to one digital or ADC IO line on the module: - :: - - bit 0 = DIO01 - bit 1 = DIO10 - bit 2 = DIO20 - bit 3 = DIO31 - bit 4 = DIO40 - bit 5 = DIO51 - bit 6 = DIO60 - bit 7 = DIO70 - bit 8 = DIO80 - bit 9 = AD00 - bit 10 = AD11 - bit 11 = AD21 - bit 12 = AD30 - bit 13 = AD40 - bit 14 = AD50 - bit 15 = NA0 - - Example: mask of 0x0C29 means DIO0, DIO3, DIO5, AD1 and AD2 enabled. - 0 0 0 0 1 1 0 0 0 0 1 0 1 0 0 1 - - Digital Channel Mask - Indicates which digital IO lines are configured in the module. Each bit - corresponds to one digital IO line on the module: - :: - - bit 0 = DIO0AD0 - bit 1 = DIO1AD1 - bit 2 = DIO2AD2 - bit 3 = DIO3AD3 - bit 4 = DIO4AD4 - bit 5 = DIO5AD5ASSOC - bit 6 = DIO6RTS - bit 7 = DIO7CTS - bit 8 = DIO8DTRSLEEP_RQ - bit 9 = DIO9ON_SLEEP - bit 10 = DIO10PWM0RSSI - bit 11 = DIO11PWM1 - bit 12 = DIO12CD - bit 13 = DIO13 - bit 14 = DIO14 - bit 15 = NA - - Example: mask of 0x040B means DIO0, DIO1, DIO2, DIO3 and DIO10 enabled. - 0 0 0 0 0 1 0 0 0 0 0 0 1 0 1 1 - - Analog Channel Mask - Indicates which lines are configured as ADC. Each bit in the analog - channel mask corresponds to one ADC line on the module. - :: - - bit 0 = AD0DIO0 - bit 1 = AD1DIO1 - bit 2 = AD2DIO2 - bit 3 = AD3DIO3 - bit 4 = AD4DIO4 - bit 5 = AD5DIO5ASSOC - bit 6 = NA - bit 7 = Supply Voltage Value - - Example: mask of 0x83 means AD0, and AD1 enabled. - 0 0 0 0 0 0 1 1 - """ - - __pattern = "[{key}: {value}], " - """Pattern for digital and analog values in __str__ method.""" - - __pattern2 = "[Power supply voltage: {value}], " - """Pattern for power supply voltage in __str__ method.""" - - __MIN_IO_SAMPLE_PAYLOAD_LENGTH = 5 - - def __init__(self, io_sample_payload): - """ - Class constructor. Instantiates a new :class:`.IOSample` object with the provided parameters. - - Args: - io_sample_payload (Bytearray): The payload corresponding to an IO sample. - - Raises: - ValueError: if io_sample_payload length is less than 5. - """ - # dictionaries - self.__digital_values_map = {} # {IOLine : IOValue} - self.__analog_values_map = {} # {IOLine : Integer} - - # Integers: - self.__digital_hsb_mask = None - self.__digital_lsb_mask = None - self.__digital_mask = None - self.__analog_mask = None - self.__digital_hsb_values = None - self.__digital_lsb_values = None - self.__digital_values = None - self.__power_supply_voltage = None - - if len(io_sample_payload) < IOSample.__MIN_IO_SAMPLE_PAYLOAD_LENGTH: - raise ValueError("IO sample payload must be longer than 4.") - - self.__io_sample_payload = io_sample_payload - - if len(self.__io_sample_payload) % 2 != 0: - self.__parse_raw_io_sample() - else: - self.__parse_io_sample() - - def __str__(self): - s = "{" - if self.has_digital_values(): - s += (''.join([self.__pattern.format(key=x, value=self.__digital_values_map[x]) for x in - self.__digital_values_map.keys()])) - if self.has_analog_values(): - s += (''.join([self.__pattern.format(key=x, value=self.__analog_values_map[x]) for x in - self.__analog_values_map.keys()])) - if self.has_power_supply_value(): - try: - s += self.__pattern2.format(value=self.__power_supply_voltage) - except OperationNotSupportedException: - pass - s += "}" - aux = s.replace(", }", "}") - return aux - - @staticmethod - def min_io_sample_payload(): - """ - Returns the minimum IO sample payload length. - - Returns: - Integer: the minimum IO sample payload length. - """ - return IOSample.__MIN_IO_SAMPLE_PAYLOAD_LENGTH - - def __parse_raw_io_sample(self): - """ - Parses the information contained in the IO sample bytes reading the - value of each configured DIO and ADC. - """ - data_index = 3 - - # Obtain the digital mask. # Available digital IOs in 802.15.4 - self.__digital_hsb_mask = self.__io_sample_payload[1] & 0x01 # 0 0 0 0 0 0 0 1 - self.__digital_lsb_mask = self.__io_sample_payload[2] & 0xFF # 1 1 1 1 1 1 1 1 - # Combine the masks. - self.__digital_mask = (self.__digital_hsb_mask << 8) + self.__digital_lsb_mask - # Obtain the analog mask. - self.__analog_mask = ((self.__io_sample_payload[1] << 8) # Available analog IOs in 802.15.4 - + self.__io_sample_payload[2]) & 0x7E00 # 0 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 - - # Read the digital values (if any). There are 9 possible digital lines in - # 802.15.4 protocol. The digital mask indicates if there is any digital - # line enabled to read its value. If 0, no digital values are received. - if self.__digital_mask > 0: - # Obtain the digital values. - self.__digital_hsb_values = self.__io_sample_payload[3] & 0x7F - self.__digital_lsb_values = self.__io_sample_payload[4] & 0xFF - # Combine the values. - self.__digital_values = (self.__digital_hsb_values << 8) + self.__digital_lsb_values - - for i in range(16): - if not utils.is_bit_enabled(self.__digital_mask, i): - continue - if utils.is_bit_enabled(self.__digital_values, i): - self.__digital_values_map[IOLine.get(i)] = IOValue.HIGH - else: - self.__digital_values_map[IOLine.get(i)] = IOValue.LOW - - # Increase the data index to read the analog values. - data_index += 2 - - # Read the analog values (if any). There are 6 possible analog lines. - # The analog mask indicates if there is any analog line enabled to read - # its value. If 0, no analog values are received. - adc_index = 9 - while (len(self.__io_sample_payload) - data_index) > 1 and adc_index < 16: - if not (utils.is_bit_enabled(self.__analog_mask, adc_index)): - adc_index += 1 - continue - - # 802.15.4 protocol does not provide power supply value, so get just the ADC data. - self.__analog_values_map[IOLine.get(adc_index - 9)] = \ - ((self.__io_sample_payload[data_index] & 0xFF) << 8) + (self.__io_sample_payload[data_index + 1] & 0xFF) - # Increase the data index to read the next analog values. - data_index += 2 - adc_index += 1 - - def __parse_io_sample(self): - """ - Parses the information contained in the IO sample bytes reading the - value of each configured DIO and ADC. - """ - data_index = 4 - - # Obtain the digital masks. # Available digital IOs - self.__digital_hsb_mask = self.__io_sample_payload[1] & 0x7F # 0 1 1 1 1 1 1 1 - self.__digital_lsb_mask = self.__io_sample_payload[2] & 0xFF # 1 1 1 1 1 1 1 1 - # Combine the masks. - self.__digital_mask = (self.__digital_hsb_mask << 8) + self.__digital_lsb_mask - # Obtain the analog mask. # Available analog IOs - self.__analog_mask = self.__io_sample_payload[3] & 0xBF # 1 0 1 1 1 1 1 1 - - # Read the digital values (if any). There are 16 possible digital lines. - # The digital mask indicates if there is any digital line enabled to read - # its value. If 0, no digital values are received. - if self.__digital_mask > 0: - # Obtain the digital values. - self.__digital_hsb_values = self.__io_sample_payload[4] & 0x7F - self.__digital_lsb_values = self.__io_sample_payload[5] & 0xFF - # Combine the values. - self.__digital_values = (self.__digital_hsb_values << 8) + self.__digital_lsb_values - - for i in range(16): - if not utils.is_bit_enabled(self.__digital_mask, i): - continue - if utils.is_bit_enabled(self.__digital_values, i): - self.__digital_values_map[IOLine.get(i)] = IOValue.HIGH - else: - self.__digital_values_map[IOLine.get(i)] = IOValue.LOW - # Increase the data index to read the analog values. - data_index += 2 - - # Read the analog values (if any). There are 6 possible analog lines. - # The analog mask indicates if there is any analog line enabled to read - # its value. If 0, no analog values are received. - adc_index = 0 - while (len(self.__io_sample_payload) - data_index) > 1 and adc_index < 8: - if not utils.is_bit_enabled(self.__analog_mask, adc_index): - adc_index += 1 - continue - # When analog index is 7, it means that the analog value corresponds to the power - # supply voltage, therefore this value should be stored in a different value. - if adc_index == 7: - self.__power_supply_voltage = ((self.__io_sample_payload[data_index] & 0xFF) << 8) + \ - (self.__io_sample_payload[data_index + 1] & 0xFF) - else: - self.__analog_values_map[IOLine.get(adc_index)] = \ - ((self.__io_sample_payload[data_index] & 0xFF) << 8) + \ - (self.__io_sample_payload[data_index + 1] & 0xFF) - # Increase the data index to read the next analog values. - data_index += 2 - adc_index += 1 - - def __get_digital_hsb_mask(self): - """ - Returns the High Significant Byte (HSB) of the digital mask. - - Returns: - Integer: the HSB of the digital mask. - """ - return self.__digital_hsb_mask - - def __get_digital_lsb_mask(self): - """ - Returns the Low Significant Byte (HSB) of the digital mask. - - Returns: - Integer: the LSB of the digital mask. - """ - return self.__digital_lsb_mask - - def __get_digital_mask(self): - """ - Returns the combined (HSB + LSB) of the digital mask. - - Returns: - Integer: the digital mask. - """ - return self.__digital_mask - - def __get_digital_values(self): - """ - Returns the digital values map. - - To verify if this sample contains a valid digital values, use the - method :meth:`.IOSample.has_digital_values`. - - Returns: - Dictionary: the digital values map. - """ - return self.__digital_values_map.copy() - - def __get_analog_mask(self): - """ - Returns the analog mas. - - Returns: - Integer: the analog mask. - """ - return self.__analog_mask - - def __get_analog_values(self): - """ - Returns the analog values map. - - To verify if this sample contains a valid analog values, use the - method :meth:`.IOSample.has_analog_values`. - - Returns: - Dictionary: the analog values map. - """ - return self.__analog_values_map.copy() - - def __get_power_supply_value(self): - """ - Returns the value of the power supply voltage. - - To verify if this sample contains the power supply voltage, use the - method :meth:`.IOSample.has_power_supply_value`. - - Returns: - Integer: the power supply value, ``None`` if the sample does not contain power supply value. - """ - return self.__power_supply_voltage if self.has_power_supply_value() else None - - def has_digital_values(self): - """ - Checks whether the IOSample has digital values or not. - - Returns: - Boolean: ``True`` if the sample has digital values, ``False`` otherwise. - """ - return len(self.__digital_values_map) > 0 - - def has_digital_value(self, io_line): - """ - Returns whether th IO sample contains a digital value for the provided IO line or not. - - Args: - io_line (:class:`IOLine`): The IO line to check if it has a digital value. - - Returns: - Boolean: ``True`` if the given IO line has a digital value, ``False`` otherwise. - """ - return io_line in self.__digital_values_map.keys() - - def has_analog_value(self, io_line): - """ - Returns whether the given IOLine has an analog value or not. - - Returns: - Boolean: ``True`` if the given IOLine has an analog value, ``False`` otherwise. - """ - return io_line in self.__analog_values_map.keys() - - def has_analog_values(self): - """ - Returns whether the {@code IOSample} has analog values or not. - - Returns: - Boolean. ``True`` if there are analog values, ``False`` otherwise. - """ - return len(self.__analog_values_map) > 0 - - def has_power_supply_value(self): - """ - Returns whether the IOSample has power supply value or not. - - Returns: - Boolean. ``True`` if the given IOLine has a power supply value, ``False`` otherwise. - """ - return utils.is_bit_enabled(self.__analog_mask, 7) - - def get_digital_value(self, io_line): - """ - Returns the digital value of the provided IO line. - - To verify if this sample contains a digital value for the given :class:`.IOLine`, - use the method :meth:`.IOSample.has_digital_value`. - - Args: - io_line (:class:`.IOLine`): The IO line to get its digital value. - - Returns: - :class:`.IOValue`: The :class:`.IOValue` of the given IO line or ``None`` if the - IO sample does not contain a digital value for the given IO line. - - .. seealso:: - | :class:`.IOLine` - | :class:`.IOValue` - """ - if io_line in self.__digital_values_map: - return self.__digital_values_map[io_line] - return None - - def get_analog_value(self, io_line): - """ - Returns the analog value of the provided IO line. - - To verify if this sample contains an analog value for the given :class:`.IOLine`, - use the method :meth:`.IOSample.has_analog_value`. - - Args: - io_line (:class:`.IOLine`): The IO line to get its analog value. - - Returns: - Integer: The analog value of the given IO line or ``None`` if the IO sample does not - contain an analog value for the given IO line. - - .. seealso:: - | :class:`.IOLine` - """ - if io_line in self.__analog_values_map: - return self.__analog_values_map[io_line] - return None - - digital_hsb_mask = property(__get_digital_hsb_mask) - """Integer. High Significant Byte (HSB) of the digital mask.""" - - digital_lsb_mask = property(__get_digital_lsb_mask) - """Integer. Low Significant Byte (LSB) of the digital mask.""" - - digital_mask = property(__get_digital_mask) - """Integer. Digital mask of the IO sample.""" - - analog_mask = property(__get_analog_mask) - """Integer. Analog mask of the IO sample.""" - - digital_values = property(__get_digital_values) - """Dictionary. Digital values map.""" - - analog_values = property(__get_analog_values) - """Dictionary. Analog values map.""" - - power_supply_value = property(__get_power_supply_value) - """Integer. Power supply value, ``None`` if the sample does not contain power supply value.""" - - -class IOMode(Enum): - """ - Enumerates the different Input/Output modes that an IO line can be - configured with. - """ - - DISABLED = 0 - """Disabled""" - - SPECIAL_FUNCTIONALITY = 1 - """Firmware special functionality""" - - PWM = 2 - """PWM output""" - - ADC = 2 - """Analog to Digital Converter""" - - DIGITAL_IN = 3 - """Digital input""" - - DIGITAL_OUT_LOW = 4 - """Digital output, Low""" - - DIGITAL_OUT_HIGH = 5 - """Digital output, High""" diff --git a/digi/xbee/models/__init__.py b/digi/xbee/models/__init__.py deleted file mode 100644 index 8ea10d3..0000000 --- a/digi/xbee/models/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -# 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. diff --git a/digi/xbee/models/accesspoint.py b/digi/xbee/models/accesspoint.py deleted file mode 100644 index 60a7fee..0000000 --- a/digi/xbee/models/accesspoint.py +++ /dev/null @@ -1,224 +0,0 @@ -# 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 enum import Enum, unique -from digi.xbee.util import utils - - -class AccessPoint(object): - """ - This class represents an Access Point for the Wi-Fi protocol. It contains - SSID, the encryption type and the link quality between the Wi-Fi module and - the access point. - - This class is used within the library to list the access points - and connect to a specific one in the Wi-Fi protocol. - - .. seealso:: - | :class:`.WiFiEncryptionType` - """ - - __ERROR_CHANNEL = "Channel cannot be negative." - __ERROR_SIGNAL_QUALITY = "Signal quality must be between 0 and 100." - - def __init__(self, ssid, encryption_type, channel=0, signal_quality=0): - """ - Class constructor. Instantiates a new :class:`.AccessPoint` object with the provided parameters. - - Args: - ssid (String): the SSID of the access point. - encryption_type (:class:`.WiFiEncryptionType`): the encryption type configured in the access point. - channel (Integer, optional): operating channel of the access point. Optional. - signal_quality (Integer, optional): signal quality with the access point in %. Optional. - - Raises: - ValueError: if length of ``ssid`` is 0. - ValueError: if ``channel`` is less than 0. - ValueError: if ``signal_quality`` is less than 0 or greater than 100. - - .. seealso:: - | :class:`.WiFiEncryptionType` - """ - if len(ssid) == 0: - raise ValueError("SSID cannot be empty.") - if channel < 0: - raise ValueError(self.__ERROR_CHANNEL) - if signal_quality < 0 or signal_quality > 100: - raise ValueError(self.__ERROR_SIGNAL_QUALITY) - - self.__ssid = ssid - self.__encryption_type = encryption_type - self.__channel = channel - self.__signal_quality = signal_quality - - def __str__(self): - """ - Returns the string representation of the access point. - - Returns: - String: representation of the access point. - """ - return "%s (%s) - CH: %s - Signal: %s%%" % (self.__ssid, self.__encryption_type.description, - self.__channel, self.__signal_quality) - - def __get_ssid(self): - """ - Returns the SSID of the access point. - - Returns: - String: the SSID of the access point. - """ - return self.__ssid - - def __get_encryption_type(self): - """ - Returns the encryption type of the access point. - - Returns: - :class:`.WiFiEncryptionType`: the encryption type of the access point. - - .. seealso:: - | :class:`.WiFiEncryptionType` - """ - return self.__encryption_type - - def __get_channel(self): - """ - Returns the channel of the access point. - - Returns: - Integer: the channel of the access point. - - .. seealso:: - | :func:`.AccessPoint.set_channel` - """ - return self.__channel - - def __set_channel(self, channel): - """ - Sets the channel of the access point. - - Args: - channel (Integer): the new channel of the access point - - Raises: - ValueError: if ``channel`` is less than 0. - - .. seealso:: - | :func:`.AccessPoint.get_channel` - """ - if channel < 0: - raise ValueError(self.__ERROR_CHANNEL) - self.__channel = channel - - def __get_signal_quality(self): - """ - Returns the signal quality with the access point in %. - - Returns: - Integer: the signal quality with the access point in %. - - .. seealso:: - | :func:`.AccessPoint.__set_signal_quality` - """ - return self.__signal_quality - - def __set_signal_quality(self, signal_quality): - """ - Sets the channel of the access point. - - Args: - signal_quality (Integer): the new signal quality with the access point. - - Raises: - ValueError: if ``signal_quality`` is less than 0 or greater than 100. - - .. seealso:: - | :func:`.AccessPoint.__get_signal_quality` - """ - if signal_quality < 0 or signal_quality > 100: - raise ValueError(self.__ERROR_SIGNAL_QUALITY) - self.__signal_quality = signal_quality - - ssid = property(__get_ssid) - """String. SSID of the access point.""" - - encryption_type = property(__get_encryption_type) - """:class:`.WiFiEncryptionType`. Encryption type of the access point.""" - - channel = property(__get_channel, __set_channel) - """String. Channel of the access point.""" - - signal_quality = property(__get_signal_quality, __set_signal_quality) - """String. The signal quality with the access point in %.""" - - -@unique -class WiFiEncryptionType(Enum): - """ - Enumerates the different Wi-Fi encryption types. - """ - NONE = (0, "No security") - WPA = (1, "WPA (TKIP) security") - WPA2 = (2, "WPA2 (AES) security") - WEP = (3, "WEP security") - - def __init__(self, code, description): - self.__code = code - self.__description = description - - def __get_code(self): - """ - Returns the code of the WiFiEncryptionType element. - - Returns: - Integer: the code of the WiFiEncryptionType element. - """ - return self.__code - - def __get_description(self): - """ - Returns the description of the WiFiEncryptionType element. - - Returns: - String: the description of the WiFiEncryptionType element. - """ - return self.__description - - @classmethod - def get(cls, code): - """ - Returns the Wi-Fi encryption type for the given code. - - Args: - code (Integer): the code of the Wi-Fi encryption type to get. - - Returns: - :class:`.WiFiEncryptionType`: the WiFiEncryptionType with the given code, ``None`` if there is - not any Wi-Fi encryption type with the provided code. - """ - try: - return cls.lookupTable[code] - except KeyError: - return None - - code = property(__get_code) - """Integer. The Wi-Fi encryption type code.""" - - description = property(__get_description) - """String. The Wi-Fi encryption type description.""" - - -WiFiEncryptionType.lookupTable = {x.code: x for x in WiFiEncryptionType} -WiFiEncryptionType.__doc__ += utils.doc_enum(WiFiEncryptionType) diff --git a/digi/xbee/models/address.py b/digi/xbee/models/address.py deleted file mode 100644 index 628a423..0000000 --- a/digi/xbee/models/address.py +++ /dev/null @@ -1,442 +0,0 @@ -# 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. - -import re -from digi.xbee.util import utils - - -class XBee16BitAddress(object): - """ - This class represent a 16-bit network address. - - This address is only applicable for: - - 1. 802.15.4 - 2. ZigBee - 3. ZNet 2.5 - 4. XTend (Legacy) - - DigiMesh and Point-to-multipoint does not support 16-bit addressing. - - Each device has its own 16-bit address which is unique in the network. - It is automatically assigned when the radio joins the network for ZigBee - and Znet 2.5, and manually configured in 802.15.4 radios. - - | Attributes: - | **COORDINATOR_ADDRESS** (XBee16BitAddress): 16-bit address reserved for the coordinator. - | **BROADCAST_ADDRESS** (XBee16BitAddress): 16-bit broadcast address. - | **UNKNOWN_ADDRESS** (XBee16BitAddress): 16-bit unknown address. - | **PATTERN** (String): Pattern for the 16-bit address string: ``(0[xX])?[0-9a-fA-F]{1,4}`` - - """ - - PATTERN = "(0[xX])?[0-9a-fA-F]{1,4}" - __REGEXP = re.compile(PATTERN) - - COORDINATOR_ADDRESS = None - """ - 16-bit address reserved for the coordinator (value: 0000). - """ - - BROADCAST_ADDRESS = None - """ - 16-bit broadcast address (value: FFFF). - """ - - UNKNOWN_ADDRESS = None - """ - 16-bit unknown address (value: FFFE). - """ - - def __init__(self, address): - """ - Class constructor. Instantiates a new :class:`.XBee16BitAddress` object with the provided parameters. - - Args: - address (Bytearray): address as byte array. Must be 1-2 digits. - - Raises: - TypeError: if ``address`` is ``None``. - ValueError: if ``address`` has less than 1 byte or more than 2. - """ - 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") - - if len(address) == 1: - address.insert(0, 0) - self.__address = address - - @classmethod - def from_hex_string(cls, address): - """ - Class constructor. Instantiates a new :`.XBee16BitAddress` object from the provided hex string. - - Args: - address (String): String containing the address. Must be made by - hex. digits without blanks. Minimum 1 character, maximum 4 (16-bit). - - Raises: - ValueError: if ``address`` has less than 1 character. - ValueError: if ``address`` contains non-hexadecimal characters. - """ - if len(address) < 1: - raise ValueError("Address must contain at least 1 digit") - if not XBee16BitAddress.__REGEXP.match(address): - raise ValueError("Address must match with PATTERN") - return cls(utils.hex_string_to_bytes(address)) - - @classmethod - def from_bytes(cls, hsb, lsb): - """ - Class constructor. Instantiates a new :`.XBee16BitAddress` object from the provided high significant byte and - low significant byte. - - Args: - hsb (Integer): high significant byte of the address. - lsb (Integer): low significant byte of the address. - - Raises: - ValueError: if ``lsb`` is less than 0 or greater than 255. - ValueError: if ``hsb`` is less than 0 or greater than 255. - """ - if hsb > 255 or hsb < 0: - 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])) - - def __get_item__(self, index): - """ - Operator [] - - Args: - index (Integer): index to be accessed. - - Returns: - Integer. 'index' component of the address bytearray. - """ - return self.__address.__get_item__(index) - - def __str__(self): - """ - Called by the str() built-in function and by the print statement to compute the "informal" string - representation of an object. This differs from __repr__() in that it does not have to be a valid Python - expression: a more convenient or concise representation may be used instead. - - Returns: - String: "informal" representation of this XBee16BitAddress. - """ - return utils.hex_to_string(self.__address) - - def __eq__(self, other): - """ - Operator == - - Args: - other (:class`.XBee16BitAddress`): another XBee16BitAddress object. - - 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): - """ - Gets an iterator class of this instance address. - - Returns: - Iterator: iterator of this address. - """ - return self.__address.__iter__() - - def get_hsb(self): - """ - Returns the high part of the bytearray (component 0). - - Returns: - Integer: high part of the bytearray. - """ - return self.__address[0] - - def get_lsb(self): - """ - Returns the low part of the bytearray (component 1). - - Returns: - Integer: low part of the bytearray. - """ - return self.__address[1] - - def __get_value(self): - """ - Returns a bytearray representation of this XBee16BitAddress. - - Returns: - Bytearray: bytearray representation of this XBee16BitAddress. - """ - return bytearray(self.__address) - - address = property(__get_value) - """Bytearray. Bytearray representation of this XBee16BitAddress.""" - - -XBee16BitAddress.COORDINATOR_ADDRESS = XBee16BitAddress.from_hex_string("0000") -XBee16BitAddress.BROADCAST_ADDRESS = XBee16BitAddress.from_hex_string("FFFF") -XBee16BitAddress.UNKNOWN_ADDRESS = XBee16BitAddress.from_hex_string("FFFE") - - -class XBee64BitAddress(object): - """ - This class represents a 64-bit address (also known as MAC address). - - The 64-bit address is a unique device address assigned during manufacturing. - This address is unique to each physical device. - """ - __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 - """ - 64-bit address reserved for the coordinator (value: 0000000000000000). - """ - - BROADCAST_ADDRESS = None - """ - 64-bit broadcast address (value: 000000000000FFFF). - """ - - UNKNOWN_ADDRESS = None - """ - 64-bit unknown address (value: FFFFFFFFFFFFFFFF). - """ - - def __init__(self, address): - """ - Class constructor. Instantiates a new :class:`.XBee64BitAddress` object with the provided parameters. - - Args: - address (Bytearray): the XBee 64-bit address as byte array. - - Raise: - ValueError: if length of ``address`` is less than 1 or greater than 8. - """ - 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") - - self.__address = bytearray(8) - diff = 8 - len(address) - for i in range(diff): - self.__address[i] = 0 - for i in range(diff, 8): - self.__address[i] = address[i - diff] - - @classmethod - def from_hex_string(cls, address): - """ - Class constructor. Instantiates a new :class:`.XBee64BitAddress` object from the provided hex string. - - Args: - address (String): The XBee 64-bit address as a string. - - Raises: - ValueError: if the address' length is less than 1 or does not match - with the pattern: ``(0[xX])?[0-9a-fA-F]{1,16}``. - """ - 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.__XBEE_64_BIT_ADDRESS_PATTERN) - - return cls(utils.hex_string_to_bytes(address)) - - @classmethod - def from_bytes(cls, *args): - """ - Class constructor. Instantiates a new :class:`.XBee64BitAddress` object from the provided bytes. - - Args: - args (8 Integers): 8 integers that represent the bytes 1 to 8 of this XBee64BitAddress. - - Raises: - ValueError: if the amount of arguments is not 8 or if any of the arguments is not between 0 and 255. - """ - if len(args) != 8: - raise ValueError("Number of bytes given as arguments must be 8.") - 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)) - - def __str__(self): - """ - Called by the str() built-in function and by the print statement to compute the "informal" string - representation of an object. This differs from __repr__() in that it does not have to be a valid Python - expression: a more convenient or concise representation may be used instead. - - Returns: - String: "informal" representation of this XBee64BitAddress. - """ - return "".join(["%02X" % i for i in self.__address]) - - def __eq__(self, other): - """ - Operator == - - Args: - other: another XBee64BitAddress. - - 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, XBee64BitAddress): - return False - return self.__address.__eq__(other.__address) - - def __iter__(self): - """ - Gets an iterator class of this instance address. - - Returns: - Iterator: iterator of this address. - """ - return self.__address.__iter__() - - def __get_value(self): - """ - Returns a bytearray representation of this XBee64BitAddress. - - Returns: - Bytearray: bytearray representation of this XBee64BitAddress. - """ - return bytearray(self.__address) - - address = property(__get_value) - """Bytearray. Bytearray representation of this XBee64BitAddress.""" - - -XBee64BitAddress.COORDINATOR_ADDRESS = XBee64BitAddress.from_hex_string("0000") -XBee64BitAddress.BROADCAST_ADDRESS = XBee64BitAddress.from_hex_string("FFFF") -XBee64BitAddress.UNKNOWN_ADDRESS = XBee64BitAddress.from_hex_string("F"*16) - - -class XBeeIMEIAddress(object): - """ - This class represents an IMEI address used by cellular devices. - - This address is only applicable for Cellular protocol. - """ - - __IMEI_PATTERN = "^\d{0,15}$" - __REGEXP = re.compile(__IMEI_PATTERN) - - def __init__(self, address): - """ - Class constructor. Instantiates a new :`.XBeeIMEIAddress` object with the provided parameters. - - Args: - address (Bytearray): The XBee IMEI address as byte array. - - Raises: - ValueError: if ``address`` is ``None``. - ValueError: if length of ``address`` greater than 8. - """ - if address is None: - raise ValueError("IMEI address cannot be None") - if len(address) > 8: - raise ValueError("IMEI address cannot be longer than 8 bytes") - - self.__address = self.__generate_byte_array(address) - - @classmethod - def from_string(cls, address): - """ - Class constructor. Instantiates a new :`.XBeeIMEIAddress` object from the provided string. - - Args: - address (String): The XBee IMEI address as a string. - - Raises: - ValueError: if ``address`` is ``None``. - ValueError: if ``address`` does not match the pattern: ``^\d{0,15}$``. - """ - 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.__IMEI_PATTERN) - - return cls(utils.hex_string_to_bytes(address)) - - @staticmethod - def __generate_byte_array(byte_address): - """ - Generates the IMEI byte address based on the given byte array. - - Args: - byte_address (Bytearray): the byte array used to generate the final IMEI byte address. - - Returns: - Bytearray: the IMEI in byte array format. - """ - return bytearray(8 - len(byte_address)) + byte_address # Pad zeros in the MSB of the address - - def __get_value(self): - """ - Returns a string representation of this XBeeIMEIAddress. - - Returns: - String: the IMEI address in string format. - """ - return "".join(["%02X" % i for i in self.__address])[1:] - - def __str__(self): - """ - Called by the str() built-in function and by the print statement to compute the "informal" string - representation of an object. This differs from __repr__() in that it does not have to be a valid Python - expression: a more convenient or concise representation may be used instead. - - Returns: - String: "informal" representation of this XBeeIMEIAddress. - """ - return self.__get_value() - - def __eq__(self, other): - """ - Operator == - - Args: - other (:class:`.XBeeIMEIAddress`): another XBeeIMEIAddress. - - 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, XBeeIMEIAddress): - return False - return self.__address.__eq__(other.__address) - - address = property(__get_value) - """String. String representation of this XBeeIMEIAddress.""" diff --git a/digi/xbee/models/atcomm.py b/digi/xbee/models/atcomm.py deleted file mode 100644 index 6f5a9c1..0000000 --- a/digi/xbee/models/atcomm.py +++ /dev/null @@ -1,273 +0,0 @@ -# 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 digi.xbee.models.status import ATCommandStatus -from enum import Enum, unique -from digi.xbee.util import utils - - -@unique -class ATStringCommand(Enum): - """ - This class represents basic AT commands. - - | Inherited properties: - | **name** (String): name (ID) of this ATStringCommand. - | **value** (String): value of this ATStringCommand. - """ - - NI = "NI" - KY = "KY" - NK = "NK" - ZU = "ZU" - ZV = "ZV" - CC = "CC" - - def __init__(self, command): - self.__command = command - - def __get_command(self): - return self.__command - - command = property(__get_command) - """String. AT Command alias.""" - - -ATStringCommand.__doc__ += utils.doc_enum(ATStringCommand) - - -@unique -class SpecialByte(Enum): - """ - Enumerates all the special bytes of the XBee protocol that must be escaped - when working on API 2 mode. - - | Inherited properties: - | **name** (String): name (ID) of this SpecialByte. - | **value** (String): the value of this SpecialByte. - """ - - ESCAPE_BYTE = 0x7D - HEADER_BYTE = 0x7E - XON_BYTE = 0x11 - XOFF_BYTE = 0x13 - - def __init__(self, code): - self.__code = code - - def __get_code(self): - """ - Returns the code of the SpecialByte element. - - Returns: - Integer: the code of the SpecialByte element. - """ - return self.__code - - @classmethod - def get(cls, value): - """ - Returns the special byte for the given value. - - Args: - value (Integer): value associated to the special byte. - - Returns: - SpecialByte: SpecialByte with the given value. - """ - return SpecialByte.lookupTable[value] - - @staticmethod - def escape(value): - """ - Escapes the byte by performing a XOR operation with 0x20 value. - - Args: - value (Integer): value to escape. - - Returns: - Integer: value ^ 0x20 (escaped). - """ - return value ^ 0x20 - - @staticmethod - def is_special_byte(value): - """ - Checks whether the given byte is special or not. - - Args: - value (Integer): byte to check. - - Returns: - Boolean: ``True`` if value is a special byte, ``False`` in other case. - """ - return True if value in [i.value for i in SpecialByte] else False - - code = property(__get_code) - """Integer. The special byte code.""" - - -SpecialByte.lookupTable = {x.code: x for x in SpecialByte} -SpecialByte.__doc__ += utils.doc_enum(SpecialByte) - - -class ATCommand(object): - """ - This class represents an AT command used to read or set different properties - of the XBee device. - - AT commands can be sent directly to the connected device or to remote - devices and may have parameters. - - After executing an AT Command, an AT Response is received from the device. - """ - - def __init__(self, command, parameter=None): - """ - Class constructor. Instantiates a new :class:`.ATCommand` object with the provided parameters. - - Args: - command (String): AT Command, must have length 2. - parameter (String or Bytearray, optional): The AT parameter value. Defaults to ``None``. Optional. - - Raises: - ValueError: if command length is not 2. - """ - if len(command) != 2: - raise ValueError("Command length must be 2.") - - self.__command = command - self.__set_parameter(parameter) - - def __str__(self): - """ - Returns a string representation of this ATCommand. - - Returns: - String: representation of this ATCommand. - """ - return "Command: " + self.__command + "\n" + "Parameter: " + str(self.__parameter) - - def __len__(self): - """ - Returns the length of this ATCommand. - - Returns: - Integer: length of command + length of parameter. - """ - if self.__parameter: - return len(self.__command) + len(self.__parameter) - else: - return len(self.__command) - - def __get_command(self): - """ - Returns the AT command. - - Returns: - ATCommand: the AT command. - """ - return self.__command - - def __get_parameter(self): - """ - Returns the AT command parameter. - - Returns: - Bytearray: the AT command parameter. ``None`` if there is no parameter. - """ - return self.__parameter - - def get_parameter_string(self): - """ - Returns this ATCommand parameter as a String. - - Returns: - String: this ATCommand parameter. ``None`` if there is no parameter. - """ - return self.__parameter.decode() if self.__parameter else None - - def __set_parameter(self, parameter): - """ - Sets the AT command parameter. - - Args: - parameter (Bytearray): the parameter to be set. - """ - if isinstance(parameter, str): - self.__parameter = bytearray(parameter, 'utf8') - else: - self.__parameter = parameter - - command = property(__get_command) - """String. The AT command""" - - parameter = property(__get_parameter, __set_parameter) - """Bytearray. The AT command parameter""" - - -class ATCommandResponse(object): - """ - This class represents the response of an AT Command sent by the connected - XBee device or by a remote device after executing an AT Command. - """ - - def __init__(self, command, response=None, status=ATCommandStatus.OK): - """ - Class constructor. - - Args: - command (ATCommand): The AT command that generated the response. - response (bytearray, optional): The command response. Default to ``None``. - status (ATCommandStatus, optional): The AT command status. Default to ATCommandStatus.OK - """ - self.__atCommand = command - self.__response = response - self.__comm_status = status - - def __get_command(self): - """ - Returns the AT command. - - Returns: - ATCommand: the AT command. - """ - return self.__atCommand - - def __get_response(self): - """ - Returns the AT command response. - - Returns: - Bytearray: the AT command response. - """ - return self.__response - - def __get_status(self): - """ - Returns the AT command response status. - - Returns: - ATCommandStatus: The AT command response status. - """ - return self.__comm_status - - command = property(__get_command) - """String. The AT command.""" - - response = property(__get_response) - """Bytearray. The AT command response data.""" - - status = property(__get_status) - """ATCommandStatus. The AT command response status.""" diff --git a/digi/xbee/models/hw.py b/digi/xbee/models/hw.py deleted file mode 100644 index 9d75562..0000000 --- a/digi/xbee/models/hw.py +++ /dev/null @@ -1,145 +0,0 @@ -# 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 enum import Enum, unique -from digi.xbee.util import utils - - -@unique -class HardwareVersion(Enum): - """ - This class lists all hardware versions. - - | Inherited properties: - | **name** (String): The name of this HardwareVersion. - | **value** (Integer): The ID of this HardwareVersion. - """ - X09_009 = (0x01, "X09-009") - X09_019 = (0x02, "X09-019") - XH9_009 = (0x03, "XH9-009") - XH9_019 = (0x04, "XH9-019") - X24_009 = (0x05, "X24-009") - X24_019 = (0x06, "X24-019") - X09_001 = (0x07, "X09-001") - XH9_001 = (0x08, "XH9-001") - X08_004 = (0x09, "X08-004") - XC09_009 = (0x0A, "XC09-009") - XC09_038 = (0x0B, "XC09-038") - X24_038 = (0x0C, "X24-038") - X09_009_TX = (0x0D, "X09-009-TX") - X09_019_TX = (0x0E, "X09-019-TX") - XH9_009_TX = (0x0F, "XH9-009-TX") - XH9_019_TX = (0x10, "XH9-019-TX") - X09_001_TX = (0x11, "X09-001-TX") - XH9_001_TX = (0x12, "XH9-001-TX") - XT09B_XXX = (0x13, "XT09B-xxx (Attenuator version)") - XT09_XXX = (0x14, "XT09-xxx") - XC08_009 = (0x15, "XC08-009") - XC08_038 = (0x16, "XC08-038") - XB24_AXX_XX = (0x17, "XB24-Axx-xx") - XBP24_AXX_XX = (0x18, "XBP24-Axx-xx") - XB24_BXIX_XXX = (0x19, "XB24-BxIx-xxx and XB24-Z7xx-xxx") - XBP24_BXIX_XXX = (0x1A, "XBP24-BxIx-xxx and XBP24-Z7xx-xxx") - XBP09_DXIX_XXX = (0x1B, "XBP09-DxIx-xxx Digi Mesh") - XBP09_XCXX_XXX = (0x1C, "XBP09-XCxx-xxx: S3 XSC Compatibility") - XBP08_DXXX_XXX = (0x1D, "XBP08-Dxx-xxx 868MHz") - XBP24B = (0x1E, "XBP24B: Low cost ZB PRO and PLUS S2B") - XB24_WF = (0x1F, "XB24-WF: XBee 802.11 (Redpine module)") - AMBER_MBUS = (0x20, "??????: M-Bus module made by Amber") - XBP24C = (0x21, "XBP24C: XBee PRO SMT Ember 357 S2C PRO") - XB24C = (0x22, "XB24C: XBee SMT Ember 357 S2C") - XSC_GEN3 = (0x23, "XSC_GEN3: XBP9 XSC 24 dBm") - SRD_868_GEN3 = (0x24, "SDR_868_GEN3: XB8 12 dBm") - ABANDONATED = (0x25, "Abandonated") - SMT_900LP = (0x26, "900LP (SMT): 900LP on 'S8 HW'") - WIFI_ATHEROS = (0x27, "WiFi Atheros (TH-DIP) XB2S-WF") - SMT_WIFI_ATHEROS = (0x28, "WiFi Atheros (SMT) XB2B-WF") - SMT_475LP = (0x29, "475LP (SMT): Beta 475MHz") - XBEE_CELL_TH = (0x2A, "XBee-Cell (TH): XBee Cellular") - XLR_MODULE = (0x2B, "XLR Module") - XB900HP_NZ = (0x2C, "XB900HP (New Zealand): XB9 NZ HW/SW") - XBP24C_TH_DIP = (0x2D, "XBP24C (TH-DIP): XBee PRO DIP") - XB24C_TH_DIP = (0x2E, "XB24C (TH-DIP): XBee DIP") - XLR_BASEBOARD = (0x2F, "XLR Baseboard") - XBP24C_S2C_SMT = (0x30, "XBee PRO SMT") - SX_PRO = (0x31, "SX Pro") - S2D_SMT_PRO = (0x32, "XBP24D: S2D SMT PRO") - S2D_SMT_REG = (0x33, "XB24D: S2D SMT Reg") - S2D_TH_PRO = (0x34, "XBP24D: S2D TH PRO") - S2D_TH_REG = (0x35, "XB24D: S2D TH Reg") - SX = (0x3E, "SX") - XTR = (0x3F, "XTR") - CELLULAR_CAT1_LTE_VERIZON = (0x40, "XBee Cellular Cat 1 LTE Verizon") - XBEE3 = (0x41, "XBEE3") - XBEE3_SMT = (0x42, "XBEE3 SMT") - XBEE3_TH = (0x43, "XBEE3 TH") - CELLULAR_3G = (0x44, "XBee Cellular 3G") - XB8X = (0x45, "XB8X") - CELLULAR_LTE_VERIZON = (0x46, "XBee Cellular LTE-M Verizon") - CELLULAR_LTE_ATT = (0x47, "XBee Cellular LTE-M AT&T") - CELLULAR_NBIOT_EUROPE = (0x48, "XBee Cellular NBIoT Europe") - CELLULAR_3_CAT1_LTE_ATT = (0x49, "XBee Cellular 3 Cat 1 LTE AT&T") - CELLULAR_3_LTE_M_VERIZON = (0x4A, "XBee Cellular 3 LTE-M Verizon") - CELLULAR_3_LTE_M_ATT = (0x4B, "XBee Cellular 3 LTE-M AT&T") - - def __init__(self, code, description): - self.__code = code - self.__description = description - - def __get_code(self): - """ - Returns the code of the HardwareVersion element. - - Returns: - Integer: the code of the HardwareVersion element. - """ - return self.__code - - def __get_description(self): - """ - Returns the description of the HardwareVersion element. - - Returns: - String: the description of the HardwareVersion element. - """ - return self.__description - - @classmethod - def get(cls, code): - """ - Returns the HardwareVersion for the given code. - - Args: - code (Integer): the code of the hardware version to get. - - Returns: - :class:`HardwareVersion`: the HardwareVersion with the given code, ``None`` if there is not a - HardwareVersion with that code. - """ - try: - return cls.lookupTable[code] - except KeyError: - return None - - code = property(__get_code) - """Integer. The hardware version code.""" - - description = property(__get_description) - """String. The hardware version description.""" - - -# Class variable of HardwareVersion. Dictionary for -# search HardwareVersion by code. -HardwareVersion.lookupTable = {x.code: x for x in HardwareVersion} -HardwareVersion.__doc__ += utils.doc_enum(HardwareVersion) diff --git a/digi/xbee/models/message.py b/digi/xbee/models/message.py deleted file mode 100644 index ff6d198..0000000 --- a/digi/xbee/models/message.py +++ /dev/null @@ -1,457 +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 re - - -class XBeeMessage(object): - """ - This class represents a XBee message, which is formed by a :class:`.RemoteXBeeDevice` - (the sender) and some data (the data sent) as a bytearray. - """ - - def __init__(self, data, remote_device, timestamp, broadcast=False): - """ - Class constructor. - - Args: - data (Bytearray): the data sent. - remote_device (:class:`.RemoteXBeeDevice`): the sender. - broadcast (Boolean, optional, default=``False``): flag indicating whether the message is - broadcast (``True``) or not (``False``). Optional. - timestamp: instant of time when the message was received. - """ - self.__data = data - self.__remote_device = remote_device - self.__is_broadcast = broadcast - self.__timestamp = timestamp - - def __get_data(self): - """ - Returns the data of the message. - - Returns: - Bytearray: the data of the message. - """ - return self.__data - - def __get_remote_device(self): - """ - Returns the device which has sent the message. - - Returns: - :class:`.RemoteXBeeDevice`: the device which has sent the message. - """ - return self.__remote_device - - def __is_broadcast(self): - """ - Returns whether the message is broadcast or not. - - Returns: - Boolean: ``True`` if the message is broadcast, ``False`` otherwise. - """ - return self.__is_broadcast - - def __get_timestamp(self): - """ - Returns the moment when the message was received as a ``time.time()`` - function returned value. - - Returns: - Float: the returned value of using :meth:`time.time()` function when the message was received. - """ - return self.__timestamp - - def to_dict(self): - """ - Returns the message information as a dictionary. - """ - return {"Data: ": self.__data, - "Sender: ": str(self.__remote_device.get_64bit_addr()), - "Broadcast: ": self.__is_broadcast, - "Received at: ": self.__timestamp} - - data = property(__get_data) - """Bytearray. Bytearray containing the data of the message.""" - - remote_device = property(__get_remote_device) - """:class:`.RemoteXBeeDevice`. The device that has sent the message.""" - - is_broadcast = property(__is_broadcast) - """Boolean. ``True`` to indicate that the message is broadcast, ``False`` otherwise.""" - - timestamp = property(__get_timestamp) - """Integer. Instant of time when the message was received.""" - - -class ExplicitXBeeMessage(XBeeMessage): - """ - This class represents an Explicit XBee message, which is formed by all parameters of a common XBee message and: - Source endpoint, destination endpoint, cluster ID, profile ID. - """ - - def __init__(self, data, remote_device, timestamp, source_endpoint, - dest_endpoint, cluster_id, profile_id, broadcast=False): - """ - Class constructor. - - Args: - data (Bytearray): the data sent. - remote_device (:class:`.RemoteXBeeDevice`): the sender device. - timestamp: instant of time when the message was received. - source_endpoint (Integer): source endpoint of the message. 1 byte. - dest_endpoint (Integer): destination endpoint of the message. 1 byte. - cluster_id (Integer): cluster id of the message. 2 bytes. - profile_id (Integer): profile id of the message. 2 bytes. - broadcast (Boolean, optional, default=``False``): flag indicating whether the message is - broadcast (``True``) or not (``False``). Optional. - """ - XBeeMessage.__init__(self, data, remote_device, timestamp, broadcast) - self.__source_endpoint = source_endpoint - self.__dest_endpoint = dest_endpoint - self.__cluster_id = cluster_id - self.__profile_id = profile_id - - def __get_source_endpoint(self): - """ - Returns the source endpoint of the message. - - Returns: - Integer: the source endpoint of the message. 1 byte. - """ - return self.__source_endpoint - - def __get_dest_endpoint(self): - """ - Returns the destination endpoint of the message. - - Returns: - Integer: the destination endpoint of the message. 1 byte. - """ - return self.__dest_endpoint - - def __get_cluster_id(self): - """ - Returns the cluster ID of the message. - - Returns: - Integer: the cluster ID of the message. 2 bytes. - """ - return self.__cluster_id - - def __get_profile_id(self): - """ - Returns the profile ID of the message. - - Returns: - Integer: the profile ID of the message. 2 bytes. - """ - return self.__profile_id - - def __set_source_endpoint(self, source_endpoint): - """ - Sets the source endpoint of the message. - - Args: - source_endpoint (Integer): the new source endpoint of the message. - """ - self.__source_endpoint = source_endpoint - - def __set_dest_endpoint(self, dest_endpoint): - """ - Sets the destination endpoint of the message. - - Args: - dest_endpoint (Integer): the new destination endpoint of the message. - """ - self.__dest_endpoint = dest_endpoint - - def __set_cluster_id(self, cluster_id): - """ - Sets the cluster ID of the message. - - Args: - cluster_id (Integer): the new cluster ID of the message. - """ - self.__cluster_id = cluster_id - - def __set_profile_id(self, profile_id): - """ - Sets the profile ID of the message. - - Args: - profile_id (Integer): the new profile ID of the message. - """ - self.__profile_id = profile_id - - def to_dict(self): - dc = XBeeMessage.to_dict(self) - dc.update({"Src_endpoint": self.__source_endpoint, - "Dest_endpoint": self.__dest_endpoint, - "Cluster_id": self.__cluster_id, - "Profile_id": self.__profile_id}) - return dc - - source_endpoint = property(__get_source_endpoint, __set_source_endpoint) - """Integer. The source endpoint of the message""" - - dest_endpoint = property(__get_dest_endpoint, __set_dest_endpoint) - """Integer. The destination endpoint of the message""" - - cluster_id = property(__get_cluster_id, __set_cluster_id) - """Integer. The Cluster ID of the message.""" - - profile_id = property(__get_profile_id, __set_profile_id) - """Integer. The profile ID of the message.""" - - -class IPMessage(object): - """ - This class represents an IP message containing the IP address the message belongs to, the source and destination - ports, the IP protocol, and the content (data) of the message. - """ - - def __init__(self, ip_addr, source_port, dest_port, protocol, data): - """ - Class constructor. - - Args: - ip_addr (:class:`ipaddress.IPv4Address`): The IP address the message comes from. - source_port (Integer): TCP or UDP source port of the transmission. - dest_port (Integer): TCP or UDP destination port of the transmission. - protocol (:class:`.IPProtocol`): IP protocol used in the transmission. - data (Bytearray): the data sent. - - Raises: - ValueError: if ``ip_addr`` is ``None``. - ValueError: if ``protocol`` is ``None``. - ValueError: if ``data`` is ``None``. - ValueError: if ``source_port`` is less than 0 or greater than 65535. - ValueError: if ``dest_port`` is less than 0 or greater than 65535. - """ - if ip_addr is None: - raise ValueError("IP address cannot be None") - if protocol is None: - raise ValueError("Protocol cannot be None") - if data is None: - raise ValueError("Data cannot be None") - - if not 0 <= source_port <= 65535: - raise ValueError("Source port must be between 0 and 65535") - if not 0 <= dest_port <= 65535: - raise ValueError("Destination port must be between 0 and 65535") - - self.__ip_addr = ip_addr - self.__source_port = source_port - self.__dest_port = dest_port - self.__protocol = protocol - self.__data = data - - def __get_ip_addr(self): - """ - Returns the IPv4 address this message is associated to. - - Returns: - :class:`ipaddress.IPv4Address`: The IPv4 address this message is associated to. - """ - return self.__ip_addr - - def __get_source_port(self): - """ - Returns the source port of the transmission. - - Returns: - Integer: The source port of the transmission. - """ - return self.__source_port - - def __get_dest_port(self): - """ - Returns the destination port of the transmission. - - Returns: - Integer: The destination port of the transmission. - """ - return self.__dest_port - - def __get_protocol(self): - """ - Returns the protocol used in the transmission. - - Returns: - :class:`.IPProtocol`: The protocol used in the transmission. - """ - return self.__protocol - - def __get_data(self): - """ - Returns the data of the message. - - Returns: - Bytearray: the data of the message. - """ - return self.__data - - def to_dict(self): - """ - Returns the message information as a dictionary. - """ - return {"IP address: ": self.__ip_addr, - "Source port: ": self.__source_port, - "Destination port: ": self.__dest_port, - "Protocol: ": self.__protocol, - "Data: ": self.__data} - - ip_addr = property(__get_ip_addr) - """:class:`ipaddress.IPv4Address`. The IPv4 address this message is associated to.""" - - source_port = property(__get_source_port) - """Integer. The source port of the transmission.""" - - dest_port = property(__get_dest_port) - """Integer. The destination port of the transmission.""" - - protocol = property(__get_protocol) - """:class:`.IPProtocol`. The protocol used in the transmission.""" - - data = property(__get_data) - """Bytearray. Bytearray containing the data of the message.""" - - -class SMSMessage(object): - """ - This class represents an SMS message containing the phone number that sent - the message and the content (data) of the message. - - This class is used within the library to read SMS sent to Cellular devices. - """ - - __PHONE_NUMBER_PATTERN = "^\+?\d+$" - - def __init__(self, phone_number, data): - """ - Class constructor. Instantiates a new :class:`.SMSMessage` object with the provided parameters. - - Args: - phone_number (String): The phone number that sent the message. - data (String): The message text. - - Raises: - ValueError: if ``phone_number`` is ``None``. - ValueError: if ``data`` is ``None``. - ValueError: if ``phone_number`` is not a valid phone number. - """ - if phone_number is None: - raise ValueError("Phone number cannot be None") - if data is None: - raise ValueError("Data cannot be None") - if not re.compile(SMSMessage.__PHONE_NUMBER_PATTERN).match(phone_number): - raise ValueError("Invalid phone number") - - self.__phone_number = phone_number - self.__data = data - - def __get_phone_number(self): - """ - Returns the phone number that sent the message. - - Returns: - String: The phone number that sent the message. - """ - return self.__phone_number - - def __get_data(self): - """ - Returns the data of the message. - - Returns: - String: The data of the message. - """ - return self.__data - - def to_dict(self): - """ - Returns the message information as a dictionary. - """ - return {"Phone number: ": self.__phone_number, - "Data: ": self.__data} - - phone_number = property(__get_phone_number) - """String. The phone number that sent the message.""" - - data = property(__get_data) - """String. The data of the message.""" - - -class UserDataRelayMessage(object): - """ - This class represents a user data relay message containing the source - interface and the content (data) of the message. - - .. seealso:: - | :class:`.XBeeLocalInterface` - """ - - def __init__(self, local_interface, data): - """ - Class constructor. Instantiates a new :class:`.UserDataRelayMessage` object with - the provided parameters. - - Args: - local_interface (:class:`.XBeeLocalInterface`): The source XBee local interface. - data (Bytearray): Byte array containing the data of the message. - - Raises: - ValueError: if ``relay_interface`` is ``None``. - - .. seealso:: - | :class:`.XBeeLocalInterface` - """ - if local_interface is None: - raise ValueError("XBee local interface cannot be None") - - self.__local_interface = local_interface - self.__data = data - - def __get_src_interface(self): - """ - Returns the source interface that sent the message. - - Returns: - :class:`.XBeeLocalInterface`: The source interface that sent the message. - """ - return self.__local_interface - - def __get_data(self): - """ - Returns the data of the message. - - Returns: - Bytearray: The data of the message. - """ - return self.__data - - def to_dict(self): - """ - Returns the message information as a dictionary. - """ - return {"XBee local interface: ": self.__local_interface, - "Data: ": self.__data} - - local_interface = property(__get_src_interface) - """:class:`.XBeeLocalInterface`. Source interface that sent the message.""" - - data = property(__get_data) - """Bytearray. The data of the message.""" diff --git a/digi/xbee/models/mode.py b/digi/xbee/models/mode.py deleted file mode 100644 index 8c46bab..0000000 --- a/digi/xbee/models/mode.py +++ /dev/null @@ -1,204 +0,0 @@ -# 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 enum import Enum, unique -from digi.xbee.util import utils - - -@unique -class OperatingMode(Enum): - """ - This class represents all operating modes available. - - | Inherited properties: - | **name** (String): the name (id) of this OperatingMode. - | **value** (String): the value of this OperatingMode. - """ - - AT_MODE = (0, "AT mode") - API_MODE = (1, "API mode") - ESCAPED_API_MODE = (2, "API mode with escaped characters") - UNKNOWN = (99, "Unknown") - - def __init__(self, code, description): - self.__code = code - self.__description = description - - def __get_code(self): - """ - Returns the code of the OperatingMode element. - - Returns: - String: the code of the OperatingMode element. - """ - return self.__code - - def __get_description(self): - """ - Returns the description of the OperatingMode element. - - Returns: - String: the description of the OperatingMode element. - """ - return self.__description - - @classmethod - def get(cls, code): - """ - Returns the OperatingMode for the given code. - - Args: - code (Integer): the code corresponding to the operating mode to get. - - Returns: - :class:`.OperatingMode`: the OperatingMode with the given code. - """ - try: - return cls.lookupTable[code] - except KeyError: - return OperatingMode.UNKNOWN - - code = property(__get_code) - """Integer. The operating mode code.""" - - description = property(__get_description) - """String: The operating mode description.""" - - -OperatingMode.lookupTable = {x.code: x for x in OperatingMode} -OperatingMode.__doc__ += utils.doc_enum(OperatingMode) - - -@unique -class APIOutputMode(Enum): - """ - Enumerates the different API output modes. The API output mode establishes - the way data will be output through the serial interface of an XBee device. - - | Inherited properties: - | **name** (String): the name (id) of this OperatingMode. - | **value** (String): the value of this OperatingMode. - """ - - NATIVE = (0x00, "Native") - EXPLICIT = (0x01, "Explicit") - EXPLICIT_ZDO_PASSTHRU = (0x03, "Explicit with ZDO Passthru") - - def __init__(self, code, description): - self.__code = code - self.__description = description - - def __get_code(self): - """ - Returns the code of the APIOutputMode element. - - Returns: - String: the code of the APIOutputMode element. - """ - return self.__code - - def __get_description(self): - """ - Returns the description of the APIOutputMode element. - - Returns: - String: the description of the APIOutputMode element. - """ - return self.__description - - @classmethod - def get(cls, code): - """ - Returns the APIOutputMode for the given code. - - Args: - code (Integer): the code corresponding to the API output mode to get. - - Returns: - :class:`.OperatingMode`: the APIOutputMode with the given code, ``None`` if there is not an - APIOutputMode with that code. - """ - try: - return cls.lookupTable[code] - except KeyError: - return None - - code = property(__get_code) - """Integer. The API output mode code.""" - - description = property(__get_description) - """String: The API output mode description.""" - - -APIOutputMode.lookupTable = {x.code: x for x in APIOutputMode} -APIOutputMode.__doc__ += utils.doc_enum(APIOutputMode) - - -@unique -class IPAddressingMode(Enum): - """ - Enumerates the different IP addressing modes. - """ - - DHCP = (0x00, "DHCP") - STATIC = (0x01, "Static") - - def __init__(self, code, description): - self.__code = code - self.__description = description - - def __get_code(self): - """ - Returns the code of the IPAddressingMode element. - - Returns: - String: the code of the IPAddressingMode element. - """ - return self.__code - - def __get_description(self): - """ - Returns the description of the IPAddressingMode element. - - Returns: - String: the description of the IPAddressingMode element. - """ - return self.__description - - @classmethod - def get(cls, code): - """ - Returns the IPAddressingMode for the given code. - - Args: - code (Integer): the code corresponding to the IP addressing mode to get. - - Returns: - :class:`.IPAddressingMode`: the IPAddressingMode with the given code, ``None`` if there is not an - IPAddressingMode with that code. - """ - try: - return cls.lookupTable[code] - except KeyError: - return None - - code = property(__get_code) - """Integer. The IP addressing mode code.""" - - description = property(__get_description) - """String. The IP addressing mode description.""" - - -IPAddressingMode.lookupTable = {x.code: x for x in IPAddressingMode} -IPAddressingMode.__doc__ += utils.doc_enum(IPAddressingMode) diff --git a/digi/xbee/models/options.py b/digi/xbee/models/options.py deleted file mode 100644 index 92c1d0e..0000000 --- a/digi/xbee/models/options.py +++ /dev/null @@ -1,470 +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. - -from enum import Enum, unique - -from digi.xbee.models.protocol import XBeeProtocol -from digi.xbee.util import utils - - -class ReceiveOptions(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. - """ - - NONE = 0x00 - """ - No special receive options. - """ - - PACKET_ACKNOWLEDGED = 0x01 - """ - Packet was acknowledged. - - Not valid for WiFi protocol. - """ - - BROADCAST_PACKET = 0x02 - """ - Packet was a broadcast packet. - - Not valid for WiFi protocol. - """ - - APS_ENCRYPTED = 0x20 - """ - Packet encrypted with APS encryption. - - Only valid for ZigBee XBee protocol. - """ - - SENT_FROM_END_DEVICE = 0x40 - """ - Packet was sent from an end device (if known). - - Only valid for ZigBee XBee protocol. - """ - - -ReceiveOptions.__doc__ += utils.doc_enum(ReceiveOptions) - - -class TransmitOptions(Enum): - """ - This class lists all the possible options that can be set while - transmitting an XBee packet. - - The transmit options are usually set as a bitfield meaning that the options - can be combined using the '|' operand. - - Not all options are available for all cases, that's why there are different - names with same values. In each moment, you must be sure that the option - your are going to use, is a valid option in your context. - """ - - NONE = 0x00 - """ - No special transmit options. - """ - - DISABLE_ACK = 0x01 - """ - Disables acknowledgments on all unicasts . - - Only valid for DigiMesh, 802.15.4 and Point-to-multipoint - protocols. - """ - - DISABLE_RETRIES_AND_REPAIR = 0x01 - """ - Disables the retries and router repair in the frame . - - Only valid for ZigBee protocol. - """ - - DONT_ATTEMPT_RD = 0x02 - """ - Doesn't attempt Route Discovery . - - Disables Route Discovery on all DigiMesh unicasts. - - Only valid for DigiMesh protocol. - """ - - USE_BROADCAST_PAN_ID = 0x04 - """ - Sends packet with broadcast {@code PAN ID}. Packet will be sent to all - devices in the same channel ignoring the {@code PAN ID}. - - It cannot be combined with other options. - - Only valid for 802.15.4 XBee protocol. - """ - - ENABLE_UNICAST_NACK = 0x04 - """ - Enables unicast NACK messages . - - NACK message is enabled on the packet. - - Only valid for DigiMesh 868/900 protocol. - """ - - ENABLE_UNICAST_TRACE_ROUTE = 0x04 - """ - Enables unicast trace route messages . - - Trace route is enabled on the packets. - - Only valid for DigiMesh 868/900 protocol. - """ - - ENABLE_MULTICAST = 0x08 - """ - Enables multicast transmission request. - - Only valid for ZigBee XBee protocol. - """ - - ENABLE_APS_ENCRYPTION = 0x20 - """ - Enables APS encryption, only if {@code EE=1} . - - Enabling APS encryption decreases the maximum number of RF payload - bytes by 4 (below the value reported by {@code NP}). - - Only valid for ZigBee XBee protocol. - """ - - USE_EXTENDED_TIMEOUT = 0x40 - """ - Uses the extended transmission timeout . - - Setting the extended timeout bit causes the stack to set the - extended transmission timeout for the destination address. - - Only valid for ZigBee XBee protocol. - """ - - POINT_MULTIPOINT_MODE = 0x40 - """ - Transmission is performed using point-to-Multipoint mode. - - Only valid for DigiMesh 868/900 and Point-to-Multipoint 868/900 - protocols. - """ - - REPEATER_MODE = 0x80 - """ - Transmission is performed using repeater mode . - - Only valid for DigiMesh 868/900 and Point-to-Multipoint 868/900 - protocols. - """ - - DIGIMESH_MODE = 0xC0 - """ - Transmission is performed using DigiMesh mode . - - Only valid for DigiMesh 868/900 and Point-to-Multipoint 868/900 - protocols. - """ - - -TransmitOptions.__doc__ += utils.doc_enum(TransmitOptions) - - -class RemoteATCmdOptions(Enum): - """ - This class lists all the possible options that can be set while transmitting - a remote AT Command. - - These options are usually set as a bitfield meaning that the options - can be combined using the '|' operand. - """ - - NONE = 0x00 - """ - No special transmit options - """ - - DISABLE_ACK = 0x01 - """ - Disables ACK - """ - - APPLY_CHANGES = 0x02 - """ - Applies changes in the remote device. - - If this option is not set, AC command must be sent before changes - will take effect. - """ - - EXTENDED_TIMEOUT = 0x40 - """ - Uses the extended transmission timeout - - Setting the extended timeout bit causes the stack to set the extended - transmission timeout for the destination address. - - Only valid for ZigBee XBee protocol. - """ - - -RemoteATCmdOptions.__doc__ += utils.doc_enum(RemoteATCmdOptions) - - -@unique -class SendDataRequestOptions(Enum): - """ - Enumerates the different options for the :class:`.SendDataRequestPacket`. - """ - OVERWRITE = (0, "Overwrite") - ARCHIVE = (1, "Archive") - APPEND = (2, "Append") - TRANSIENT = (3, "Transient data (do not store)") - - def __init__(self, code, description): - self.__code = code - self.__description = description - - def __get_code(self): - """ - Returns the code of the SendDataRequestOptions element. - - Returns: - Integer: the code of the SendDataRequestOptions element. - """ - return self.__code - - def __get_description(self): - """ - Returns the description of the SendDataRequestOptions element. - - Returns: - String: the description of the SendDataRequestOptions element. - """ - return self.__description - - @classmethod - def get(cls, code): - """ - Returns the send data request option for the given code. - - Args: - code (Integer): the code of the send data request option to get. - - Returns: - :class:`.FrameError`: the SendDataRequestOptions with the given code, ``None`` if there is not - any send data request option with the provided code. - """ - try: - return cls.lookupTable[code] - except KeyError: - return None - - code = property(__get_code) - """Integer. The send data request option code.""" - - description = property(__get_description) - """String. The send data request option description.""" - - -SendDataRequestOptions.lookupTable = {x.code: x for x in SendDataRequestOptions} -SendDataRequestOptions.__doc__ += utils.doc_enum(SendDataRequestOptions) - - -@unique -class DiscoveryOptions(Enum): - """ - Enumerates the different options used in the discovery process. - """ - - APPEND_DD = (0x01, "Append device type identifier (DD)") - """ - Append device type identifier (DD) to the discovery response. - - Valid for the following protocols: - * DigiMesh - * Point-to-multipoint (Digi Point) - * ZigBee - """ - - DISCOVER_MYSELF = (0x02, "Local device sends response frame") - """ - Local device sends response frame when discovery is issued. - - Valid for the following protocols: - * DigiMesh - * Point-to-multipoint (Digi Point) - * ZigBee - * 802.15.4 - """ - - APPEND_RSSI = (0x04, "Append RSSI (of the last hop)") - """ - Append RSSI of the last hop to the discovery response. - - Valid for the following protocols: - * DigiMesh - * Point-to-multipoint (Digi Point) - """ - - def __init__(self, code, description): - self.__code = code - self.__description = description - - def __get_code(self): - """ - Returns the code of the ``DiscoveryOptions`` element. - - Returns: - Integer: the code of the ``DiscoveryOptions`` element. - """ - return self.__code - - def __get_description(self): - """ - Returns the description of the ``DiscoveryOptions`` element. - - Returns: - String: the description of the ``DiscoveryOptions`` element. - """ - return self.__description - - @classmethod - def get(cls, code): - """ - Returns the ``DiscoveryOptions`` for the given code. - - Args: - code (Integer): the code of the ``DiscoveryOptions`` to get. - - Returns: - :class:`.FrameError`: the ``DiscoveryOptions`` with the given code, ``None`` if there is not - any discovery option with the provided code. - """ - try: - return cls.lookupTable[code] - except KeyError: - return None - - @staticmethod - def calculate_discovery_value(protocol, options): - """ - Calculates the total value of a combination of several options for the - given protocol. - - Args: - protocol (:class:`.XBeeProtocol`): the ``XBeeProtocol`` to calculate the value of all the given - discovery options. - options: collection of options to get the final value. - - Returns: - Integer: The value to be configured in the module depending on the given - collection of options and the protocol. - """ - value = 0 - if protocol in [XBeeProtocol.ZIGBEE, XBeeProtocol.ZNET]: - for op in options: - if op == DiscoveryOptions.APPEND_RSSI: - continue - value = value + op.code - elif protocol in [XBeeProtocol.DIGI_MESH, XBeeProtocol.DIGI_POINT, XBeeProtocol.XLR, XBeeProtocol.XLR_DM]: - for op in options: - value = value + op.code - else: - if DiscoveryOptions.DISCOVER_MYSELF in options: - value = 1 # This is different for 802.15.4. - return value - - code = property(__get_code) - """Integer. The discovery option code.""" - - description = property(__get_description) - """String. The discovery option description.""" - - -DiscoveryOptions.lookupTable = {x.code: x for x in DiscoveryOptions} -DiscoveryOptions.__doc__ += utils.doc_enum(DiscoveryOptions) - - -@unique -class XBeeLocalInterface(Enum): - """ - Enumerates the different interfaces for the :class:`.UserDataRelayPacket` - and :class:`.UserDataRelayOutputPacket`. - - | Inherited properties: - | **name** (String): the name (id) of the XBee local interface. - | **value** (String): the value of the XBee local interface. - """ - SERIAL = (0, "Serial port (UART when in API mode, or SPI interface)") - BLUETOOTH = (1, "BLE API interface (on XBee devices which support BLE)") - MICROPYTHON = (2, "MicroPython") - UNKNOWN = (255, "Unknown interface") - - def __init__(self, code, description): - self.__code = code - self.__description = description - - def __get_code(self): - """ - Returns the code of the ``XBeeLocalInterface`` element. - - Returns: - Integer: the code of the ``XBeeLocalInterface`` element. - """ - return self.__code - - def __get_description(self): - """ - Returns the description of the ``XBeeLocalInterface`` element. - - Returns: - String: the description of the ``XBeeLocalInterface`` element. - """ - return self.__description - - @classmethod - def get(cls, code): - """ - Returns the XBee local interface option for the given code. - - Args: - code (Integer): the code of the XBee local interface to get. - - Returns: - :class:`.XBeeLocalInterface`: the ``XBeeLocalInterface`` with the given code, - ``UNKNOWN`` if there is not any ``XBeeLocalInterface`` with the provided code. - """ - try: - return cls.lookupTable[code] - except KeyError: - return XBeeLocalInterface.UNKNOWN - - code = property(__get_code) - """Integer. The XBee local interface code.""" - - description = property(__get_description) - """String. The XBee local interface description.""" - - -XBeeLocalInterface.lookupTable = {x.code: x for x in XBeeLocalInterface} -XBeeLocalInterface.__doc__ += utils.doc_enum(XBeeLocalInterface) diff --git a/digi/xbee/models/protocol.py b/digi/xbee/models/protocol.py deleted file mode 100644 index 4751afe..0000000 --- a/digi/xbee/models/protocol.py +++ /dev/null @@ -1,331 +0,0 @@ -# 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 -# 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 enum import Enum, unique -from digi.xbee.models.hw import HardwareVersion -from digi.xbee.util import utils - - -@unique -class XBeeProtocol(Enum): - """ - Enumerates the available XBee protocols. The XBee protocol is determined - by the combination of hardware and firmware of an XBee device. - - | Inherited properties: - | **name** (String): the name (id) of this XBeeProtocol. - | **value** (String): the value of this XBeeProtocol. - """ - - ZIGBEE = (0, "ZigBee") - RAW_802_15_4 = (1, "802.15.4") - XBEE_WIFI = (2, "Wi-Fi") - DIGI_MESH = (3, "DigiMesh") - XCITE = (4, "XCite") - XTEND = (5, "XTend (Legacy)") - XTEND_DM = (6, "XTend (DigiMesh)") - SMART_ENERGY = (7, "Smart Energy") - DIGI_POINT = (8, "Point-to-multipoint") - ZNET = (9, "ZNet 2.5") - XC = (10, "XSC") - XLR = (11, "XLR") - XLR_DM = (12, "XLR") - SX = (13, "XBee SX") - XLR_MODULE = (14, "XLR Module") - CELLULAR = (15, "Cellular") - CELLULAR_NBIOT = (16, "Cellular NB-IoT") - UNKNOWN = (99, "Unknown") - - def __init__(self, code, description): - self.__code = code - self.__description = description - - def __get_code(self): - """ - Returns the code of the XBeeProtocol element. - - Returns: - Integer: the code of the XBeeProtocol element. - """ - return self.__code - - def __get_description(self): - """ - Returns the description of the XBeeProtocol element. - - Returns: - String: the description of the XBeeProtocol element. - """ - return self.__description - - @classmethod - def get(cls, code): - """ - Returns the XBeeProtocol for the given code. - - Args: - code (Integer): code of the XBeeProtocol to get. - - Returns: - XBeeProtocol: XBeeProtocol for the given code. - """ - try: - return cls.lookupTable[code] - except KeyError: - return XBeeProtocol.UNKNOWN - - @staticmethod - def determine_protocol(hardware_version, firmware_version): - """ - Determines the XBee protocol based on the given hardware and firmware - versions. - - Args: - hardware_version (Integer): hardware version of the protocol to determine. - firmware_version (String): firmware version of the protocol to determine. - - Returns: - The XBee protocol corresponding to the given hardware and firmware versions. - """ - firmware_version = "".join(["%02X" % i for i in firmware_version]) - - if hardware_version is None or firmware_version is None or hardware_version < 0x09 or \ - HardwareVersion.get(hardware_version) is None: - return XBeeProtocol.UNKNOWN - - elif hardware_version in [HardwareVersion.XC09_009.code, - HardwareVersion.XC09_038.code]: - return XBeeProtocol.XCITE - - elif hardware_version in [HardwareVersion.XT09_XXX.code, - HardwareVersion.XT09B_XXX.code]: - if ((len(firmware_version) == 4 and firmware_version.startswith("8")) or - (len(firmware_version) == 5 and firmware_version[1] == '8')): - return XBeeProtocol.XTEND_DM - return XBeeProtocol.XTEND - - elif hardware_version in [HardwareVersion.XB24_AXX_XX.code, - HardwareVersion.XBP24_AXX_XX.code]: - if len(firmware_version) == 4 and firmware_version.startswith("8"): - return XBeeProtocol.DIGI_MESH - return XBeeProtocol.RAW_802_15_4 - - elif hardware_version in [HardwareVersion.XB24_BXIX_XXX.code, - HardwareVersion.XBP24_BXIX_XXX.code]: - if ((len(firmware_version) == 4 and firmware_version.startswith("1") and firmware_version.endswith("20")) - or (len(firmware_version) == 4 and firmware_version.startswith("2"))): - return XBeeProtocol.ZIGBEE - elif len(firmware_version) == 4 and firmware_version.startswith("3"): - return XBeeProtocol.SMART_ENERGY - return XBeeProtocol.ZNET - - elif hardware_version == HardwareVersion.XBP09_DXIX_XXX.code: - if ((len(firmware_version) == 4 and firmware_version.startswith("8") or - (len(firmware_version) == 4 and firmware_version[1] == '8')) or - (len(firmware_version) == 5 and firmware_version[1] == '8')): - return XBeeProtocol.DIGI_MESH - return XBeeProtocol.DIGI_POINT - - elif hardware_version == HardwareVersion.XBP09_XCXX_XXX.code: - return XBeeProtocol.XC - - elif hardware_version == HardwareVersion.XBP08_DXXX_XXX.code: - return XBeeProtocol.DIGI_POINT - - elif hardware_version == HardwareVersion.XBP24B.code: - if len(firmware_version) == 4 and firmware_version.startswith("3"): - return XBeeProtocol.SMART_ENERGY - return XBeeProtocol.ZIGBEE - - elif hardware_version in [HardwareVersion.XB24_WF.code, - HardwareVersion.WIFI_ATHEROS.code, - HardwareVersion.SMT_WIFI_ATHEROS.code]: - return XBeeProtocol.XBEE_WIFI - - elif hardware_version in [HardwareVersion.XBP24C.code, - HardwareVersion.XB24C.code]: - if (len(firmware_version) == 4 and (firmware_version.startswith("5")) or - (firmware_version.startswith("6"))): - return XBeeProtocol.SMART_ENERGY - elif firmware_version.startswith("2"): - return XBeeProtocol.RAW_802_15_4 - elif firmware_version.startswith("9"): - return XBeeProtocol.DIGI_MESH - return XBeeProtocol.ZIGBEE - - elif hardware_version in [HardwareVersion.XSC_GEN3.code, - HardwareVersion.SRD_868_GEN3.code]: - if len(firmware_version) == 4 and firmware_version.startswith("8"): - return XBeeProtocol.DIGI_MESH - elif len(firmware_version) == 4 and firmware_version.startswith("1"): - return XBeeProtocol.DIGI_POINT - return XBeeProtocol.XC - - elif hardware_version == HardwareVersion.XBEE_CELL_TH.code: - return XBeeProtocol.UNKNOWN - - elif hardware_version == HardwareVersion.XLR_MODULE.code: - # This is for the old version of the XLR we have (K60), and it is - # reporting the firmware of the module (8001), this will change in - # future (after K64 integration) reporting the hardware and firmware - # version of the baseboard (see the case HardwareVersion.XLR_BASEBOARD). - # TODO maybe this should be removed in future, since this case will never be released. - if firmware_version.startswith("1"): - return XBeeProtocol.XLR - else: - return XBeeProtocol.XLR_MODULE - - elif hardware_version == HardwareVersion.XLR_BASEBOARD.code: - # XLR devices with K64 will report the baseboard hardware version, - # and also firmware version (the one we have here is 1002, but this value - # is not being reported since is an old K60 version, the module fw version - # is reported instead). - - # TODO [XLR_DM] The next version of the XLR will add DigiMesh support should be added. - # Probably this XLR_DM and XLR will depend on the firmware version. - if firmware_version.startswith("1"): - return XBeeProtocol.XLR - else: - return XBeeProtocol.XLR_MODULE - - elif hardware_version == HardwareVersion.XB900HP_NZ.code: - return XBeeProtocol.DIGI_POINT - - elif hardware_version in [HardwareVersion.XBP24C_TH_DIP.code, - HardwareVersion.XB24C_TH_DIP.code, - HardwareVersion.XBP24C_S2C_SMT.code]: - if (len(firmware_version) == 4 and - (firmware_version.startswith("5") or firmware_version.startswith("6"))): - return XBeeProtocol.SMART_ENERGY - elif firmware_version.startswith("2"): - return XBeeProtocol.RAW_802_15_4 - elif firmware_version.startswith("9"): - return XBeeProtocol.DIGI_MESH - return XBeeProtocol.ZIGBEE - - elif hardware_version in [HardwareVersion.SX_PRO.code, - HardwareVersion.SX.code, - HardwareVersion.XTR.code]: - if firmware_version.startswith("2"): - return XBeeProtocol.XTEND - elif firmware_version.startswith("8"): - return XBeeProtocol.XTEND_DM - return XBeeProtocol.DIGI_MESH - - elif hardware_version in [HardwareVersion.S2D_SMT_PRO.code, - HardwareVersion.S2D_SMT_REG.code, - HardwareVersion.S2D_TH_PRO.code, - HardwareVersion.S2D_TH_REG.code]: - return XBeeProtocol.ZIGBEE - - elif hardware_version in [HardwareVersion.CELLULAR_CAT1_LTE_VERIZON.code, - HardwareVersion.CELLULAR_3G.code, - HardwareVersion.CELLULAR_LTE_ATT.code, - HardwareVersion.CELLULAR_LTE_VERIZON.code, - HardwareVersion.CELLULAR_3_CAT1_LTE_ATT.code, - HardwareVersion.CELLULAR_3_LTE_M_VERIZON.code, - HardwareVersion.CELLULAR_3_LTE_M_ATT.code]: - return XBeeProtocol.CELLULAR - - elif hardware_version == HardwareVersion.CELLULAR_NBIOT_EUROPE.code: - return XBeeProtocol.CELLULAR_NBIOT - - elif hardware_version in [HardwareVersion.XBEE3.code, - HardwareVersion.XBEE3_SMT.code, - HardwareVersion.XBEE3_TH.code]: - if firmware_version.startswith("2"): - return XBeeProtocol.RAW_802_15_4 - elif firmware_version.startswith("3"): - return XBeeProtocol.DIGI_MESH - else: - return XBeeProtocol.ZIGBEE - - elif hardware_version == HardwareVersion.XB8X.code: - return XBeeProtocol.DIGI_MESH - - return XBeeProtocol.ZIGBEE - - code = property(__get_code) - """Integer. XBee protocol code.""" - - description = property(__get_description) - """String. XBee protocol description.""" - - -XBeeProtocol.lookupTable = {x.code: x for x in XBeeProtocol} -XBeeProtocol.__doc__ += utils.doc_enum(XBeeProtocol) - - -@unique -class IPProtocol(Enum): - """ - Enumerates the available network protocols. - - | Inherited properties: - | **name** (String): the name (id) of this IPProtocol. - | **value** (String): the value of this IPProtocol. - """ - - UDP = (0, "UDP") - TCP = (1, "TCP") - TCP_SSL = (4, "TCP SSL") - - def __init__(self, code, description): - self.__code = code - self.__description = description - - def __get_code(self): - """ - Returns the code of the IP protocol. - - Returns: - Integer: code of the IP protocol. - """ - return self.__code - - def __get_description(self): - """ - Returns the description of the IP protocol. - - Returns: - String: description of the IP protocol. - """ - return self.__description - - @classmethod - def get(cls, code): - """ - Returns the IPProtocol for the given code. - - Args: - code (Integer): code associated to the IP protocol. - - Returns: - :class:`.IPProtocol`: IP protocol for the given code or ``None`` if there - is not any ``IPProtocol`` with the given code. - """ - try: - return cls.lookupTable[code] - except KeyError: - return None - - code = property(__get_code) - """Integer: IP protocol code.""" - - description = property(__get_description) - """String: IP protocol description.""" - - -IPProtocol.lookupTable = {x.code: x for x in IPProtocol} -IPProtocol.__doc__ += utils.doc_enum(IPProtocol) diff --git a/digi/xbee/models/status.py b/digi/xbee/models/status.py deleted file mode 100644 index 5937789..0000000 --- a/digi/xbee/models/status.py +++ /dev/null @@ -1,805 +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. - -from enum import Enum, unique -from digi.xbee.util import utils - - -@unique -class ATCommandStatus(Enum): - """ - This class lists all the possible states of an AT command after executing it. - - | Inherited properties: - | **name** (String): the name (id) of the ATCommandStatus. - | **value** (String): the value of the ATCommandStatus. - """ - OK = (0, "Status OK") - ERROR = (1, "Status Error") - INVALID_COMMAND = (2, "Invalid command") - INVALID_PARAMETER = (3, "Invalid parameter") - TX_FAILURE = (4, "TX failure") - UNKNOWN = (255, "Unknown status") - - def __init__(self, code, description): - self.__code = code - self.__description = description - - def __get_code(self): - """ - Returns the code of the ATCommandStatus element. - - Returns: - Integer: the code of the ATCommandStatus element. - """ - return self.__code - - def __get_description(self): - """ - Returns the description of the ATCommandStatus element. - - Returns: - String: the description of the ATCommandStatus element. - """ - return self.__description - - @classmethod - def get(cls, code): - """ - Returns the AT command status for the given code. - - Args: - code (Integer): the code of the AT command status to get. - - Returns: - :class:`.ATCommandStatus`: the AT command status with the given code. - """ - try: - return cls.lookupTable[code] - except KeyError: - return ATCommandStatus.UNKNOWN - - code = property(__get_code) - """Integer. The AT command status code.""" - - description = property(__get_description) - """String. The AT command status description.""" - - -ATCommandStatus.lookupTable = {x.code: x for x in ATCommandStatus} -ATCommandStatus.__doc__ += utils.doc_enum(ATCommandStatus) - - -@unique -class DiscoveryStatus(Enum): - """ - This class lists all the possible states of the discovery process. - - | Inherited properties: - | **name** (String): The name of the DiscoveryStatus. - | **value** (Integer): The ID of the DiscoveryStatus. - """ - NO_DISCOVERY_OVERHEAD = (0x00, "No discovery overhead") - ADDRESS_DISCOVERY = (0x01, "Address discovery") - ROUTE_DISCOVERY = (0x02, "Route discovery") - ADDRESS_AND_ROUTE = (0x03, "Address and route") - EXTENDED_TIMEOUT_DISCOVERY = (0x40, "Extended timeout discovery") - UNKNOWN = (0xFF, "Unknown") - - def __init__(self, code, description): - self.__code = code - self.__description = description - - def __get_code(self): - """ - Returns the code of the DiscoveryStatus element. - - Returns: - Integer: the code of the DiscoveryStatus element. - """ - return self.__code - - def __get_description(self): - """ - Returns the description of the DiscoveryStatus element. - - Returns: - String: The description of the DiscoveryStatus element. - - """ - return self.__description - - @classmethod - def get(cls, code): - """ - Returns the discovery status for the given code. - - Args: - code (Integer): the code of the discovery status to get. - - Returns: - :class:`.DiscoveryStatus`: the discovery status with the given code. - """ - try: - return cls.lookupTable[code] - except KeyError: - return DiscoveryStatus.UNKNOWN - - code = property(__get_code) - """Integer. The discovery status code.""" - - description = property(__get_description) - """String. The discovery status description.""" - - -DiscoveryStatus.lookupTable = {x.code: x for x in DiscoveryStatus} -DiscoveryStatus.__doc__ += utils.doc_enum(DiscoveryStatus) - - -@unique -class TransmitStatus(Enum): - """ - This class represents all available transmit status. - - | Inherited properties: - | **name** (String): the name (id) of ths TransmitStatus. - | **value** (String): the value of ths TransmitStatus. - """ - SUCCESS = (0x00, "Success.") - NO_ACK = (0x01, "No acknowledgement received.") - CCA_FAILURE = (0x02, "CCA failure.") - PURGED = (0x03, "Transmission purged, it was attempted before stack was up.") - WIFI_PHYSICAL_ERROR = (0x04, "Physical error occurred on the interface with the WiFi transceiver.") - INVALID_DESTINATION = (0x15, "Invalid destination endpoint.") - NO_BUFFERS = (0x18, "No buffers.") - NETWORK_ACK_FAILURE = (0x21, "Network ACK Failure.") - NOT_JOINED_NETWORK = (0x22, "Not joined to network.") - SELF_ADDRESSED = (0x23, "Self-addressed.") - ADDRESS_NOT_FOUND = (0x24, "Address not found.") - ROUTE_NOT_FOUND = (0x25, "Route not found.") - BROADCAST_FAILED = (0x26, "Broadcast source failed to hear a neighbor relay the message.") - INVALID_BINDING_TABLE_INDEX = (0x2B, "Invalid binding table index.") - INVALID_ENDPOINT = (0x2C, "Invalid endpoint") - BROADCAST_ERROR_APS = (0x2D, "Attempted broadcast with APS transmission.") - BROADCAST_ERROR_APS_EE0 = (0x2E, "Attempted broadcast with APS transmission, but EE=0.") - SOFTWARE_ERROR = (0x31, "A software error occurred.") - RESOURCE_ERROR = (0x32, "Resource error lack of free buffers, timers, etc.") - PAYLOAD_TOO_LARGE = (0x74, "Data payload too large.") - INDIRECT_MESSAGE_UNREQUESTED = (0x75, "Indirect message unrequested") - SOCKET_CREATION_FAILED = (0x76, "Attempt to create a client socket failed.") - IP_PORT_NOT_EXIST = (0x77, "TCP connection to given IP address and port doesn't exist. Source port is non-zero so " - "that a new connection is not attempted.") - UDP_SRC_PORT_NOT_MATCH_LISTENING_PORT = (0x78, "Source port on a UDP transmission doesn't match a listening port " - "on the transmitting module.") - TCP_SRC_PORT_NOT_MATCH_LISTENING_PORT = (0x79, "Source port on a TCP transmission doesn't match a listening port " - "on the transmitting module.") - INVALID_IP_ADDRESS = (0x7A, "Destination IPv4 address is not valid.") - INVALID_IP_PROTOCOL = (0x7B, "Protocol on an IPv4 transmission is not valid.") - RELAY_INTERFACE_INVALID = (0x7C, "Destination interface on a User Data Relay Frame " - "doesn't exist.") - RELAY_INTERFACE_REJECTED = (0x7D, "Destination interface on a User Data Relay Frame " - "exists, but the interface is not accepting data.") - SOCKET_CONNECTION_REFUSED = (0x80, "Destination server refused the connection.") - SOCKET_CONNECTION_LOST = (0x81, "The existing connection was lost before the data was sent.") - SOCKET_ERROR_NO_SERVER = (0x82, "The attempted connection timed out.") - SOCKET_ERROR_CLOSED = (0x83, "The existing connection was closed.") - SOCKET_ERROR_UNKNOWN_SERVER = (0x84, "The server could not be found.") - SOCKET_ERROR_UNKNOWN_ERROR = (0x85, "An unknown error occurred.") - INVALID_TLS_CONFIGURATION = (0x86, "TLS Profile on a 0x23 API request doesn't exist, or " - "one or more certificates is not valid.") - KEY_NOT_AUTHORIZED = (0xBB, "Key not authorized.") - UNKNOWN = (0xFF, "Unknown.") - - def __init__(self, code, description): - self.__code = code - self.__description = description - - def __get_code(self): - """ - Returns the code of the TransmitStatus element. - - Returns: - Integer: the code of the TransmitStatus element. - """ - return self.__code - - def __get_description(self): - """ - Returns the description of the TransmitStatus element. - - Returns: - String: the description of the TransmitStatus element. - """ - return self.__description - - @classmethod - def get(cls, code): - """ - Returns the transmit status for the given code. - - Args: - code (Integer): the code of the transmit status to get. - - Returns: - :class:`.TransmitStatus`: the transmit status with the given code. - """ - try: - return cls.lookupTable[code] - except KeyError: - return TransmitStatus.UNKNOWN - - code = property(__get_code) - """Integer. The transmit status code.""" - - description = property(__get_description) - """String. The transmit status description.""" - - -TransmitStatus.lookupTable = {x.code: x for x in TransmitStatus} -TransmitStatus.__doc__ += utils.doc_enum(TransmitStatus) - - -@unique -class ModemStatus(Enum): - """ - Enumerates the different modem status events. This enumeration list is - intended to be used within the :class:`.ModemStatusPacket` packet. - """ - HARDWARE_RESET = (0x00, "Device was reset") - WATCHDOG_TIMER_RESET = (0x01, "Watchdog timer was reset") - JOINED_NETWORK = (0x02, "Device joined to network") - DISASSOCIATED = (0x03, "Device disassociated") - ERROR_SYNCHRONIZATION_LOST = (0x04, "Configuration error/synchronization lost") - COORDINATOR_REALIGNMENT = (0x05, "Coordinator realignment") - COORDINATOR_STARTED = (0x06, "The coordinator started") - NETWORK_SECURITY_KEY_UPDATED = (0x07, "Network security key was updated") - NETWORK_WOKE_UP = (0x0B, "Network Woke Up") - NETWORK_WENT_TO_SLEEP = (0x0C, "Network Went To Sleep") - VOLTAGE_SUPPLY_LIMIT_EXCEEDED = (0x0D, "Voltage supply limit exceeded") - REMOTE_MANAGER_CONNECTED = (0x0E, "Remote Manager connected") - REMOTE_MANAGER_DISCONNECTED = (0x0F, "Remote Manager disconnected") - MODEM_CONFIG_CHANGED_WHILE_JOINING = (0x11, "Modem configuration changed while joining") - BLUETOOTH_CONNECTED = (0x32, "A Bluetooth connection has been made and API mode has been unlocked.") - BLUETOOTH_DISCONNECTED = (0x33, "An unlocked Bluetooth connection has been disconnected.") - BANDMASK_CONFIGURATION_ERROR = (0x34, "LTE-M/NB-IoT bandmask configuration has failed.") - ERROR_STACK = (0x80, "Stack error") - ERROR_AP_NOT_CONNECTED = (0x82, "Send/join command issued without connecting from AP") - ERROR_AP_NOT_FOUND = (0x83, "Access point not found") - ERROR_PSK_NOT_CONFIGURED = (0x84, "PSK not configured") - ERROR_SSID_NOT_FOUND = (0x87, "SSID not found") - ERROR_FAILED_JOIN_SECURITY = (0x88, "Failed to join with security enabled") - ERROR_INVALID_CHANNEL = (0x8A, "Invalid channel") - ERROR_FAILED_JOIN_AP = (0x8E, "Failed to join access point") - UNKNOWN = (0xFF, "UNKNOWN") - - def __init__(self, code, description): - self.__code = code - self.__description = description - - def __get_code(self): - """ - Returns the code of the ModemStatus element. - - Returns: - Integer: the code of the ModemStatus element. - """ - return self.__code - - def __get_description(self): - """ - Returns the description of the ModemStatus element. - - Returns: - String: the description of the ModemStatus element. - """ - return self.__description - - @classmethod - def get(cls, code): - """ - Returns the modem status for the given code. - - Args: - code (Integer): the code of the modem status to get. - - Returns: - :class:`.ModemStatus`: the ModemStatus with the given code. - """ - try: - return cls.lookupTable[code] - except KeyError: - return ModemStatus.UNKNOWN - - code = property(__get_code) - """Integer. The modem status code.""" - - description = property(__get_description) - """String. The modem status description.""" - - -ModemStatus.lookupTable = {x.code: x for x in ModemStatus} -ModemStatus.__doc__ += utils.doc_enum(ModemStatus) - - -@unique -class PowerLevel(Enum): - """ - Enumerates the different power levels. The power level indicates the output - power value of a radio when transmitting data. - """ - LEVEL_LOWEST = (0x00, "Lowest") - LEVEL_LOW = (0x01, "Low") - LEVEL_MEDIUM = (0x02, "Medium") - LEVEL_HIGH = (0x03, "High") - LEVEL_HIGHEST = (0x04, "Highest") - LEVEL_UNKNOWN = (0xFF, "Unknown") - - def __init__(self, code, description): - self.__code = code - self.__description = description - - def __get_code(self): - """ - Returns the code of the PowerLevel element. - - Returns: - Integer: the code of the PowerLevel element. - """ - return self.__code - - def __get_description(self): - """ - Returns the description of the PowerLevel element. - - Returns: - String: the description of the PowerLevel element. - """ - return self.__description - - @classmethod - def get(cls, code): - """ - Returns the power level for the given code. - - Args: - code (Integer): the code of the power level to get. - - Returns: - :class:`.PowerLevel`: the PowerLevel with the given code. - """ - try: - return cls.lookupTable[code] - except KeyError: - return PowerLevel.LEVEL_UNKNOWN - - code = property(__get_code) - """Integer. The power level code.""" - - description = property(__get_description) - """String. The power level description.""" - - -PowerLevel.lookupTable = {x.code: x for x in PowerLevel} -PowerLevel.__doc__ += utils.doc_enum(PowerLevel) - - -@unique -class AssociationIndicationStatus(Enum): - """ - Enumerates the different association indication statuses. - """ - SUCCESSFULLY_JOINED = (0x00, "Successfully formed or joined a network.") - AS_TIMEOUT = (0x01, "Active Scan Timeout.") - AS_NO_PANS_FOUND = (0x02, "Active Scan found no PANs.") - AS_ASSOCIATION_NOT_ALLOWED = (0x03, "Active Scan found PAN, but the CoordinatorAllowAssociation bit is not set.") - AS_BEACONS_NOT_SUPPORTED = (0x04, "Active Scan found PAN, but Coordinator and End Device are not configured to " - "support beacons.") - AS_ID_DOESNT_MATCH = (0x05, "Active Scan found PAN, but the Coordinator ID parameter does not match the ID " - "parameter of the End Device.") - AS_CHANNEL_DOESNT_MATCH = (0x06, "Active Scan found PAN, but the Coordinator CH parameter does not match " - "the CH parameter of the End Device.") - ENERGY_SCAN_TIMEOUT = (0x07, "Energy Scan Timeout.") - COORDINATOR_START_REQUEST_FAILED = (0x08, "Coordinator start request failed.") - COORDINATOR_INVALID_PARAMETER = (0x09, "Coordinator could not start due to invalid parameter.") - COORDINATOR_REALIGNMENT = (0x0A, "Coordinator Realignment is in progress.") - AR_NOT_SENT = (0x0B, "Association Request not sent.") - AR_TIMED_OUT = (0x0C, "Association Request timed out - no reply was received.") - AR_INVALID_PARAMETER = (0x0D, "Association Request had an Invalid Parameter.") - AR_CHANNEL_ACCESS_FAILURE = (0x0E, "Association Request Channel Access Failure. Request was not " - "transmitted - CCA failure.") - AR_COORDINATOR_ACK_WASNT_RECEIVED = (0x0F, "Remote Coordinator did not send an ACK after Association " - "Request was sent.") - AR_COORDINATOR_DIDNT_REPLY = (0x10, "Remote Coordinator did not reply to the Association Request, but an ACK " - "was received after sending the request.") - SYNCHRONIZATION_LOST = (0x12, "Sync-Loss - Lost synchronization with a Beaconing Coordinator.") - DISASSOCIATED = (0x13, " Disassociated - No longer associated to Coordinator.") - NO_PANS_FOUND = (0x21, "Scan found no PANs.") - NO_PANS_WITH_ID_FOUND = (0x22, "Scan found no valid PANs based on current SC and ID settings.") - NJ_EXPIRED = (0x23, "Valid Coordinator or Routers found, but they are not allowing joining (NJ expired).") - NO_JOINABLE_BEACONS_FOUND = (0x24, "No joinable beacons were found.") - UNEXPECTED_STATE = (0x25, "Unexpected state, node should not be attempting to join at this time.") - JOIN_FAILED = (0x27, "Node Joining attempt failed (typically due to incompatible security settings).") - COORDINATOR_START_FAILED = (0x2A, "Coordinator Start attempt failed.") - CHECKING_FOR_COORDINATOR = (0x2B, "Checking for an existing coordinator.") - NETWORK_LEAVE_FAILED = (0x2C, "Attempt to leave the network failed.") - DEVICE_DIDNT_RESPOND = (0xAB, "Attempted to join a device that did not respond.") - UNSECURED_KEY_RECEIVED = (0xAC, "Secure join error - network security key received unsecured.") - KEY_NOT_RECEIVED = (0xAD, "Secure join error - network security key not received.") - INVALID_SECURITY_KEY = (0xAF, "Secure join error - joining device does not have the right preconfigured link key.") - SCANNING_NETWORK = (0xFF, "Scanning for a network/Attempting to associate.") - - def __init__(self, code, description): - self.__code = code - self.__description = description - - def __get_code(self): - """ - Returns the code of the ``AssociationIndicationStatus`` element. - - Returns: - Integer: the code of the ``AssociationIndicationStatus`` element. - """ - return self.__code - - def __get_description(self): - """ - Returns the description of the ``AssociationIndicationStatus`` element. - - Returns: - String: the description of the ``AssociationIndicationStatus`` element. - """ - return self.__description - - @classmethod - def get(cls, code): - """ - Returns the ``AssociationIndicationStatus`` for the given code. - - Args: - code (Integer): the code of the ``AssociationIndicationStatus`` to get. - - Returns: - :class:`.AssociationIndicationStatus`: the ``AssociationIndicationStatus`` with the given code. - """ - try: - return cls.lookupTable[code] - except KeyError: - return None - - code = property(__get_code) - """Integer. The association indication status code.""" - - description = property(__get_description) - """String. The association indication status description.""" - - -AssociationIndicationStatus.lookupTable = {x.code: x for x in AssociationIndicationStatus} -AssociationIndicationStatus.__doc__ += utils.doc_enum(AssociationIndicationStatus) - - -@unique -class CellularAssociationIndicationStatus(Enum): - """ - Enumerates the different association indication statuses for the Cellular protocol. - """ - SUCCESSFULLY_CONNECTED = (0x00, "Connected to the Internet.") - REGISTERING_CELLULAR_NETWORK = (0x22, "Registering to cellular network") - CONNECTING_INTERNET = (0x23, "Connecting to the Internet") - MODEM_FIRMWARE_CORRUPT = (0x24, "The cellular component requires a new firmware image.") - REGISTRATION_DENIED = (0x25, "Cellular network registration was denied.") - AIRPLANE_MODE = (0x2A, "Airplane mode is active.") - USB_DIRECT = (0x2B, "USB Direct mode is active.") - PSM_LOW_POWER = (0x2C, "The cellular component is in the PSM low-power state.") - BYPASS_MODE = (0x2F, "Bypass mode active") - INITIALIZING = (0xFF, "Initializing") - - def __init__(self, code, description): - self.__code = code - self.__description = description - - def __get_code(self): - """ - Returns the code of the ``CellularAssociationIndicationStatus`` element. - - Returns: - Integer: the code of the ``CellularAssociationIndicationStatus`` element. - """ - return self.__code - - def __get_description(self): - """ - Returns the description of the ``CellularAssociationIndicationStatus`` element. - - Returns: - String: the description of the ``CellularAssociationIndicationStatus`` element. - """ - return self.__description - - @classmethod - def get(cls, code): - """ - Returns the ``CellularAssociationIndicationStatus`` for the given code. - - Args: - code (Integer): the code of the ``CellularAssociationIndicationStatus`` to get. - - Returns: - :class:`.CellularAssociationIndicationStatus`: the ``CellularAssociationIndicationStatus`` - with the given code. - """ - try: - return cls.lookupTable[code] - except KeyError: - return None - - code = property(__get_code) - """Integer. The cellular association indication status code.""" - - description = property(__get_description) - """String. The cellular association indication status description.""" - - -CellularAssociationIndicationStatus.lookupTable = {x.code: x for x in CellularAssociationIndicationStatus} -CellularAssociationIndicationStatus.__doc__ += utils.doc_enum(CellularAssociationIndicationStatus) - - -@unique -class DeviceCloudStatus(Enum): - """ - Enumerates the different Device Cloud statuses. - """ - SUCCESS = (0x00, "Success") - BAD_REQUEST = (0x01, "Bad request") - RESPONSE_UNAVAILABLE = (0x02, "Response unavailable") - DEVICE_CLOUD_ERROR = (0x03, "Device Cloud error") - CANCELED = (0x20, "Device Request canceled by user") - TIME_OUT = (0x21, "Session timed out") - UNKNOWN_ERROR = (0x40, "Unknown error") - - def __init__(self, code, description): - self.__code = code - self.__description = description - - def __get_code(self): - """ - Returns the code of the ``DeviceCloudStatus`` element. - - Returns: - Integer: the code of the ``DeviceCloudStatus`` element. - """ - return self.__code - - def __get_description(self): - """ - Returns the description of the ``DeviceCloudStatus`` element. - - Returns: - String: the description of the ``DeviceCloudStatus`` element. - """ - return self.__description - - @classmethod - def get(cls, code): - """ - Returns the Device Cloud status for the given code. - - Args: - code (Integer): the code of the Device Cloud status to get. - - Returns: - :class:`.DeviceCloudStatus`: the ``DeviceCloudStatus`` with the given code, ``None`` if there is not any - status with the provided code. - """ - try: - return cls.lookupTable[code] - except KeyError: - return None - - code = property(__get_code) - """Integer. The Device Cloud status code.""" - - description = property(__get_description) - """String. The Device Cloud status description.""" - - -DeviceCloudStatus.lookupTable = {x.code: x for x in DeviceCloudStatus} -DeviceCloudStatus.__doc__ += utils.doc_enum(DeviceCloudStatus) - - -@unique -class FrameError(Enum): - """ - Enumerates the different frame errors. - """ - INVALID_TYPE = (0x02, "Invalid frame type") - INVALID_LENGTH = (0x03, "Invalid frame length") - INVALID_CHECKSUM = (0x04, "Erroneous checksum on last frame") - PAYLOAD_TOO_BIG = (0x05, "Payload of last API frame was too big to fit into a buffer") - STRING_ENTRY_TOO_BIG = (0x06, "String entry was too big on last API frame sent") - WRONG_STATE = (0x07, "Wrong state to receive frame") - WRONG_REQUEST_ID = (0x08, "Device request ID of device response didn't match the number in the request") - - def __init__(self, code, description): - self.__code = code - self.__description = description - - def __get_code(self): - """ - Returns the code of the ``FrameError`` element. - - Returns: - Integer: the code of the ``FrameError`` element. - """ - return self.__code - - def __get_description(self): - """ - Returns the description of the ``FrameError`` element. - - Returns: - String: the description of the ``FrameError`` element. - """ - return self.__description - - @classmethod - def get(cls, code): - """ - Returns the frame error for the given code. - - Args: - code (Integer): the code of the frame error to get. - - Returns: - :class:`.FrameError`: the ``FrameError`` with the given code, ``None`` if there is not any frame error - with the provided code. - """ - try: - return cls.lookupTable[code] - except KeyError: - return None - - code = property(__get_code) - """Integer. The frame error code.""" - - description = property(__get_description) - """String. The frame error description.""" - - -FrameError.lookupTable = {x.code: x for x in FrameError} -FrameError.__doc__ += utils.doc_enum(FrameError) - - -@unique -class WiFiAssociationIndicationStatus(Enum): - """ - Enumerates the different Wi-Fi association indication statuses. - """ - SUCCESSFULLY_JOINED = (0x00, "Successfully joined to access point.") - INITIALIZING = (0x01, "Initialization in progress.") - INITIALIZED = (0x02, "Initialized, but not yet scanning.") - DISCONNECTING = (0x13, "Disconnecting from access point.") - SSID_NOT_CONFIGURED = (0x23, "SSID not configured") - INVALID_KEY = (0x24, "Encryption key invalid (NULL or invalid length).") - JOIN_FAILED = (0x27, "SSID found, but join failed.") - WAITING_FOR_AUTH = (0x40, "Waiting for WPA or WPA2 authentication.") - WAITING_FOR_IP = (0x41, "Joined to a network and waiting for IP address.") - SETTING_UP_SOCKETS = (0x42, "Joined to a network and IP configured. Setting up listening sockets.") - SCANNING_FOR_SSID = (0xFF, "Scanning for the configured SSID.") - - def __init__(self, code, description): - self.__code = code - self.__description = description - - def __get_code(self): - """ - Returns the code of the ``WiFiAssociationIndicationStatus`` element. - - Returns: - Integer: the code of the ``WiFiAssociationIndicationStatus`` element. - """ - return self.__code - - def __get_description(self): - """ - Returns the description of the ``WiFiAssociationIndicationStatus`` element. - - Returns: - String: the description of the ``WiFiAssociationIndicationStatus`` element. - """ - return self.__description - - @classmethod - def get(cls, code): - """ - Returns the Wi-Fi association indication status for the given code. - - Args: - code (Integer): the code of the Wi-Fi association indication status to get. - - Returns: - :class:`.WiFiAssociationIndicationStatus`: the ``WiFiAssociationIndicationStatus`` with the given code, - ``None`` if there is not any Wi-Fi association indication status with the provided code. - """ - try: - return cls.lookupTable[code] - except KeyError: - return None - - code = property(__get_code) - """Integer. The Wi-Fi association indication status code.""" - - description = property(__get_description) - """String. The Wi-Fi association indication status description.""" - - -WiFiAssociationIndicationStatus.lookupTable = {x.code: x for x in WiFiAssociationIndicationStatus} -WiFiAssociationIndicationStatus.__doc__ += utils.doc_enum(WiFiAssociationIndicationStatus) - - -@unique -class NetworkDiscoveryStatus(Enum): - """ - Enumerates the different statuses of the network discovery process. - """ - SUCCESS = (0x00, "Success") - ERROR_READ_TIMEOUT = (0x01, "Read timeout error") - - def __init__(self, code, description): - self.__code = code - self.__description = description - - def __get_code(self): - """ - Returns the code of the ``NetworkDiscoveryStatus`` element. - - Returns: - Integer: the code of the ``NetworkDiscoveryStatus`` element. - """ - return self.__code - - def __get_description(self): - """ - Returns the description of the ``NetworkDiscoveryStatus`` element. - - Returns: - String: the description of the ``NetworkDiscoveryStatus`` element. - """ - return self.__description - - @classmethod - def get(cls, code): - """ - Returns the network discovery status for the given code. - - Args: - code (Integer): the code of the network discovery status to get. - - Returns: - :class:`.NetworkDiscoveryStatus`: the ``NetworkDiscoveryStatus`` with the given code, ``None`` if - there is not any status with the provided code. - """ - try: - return cls.lookupTable[code] - except KeyError: - return None - - code = property(__get_code) - """Integer. The network discovery status code.""" - - description = property(__get_description) - """String. The network discovery status description.""" - - -NetworkDiscoveryStatus.lookupTable = {x.code: x for x in NetworkDiscoveryStatus} -NetworkDiscoveryStatus.__doc__ += utils.doc_enum(NetworkDiscoveryStatus) diff --git a/digi/xbee/packets/__init__.py b/digi/xbee/packets/__init__.py deleted file mode 100644 index 8ea10d3..0000000 --- a/digi/xbee/packets/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -# 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. diff --git a/digi/xbee/packets/aft.py b/digi/xbee/packets/aft.py deleted file mode 100644 index 79e993d..0000000 --- a/digi/xbee/packets/aft.py +++ /dev/null @@ -1,114 +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. - -from enum import Enum, unique -from digi.xbee.util import utils - - -@unique -class ApiFrameType(Enum): - """ - This enumeration lists all the available frame types used in any XBee protocol. - - | Inherited properties: - | **name** (String): the name (id) of this ApiFrameType. - | **value** (String): the value of this ApiFrameType. - - """ - TX_64 = (0x00, "TX (Transmit) Request 64-bit address") - TX_16 = (0x01, "TX (Transmit) Request 16-bit address") - REMOTE_AT_COMMAND_REQUEST_WIFI = (0x07, "Remote AT Command Request (Wi-Fi)") - AT_COMMAND = (0x08, "AT Command") - AT_COMMAND_QUEUE = (0x09, "AT Command Queue") - TRANSMIT_REQUEST = (0x10, "Transmit Request") - EXPLICIT_ADDRESSING = (0x11, "Explicit Addressing Command Frame") - REMOTE_AT_COMMAND_REQUEST = (0x17, "Remote AT Command Request") - TX_SMS = (0x1F, "TX SMS") - TX_IPV4 = (0x20, "TX IPv4") - SEND_DATA_REQUEST = (0x28, "Send Data Request") - DEVICE_RESPONSE = (0x2A, "Device Response") - USER_DATA_RELAY_REQUEST = (0x2D, "User Data Relay Request") - 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") - RX_IO_16 = (0x83, "IO Data Sample RX 16-bit Address Indicator") - REMOTE_AT_COMMAND_RESPONSE_WIFI = (0x87, "Remote AT Command Response (Wi-Fi)") - AT_COMMAND_RESPONSE = (0x88, "AT Command Response") - TX_STATUS = (0x89, "TX (Transmit) Status") - MODEM_STATUS = (0x8A, "Modem Status") - TRANSMIT_STATUS = (0x8B, "Transmit Status") - IO_DATA_SAMPLE_RX_INDICATOR_WIFI = (0x8F, "IO Data Sample RX Indicator (Wi-Fi)") - RECEIVE_PACKET = (0x90, "Receive Packet") - EXPLICIT_RX_INDICATOR = (0x91, "Explicit RX Indicator") - IO_DATA_SAMPLE_RX_INDICATOR = (0x92, "IO Data Sample RX Indicator") - REMOTE_AT_COMMAND_RESPONSE = (0x97, "Remote Command Response") - RX_SMS = (0x9F, "RX SMS") - 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") - FRAME_ERROR = (0xFE, "Frame Error") - GENERIC = (0xFF, "Generic") - UNKNOWN = (-1, "Unknown Packet") - - def __init__(self, code, description): - self.__code = code - self.__description = description - - def __get_code(self): - """ - Returns the code of the ApiFrameType element. - - Returns: - Integer: the code of the ApiFrameType element. - """ - return self.__code - - def __get_description(self): - """ - Returns the description of the ApiFrameType element. - - Returns: - Integer: the description of the ApiFrameType element. - """ - return self.__description - - @classmethod - def get(cls, code): - """ - Retrieves the api frame type associated to the given ID. - - Args: - code (Integer): the code of the API frame type to get. - - Returns: - :class:`.ApiFrameType`: the API frame type associated to the given code or ``UNKNOWN`` if - the given code is not a valid ApiFrameType code. - """ - try: - return cls.lookupTable[code] - except KeyError: - return ApiFrameType.UNKNOWN - - code = property(__get_code) - """Integer. The API frame type code.""" - - description = property(__get_description) - """String. The API frame type description.""" - - -# Dictionary 255: - raise ValueError("Frame ID must be between 0 and 255.") - self._frame_id = frame_id - - @staticmethod - def _check_api_packet(raw, min_length=5): - """ - Checks the not escaped bytearray 'raw' meets conditions. - - Args: - raw (Bytearray): non-escaped bytearray to be checked. - min_length (Integer): the minimum length of the packet in bytes. - - Raises: - InvalidPacketException: if the bytearray length is less than 5. (start delim. + length (2 bytes) + - frame type + checksum = 5 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). - - .. seealso:: - | :mod:`.factory` - """ - if len(raw) < min_length: - 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("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("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("Wrong checksum") - - @abstractmethod - def _get_api_packet_spec_data(self): - """ - Returns the frame specific data without frame type and frame ID fields. - - Returns: - Bytearray: the frame specific data without frame type and frame ID fields. - """ - pass - - @abstractmethod - def needs_id(self): - """ - Returns whether the packet requires frame ID or not. - - Returns: - Boolean: ``True`` if the packet needs frame ID, ``False`` otherwise. - """ - pass - - @abstractmethod - def _get_api_packet_spec_data_dict(self): - """ - Similar to :meth:`XBeeAPIPacket._get_api_packet_spec_data` but returns data as dictionary or list. - - Returns: - Dictionary: data as dictionary or list. - """ - pass - - frame_id = property(__get_frame_id, __set_frame_id) - - -class GenericXBeePacket(XBeeAPIPacket): - """ - This class represents a basic and Generic XBee packet. - - .. seealso:: - | :class:`.XBeeAPIPacket` - """ - - __MIN_PACKET_LENGTH = 5 - - def __init__(self, rf_data): - """ - Class constructor. Instantiates a :class:`.GenericXBeePacket` object with the provided parameters. - - Args: - rf_data (bytearray): the frame specific data without frame type and frame ID. - - .. seealso:: - | :mod:`.factory` - | :class:`.XBeeAPIPacket` - """ - super(GenericXBeePacket, self).__init__(api_frame_type=ApiFrameType.GENERIC) - self.__rf_data = rf_data - - @staticmethod - def create_packet(raw, operating_mode=OperatingMode.API_MODE): - """ - Override method. - - Returns: - :class:`.GenericXBeePacket`: the GenericXBeePacket generated. - - Raises: - InvalidPacketException: if the bytearray length is less than 5. (start delim. + length (2 bytes) + - frame type + checksum = 5 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 different than :attr:`.ApiFrameType.GENERIC`. - InvalidOperatingModeException: if ``operating_mode`` is not supported. - - .. seealso:: - | :meth:`.XBeePacket.create_packet` - | :meth:`.XBeeAPIPacket._check_api_packet` - """ - if operating_mode != OperatingMode.ESCAPED_API_MODE and operating_mode != OperatingMode.API_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("Wrong frame type, expected: " + ApiFrameType.GENERIC.description + - ". Value: " + ApiFrameType.GENERIC.code) - - return GenericXBeePacket(raw[4:-1]) - - def _get_api_packet_spec_data(self): - """ - Override method. - - .. seealso:: - | :meth:`.XBeeAPIPacket._get_api_packet_spec_data` - """ - return bytearray(self.__rf_data) - - def needs_id(self): - """ - Override method. - - .. seealso:: - | :meth:`.XBeeAPIPacket.needs_id` - """ - return False - - def _get_api_packet_spec_data_dict(self): - """ - Override method. - - .. seealso:: - | :meth:`.XBeeAPIPacket._get_api_packet_spec_data_dict` - """ - return {DictKeys.RF_DATA: self.__rf_data} - - -class UnknownXBeePacket(XBeeAPIPacket): - """ - This class represents an unknown XBee packet. - - .. seealso:: - | :class:`.XBeeAPIPacket` - """ - - __MIN_PACKET_LENGTH = 5 - - def __init__(self, api_frame, rf_data): - """ - Class constructor. Instantiates a :class:`.UnknownXBeePacket` object with the provided parameters. - - Args: - api_frame (Integer): the API frame integer value of this packet. - rf_data (bytearray): the frame specific data without frame type and frame ID. - - .. seealso:: - | :mod:`.factory` - | :class:`.XBeeAPIPacket` - """ - super(UnknownXBeePacket, self).__init__(api_frame_type=api_frame) - self.__rf_data = rf_data - - @staticmethod - def create_packet(raw, operating_mode=OperatingMode.API_MODE): - """ - Override method. - - Returns: - :class:`.UnknownXBeePacket`: the UnknownXBeePacket generated. - - Raises: - InvalidPacketException: if the bytearray length is less than 5. (start delim. + length (2 bytes) + - frame type + checksum = 5 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). - InvalidOperatingModeException: if ``operating_mode`` is not supported. - - .. seealso:: - | :meth:`.XBeePacket.create_packet` - | :meth:`.XBeeAPIPacket._check_api_packet` - """ - if operating_mode != OperatingMode.ESCAPED_API_MODE and operating_mode != OperatingMode.API_MODE: - raise InvalidOperatingModeException(operating_mode + " is not supported.") - - XBeeAPIPacket._check_api_packet(raw, min_length=UnknownXBeePacket.__MIN_PACKET_LENGTH) - - return UnknownXBeePacket(raw[3], raw[4:-1]) - - def _get_api_packet_spec_data(self): - """ - Override method. - - .. seealso:: - | :meth:`.XBeeAPIPacket._get_api_packet_spec_data` - """ - return bytearray(self.__rf_data) - - def needs_id(self): - """ - Override method. - - .. seealso:: - | :meth:`.XBeeAPIPacket.needs_id` - """ - return False - - def _get_api_packet_spec_data_dict(self): - """ - Override method. - - .. seealso:: - | :meth:`.XBeeAPIPacket._get_api_packet_spec_data_dict` - """ - return {DictKeys.RF_DATA: self.__rf_data} diff --git a/digi/xbee/packets/cellular.py b/digi/xbee/packets/cellular.py deleted file mode 100644 index c754e02..0000000 --- a/digi/xbee/packets/cellular.py +++ /dev/null @@ -1,353 +0,0 @@ -# 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 -# file, You can obtain one at http://mozilla.org/MPL/2.0/. -# -# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR -# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF -# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - -from digi.xbee.packets.base import XBeeAPIPacket, DictKeys -from digi.xbee.exception import InvalidOperatingModeException, InvalidPacketException -from digi.xbee.packets.aft import ApiFrameType -from digi.xbee.models.mode import OperatingMode -from digi.xbee.models.options import TransmitOptions -from digi.xbee.util import utils -import re - - -PATTERN_PHONE_NUMBER = "^\+?\d+$" -"""Pattern used to validate the phone number parameter of SMS packets.""" - - -class RXSMSPacket(XBeeAPIPacket): - """ - This class represents an RX (Receive) SMS packet. Packet is built - using the parameters of the constructor or providing a valid byte array. - - .. seealso:: - | :class:`.TXSMSPacket` - | :class:`.XBeeAPIPacket` - """ - - __MIN_PACKET_LENGTH = 25 - - def __init__(self, phone_number, data): - """ - Class constructor. Instantiates a new :class:`.RXSMSPacket` object withe the provided parameters. - - Args: - phone_number (String): phone number of the device that sent the SMS. - data (String): packet data (text of the SMS). - - Raises: - ValueError: if length of ``phone_number`` is greater than 20. - ValueError: if ``phone_number`` is not a valid phone number. - """ - if len(phone_number) > 20: - raise ValueError("Phone number length cannot be greater than 20 bytes") - if not re.match(PATTERN_PHONE_NUMBER, phone_number): - raise ValueError("Phone number invalid, only numbers and '+' prefix allowed.") - super().__init__(ApiFrameType.RX_SMS) - - self.__phone_number = bytearray(20) - self.__phone_number[0:len(phone_number)] = phone_number.encode("utf8") - self.__data = data - - @staticmethod - def create_packet(raw, operating_mode): - """ - Override method. - - Returns: - :class:`.RXSMSPacket` - - Raises: - InvalidPacketException: if the bytearray length is less than 25. (start delim + length (2 bytes) + - frame type + phone number (20 bytes) + checksum = 25 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:`.SPECIAL_BYTE`. - InvalidPacketException: if the calculated checksum is different than the checksum field value (last byte). - InvalidPacketException: if the frame type is different than :py:attr:`.ApiFrameType.RX_SMS`. - InvalidOperatingModeException: if ``operating_mode`` is not supported. - - .. seealso:: - | :meth:`.XBeePacket.create_packet` - """ - if operating_mode != OperatingMode.ESCAPED_API_MODE and operating_mode != OperatingMode.API_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("This packet is not an RXSMSPacket") - - return RXSMSPacket(raw[4:23].decode("utf8").replace("\0", ""), raw[24:-1].decode("utf8")) - - def needs_id(self): - """ - Override method. - - .. seealso:: - | :meth:`.XBeeAPIPacket.needs_id` - """ - return False - - def get_phone_number_byte_array(self): - """ - Returns the phone number byte array. - - Returns: - Bytearray: phone number of the device that sent the SMS. - """ - return self.__phone_number - - def __get_phone_number(self): - """ - Returns the phone number of the device that sent the SMS. - - Returns: - String: phone number of the device that sent the SMS. - """ - return self.__phone_number.decode("utf8").replace("\0", "") - - def __set_phone_number(self, phone_number): - """ - Sets the phone number of the device that sent the SMS. - - Args: - phone_number (String): the new phone number. - - Raises: - ValueError: if length of ``phone_number`` is greater than 20. - ValueError: if ``phone_number`` is not a valid phone number. - """ - if len(phone_number) > 20: - raise ValueError("Phone number length cannot be greater than 20 bytes") - if not re.match(PATTERN_PHONE_NUMBER, phone_number): - raise ValueError("Phone number invalid, only numbers and '+' prefix allowed.") - - self.__phone_number = bytearray(20) - self.__phone_number[0:len(phone_number)] = phone_number.encode("utf8") - - def __get_data(self): - """ - Returns the data of the packet (SMS text). - - Returns: - String: the data of the packet. - """ - return self.__data - - def __set_data(self, data): - """ - Sets the data of the packet. - - Args: - data (String): the new data of the packet. - """ - self.__data = data - - def _get_api_packet_spec_data(self): - """ - Override method. - - .. seealso:: - | :meth:`.XBeeAPIPacket._get_API_packet_spec_data` - """ - ret = bytearray() - ret += self.__phone_number - if self.__data is not None: - ret += self.__data.encode("utf8") - return ret - - def _get_api_packet_spec_data_dict(self): - """ - Override method. - - .. seealso:: - | :meth:`.XBeeAPIPacket._get_API_packet_spec_data_dict` - """ - return {DictKeys.PHONE_NUMBER: self.__phone_number, - DictKeys.RF_DATA: self.__data} - - phone_number = property(__get_phone_number, __set_phone_number) - """String. Phone number that sent the SMS.""" - - data = property(__get_data, __set_data) - """String. Data of the SMS.""" - - -class TXSMSPacket(XBeeAPIPacket): - """ - This class represents a TX (Transmit) SMS packet. Packet is built - using the parameters of the constructor or providing a valid byte array. - - .. seealso:: - | :class:`.RXSMSPacket` - | :class:`.XBeeAPIPacket` - """ - - __MIN_PACKET_LENGTH = 27 - - def __init__(self, frame_id, phone_number, data): - """ - Class constructor. Instantiates a new :class:`.TXSMSPacket` object with the provided parameters. - - Args: - frame_id (Integer): the frame ID. Must be between 0 and 255. - phone_number (String): the phone number. - data (String): this packet's data. - - Raises: - ValueError: if ``frame_id`` is not between 0 and 255. - ValueError: if length of ``phone_number`` is greater than 20. - ValueError: if ``phone_number`` is not a valid phone number. - - .. seealso:: - | :class:`.XBeeAPIPacket` - """ - if frame_id < 0 or frame_id > 255: - raise ValueError("Frame id must be between 0 and 255") - if len(phone_number) > 20: - raise ValueError("Phone number length cannot be greater than 20 bytes") - if not re.match(PATTERN_PHONE_NUMBER, phone_number): - raise ValueError("Phone number invalid, only numbers and '+' prefix allowed.") - super().__init__(ApiFrameType.TX_SMS) - - self._frame_id = frame_id - self.__transmit_options = TransmitOptions.NONE.value - self.__phone_number = bytearray(20) - self.__phone_number[0:len(phone_number)] = phone_number.encode("utf8") - self.__data = data - - @staticmethod - def create_packet(raw, operating_mode): - """ - Override method. - - Returns: - :class:`.TXSMSPacket` - - Raises: - InvalidPacketException: if the bytearray length is less than 27. (start delim, length (2 bytes), frame type, - frame id, transmit options, phone number (20 bytes), checksum) - 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:`.SPECIAL_BYTE`. - InvalidPacketException: if the calculated checksum is different than the checksum field value (last byte). - InvalidPacketException: if the frame type is different than :py:attr:`.ApiFrameType.TX_SMS`. - InvalidOperatingModeException: if ``operating_mode`` is not supported. - - .. seealso:: - | :meth:`.XBeePacket.create_packet` - """ - if operating_mode != OperatingMode.ESCAPED_API_MODE and operating_mode != OperatingMode.API_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("This packet is not a TXSMSPacket") - - return TXSMSPacket(raw[4], raw[6:25].decode("utf8").replace("\0", ""), raw[26:-1].decode("utf8")) - - def needs_id(self): - """ - Override method. - - .. seealso:: - | :meth:`.XBeeAPIPacket.needs_id` - """ - return True - - def get_phone_number_byte_array(self): - """ - Returns the phone number byte array. - - Returns: - Bytearray: phone number of the device that sent the SMS. - """ - return self.__phone_number - - def __get_phone_number(self): - """ - Returns the phone number of the transmitter device. - - Returns: - String: the phone number of the transmitter device. - """ - return self.__phone_number.decode("utf8").replace("\0", "") - - def __set_phone_number(self, phone_number): - """ - Sets the phone number of the transmitter device. - - Args: - phone_number (String): the new phone number. - - Raises: - ValueError: if length of ``phone_number`` is greater than 20. - ValueError: if ``phone_number`` is not a valid phone number. - """ - if len(phone_number) > 20: - raise ValueError("Phone number length cannot be greater than 20 bytes") - if not re.match(PATTERN_PHONE_NUMBER, phone_number): - raise ValueError("Phone number invalid, only numbers and '+' prefix allowed.") - - self.__phone_number = bytearray(20) - self.__phone_number[0:len(phone_number)] = phone_number.encode("utf8") - - def __get_data(self): - """ - Returns the data of the packet (SMS text). - - Returns: - Bytearray: packet's data. - """ - return self.__data - - def __set_data(self, data): - """ - Sets the data of the packet. - - Args: - data (Bytearray): the new data of the packet. - """ - self.__data = data - - def _get_api_packet_spec_data(self): - """ - Override method. - - .. seealso:: - | :meth:`.XBeeAPIPacket._get_API_packet_spec_data` - """ - ret = utils.int_to_bytes(self.__transmit_options, num_bytes=1) - ret += self.__phone_number - if self.__data is not None: - ret += self.__data.encode("utf8") - return ret - - def _get_api_packet_spec_data_dict(self): - """ - Override method. - - .. seealso:: - | :meth:`.XBeeAPIPacket._get_API_packet_spec_data_dict` - """ - return {DictKeys.OPTIONS: self.__transmit_options, - DictKeys.PHONE_NUMBER: self.__phone_number, - DictKeys.RF_DATA: self.__data} - - phone_number = property(__get_phone_number, __set_phone_number) - """String. Phone number that sent the SMS.""" - - data = property(__get_data, __set_data) - """String. Data of the SMS.""" diff --git a/digi/xbee/packets/common.py b/digi/xbee/packets/common.py deleted file mode 100644 index 8fba785..0000000 --- a/digi/xbee/packets/common.py +++ /dev/null @@ -1,2894 +0,0 @@ -# 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 -# 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.mode import OperatingMode -from digi.xbee.models.address import XBee16BitAddress, XBee64BitAddress -from digi.xbee.models.status import ATCommandStatus, DiscoveryStatus, TransmitStatus, ModemStatus -from digi.xbee.packets.aft import ApiFrameType -from digi.xbee.packets.base import XBeeAPIPacket, DictKeys -from digi.xbee.util import utils -from digi.xbee.exception import InvalidOperatingModeException, InvalidPacketException -from digi.xbee.io import IOSample, IOLine - - -class ATCommPacket(XBeeAPIPacket): - """ - This class represents an AT command packet. - - Used to query or set module parameters on the local device. This API - command applies changes after executing the command. (Changes made to - module parameters take effect once changes are applied.). - - Command response is received as an :class:`.ATCommResponsePacket`. - - .. seealso:: - | :class:`.ATCommResponsePacket` - | :class:`.XBeeAPIPacket` - """ - - __MIN_PACKET_LENGTH = 6 - - def __init__(self, frame_id, command, parameter=None): - """ - Class constructor. Instantiates a new :class:`.ATCommPacket` object with the provided parameters. - - Args: - frame_id (Integer): the frame ID of the packet. - command (String): the AT command of the packet. Must be a string. - parameter (Bytearray, optional): the AT command parameter. Optional. - - Raises: - ValueError: if ``frame_id`` is less than 0 or greater than 255. - ValueError: if length of ``command`` is different than 2. - - .. seealso:: - | :class:`.XBeeAPIPacket` - """ - if len(command) != 2: - raise ValueError("Invalid command " + command) - - if frame_id < 0 or frame_id > 255: - raise ValueError("Frame id must be between 0 and 255.") - - super(ATCommPacket, self).__init__(ApiFrameType.AT_COMMAND) - self.__command = command - self.__parameter = parameter - self._frame_id = frame_id - - @staticmethod - def create_packet(raw, operating_mode): - """ - Override method. - - Returns: - :class:`.ATCommPacket` - - Raises: - InvalidPacketException: if the bytearray length is less than 6. (start delim. + length (2 bytes) + frame - type + frame id + checksum = 6 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 different than :attr:`.ApiFrameType.AT_COMMAND`. - InvalidOperatingModeException: if ``operating_mode`` is not supported. - - .. seealso:: - | :meth:`.XBeePacket.create_packet` - | :meth:`.XBeeAPIPacket._check_api_packet` - """ - if operating_mode != OperatingMode.ESCAPED_API_MODE and operating_mode != OperatingMode.API_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("This packet is not an AT command packet.") - - return ATCommPacket(raw[4], raw[5:7].decode("utf8"), 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` - """ - if self.__parameter is not None: - return bytearray(self.__command, "utf8") + self.__parameter - return bytearray(self.__command, "utf8") - - def _get_api_packet_spec_data_dict(self): - """ - Override method. - - .. seealso:: - | :meth:`.XBeeAPIPacket._get_api_packet_spec_data_dict` - """ - return {DictKeys.COMMAND: self.__command, - DictKeys.PARAMETER: list(self.__parameter) if self.__parameter is not None else None} - - def __get_command(self): - """ - Returns the AT command of the packet. - - Returns: - String: the AT command of the packet. - """ - return self.__command - - def __set_command(self, command): - """ - Sets the AT command of the packet. - - Args: - command (String): the new AT command of the packet. Must have length = 2. - - Raises: - ValueError: if length of ``command`` is different than 2. - """ - if len(command) != 2: - raise ValueError("Invalid command " + command) - self.__command = command - - def __get_parameter(self): - """ - Returns the parameter of the packet. - - Returns: - Bytearray: the parameter of the packet. - """ - return self.__parameter - - def __set_parameter(self, param): - """ - Sets the parameter of the packet. - - Args: - param (Bytearray): the new parameter of the packet. - """ - self.__parameter = param - - command = property(__get_command, __set_command) - """String. AT command.""" - - parameter = property(__get_parameter, __set_parameter) - """Bytearray. AT command parameter.""" - - -class ATCommQueuePacket(XBeeAPIPacket): - """ - This class represents an AT command Queue packet. - - Used to query or set module parameters on the local device. - - In contrast to the :class:`.ATCommPacket` API packet, new parameter - values are queued and not applied until either an :class:`.ATCommPacket` - is sent or the ``applyChanges()`` method of the :class:`.XBeeDevice` - class is issued. - - Command response is received as an :class:`.ATCommResponsePacket`. - - .. seealso:: - | :class:`.ATCommResponsePacket` - | :class:`.XBeeAPIPacket` - """ - - __MIN_PACKET_LENGTH = 6 - - def __init__(self, frame_id, command, parameter=None): - """ - Class constructor. Instantiates a new :class:`.ATCommQueuePacket` object with the provided parameters. - - Args: - frame_id (Integer): the frame ID of the packet. - command (String): the AT command of the packet. Must be a string. - parameter (Bytearray, optional): the AT command parameter. Optional. - - Raises: - ValueError: if ``frame_id`` is less than 0 or greater than 255. - ValueError: if length of ``command`` is different than 2. - - .. seealso:: - | :class:`.XBeeAPIPacket` - """ - if len(command) != 2: - raise ValueError("Invalid command " + command) - - if frame_id < 0 or frame_id > 255: - raise ValueError("Frame id must be between 0 and 255.") - - super(ATCommQueuePacket, self).__init__(ApiFrameType.AT_COMMAND_QUEUE) - self.__command = command - self.__parameter = parameter - self._frame_id = frame_id - - @staticmethod - def create_packet(raw, operating_mode): - """ - Override method. - - Returns: - :class:`.ATCommQueuePacket` - - Raises: - InvalidPacketException: if the bytearray length is less than 6. (start delim. + length (2 bytes) + frame - type + frame id + checksum = 6 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 different than :attr:`.ApiFrameType.AT_COMMAND_QUEUE`. - InvalidOperatingModeException: if ``operating_mode`` is not supported. - - .. seealso:: - | :meth:`.XBeePacket.create_packet` - | :meth:`.XBeeAPIPacket._check_api_packet` - """ - if operating_mode != OperatingMode.ESCAPED_API_MODE and operating_mode != OperatingMode.API_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("This packet is not an AT command Queue packet.") - - return ATCommQueuePacket(raw[4], raw[5:7].decode("utf8"), 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` - """ - if self.__parameter is not None: - return bytearray(self.__command, "utf8") + self.__parameter - return bytearray(self.__command, "utf8") - - def _get_api_packet_spec_data_dict(self): - """ - Override method. - - .. seealso:: - | :meth:`.XBeeAPIPacket._get_api_packet_spec_data_dict` - """ - return {DictKeys.COMMAND: self.__command, - DictKeys.PARAMETER: list(self.__parameter) if self.__parameter is not None else None} - - def __get_command(self): - """ - Returns the AT command of the packet. - - Returns: - String: the AT command of the packet. - """ - return self.__command - - def __set_command(self, command): - """ - Sets the AT command of the packet. - - Args: - command (String): the new AT command of the packet. Must have length = 2. - - Raises: - ValueError: if length of ``command`` is different than 2. - """ - if len(command) != 2: - raise ValueError("Invalid command " + command) - self.__command = command - - def __get_parameter(self): - """ - Returns the parameter of the packet. - - Returns: - Bytearray: the parameter of the packet. - """ - return self.__parameter - - def __set_parameter(self, param): - """ - Sets the parameter of the packet. - - Args: - param (Bytearray): the new parameter of the packet. - """ - self.__parameter = param - - command = property(__get_command, __set_command) - """String. AT command.""" - - parameter = property(__get_parameter, __set_parameter) - """Bytearray. AT command parameter.""" - - -class ATCommResponsePacket(XBeeAPIPacket): - """ - This class represents an AT command response packet. - - In response to an AT command message, the module will send an AT command - response message. Some commands will send back multiple frames (for example, - the ``ND`` - Node Discover command). - - This packet is received in response of an :class:`.ATCommPacket`. - - Response also includes an :class:`.ATCommandStatus` object with the status - of the AT command. - - .. seealso:: - | :class:`.ATCommPacket` - | :class:`.ATCommandStatus` - | :class:`.XBeeAPIPacket` - """ - - __MIN_PACKET_LENGTH = 9 - - 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`): the status of the AT command. - comm_value (Bytearray, optional): the AT command response value. Optional. - - Raises: - ValueError: if ``frame_id`` is less than 0 or greater than 255. - ValueError: if length of ``command`` is different than 2. - - .. seealso:: - | :class:`.ATCommandStatus` - | :class:`.XBeeAPIPacket` - """ - if frame_id < 0 or frame_id > 255: - raise ValueError("Frame id must be between 0 and 255.") - if len(command) != 2: - raise ValueError("Invalid command " + command) - - super(ATCommResponsePacket, self).__init__(ApiFrameType.AT_COMMAND_RESPONSE) - self._frame_id = frame_id - self.__command = command - self.__response_status = response_status - self.__comm_value = comm_value - - @staticmethod - def create_packet(raw, operating_mode): - """ - Override method. - - Returns: - :class:`.ATCommResponsePacket` - - Raises: - InvalidPacketException: if the bytearray length is less than 9. (start delim. + length (2 bytes) + - frame type + frame id + at command (2 bytes) + command 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 different than :attr:`.ApiFrameType.AT_COMMAND_RESPONSE`. - InvalidPacketException: if the command status field is not a valid value. See :class:`.ATCommandStatus`. - InvalidOperatingModeException: if ``operating_mode`` is not supported. - - .. seealso:: - | :meth:`.XBeePacket.create_packet` - | :meth:`.XBeeAPIPacket._check_api_packet` - """ - if operating_mode != OperatingMode.ESCAPED_API_MODE and operating_mode != OperatingMode.API_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("This packet is not an AT command response packet.") - if ATCommandStatus.get(raw[7]) is None: - raise InvalidPacketException("Invalid command status.") - - return ATCommResponsePacket(raw[4], raw[5:7].decode("utf8"), ATCommandStatus.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(self.__command, "utf8") - ret.append(self.__response_status.code) - if self.__comm_value is not None: - ret += self.__comm_value - return ret - - def _get_api_packet_spec_data_dict(self): - """ - Override method. - - .. seealso:: - | :meth:`.XBeeAPIPacket._get_api_packet_spec_data_dict` - """ - return {DictKeys.COMMAND: self.__command, - DictKeys.AT_CMD_STATUS: self.__response_status, - DictKeys.RF_DATA: list(self.__comm_value) if self.__comm_value is not None else None} - - def __get_command(self): - """ - Returns the AT command of the packet. - - Returns: - String: the AT command of the packet. - """ - return self.__command - - def __set_command(self, command): - """ - Sets the AT command of the packet. - - Args: - command (String): the new AT command of the packet. Must have length = 2. - - Raises: - ValueError: if length of ``command`` is different than 2. - """ - if len(command) != 2: - raise ValueError("Invalid command " + command) - self.__command = command - - def __get_value(self): - """ - Returns the AT command response value. - - Returns: - Bytearray: the AT command response value. - """ - return self.__comm_value - - def __set_value(self, __comm_value): - """ - Sets the AT command response value. - - Args: - __comm_value (Bytearray): the new AT command response value. - """ - self.__comm_value = __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 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. - - .. seealso:: - | :class:`.ATCommandStatus` - """ - self.__response_status = response_status - - command = property(__get_command, __set_command) - """String. AT command.""" - - command_value = property(__get_value, __set_value) - """Bytearray. AT command value.""" - - status = property(__get_response_status, __set_response_status) - """:class:`.ATCommandStatus`. AT command response status.""" - - -class ReceivePacket(XBeeAPIPacket): - """ - This class represents a receive packet. Packet is built using the parameters - of the constructor or providing a valid byte array. - - When the module receives an RF packet, it is sent out the UART using this - message type. - - This packet is received when external devices send transmit request - packets to this module. - - Among received data, some options can also be received indicating - transmission parameters. - - .. seealso:: - | :class:`.TransmitPacket` - | :class:`.ReceiveOptions` - | :class:`.XBeeAPIPacket` - """ - - __MIN_PACKET_LENGTH = 16 - - def __init__(self, x64bit_addr, x16bit_addr, receive_options, rf_data=None): - """ - Class constructor. Instantiates a new :class:`.ReceivePacket` object with the provided parameters. - - Args: - x64bit_addr (:class:`.XBee64BitAddress`): the 64-bit source address. - x16bit_addr (:class:`.XBee16BitAddress`): the 16-bit source address. - receive_options (Integer): bitfield indicating the receive options. - rf_data (Bytearray, optional): received RF data. Optional. - - .. seealso:: - | :class:`.ReceiveOptions` - | :class:`.XBee16BitAddress` - | :class:`.XBee64BitAddress` - | :class:`.XBeeAPIPacket` - """ - super(ReceivePacket, self).__init__(ApiFrameType.RECEIVE_PACKET) - self.__x64bit_addr = x64bit_addr - self.__x16bit_addr = x16bit_addr - self.__receive_options = receive_options - self.__rf_data = rf_data - - @staticmethod - def create_packet(raw, operating_mode): - """ - Override method. - - Returns: - :class:`.ATCommResponsePacket` - - Raises: - InvalidPacketException: if the bytearray length is less than 16. (start delim. + length (2 bytes) + frame - type + frame id + 64bit addr. + 16bit addr. + Receive options + checksum = 16 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.RECEIVE_PACKET`. - InvalidOperatingModeException: if ``operating_mode`` is not supported. - - .. seealso:: - | :meth:`.XBeePacket.create_packet` - | :meth:`.XBeeAPIPacket._check_api_packet` - """ - if operating_mode != OperatingMode.ESCAPED_API_MODE and operating_mode != OperatingMode.API_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("This packet is not a receive packet.") - return ReceivePacket(XBee64BitAddress(raw[4:12]), - XBee16BitAddress(raw[12:14]), - raw[14], - raw[15:-1]) - - 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 = self.__x64bit_addr.address - ret += self.__x16bit_addr.address - ret.append(self.__receive_options) - if self.__rf_data is not None: - return ret + self.__rf_data - 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: self.__x64bit_addr.address, - DictKeys.X16BIT_ADDR: self.__x16bit_addr.address, - DictKeys.RECEIVE_OPTIONS: self.__receive_options, - DictKeys.RF_DATA: list(self.__rf_data) if self.__rf_data is not None else None} - - def __get_64bit_addr(self): - """ - Returns the 64-bit source address. - - Returns: - :class:`.XBee64BitAddress`: the 64-bit source address. - - .. seealso:: - | :class:`.XBee64BitAddress` - """ - return self.__x64bit_addr - - def __set_64bit_addr(self, x64bit_addr): - """ - Sets the 64-bit source address. - - Args: - x64bit_addr (:class:`.XBee64BitAddress`): the new 64-bit source address. - - .. seealso:: - | :class:`.XBee64BitAddress` - """ - self.__x64bit_addr = x64bit_addr - - def __get_16bit_addr(self): - """ - Returns the 16-bit source address. - - Returns: - :class:`.XBee16BitAddress`: the 16-bit source address. - - .. seealso:: - | :class:`.XBee16BitAddress` - """ - return self.__x16bit_addr - - def __set_16bit_addr(self, x16bit_addr): - """ - Sets the 16-bit source address. - - Args: - x16bit_addr (:class:`.XBee16BitAddress`): the new 16-bit source address. - - .. seealso:: - | :class:`.XBee16BitAddress` - """ - self.__x16bit_addr = x16bit_addr - - def __get_options(self): - """ - Returns the receive options bitfield. - - Returns: - Integer: the receive options bitfield. - - .. seealso:: - | :class:`.ReceiveOptions` - """ - return self.__receive_options - - def __set_options(self, receive_options): - """ - Sets the receive options bitfield. - - Args: - receive_options (Integer): the new receive options bitfield. - - .. seealso:: - | :class:`.ReceiveOptions` - """ - self.__receive_options = receive_options - - def __get_rf_data(self): - """ - Returns the received RF data. - - Returns: - Bytearray: the received RF data. - """ - if self.__rf_data is None: - return None - return self.__rf_data.copy() - - def __set_rf_data(self, rf_data): - """ - Sets the received RF data. - - Args: - rf_data (Bytearray): the new received RF data. - """ - if rf_data is None: - self.__rf_data = None - else: - self.__rf_data = rf_data.copy() - - x64bit_source_addr = property(__get_64bit_addr, __set_64bit_addr) - """:class:`.XBee64BitAddress`. 64-bit source address.""" - - x16bit_source_addr = property(__get_16bit_addr, __set_16bit_addr) - """:class:`.XBee16BitAddress`. 16-bit source address.""" - - receive_options = property(__get_options, __set_options) - """Integer. Receive options bitfield.""" - - rf_data = property(__get_rf_data, __set_rf_data) - """Bytearray. Received RF data.""" - - -class RemoteATCommandPacket(XBeeAPIPacket): - """ - This class represents a Remote AT command Request packet. Packet is built - using the parameters of the constructor or providing a valid byte array. - - Used to query or set module parameters on a remote device. For parameter - changes on the remote device to take effect, changes must be applied, either - by setting the apply changes options bit, or by sending an ``AC`` command - to the remote node. - - Remote command options are set as a bitfield. - - If configured, command response is received as a :class:`.RemoteATCommandResponsePacket`. - - .. seealso:: - | :class:`.RemoteATCommandResponsePacket` - | :class:`.XBeeAPIPacket` - """ - - __MIN_PACKET_LENGTH = 19 - - def __init__(self, frame_id, x64bit_addr, x16bit_addr, transmit_options, command, parameter=None): - """ - Class constructor. Instantiates a new :class:`.RemoteATCommandPacket` object with the provided parameters. - - Args: - frame_id (integer): the frame ID of the packet. - x64bit_addr (:class:`.XBee64BitAddress`): the 64-bit destination address. - x16bit_addr (:class:`.XBee16BitAddress`): the 16-bit destination address. - transmit_options (Integer): bitfield of supported transmission options. - command (String): AT command to send. - parameter (Bytearray, optional): AT command parameter. Optional. - - Raises: - ValueError: if ``frame_id`` is less than 0 or greater than 255. - ValueError: if length of ``command`` is different than 2. - - .. seealso:: - | :class:`.RemoteATCmdOptions` - | :class:`.XBee16BitAddress` - | :class:`.XBee64BitAddress` - | :class:`.XBeeAPIPacket` - """ - if frame_id < 0 or frame_id > 255: - raise ValueError("Frame id must be between 0 and 255.") - - if len(command) != 2: - raise ValueError("Invalid command " + command) - - super(RemoteATCommandPacket, self).__init__(ApiFrameType.REMOTE_AT_COMMAND_REQUEST) - self._frame_id = frame_id - self.__x64bit_addr = x64bit_addr - self.__x16bit_addr = x16bit_addr - self.__transmit_options = transmit_options - self.__command = command - self.__parameter = parameter - - @staticmethod - def create_packet(raw, operating_mode): - """ - Override method. - - Returns: - :class:`.RemoteATCommandPacket` - - Raises: - InvalidPacketException: if the Bytearray length is less than 19. (start delim. + length (2 bytes) + frame - type + frame id + 64bit addr. + 16bit addr. + transmit options + command (2 bytes) + checksum = - 19 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.REMOTE_AT_COMMAND_REQUEST`. - InvalidOperatingModeException: if ``operating_mode`` is not supported. - - .. seealso:: - | :meth:`.XBeePacket.create_packet` - | :meth:`.XBeeAPIPacket._check_api_packet` - """ - if operating_mode != OperatingMode.ESCAPED_API_MODE and operating_mode != OperatingMode.API_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("This packet is not a remote AT command request packet.") - - return RemoteATCommandPacket( - raw[4], - XBee64BitAddress(raw[5:13]), - XBee16BitAddress(raw[13:15]), - raw[15], - raw[16:18].decode("utf8"), - raw[18:-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.__x64bit_addr.address - ret += self.__x16bit_addr.address - ret.append(self.__transmit_options) - ret += bytearray(self.__command, "utf8") - return ret if self.__parameter is None else ret + self.__parameter - - def _get_api_packet_spec_data_dict(self): - """ - Override method. - - .. seealso:: - | :meth:`.XBeeAPIPacket._get_api_packet_spec_data_dict` - """ - return {DictKeys.X64BIT_ADDR: self.__x64bit_addr.address, - DictKeys.X16BIT_ADDR: self.__x16bit_addr.address, - DictKeys.TRANSMIT_OPTIONS: self.__transmit_options, - DictKeys.COMMAND: self.__command, - DictKeys.PARAMETER: list(self.__parameter) if self.__parameter is not None else None} - - def __get_64bit_addr(self): - """ - Returns the 64-bit destination address. - - Returns: - :class:`.XBee64BitAddress`: the 64-bit destination address. - - .. seealso:: - | :class:`.XBee64BitAddress` - """ - return self.__x64bit_addr - - def __set_64bit_addr(self, x64bit_addr): - """ - Sets the 64-bit destination address. - - Args: - x64bit_addr (:class:`.XBee64BitAddress`): the new 64-bit destination address. - - .. seealso:: - | :class:`.XBee64BitAddress` - """ - self.__x64bit_addr = x64bit_addr - - def __get_16bit_addr(self): - """ - Returns the 16-bit destination address. - - Returns: - :class:`.XBee16BitAddress`: the 16-bit destination address. - - .. seealso:: - | :class:`.XBee16BitAddress` - """ - return self.__x16bit_addr - - def __set_16bit_addr(self, x16bit_addr): - """ - Sets the 16-bit destination address. - - Args: - x16bit_addr (:class:`.XBee16BitAddress`): the new 16-bit destination address. - - .. seealso:: - | :class:`.XBee16BitAddress` - """ - self.__x16bit_addr = x16bit_addr - - def __get_transmit_options(self): - """ - Returns the transmit options bitfield. - - Returns: - Integer: the transmit options bitfield. - - .. seealso:: - | :class:`.RemoteATCmdOptions` - """ - return self.__transmit_options - - def __set_transmit_options(self, transmit_options): - """ - Sets the transmit options bitfield. - - Args: - transmit_options (Integer): the new transmit options bitfield. - - .. seealso:: - | :class:`.RemoteATCmdOptions` - """ - self.__transmit_options = transmit_options - - def __get_parameter(self): - """ - Returns the AT command parameter. - - Returns: - Bytearray: the AT command parameter. - """ - return self.__parameter - - def __set_parameter(self, parameter): - """ - Sets the AT command parameter. - - Args: - parameter (Bytearray): the new AT command parameter. - """ - self.__parameter = parameter - - def __get_command(self): - """ - Returns the AT command. - - Returns: - String: the AT command. - """ - return self.__command - - def __set_command(self, command): - """ - Sets the AT command. - - Args: - command (String): the new AT command. - """ - self.__command = command - - x64bit_dest_addr = property(__get_64bit_addr, __set_64bit_addr) - """:class:`.XBee64BitAddress`. 64-bit destination address.""" - - x16bit_dest_addr = property(__get_16bit_addr, __set_16bit_addr) - """:class:`.XBee16BitAddress`. 16-bit destination address.""" - - transmit_options = property(__get_transmit_options, __set_transmit_options) - """Integer. Transmit options bitfield.""" - - command = property(__get_command, __set_command) - """String. AT command.""" - - parameter = property(__get_parameter, __set_parameter) - """Bytearray. AT command parameter.""" - - -class RemoteATCommandResponsePacket(XBeeAPIPacket): - """ - This class represents a remote AT command response packet. Packet is built - using the parameters of the constructor or providing a valid byte array. - - If a module receives a remote command response RF data frame in response - to a remote AT command request, the module will send a remote AT command - response message out the UART. Some commands may send back multiple frames, - for example, Node Discover (``ND``) command. - - This packet is received in response of a :class:`.RemoteATCommandPacket`. - - Response also includes an object with the status of the AT command. - - .. seealso:: - | :class:`.RemoteATCommandPacket` - | :class:`.ATCommandStatus` - | :class:`.XBeeAPIPacket` - """ - - __MIN_PACKET_LENGTH = 19 - - def __init__(self, frame_id, x64bit_addr, x16bit_addr, command, response_status, comm_value=None): - """ - Class constructor. Instantiates a new :class:`.RemoteATCommandResponsePacket` object with the provided - parameters. - - Args: - frame_id (Integer): the frame ID of the packet. - 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`): the status of the AT command. - comm_value (Bytearray, optional): the AT command response value. Optional. - - Raises: - ValueError: if ``frame_id`` is less than 0 or greater than 255. - ValueError: if length of ``command`` is different than 2. - - .. seealso:: - | :class:`.ATCommandStatus` - | :class:`.XBee16BitAddress` - | :class:`.XBee64BitAddress` - | :class:`.XBeeAPIPacket` - """ - if frame_id > 255 or frame_id < 0: - raise ValueError("frame_id must be between 0 and 255.") - if len(command) != 2: - raise ValueError("Invalid command " + command) - - 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 - self.__response_status = response_status - self.__comm_value = comm_value - - @staticmethod - def create_packet(raw, operating_mode): - """ - Override method. - - Returns: - :class:`.RemoteATCommandResponsePacket`. - - Raises: - InvalidPacketException: if the bytearray length is less than 19. (start delim. + length (2 bytes) + frame - type + frame id + 64bit addr. + 16bit addr. + receive options + command (2 bytes) + checksum = - 19 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.REMOTE_AT_COMMAND_RESPONSE`. - InvalidOperatingModeException: if ``operating_mode`` is not supported. - - .. seealso:: - | :meth:`.XBeePacket.create_packet` - | :meth:`.XBeeAPIPacket._check_api_packet` - """ - if operating_mode != OperatingMode.ESCAPED_API_MODE and operating_mode != OperatingMode.API_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("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"), - ATCommandStatus.get(raw[17]), raw[18:-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.__x64bit_addr.address - ret += self.__x16bit_addr.address - ret += bytearray(self.__command, "utf8") - ret.append(self.__response_status.code) - if self.__comm_value is not None: - ret += self.__comm_value - return ret - - def _get_api_packet_spec_data_dict(self): - return {DictKeys.X64BIT_ADDR: self.__x64bit_addr.address, - DictKeys.X16BIT_ADDR: self.__x16bit_addr.address, - DictKeys.COMMAND: self.__command, - DictKeys.AT_CMD_STATUS: self.__response_status, - DictKeys.RF_DATA: list(self.__comm_value) if self.__comm_value is not None else None} - - def __get_command(self): - """ - Returns the AT command of the packet. - - Returns: - String: the AT command of the packet. - """ - return self.__command - - def __set_command(self, command): - """ - Sets the AT command of the packet. - - Args: - command (String): the new AT command of the packet. Must have length = 2. - - Raises: - ValueError: if length of ``command`` is different than 2. - """ - if len(command) != 2: - raise ValueError("Invalid command " + command) - self.__command = command - - def __get_value(self): - """ - Returns the AT command response value. - - Returns: - Bytearray: the AT command response value. - """ - return self.__comm_value - - def __set_value(self, comm_value): - """ - Sets the AT command response value. - - Args: - comm_value (Bytearray): the new AT command response value. - """ - self.__comm_value = 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 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. - - .. seealso:: - | :class:`.ATCommandStatus` - """ - self.__response_status = response_status - - def __get_64bit_addr(self): - """ - Returns the 64-bit source address. - - Returns: - :class:`.XBee64BitAddress`: the 64-bit source address. - - .. seealso:: - | :class:`.XBee64BitAddress` - """ - return self.__x64bit_addr - - def __set_64bit_addr(self, x64bit_addr): - """ - Sets the 64-bit source address. - - Args: - x64bit_addr (:class:`.XBee64BitAddress`): the new 64-bit source address - - .. seealso:: - | :class:`.XBee64BitAddress` - """ - self.__x64bit_addr = x64bit_addr - - def __get_16bit_addr(self): - """ - Returns the 16-bit source address. - - Returns: - :class:`.XBee16BitAddress`: the 16-bit source address. - - .. seealso:: - | :class:`.XBee16BitAddress` - """ - return self.__x16bit_addr - - def __set_16bit_addr(self, x16bit_addr): - """ - Sets the 16-bit source address. - - Args: - x16bit_addr (:class:`.XBee16BitAddress`): the new 16-bit source address. - - .. seealso:: - | :class:`.XBee16BitAddress` - """ - self.__x16bit_addr = x16bit_addr - - x64bit_source_addr = property(__get_64bit_addr, __set_64bit_addr) - """:class:`.XBee64BitAddress`. 64-bit source address.""" - - x16bit_source_addr = property(__get_16bit_addr, __set_16bit_addr) - """:class:`.XBee16BitAddress`. 16-bit source address.""" - - command = property(__get_command, __set_command) - """String. AT command.""" - - command_value = property(__get_value, __set_value) - """Bytearray. AT command value.""" - - status = property(__get_response_status, __set_response_status) - """:class:`.ATCommandStatus`. AT command response status.""" - - -class TransmitPacket(XBeeAPIPacket): - """ - This class represents a transmit request packet. Packet is built using the parameters - of the constructor or providing a valid API byte array. - - A transmit request API frame causes the module to send data as an RF - packet to the specified destination. - - The 64-bit destination address should be set to ``0x000000000000FFFF`` - for a broadcast transmission (to all devices). - - The coordinator can be addressed by either setting the 64-bit address to - all ``0x00``} and the 16-bit address to ``0xFFFE``, OR by setting the - 64-bit address to the coordinator's 64-bit address and the 16-bit address to - ``0x0000``. - - For all other transmissions, setting the 16-bit address to the correct - 16-bit address can help improve performance when transmitting to multiple - destinations. - - If a 16-bit address is not known, this field should be set to - ``0xFFFE`` (unknown). - - The transmit status frame ( :attr:`.ApiFrameType.TRANSMIT_STATUS`) will - indicate the discovered 16-bit address, if successful (see :class:`.TransmitStatusPacket`). - - The broadcast radius can be set from ``0`` up to ``NH``. If set - to ``0``, the value of ``NH`` specifies the broadcast radius - (recommended). This parameter is only used for broadcast transmissions. - - The maximum number of payload bytes can be read with the ``NP`` - command. - - Several transmit options can be set using the transmit options bitfield. - - .. seealso:: - | :class:`.TransmitOptions` - | :attr:`.XBee16BitAddress.COORDINATOR_ADDRESS` - | :attr:`.XBee16BitAddress.UNKNOWN_ADDRESS` - | :attr:`.XBee64BitAddress.BROADCAST_ADDRESS` - | :attr:`.XBee64BitAddress.COORDINATOR_ADDRESS` - | :class:`.XBeeAPIPacket` - """ - - __MIN_PACKET_LENGTH = 18 - - def __init__(self, frame_id, x64bit_addr, x16bit_addr, broadcast_radius, transmit_options, rf_data=None): - """ - Class constructor. Instantiates a new :class:`.TransmitPacket` object with the provided parameters. - - Args: - frame_id (integer): the frame ID of the packet. - x64bit_addr (:class:`.XBee64BitAddress`): the 64-bit destination address. - x16bit_addr (:class:`.XBee16BitAddress`): the 16-bit destination address. - broadcast_radius (Integer): maximum number of hops a broadcast transmission can occur. - transmit_options (Integer): bitfield of supported transmission options. - rf_data (Bytearray, optional): RF data that is sent to the destination device. Optional. - - .. seealso:: - | :class:`.TransmitOptions` - | :class:`.XBee16BitAddress` - | :class:`.XBee64BitAddress` - | :class:`.XBeeAPIPacket` - - Raises: - ValueError: if ``frame_id`` is less than 0 or greater than 255. - """ - if frame_id > 255 or frame_id < 0: - raise ValueError("frame_id must be between 0 and 255.") - - super(TransmitPacket, self).__init__(ApiFrameType.TRANSMIT_REQUEST) - self._frame_id = frame_id - self.__x64bit_addr = x64bit_addr - self.__x16bit_addr = x16bit_addr - self.__broadcast_radius = broadcast_radius - self.__transmit_options = transmit_options - self.__rf_data = rf_data - - @staticmethod - def create_packet(raw, operating_mode): - """ - Override method. - - Returns: - :class:`.TransmitPacket`. - - Raises: - InvalidPacketException: if the bytearray length is less than 18. (start delim. + length (2 bytes) + frame - type + frame id + 64bit addr. + 16bit addr. + Receive options + checksum = 16 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.TRANSMIT_REQUEST`. - InvalidOperatingModeException: if ``operating_mode`` is not supported. - - .. seealso:: - | :meth:`.XBeePacket.create_packet` - | :meth:`.XBeeAPIPacket._check_api_packet` - """ - if operating_mode != OperatingMode.ESCAPED_API_MODE and operating_mode != OperatingMode.API_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("This packet is not a transmit request packet.") - - return TransmitPacket(raw[4], XBee64BitAddress(raw[5:13]), - XBee16BitAddress(raw[13:15]), raw[15], - raw[16], raw[17:-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.__x64bit_addr.address - ret += self.__x16bit_addr.address - ret.append(self.__broadcast_radius) - ret.append(self.__transmit_options) - if self.__rf_data is not None: - return ret + bytes(self.__rf_data) - 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: self.__x64bit_addr.address, - DictKeys.X16BIT_ADDR: self.__x16bit_addr.address, - DictKeys.BROADCAST_RADIUS: self.__broadcast_radius, - DictKeys.TRANSMIT_OPTIONS: self.__transmit_options, - DictKeys.RF_DATA: list(self.__rf_data) if self.__rf_data is not None else None} - - def __get_rf_data(self): - """ - Returns the RF data to send. - - Returns: - Bytearray: the RF data to send. - """ - if self.__rf_data is None: - return None - return self.__rf_data.copy() - - def __set_rf_data(self, rf_data): - """ - Sets the RF data to send. - - Args: - rf_data (Bytearray): the new RF data to send. - """ - if rf_data is None: - self.__rf_data = None - else: - self.__rf_data = rf_data.copy() - - def __get_transmit_options(self): - """ - Returns the transmit options bitfield. - - Returns: - Integer: the transmit options bitfield. - - .. seealso:: - | :class:`.TransmitOptions` - """ - return self.__transmit_options - - def __set_transmit_options(self, transmit_options): - """ - Sets the transmit options bitfield. - - Args: - transmit_options (Integer): the new transmit options bitfield. - - .. seealso:: - | :class:`.TransmitOptions` - """ - self.__transmit_options = transmit_options - - def __get_broadcast_radius(self): - """ - Returns the broadcast radius. Broadcast radius is the maximum number of hops a broadcast transmission. - - Returns: - Integer: the broadcast radius. - """ - return self.__broadcast_radius - - def __set_broadcast_radius(self, br_radius): - """ - Sets the broadcast radius. Broadcast radius is the maximum number of hops a broadcast transmission. - - Args: - br_radius (Integer): the new broadcast radius. - """ - self.__broadcast_radius = br_radius - - def __get_64bit_addr(self): - """ - Returns the 64-bit destination address. - - Returns: - :class:`.XBee64BitAddress`: the 64-bit destination address. - - .. seealso:: - | :class:`.XBee64BitAddress` - """ - return self.__x64bit_addr - - def __set_64bit_addr(self, x64bit_addr): - """ - Sets the 64-bit destination address. - - Args: - x64bit_addr (:class:`.XBee64BitAddress`): the new 64-bit destination address. - - .. seealso:: - | :class:`.XBee64BitAddress` - """ - self.__x64bit_addr = x64bit_addr - - def __get_16bit_addr(self): - """ - Returns the 16-bit destination address. - - Returns: - :class:`XBee16BitAddress`: the 16-bit destination address. - - .. seealso:: - | :class:`.XBee16BitAddress` - """ - return self.__x16bit_addr - - def __set_16bit_addr(self, x16bit_addr): - """ - Sets the 16-bit destination address. - - Args: - x16bit_addr (:class:`.XBee16BitAddress`): the new 16-bit destination address. - - .. seealso:: - | :class:`.XBee16BitAddress` - """ - self.__x16bit_addr = x16bit_addr - - x64bit_dest_addr = property(__get_64bit_addr, __set_64bit_addr) - """:class:`.XBee64BitAddress`. 64-bit destination address.""" - - x16bit_dest_addr = property(__get_16bit_addr, __set_16bit_addr) - """:class:`.XBee16BitAddress`. 16-bit destination address.""" - - transmit_options = property(__get_transmit_options, __set_transmit_options) - """Integer. Transmit options bitfield.""" - - broadcast_radius = property(__get_broadcast_radius, __set_broadcast_radius) - """Integer. Broadcast radius.""" - - rf_data = property(__get_rf_data, __set_rf_data) - """Bytearray. RF data to send.""" - - -class TransmitStatusPacket(XBeeAPIPacket): - """ - This class represents a transmit status packet. Packet is built using the - parameters of the constructor or providing a valid raw byte array. - - When a Transmit Request is completed, the module sends a transmit status - message. This message will indicate if the packet was transmitted - successfully or if there was a failure. - - This packet is the response to standard and explicit transmit requests. - - .. seealso:: - | :class:`.TransmitPacket` - """ - - __MIN_PACKET_LENGTH = 11 - - def __init__(self, frame_id, x16bit_addr, transmit_retry_count, transmit_status=TransmitStatus.SUCCESS, - discovery_status=DiscoveryStatus.NO_DISCOVERY_OVERHEAD): - """ - Class constructor. Instantiates a new :class:`.TransmitStatusPacket` object with the provided parameters. - - Args: - frame_id (Integer): the frame ID of the packet. - x16bit_addr (:class:`.XBee16BitAddress`): 16-bit network address the packet was delivered to. - transmit_retry_count (Integer): the number of application transmission retries that took place. - transmit_status (:class:`.TransmitStatus`, optional): transmit status. Default: SUCCESS. Optional. - discovery_status (:class:`DiscoveryStatus`, optional): discovery status. Default: NO_DISCOVERY_OVERHEAD. - Optional. - - Raises: - ValueError: if ``frame_id`` is less than 0 or greater than 255. - - .. seealso:: - | :class:`.DiscoveryStatus` - | :class:`.TransmitStatus` - | :class:`.XBee16BitAddress` - | :class:`.XBeeAPIPacket` - """ - if frame_id < 0 or frame_id > 255: - raise ValueError("Frame id must be between 0 and 255.") - - super(TransmitStatusPacket, self).__init__(ApiFrameType.TRANSMIT_STATUS) - self._frame_id = frame_id - self.__x16bit_addr = x16bit_addr - self.__transmit_retry_count = transmit_retry_count - self.__transmit_status = transmit_status - self.__discovery_status = discovery_status - - @staticmethod - def create_packet(raw, operating_mode): - """ - Override method. - - Returns: - :class:`.TransmitStatusPacket` - - Raises: - InvalidPacketException: if the bytearray length is less than 11. (start delim. + length (2 bytes) + frame - type + frame id + 16bit addr. + transmit retry count + delivery status + discovery status + 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.TRANSMIT_STATUS`. - InvalidOperatingModeException: if ``operating_mode`` is not supported. - - .. seealso:: - | :meth:`.XBeePacket.create_packet` - | :meth:`.XBeeAPIPacket._check_api_packet` - """ - if operating_mode != OperatingMode.ESCAPED_API_MODE and operating_mode != OperatingMode.API_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("This packet is not a transmit status packet.") - - return TransmitStatusPacket(raw[4], XBee16BitAddress(raw[5:7]), raw[7], - TransmitStatus.get(raw[8]), DiscoveryStatus.get(raw[9])) - - 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.__x16bit_addr.address - ret.append(self.__transmit_retry_count) - ret.append(self.__transmit_status.code) - ret.append(self.__discovery_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.X16BIT_ADDR: self.__x16bit_addr.address, - DictKeys.TRANS_R_COUNT: self.__transmit_retry_count, - DictKeys.TS_STATUS: self.__transmit_status, - DictKeys.DS_STATUS: self.__discovery_status} - - def __get_16bit_addr(self): - """ - Returns the 16-bit destination address. - - Returns: - :class:`.XBee16BitAddress`: the 16-bit destination address. - - .. seealso:: - | :class:`.XBee16BitAddress` - """ - return self.__x16bit_addr - - def __set_16bit_addr(self, x16bit_addr): - """ - Sets the 16-bit destination address. - - Args: - x16bit_addr (:class:`.XBee16BitAddress`): the new 16-bit destination address. - - .. seealso:: - | :class:`.XBee16BitAddress` - """ - self.__x16bit_addr = x16bit_addr - - def __get_transmit_status(self): - """ - Returns the transmit status. - - Returns: - :class:`.TransmitStatus`: the transmit status. - - .. seealso:: - | :class:`.TransmitStatus` - """ - return self.__transmit_status - - def __set_transmit_status(self, transmit_status): - """ - Sets the transmit status. - - Args: - transmit_status (:class:`.TransmitStatus`): the new transmit status to set. - - .. seealso:: - | :class:`.TransmitStatus` - """ - self.__transmit_status = transmit_status - - def __get_transmit_retry_count(self): - """ - Returns the transmit retry count. - - Returns: - Integer: the transmit retry count. - """ - return self.__transmit_retry_count - - def __set_transmit_retry_count(self, transmit_retry_count): - """ - Sets the transmit retry count. - - Args: - transmit_retry_count (Integer): the new transmit retry count. - """ - self.__transmit_retry_count = transmit_retry_count - - def __get_discovery_status(self): - """ - Returns the discovery status. - - Returns: - :class:`.DiscoveryStatus`: the discovery status. - - .. seealso:: - | :class:`.DiscoveryStatus` - """ - return self.__discovery_status - - def __set_discovery_status(self, discovery_status): - """ - Sets the discovery status. - - Args: - discovery_status (:class:`.DiscoveryStatus`): the new discovery status to set. - - .. seealso:: - | :class:`.DiscoveryStatus` - """ - self.__discovery_status = discovery_status - - x16bit_dest_addr = property(__get_16bit_addr, __set_16bit_addr) - """:class:`.XBee16BitAddress`. 16-bit destination address.""" - - transmit_retry_count = property(__get_transmit_retry_count, __set_transmit_retry_count) - """Integer. Transmit retry count value.""" - - transmit_status = property(__get_transmit_status, __set_transmit_status) - """:class:`.TransmitStatus`. Transmit status.""" - - discovery_status = property(__get_discovery_status, __set_discovery_status) - """:class:`.DiscoveryStatus`. Discovery status.""" - - -class ModemStatusPacket(XBeeAPIPacket): - """ - This class represents a modem status packet. Packet is built using the - parameters of the constructor or providing a valid API raw byte array. - - RF module status messages are sent from the module in response to specific - conditions and indicates the state of the modem in that moment. - - .. seealso:: - | :class:`.XBeeAPIPacket` - """ - - __MIN_PACKET_LENGTH = 6 - - def __init__(self, modem_status): - """ - Class constructor. Instantiates a new :class:`.ModemStatusPacket` object with the provided parameters. - - Args: - modem_status (:class:`.ModemStatus`): the modem status event. - - .. seealso:: - | :class:`.ModemStatus` - | :class:`.XBeeAPIPacket` - """ - super(ModemStatusPacket, self).__init__(ApiFrameType.MODEM_STATUS) - self.__modem_status = modem_status - - @staticmethod - def create_packet(raw, operating_mode): - """ - Override method. - - Returns: - :class:`.ModemStatusPacket`. - - Raises: - InvalidPacketException: if the bytearray length is less than 6. (start delim. + length (2 bytes) + frame - type + modem status + checksum = 6 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.MODEM_STATUS`. - InvalidOperatingModeException: if ``operating_mode`` is not supported. - - .. seealso:: - | :meth:`.XBeePacket.create_packet` - | :meth:`.XBeeAPIPacket._check_api_packet` - """ - if operating_mode != OperatingMode.ESCAPED_API_MODE and operating_mode != OperatingMode.API_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("This packet is not a modem status packet.") - - return ModemStatusPacket(ModemStatus.get(raw[4])) - - 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_dict` - """ - return bytearray([self.__modem_status.code]) - - def _get_api_packet_spec_data_dict(self): - """ - Override method. - - .. seealso:: - | :meth:`.XBeeAPIPacket._get_api_packet_spec_data_dict` - """ - return {DictKeys.MODEM_STATUS: self.__modem_status} - - def __get_modem_status(self): - """ - Returns the modem status event. - - Returns: - :class:`.ModemStatus`: The modem status event. - - .. seealso:: - | :class:`.ModemStatus` - """ - return self.__modem_status - - def __set_modem_status(self, modem_status): - """ - Sets the modem status event. - - Args: - modem_status (:class:`.ModemStatus`): the new modem status event to set. - - .. seealso:: - | :class:`.ModemStatus` - """ - self.__modem_status = modem_status - - modem_status = property(__get_modem_status, __set_modem_status) - """:class:`.ModemStatus`. Modem status event.""" - - -class IODataSampleRxIndicatorPacket(XBeeAPIPacket): - """ - This class represents an IO data sample RX indicator packet. Packet is built - using the parameters of the constructor or providing a valid API byte array. - - When the module receives an IO sample frame from a remote device, it - sends the sample out the UART using this frame type (when ``AO=0``). Only modules - running API firmware will send IO samples out the UART. - - Among received data, some options can also be received indicating - transmission parameters. - - .. seealso:: - | :class:`.XBeeAPIPacket` - | :class:`.ReceiveOptions` - """ - - __MIN_PACKET_LENGTH = 20 - - def __init__(self, x64bit_addr, x16bit_addr, receive_options, rf_data=None): - """ - Class constructor. Instantiates a new :class:`.IODataSampleRxIndicatorPacket` object with the provided - parameters. - - Args: - x64bit_addr (:class:`.XBee64BitAddress`): the 64-bit source address. - x16bit_addr (:class:`.XBee16BitAddress`): the 16-bit source address. - receive_options (Integer): bitfield indicating the receive options. - rf_data (Bytearray, optional): received RF data. Optional. - - Raises: - ValueError: if ``rf_data`` is not ``None`` and it's not valid for create an :class:`.IOSample`. - - .. seealso:: - | :class:`.IOSample` - | :class:`.ReceiveOptions` - | :class:`.XBee16BitAddress` - | :class:`.XBee64BitAddress` - | :class:`.XBeeAPIPacket` - """ - 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 - self.__rf_data = rf_data - self.__io_sample = IOSample(rf_data) if rf_data is not None and len(rf_data) >= 5 else None - - @staticmethod - def create_packet(raw, operating_mode): - """ - Override method. - - Returns: - :class:`.IODataSampleRxIndicatorPacket`. - - Raises: - InvalidPacketException: if the bytearray length is less than 20. (start delim. + length (2 bytes) + frame - type + 64bit addr. + 16bit addr. + rf data (5 bytes) + checksum = 20 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.IO_DATA_SAMPLE_RX_INDICATOR`. - InvalidOperatingModeException: if ``operating_mode`` is not supported. - - .. seealso:: - | :meth:`.XBeePacket.create_packet` - | :meth:`.XBeeAPIPacket._check_api_packet` - """ - if operating_mode != OperatingMode.ESCAPED_API_MODE and operating_mode != OperatingMode.API_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("This packet is not an IO data sample RX indicator packet.") - - return IODataSampleRxIndicatorPacket(XBee64BitAddress(raw[4:12]), XBee16BitAddress(raw[12:14]), - raw[14], raw[15:-1]) - - 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 = self.__x64bit_addr.address - ret += self.__x16bit_addr.address - ret.append(self.__receive_options) - if self.__rf_data is not None: - ret += self.__rf_data - return ret - - def _get_api_packet_spec_data_dict(self): - """ - Override method. - - .. seealso:: - | :meth:`.XBeeAPIPacket._get_api_packet_spec_data_dict` - """ - base = {DictKeys.X64BIT_ADDR: self.__x64bit_addr.address, - DictKeys.X16BIT_ADDR: self.__x16bit_addr.address, - DictKeys.RECEIVE_OPTIONS: self.__receive_options} - - if self.__io_sample is not None: - base[DictKeys.NUM_SAMPLES] = 1 - base[DictKeys.DIGITAL_MASK] = self.__io_sample.digital_mask - base[DictKeys.ANALOG_MASK] = self.__io_sample.analog_mask - - # Digital values - for i in range(16): - if self.__io_sample.has_digital_value(IOLine.get(i)): - base[IOLine.get(i).description + " digital value"] = \ - self.__io_sample.get_digital_value(IOLine.get(i)).name - - # Analog values - for i in range(6): - if self.__io_sample.has_analog_value(IOLine.get(i)): - base[IOLine.get(i).description + " analog value"] = \ - self.__io_sample.get_analog_value(IOLine.get(i)) - - # Power supply - if self.__io_sample.has_power_supply_value(): - base["Power supply value "] = "%02X" % self.__io_sample.power_supply_value - - elif self.__rf_data is not None: - base[DictKeys.RF_DATA] = utils.hex_to_string(self.__rf_data) - - return base - - def is_broadcast(self): - """ - Override method. - - .. seealso:: - | :meth:`XBeeAPIPacket.is_broadcast` - """ - return utils.is_bit_enabled(self.__receive_options, 1) - - def __get_64bit_addr(self): - """ - Returns the 64-bit source address. - - Returns: - :class:`.XBee64BitAddress`: the 64-bit source address. - - .. seealso:: - | :class:`.XBee64BitAddress` - """ - return self.__x64bit_addr - - def __set_64bit_addr(self, x64bit_addr): - """ - Sets the 64-bit source address. - - Args: - x64bit_addr (:class:`.XBee64BitAddress`): the new 64-bit source address - - .. seealso:: - | :class:`.XBee64BitAddress` - """ - self.__x64bit_addr = x64bit_addr - - def __get_16bit_addr(self): - """ - Returns the 16-bit source address. - - Returns: - :class:`.XBee16BitAddress`: the 16-bit source address. - - .. seealso:: - | :class:`.XBee16BitAddress` - """ - return self.__x16bit_addr - - def __set_16bit_addr(self, x16bit_addr): - """ - Sets the 16-bit source address. - - Args: - x16bit_addr (:class:`.XBee16BitAddress`): the new 16-bit source address. - - .. seealso:: - | :class:`.XBee16BitAddress` - """ - self.__x16bit_addr = x16bit_addr - - def __get_options(self): - """ - Returns the receive options bitfield. - - Returns: - Integer: the receive options bitfield. - - .. seealso:: - | :class:`.ReceiveOptions` - """ - return self.__receive_options - - def __set_options(self, receive_options): - """ - Sets the receive options bitfield. - - Args: - receive_options (Integer): the new receive options bitfield. - - .. seealso:: - | :class:`.ReceiveOptions` - """ - self.__receive_options = receive_options - - def __get_rf_data(self): - """ - Returns the received RF data. - - Returns: - Bytearray: the received RF data. - """ - if self.__rf_data is None: - return None - return self.__rf_data.copy() - - def __set_rf_data(self, rf_data): - """ - Sets the received RF data. - - Args: - rf_data (Bytearray): the new received RF data. - """ - if rf_data is None: - self.__rf_data = None - else: - self.__rf_data = rf_data.copy() - - # Modify the ioSample accordingly - if rf_data is not None and len(rf_data) >= 5: - self.__io_sample = IOSample(self.__rf_data) - else: - self.__io_sample = None - - def __get_io_sample(self): - """ - Returns the IO sample corresponding to the data contained in the packet. - - Returns: - :class:`.IOSample`: the IO sample of the packet, ``None`` if the packet has not any data or if the - sample could not be generated correctly. - - .. seealso:: - | :class:`.IOSample` - """ - return self.__io_sample - - def __set_io_sample(self, io_sample): - """ - Sets the IO sample of the packet. - - Args: - io_sample (:class:`.IOSample`): the new IO sample to set. - - .. seealso:: - | :class:`.IOSample` - """ - self.__io_sample = io_sample - - x64bit_source_addr = property(__get_64bit_addr, __set_64bit_addr) - """:class:`.XBee64BitAddress`. 64-bit source address.""" - - x16bit_source_addr = property(__get_16bit_addr, __set_16bit_addr) - """:class:`.XBee16BitAddress`. 16-bit source address.""" - - receive_options = property(__get_options, __set_options) - """Integer. Receive options bitfield.""" - - rf_data = property(__get_rf_data, __set_rf_data) - """Bytearray. Received RF data.""" - - io_sample = property(__get_io_sample, __set_io_sample) - """:class:`.IOSample`: IO sample corresponding to the data contained in the packet.""" - - -class ExplicitAddressingPacket(XBeeAPIPacket): - """ - This class represents an explicit addressing command packet. Packet is - built using the parameters of the constructor or providing a valid API - payload. - - Allows application layer fields (endpoint and cluster ID) to be - specified for a data transmission. Similar to the transmit request, but - also requires application layer addressing fields to be specified - (endpoints, cluster ID, profile ID). An explicit addressing request API - frame causes the module to send data as an RF packet to the specified - destination, using the specified source and destination endpoints, cluster - ID, and profile ID. - - The 64-bit destination address should be set to ``0x000000000000FFFF`` for - a broadcast transmission (to all devices). - - The coordinator can be addressed by either setting the 64-bit address to all - ``0x00`` and the 16-bit address to ``0xFFFE``, OR by setting the 64-bit - address to the coordinator's 64-bit address and the 16-bit address to ``0x0000``. - - For all other transmissions, setting the 16-bit address to the correct - 16-bit address can help improve performance when transmitting to - multiple destinations. - - If a 16-bit address is not known, this field should be set to - ``0xFFFE`` (unknown). - - The transmit status frame ( :attr:`.ApiFrameType.TRANSMIT_STATUS`) will - indicate the discovered 16-bit address, if successful (see :class:`.TransmitStatusPacket`)). - - The broadcast radius can be set from ``0`` up to ``NH``. If set - to ``0``, the value of ``NH`` specifies the broadcast radius - (recommended). This parameter is only used for broadcast transmissions. - - The maximum number of payload bytes can be read with the ``NP`` - command. Note: if source routing is used, the RF payload will be reduced - by two bytes per intermediate hop in the source route. - - Several transmit options can be set using the transmit options bitfield. - - .. seealso:: - | :class:`.TransmitOptions` - | :attr:`.XBee16BitAddress.COORDINATOR_ADDRESS` - | :attr:`.XBee16BitAddress.UNKNOWN_ADDRESS` - | :attr:`.XBee64BitAddress.BROADCAST_ADDRESS` - | :attr:`.XBee64BitAddress.COORDINATOR_ADDRESS` - | :class:`.ExplicitRXIndicatorPacket` - | :class:`.XBeeAPIPacket` - """ - - __MIN_PACKET_LENGTH = 24 - - def __init__(self, frame_id, x64bit_addr, x16bit_addr, source_endpoint, dest_endpoint, cluster_id, - profile_id, broadcast_radius=0x00, transmit_options=0x00, rf_data=None): - """ - Class constructor. . Instantiates a new :class:`.ExplicitAddressingPacket` object with the provided parameters. - - Args: - frame_id (Integer): the frame ID of the packet. - x64bit_addr (:class:`.XBee64BitAddress`): the 64-bit address. - x16bit_addr (:class:`.XBee16BitAddress`): the 16-bit address. - source_endpoint (Integer): source endpoint. 1 byte. - dest_endpoint (Integer): destination endpoint. 1 byte. - cluster_id (Integer): cluster id. Must be between 0 and 0xFFFF. - profile_id (Integer): profile id. Must be between 0 and 0xFFFF. - broadcast_radius (Integer): maximum number of hops a broadcast transmission can occur. - transmit_options (Integer): bitfield of supported transmission options. - rf_data (Bytearray, optional): RF data that is sent to the destination device. Optional. - - Raises: - ValueError: if ``frame_id``, ``src_endpoint`` or ``dst_endpoint`` are less than 0 or greater than 255. - ValueError: if lengths of ``cluster_id`` or ``profile_id`` (respectively) are less than 0 or greater than - 0xFFFF. - - .. seealso:: - | :class:`.XBee16BitAddress` - | :class:`.XBee64BitAddress` - | :class:`.TransmitOptions` - | :class:`.XBeeAPIPacket` - """ - if frame_id < 0 or frame_id > 255: - raise ValueError("Frame id must be between 0 and 255.") - if source_endpoint < 0 or source_endpoint > 255: - raise ValueError("Source endpoint must be between 0 and 255.") - if dest_endpoint < 0 or dest_endpoint > 255: - raise ValueError("Destination endpoint must be between 0 and 255.") - if cluster_id < 0 or cluster_id > 0xFFFF: - raise ValueError("Cluster id must be between 0 and 0xFFFF.") - if profile_id < 0 or profile_id > 0xFFFF: - raise ValueError("Profile id must be between 0 and 0xFFFF.") - - super(ExplicitAddressingPacket, self).__init__(ApiFrameType.EXPLICIT_ADDRESSING) - self._frame_id = frame_id - self.__x64_addr = x64bit_addr - self.__x16_addr = x16bit_addr - self.__source_endpoint = source_endpoint - self.__dest_endpoint = dest_endpoint - self.__cluster_id = cluster_id - self.__profile_id = profile_id - self.__broadcast_radius = broadcast_radius - self.__transmit_options = transmit_options - self.__rf_data = rf_data - - @staticmethod - def create_packet(raw, operating_mode): - """ - Override method. - - Returns: - :class:`.ExplicitAddressingPacket`. - - Raises: - InvalidPacketException: if the bytearray length is less than 24. (start delim. + length (2 bytes) + frame - type + frame ID + 64bit addr. + 16bit addr. + source endpoint + dest. endpoint + cluster ID (2 bytes) + - profile ID (2 bytes) + broadcast radius + transmit options + checksum = 24 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 different than :attr:`.ApiFrameType.EXPLICIT_ADDRESSING`. - InvalidOperatingModeException: if ``operating_mode`` is not supported. - - .. seealso:: - | :meth:`.XBeePacket.create_packet` - | :meth:`.XBeeAPIPacket._check_api_packet` - """ - if operating_mode != OperatingMode.ESCAPED_API_MODE and operating_mode != OperatingMode.API_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("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], raw[23:-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` - """ - raw = self.__x64_addr.address - raw += self.__x16_addr.address - raw.append(self.__source_endpoint) - raw.append(self.__dest_endpoint) - 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: - raw += self.__rf_data - return raw - - def _get_api_packet_spec_data_dict(self): - """ - Override method. - - .. seealso:: - | :meth:`.XBeeAPIPacket._get_api_packet_spec_data_dict` - """ - return {DictKeys.X64BIT_ADDR: self.__x64_addr.address, - DictKeys.X16BIT_ADDR: self.__x16_addr.address, - DictKeys.SOURCE_ENDPOINT: self.__source_endpoint, - DictKeys.DEST_ENDPOINT: self.__dest_endpoint, - DictKeys.CLUSTER_ID: self.__cluster_id, - DictKeys.PROFILE_ID: self.__profile_id, - DictKeys.BROADCAST_RADIUS: self.__broadcast_radius, - DictKeys.TRANSMIT_OPTIONS: self.__transmit_options, - DictKeys.RF_DATA: self.__rf_data} - - def __get_source_endpoint(self): - """ - Returns the source endpoint of the transmission. - - Returns: - Integer: the source endpoint of the transmission. - """ - return self.__dest_endpoint - - def __set_source_endpoint(self, source_endpoint): - """ - Sets the source endpoint of the transmission. - - Args: - source_endpoint (Integer): the new source endpoint of the transmission. - """ - self.__source_endpoint = source_endpoint - - def __get_dest_endpoint(self): - """ - Returns the destination endpoint of the transmission. - - Returns: - Integer: the destination endpoint of the transmission. - """ - return self.__dest_endpoint - - def __set_dest_endpoint(self, dest_endpoint): - """ - Sets the destination endpoint of the transmission. - - Args: - dest_endpoint (Integer): the new destination endpoint of the transmission. - """ - self.__dest_endpoint = dest_endpoint - - def __get_cluster_id(self): - """ - Returns the cluster ID of the transmission. - - Returns: - Integer: the cluster ID of the transmission. - """ - return self.__cluster_id - - def __set_cluster_id(self, cluster_id): - """ - Sets the cluster ID of the transmission. - - Args: - cluster_id (Integer): the new cluster ID of the transmission. - """ - self.__cluster_id = cluster_id - - def __get_profile_id(self): - """ - Returns the profile ID of the transmission. - - Returns - Integer: the profile ID of the transmission. - """ - return self.__profile_id - - def __set_profile_id(self, profile_id): - """ - Sets the profile ID of the transmission. - - Args - profile_id (Integer): the new profile ID of the transmission. - """ - self.__profile_id = profile_id - - def __get_rf_data(self): - """ - Returns the RF data to send. - - Returns: - Bytearray: the RF data to send. - """ - if self.__rf_data is None: - return None - return self.__rf_data.copy() - - def __set_rf_data(self, rf_data): - """ - Sets the RF data to send. - - Args: - rf_data (Bytearray): the new RF data to send. - """ - if rf_data is None: - self.__rf_data = None - else: - self.__rf_data = rf_data.copy() - - def __get_transmit_options(self): - """ - Returns the transmit options bitfield. - - Returns: - Integer: the transmit options bitfield. - - .. seealso:: - | :class:`.TransmitOptions` - """ - return self.__transmit_options - - def __set_transmit_options(self, transmit_options): - """ - Sets the transmit options bitfield. - - Args: - transmit_options (Integer): the new transmit options bitfield. - - .. seealso:: - | :class:`.TransmitOptions` - """ - self.__transmit_options = transmit_options - - def __get_broadcast_radius(self): - """ - Returns the broadcast radius. Broadcast radius is the maximum number of hops a broadcast transmission. - - Returns: - Integer: the broadcast radius. - """ - return self.__broadcast_radius - - def __set_broadcast_radius(self, br_radius): - """ - Sets the broadcast radius. Broadcast radius is the maximum number of hops a broadcast transmission. - - Args: - br_radius (Integer): the new broadcast radius. - """ - self.__broadcast_radius = br_radius - - def __get_64bit_addr(self): - """ - Returns the 64-bit destination address. - - Returns: - :class:`.XBee64BitAddress`: the 64-bit destination address. - - .. seealso:: - | :class:`.XBee64BitAddress` - """ - return self.__x64bit_addr - - def __set_64bit_addr(self, x64bit_addr): - """ - Sets the 64-bit destination address. - - Args: - x64bit_addr (:class:`.XBee64BitAddress`): the new 64-bit destination address. - - .. seealso:: - | :class:`.XBee64BitAddress` - """ - self.__x64bit_addr = x64bit_addr - - def __get_16bit_addr(self): - """ - Returns the 16-bit destination address. - - Returns: - :class:`XBee16BitAddress`: the 16-bit destination address. - - .. seealso:: - | :class:`.XBee16BitAddress` - """ - return self.__x16bit_addr - - def __set_16bit_addr(self, x16bit_addr): - """ - Sets the 16-bit destination address. - - Args: - x16bit_addr (:class:`.XBee16BitAddress`): the new 16-bit destination address. - - .. seealso:: - | :class:`.XBee16BitAddress` - """ - self.__x16bit_addr = x16bit_addr - - x64bit_dest_addr = property(__get_64bit_addr, __set_64bit_addr) - """:class:`.XBee64BitAddress`. 64-bit destination address.""" - - x16bit_dest_addr = property(__get_16bit_addr, __set_16bit_addr) - """:class:`.XBee16BitAddress`. 16-bit destination address.""" - - transmit_options = property(__get_transmit_options, __set_transmit_options) - """Integer. Transmit options bitfield.""" - - broadcast_radius = property(__get_broadcast_radius, __set_broadcast_radius) - """Integer. Broadcast radius.""" - - source_endpoint = property(__get_source_endpoint, __set_source_endpoint) - """Integer. Source endpoint of the transmission.""" - - dest_endpoint = property(__get_dest_endpoint, __set_dest_endpoint) - """Integer. Destination endpoint of the transmission.""" - - cluster_id = property(__get_cluster_id, __set_cluster_id) - """Integer. Cluster ID of the transmission.""" - - profile_id = property(__get_profile_id, __set_profile_id) - """Integer. Profile ID of the transmission.""" - - rf_data = property(__get_rf_data, __set_rf_data) - """Bytearray. RF data to send.""" - - -class ExplicitRXIndicatorPacket(XBeeAPIPacket): - """ - This class represents an explicit RX indicator packet. Packet is - built using the parameters of the constructor or providing a valid API - payload. - - When the modem receives an RF packet it is sent out the UART using this - message type (when ``AO=1``). - - This packet is received when external devices send explicit addressing - packets to this module. - - Among received data, some options can also be received indicating - transmission parameters. - - .. seealso:: - | :class:`.XBeeReceiveOptions` - | :class:`.ExplicitAddressingPacket` - | :class:`.XBeeAPIPacket` - """ - - __MIN_PACKET_LENGTH = 22 - - def __init__(self, x64bit_addr, x16bit_addr, source_endpoint, - dest_endpoint, cluster_id, profile_id, receive_options, rf_data=None): - """ - Class constructor. Instantiates a new :class:`.ExplicitRXIndicatorPacket` object with the provided parameters. - - Args: - x64bit_addr (:class:`.XBee64BitAddress`): the 64-bit source address. - x16bit_addr (:class:`.XBee16BitAddress`): the 16-bit source address. - source_endpoint (Integer): source endpoint. 1 byte. - dest_endpoint (Integer): destination endpoint. 1 byte. - cluster_id (Integer): cluster ID. Must be between 0 and 0xFFFF. - profile_id (Integer): profile ID. Must be between 0 and 0xFFFF. - receive_options (Integer): bitfield indicating the receive options. - rf_data (Bytearray, optional): received RF data. Optional. - - Raises: - ValueError: if ``src_endpoint`` or ``dst_endpoint`` are less than 0 or greater than 255. - ValueError: if lengths of ``cluster_id`` or ``profile_id`` (respectively) are different than 2. - - .. seealso:: - | :class:`.XBee16BitAddress` - | :class:`.XBee64BitAddress` - | :class:`.XBeeReceiveOptions` - | :class:`.XBeeAPIPacket` - """ - if source_endpoint < 0 or source_endpoint > 255: - raise ValueError("Source endpoint must be between 0 and 255.") - if dest_endpoint < 0 or dest_endpoint > 255: - raise ValueError("Destination endpoint must be between 0 and 255.") - if cluster_id < 0 or cluster_id > 0xFFFF: - raise ValueError("Cluster id must be between 0 and 0xFFFF.") - if profile_id < 0 or profile_id > 0xFFFF: - raise ValueError("Profile id must be between 0 and 0xFFFF.") - - super(ExplicitRXIndicatorPacket, self).__init__(ApiFrameType.EXPLICIT_RX_INDICATOR) - self.__x64bit_addr = x64bit_addr - self.__x16bit_addr = x16bit_addr - self.__source_endpoint = source_endpoint - self.__dest_endpoint = dest_endpoint - self.__cluster_id = cluster_id - self.__profile_id = profile_id - self.__receive_options = receive_options - self.__rf_data = rf_data - - @staticmethod - def create_packet(raw, operating_mode): - """ - Override method. - - Returns: - :class:`.ExplicitRXIndicatorPacket`. - - Raises: - InvalidPacketException: if the bytearray length is less than 22. (start delim. + length (2 bytes) + frame - type + 64bit addr. + 16bit addr. + source endpoint + dest. endpoint + cluster ID (2 bytes) + - profile ID (2 bytes) + receive options + checksum = 22 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 different than :attr:`.ApiFrameType.EXPLICIT_RX_INDICATOR`. - InvalidOperatingModeException: if ``operating_mode`` is not supported. - - .. seealso:: - | :meth:`.XBeePacket.create_packet` - | :meth:`.XBeeAPIPacket._check_api_packet` - """ - if operating_mode != OperatingMode.ESCAPED_API_MODE and operating_mode != OperatingMode.API_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("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], raw[21:-1]) - - 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` - """ - raw = self.__x64bit_addr.address - raw += self.__x16bit_addr.address - raw.append(self.__source_endpoint) - raw.append(self.__dest_endpoint) - 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 - return raw - - def _get_api_packet_spec_data_dict(self): - """ - Override method. - - .. seealso:: - | :meth:`.XBeeAPIPacket._get_api_packet_spec_data_dict` - """ - return {DictKeys.X64BIT_ADDR: self.__x64bit_addr.address, - DictKeys.X16BIT_ADDR: self.__x16bit_addr.address, - DictKeys.SOURCE_ENDPOINT: self.__source_endpoint, - DictKeys.DEST_ENDPOINT: self.__dest_endpoint, - DictKeys.CLUSTER_ID: self.__cluster_id, - DictKeys.PROFILE_ID: self.__profile_id, - DictKeys.RECEIVE_OPTIONS: self.__receive_options, - DictKeys.RF_DATA: self.__rf_data} - - def __get_64bit_addr(self): - """ - Returns the 64-bit source address. - - Returns: - :class:`.XBee64BitAddress`: the 64-bit source address. - - .. seealso:: - | :class:`.XBee64BitAddress` - """ - return self.__x64bit_addr - - def __set_64bit_addr(self, x64bit_addr): - """ - Sets the 64-bit source address. - - Args: - x64bit_addr (:class:`.XBee64BitAddress`): the new 64-bit source address. - - .. seealso:: - | :class:`.XBee64BitAddress` - """ - self.__x64bit_addr = x64bit_addr - - def __get_16bit_addr(self): - """ - Returns the 16-bit source address. - - Returns: - :class:`.XBee16BitAddress`: the 16-bit source address. - - .. seealso:: - | :class:`.XBee16BitAddress` - """ - return self.__x16bit_addr - - def __set_16bit_addr(self, x16bit_addr): - """ - Sets the 16-bit source address. - - Args: - x16bit_addr (:class:`.XBee16BitAddress`): the new 16-bit source address. - - .. seealso:: - | :class:`.XBee16BitAddress` - """ - self.__x16bit_addr = x16bit_addr - - def __get_source_endpoint(self): - """ - Returns the source endpoint of the transmission. - - Returns: - Integer: the source endpoint of the transmission. - """ - return self.__source_endpoint - - def __set_source_endpoint(self, source_endpoint): - """ - Sets the source endpoint of the transmission. - - Args: - source_endpoint (Integer): the new source endpoint of the transmission. - """ - self.__source_endpoint = source_endpoint - - def __get_dest_endpoint(self): - """ - Returns the destination endpoint of the transmission. - - Returns: - Integer: the destination endpoint of the transmission. - """ - return self.__dest_endpoint - - def __set_dest_endpoint(self, dest_endpoint): - """ - Sets the destination endpoint of the transmission. - - Args: - dest_endpoint (Integer): the new destination endpoint of the transmission. - """ - self.__dest_endpoint = dest_endpoint - - def __get_cluster_id(self): - """ - Returns the cluster ID of the transmission. - - Returns: - Integer: the cluster ID of the transmission. - """ - return self.__cluster_id - - def __set_cluster_id(self, cluster_id): - """ - Sets the cluster ID of the transmission. - - Args: - cluster_id (Integer): the new cluster ID of the transmission. - """ - self.__cluster_id = cluster_id - - def __get_profile_id(self): - """ - Returns the profile ID of the transmission. - - Returns - Integer: the profile ID of the transmission. - """ - return self.__profile_id - - def __set_profile_id(self, profile_id): - """ - Sets the profile ID of the transmission. - - Args - profile_id (Integer): the new profile ID of the transmission. - """ - self.__profile_id = profile_id - - def __get_options(self): - """ - Returns the receive options bitfield. - - Returns: - Integer: the receive options bitfield. - - .. seealso:: - | :class:`.XBeeReceiveOptions` - """ - return self.__receive_options - - def __set_options(self, receive_options): - """ - Sets the receive options bitfield. - - Args: - receive_options (Integer): the new receive options bitfield. - - .. seealso:: - | :class:`.XBeeReceiveOptions` - """ - self.__receive_options = receive_options - - def __get_rf_data(self): - """ - Returns the received RF data. - - Returns: - Bytearray: the received RF data. - """ - if self.__rf_data is None: - return None - return self.__rf_data.copy() - - def __set_rf_data(self, rf_data): - """ - Sets the received RF data. - - Args: - rf_data (Bytearray): the new received RF data. - """ - if rf_data is None: - self.__rf_data = None - else: - self.__rf_data = rf_data.copy() - - x64bit_source_addr = property(__get_64bit_addr, __set_64bit_addr) - """:class:`.XBee64BitAddress`. 64-bit source address.""" - - x16bit_source_addr = property(__get_16bit_addr, __set_16bit_addr) - """:class:`.XBee16BitAddress`. 16-bit source address.""" - - receive_options = property(__get_options, __set_options) - """Integer. Receive options bitfield.""" - - source_endpoint = property(__get_source_endpoint, __set_source_endpoint) - """Integer. Source endpoint of the transmission.""" - - dest_endpoint = property(__get_dest_endpoint, __set_dest_endpoint) - """Integer. Destination endpoint of the transmission.""" - - cluster_id = property(__get_cluster_id, __set_cluster_id) - """Integer. Cluster ID of the transmission.""" - - profile_id = property(__get_profile_id, __set_profile_id) - """Integer. Profile ID of the transmission.""" - - rf_data = property(__get_rf_data, __set_rf_data) - """Bytearray. Received RF data.""" diff --git a/digi/xbee/packets/devicecloud.py b/digi/xbee/packets/devicecloud.py deleted file mode 100644 index 8fb66ce..0000000 --- a/digi/xbee/packets/devicecloud.py +++ /dev/null @@ -1,996 +0,0 @@ -# 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 -# file, You can obtain one at http://mozilla.org/MPL/2.0/. -# -# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR -# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF -# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - -from digi.xbee.packets.base import XBeeAPIPacket, DictKeys -from digi.xbee.util import utils -from digi.xbee.exception import InvalidOperatingModeException, InvalidPacketException -from digi.xbee.packets.aft import ApiFrameType -from digi.xbee.models.mode import OperatingMode -from digi.xbee.models.status import DeviceCloudStatus, FrameError -from digi.xbee.models.options import SendDataRequestOptions - - -class DeviceRequestPacket(XBeeAPIPacket): - """ - This class represents a device request packet. Packet is built - using the parameters of the constructor or providing a valid API payload. - - This frame type is sent out the serial port when the XBee module receives - a valid device request from Device Cloud. - - .. seealso:: - | :class:`.DeviceResponsePacket` - | :class:`.XBeeAPIPacket` - """ - - __MIN_PACKET_LENGTH = 5 - - def __init__(self, request_id, target=None, request_data=None): - """ - Class constructor. Instantiates a new :class:`.DeviceRequestPacket` object with the provided parameters. - - Args: - request_id (Integer): number that identifies the device request. (0 has no special meaning) - target (String): device request target. - request_data (Bytearray, optional): data of the request. Optional. - - Raises: - ValueError: if ``request_id`` is less than 0 or greater than 255. - ValueError: if length of ``target`` is greater than 255. - - .. seealso:: - | :class:`.XBeeAPIPacket` - """ - if request_id < 0 or request_id > 255: - raise ValueError("Device request ID must be between 0 and 255.") - - if target is not None and len(target) > 255: - raise ValueError("Target length cannot exceed 255 bytes.") - - super().__init__(ApiFrameType.DEVICE_REQUEST) - self.__request_id = request_id - self.__transport = 0x00 # Reserved. - self.__flags = 0x00 # Reserved. - self.__target = target - self.__request_data = request_data - - @staticmethod - def create_packet(raw, operating_mode): - """ - Override method. - - Returns: - :class:`.DeviceRequestPacket` - - Raises: - InvalidPacketException: if the bytearray length is less than 9. (start delim. + length (2 bytes) + frame - type + request id + transport + flags + target length + 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 different than :attr:`.ApiFrameType.DEVICE_REQUEST`. - InvalidOperatingModeException: if ``operating_mode`` is not supported. - - .. seealso:: - | :meth:`.XBeePacket.create_packet` - | :meth:`.XBeeAPIPacket._check_api_packet` - """ - if operating_mode != OperatingMode.ESCAPED_API_MODE and operating_mode != OperatingMode.API_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("This packet is not a device request packet.") - - target_length = raw[7] - return DeviceRequestPacket(raw[4], raw[8:8 + target_length].decode("utf8"), raw[8 + target_length:-1]) - - 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 = utils.int_to_bytes(self.__request_id, num_bytes=1) - ret += utils.int_to_bytes(self.__transport, num_bytes=1) - ret += utils.int_to_bytes(self.__flags, num_bytes=1) - if self.__target is not None: - ret += utils.int_to_bytes(len(self.__target), num_bytes=1) - ret += bytearray(self.__target, "utf8") - else: - ret += utils.int_to_bytes(0x00, num_bytes=1) - if self.__request_data is not None: - ret += self.__request_data - - return ret - - def _get_api_packet_spec_data_dict(self): - """ - Override method. - - See: - :meth:`.XBeeAPIPacket._get_api_packet_spec_data_dict` - """ - return {DictKeys.REQUEST_ID: self.__request_id, - DictKeys.TRANSPORT: self.__transport, - DictKeys.FLAGS: self.__flags, - DictKeys.TARGET: self.__target, - DictKeys.RF_DATA: list(self.__request_data) if self.__request_data is not None else None} - - def __get_request_id(self): - """ - Returns the request ID of the packet. - - Returns: - Integer: the request ID of the packet. - """ - return self.__request_id - - def __set_request_id(self, request_id): - """ - Sets the request ID of the packet. - - Args: - request_id (Integer): the new request ID of the packet. Must be between 0 and 255. - - Raises: - ValueError: if ``request_id`` is less than 0 or greater than 255. - """ - if request_id < 0 or request_id > 255: - raise ValueError("Device request ID must be between 0 and 255.") - self.__request_id = request_id - - def __get_transport(self): - """ - Returns the transport of the packet. - - Returns: - Integer: the transport of the packet. - """ - return self.__transport - - def __get_flags(self): - """ - Returns the flags of the packet. - - Returns: - Integer: the flags of the packet. - """ - return self.__flags - - def __get_target(self): - """ - Returns the device request target of the packet. - - Returns: - String: the device request target of the packet. - """ - return self.__target - - def __set_target(self, target): - """ - Sets the device request target of the packet. - - Args: - target (String): the new device request target of the packet. - - Raises: - ValueError: if ``target`` length is greater than 255. - """ - if target is not None and len(target) > 255: - raise ValueError("Target length cannot exceed 255 bytes.") - self.__target = target - - def __get_request_data(self): - """ - Returns the data of the device request. - - Returns: - Bytearray: the data of the device request. - """ - if self.__request_data is None: - return None - return self.__request_data.copy() - - def __set_request_data(self, request_data): - """ - Sets the data of the device request. - - Args: - request_data (Bytearray): the new data of the device request. - """ - if request_data is None: - self.__request_data = None - else: - self.__request_data = request_data.copy() - - request_id = property(__get_request_id, __set_request_id) - """Integer. Request ID of the packet.""" - - transport = property(__get_transport) - """Integer. Transport (reserved).""" - - flags = property(__get_flags) - """Integer. Flags (reserved).""" - - target = property(__get_target, __set_target) - """String. Request target of the packet.""" - - request_data = property(__get_request_data, __set_request_data) - """Bytearray. Data of the device request.""" - - -class DeviceResponsePacket(XBeeAPIPacket): - """ - This class represents a device response packet. Packet is built - using the parameters of the constructor or providing a valid API payload. - - This frame type is sent to the serial port by the host in response to the - :class:`.DeviceRequestPacket`. It should be sent within five seconds to avoid - a timeout error. - - .. seealso:: - | :class:`.DeviceRequestPacket` - | :class:`.XBeeAPIPacket` - """ - - __MIN_PACKET_LENGTH = 8 - - def __init__(self, frame_id, request_id, response_data=None): - """ - Class constructor. Instantiates a new :class:`.DeviceResponsePacket` object with the provided parameters. - - Args: - frame_id (Integer): the frame ID of the packet. - request_id (Integer): device Request ID. This number should match the device request ID in the - device request. Otherwise, an error will occur. (0 has no special meaning) - response_data (Bytearray, optional): data of the response. Optional. - - Raises: - ValueError: if ``frame_id`` is less than 0 or greater than 255. - ValueError: if ``request_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 request_id < 0 or request_id > 255: - raise ValueError("Device request ID must be between 0 and 255.") - - super().__init__(ApiFrameType.DEVICE_RESPONSE) - self._frame_id = frame_id - self.__request_id = request_id - self.__response_data = response_data - - @staticmethod - def create_packet(raw, operating_mode): - """ - Override method. - - Returns: - :class:`.DeviceResponsePacket` - - Raises: - InvalidPacketException: if the bytearray length is less than 8. (start delim. + length (2 bytes) + frame - type + frame id + request id + reserved + 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 different than :attr:`.ApiFrameType.DEVICE_RESPONSE`. - InvalidOperatingModeException: if ``operating_mode`` is not supported. - - .. seealso:: - | :meth:`.XBeePacket.create_packet` - | :meth:`.XBeeAPIPacket._check_api_packet` - """ - if operating_mode != OperatingMode.ESCAPED_API_MODE and operating_mode != OperatingMode.API_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("This packet is not a device response packet.") - - return DeviceResponsePacket(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 = utils.int_to_bytes(self.__request_id, num_bytes=1) - ret += utils.int_to_bytes(0x00, num_bytes=1) - if self.__response_data is not None: - ret += self.__response_data - - return ret - - def _get_api_packet_spec_data_dict(self): - """ - Override method. - - .. seealso:: - | :meth:`.XBeeAPIPacket._get_api_packet_spec_data_dict` - """ - return {DictKeys.REQUEST_ID: self.__request_id, - DictKeys.RESERVED: 0x00, - DictKeys.RF_DATA: list(self.__response_data) if self.__response_data is not None else None} - - def __get_request_id(self): - """ - Returns the request ID of the packet. - - Returns: - Integer: the request ID of the packet. - """ - return self.__request_id - - def __set_request_id(self, request_id): - """ - Sets the request ID of the packet. - - Args: - request_id (Integer): the new request ID of the packet. Must be between 0 and 255. - - Raises: - ValueError: if ``request_id`` is less than 0 or greater than 255. - """ - if request_id < 0 or request_id > 255: - raise ValueError("Device request ID must be between 0 and 255.") - self.__request_id = request_id - - def __get_response_data(self): - """ - Returns the data of the device response. - - Returns: - Bytearray: the data of the device response. - """ - if self.__response_data is None: - return None - return self.__response_data.copy() - - def __set_response_data(self, response_data): - """ - Sets the data of the device response. - - Args: - response_data (Bytearray): the new data of the device response. - """ - if response_data is None: - self.__response_data = None - else: - self.__response_data = response_data.copy() - - request_id = property(__get_request_id, __set_request_id) - """Integer. Request ID of the packet.""" - - request_data = property(__get_response_data, __set_response_data) - """Bytearray. Data of the device response.""" - - -class DeviceResponseStatusPacket(XBeeAPIPacket): - """ - This class represents a device response status packet. Packet is built - using the parameters of the constructor or providing a valid API payload. - - This frame type is sent to the serial port after the serial port sends a - :class:`.DeviceResponsePacket`. - - .. seealso:: - | :class:`.DeviceResponsePacket` - | :class:`.XBeeAPIPacket` - """ - - __MIN_PACKET_LENGTH = 7 - - def __init__(self, frame_id, status): - """ - Class constructor. Instantiates a new :class:`.DeviceResponseStatusPacket` object with the provided parameters. - - Args: - frame_id (Integer): the frame ID of the packet. - status (:class:`.DeviceCloudStatus`): device response status. - - Raises: - ValueError: if ``frame_id`` is less than 0 or greater than 255. - - .. seealso:: - | :class:`.DeviceCloudStatus` - | :class:`.XBeeAPIPacket` - """ - if frame_id < 0 or frame_id > 255: - raise ValueError("Frame id must be between 0 and 255.") - - super().__init__(ApiFrameType.DEVICE_RESPONSE_STATUS) - self._frame_id = frame_id - self.__status = status - - @staticmethod - def create_packet(raw, operating_mode): - """ - Override method. - - Returns: - :class:`.DeviceResponseStatusPacket` - - Raises: - InvalidPacketException: if the bytearray length is less than 7. (start delim. + length (2 bytes) + frame - type + frame id + device response status + 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 different than :attr:`.ApiFrameType.DEVICE_RESPONSE_STATUS`. - InvalidOperatingModeException: if ``operating_mode`` is not supported. - - .. seealso:: - | :meth:`.XBeePacket.create_packet` - | :meth:`.XBeeAPIPacket._check_api_packet` - """ - if operating_mode != OperatingMode.ESCAPED_API_MODE and operating_mode != OperatingMode.API_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("This packet is not a device response status packet.") - - return DeviceResponseStatusPacket(raw[4], DeviceCloudStatus.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 utils.int_to_bytes(self.__status.code, num_bytes=1) - - def _get_api_packet_spec_data_dict(self): - """ - Override method. - - See: - :meth:`.XBeeAPIPacket._get_api_packet_spec_data_dict` - """ - return {DictKeys.DC_STATUS: self.__status} - - def __get_status(self): - """ - Returns the status of the device response packet. - - Returns: - :class:`.DeviceCloudStatus`: the status of the device response packet. - - .. seealso:: - | :class:`.DeviceCloudStatus` - """ - return self.__status - - def __set_status(self, status): - """ - Sets the status of the device response packet. - - Args: - status (:class:`.DeviceCloudStatus`): the new status of the device response packet. - - .. seealso:: - | :class:`.DeviceCloudStatus` - """ - self.__status = status - - status = property(__get_status, __set_status) - """:class:`.DeviceCloudStatus`. Status of the device response.""" - - -class FrameErrorPacket(XBeeAPIPacket): - """ - This class represents a frame error packet. Packet is built - using the parameters of the constructor or providing a valid API payload. - - This frame type is sent to the serial port for any type of frame error. - - .. seealso:: - | :class:`.FrameError` - | :class:`.XBeeAPIPacket` - """ - - __MIN_PACKET_LENGTH = 6 - - def __init__(self, frame_error): - """ - Class constructor. Instantiates a new :class:`.FrameErrorPacket` object with the provided parameters. - - Args: - frame_error (:class:`.FrameError`): the frame error. - - .. seealso:: - | :class:`.FrameError` - | :class:`.XBeeAPIPacket` - """ - super().__init__(ApiFrameType.FRAME_ERROR) - self.__frame_error = frame_error - - @staticmethod - def create_packet(raw, operating_mode): - """ - Override method. - - Returns: - :class:`.FrameErrorPacket` - - Raises: - InvalidPacketException: if the bytearray length is less than 6. (start delim. + length (2 bytes) + frame - type + frame error + checksum = 6 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 different than :attr:`.ApiFrameType.FRAME_ERROR`. - InvalidOperatingModeException: if ``operating_mode`` is not supported. - - .. seealso:: - | :meth:`.XBeePacket.create_packet` - | :meth:`.XBeeAPIPacket._check_api_packet` - """ - if operating_mode != OperatingMode.ESCAPED_API_MODE and operating_mode != OperatingMode.API_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("This packet is not a frame error packet.") - - return FrameErrorPacket(FrameError.get(raw[4])) - - 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` - """ - return utils.int_to_bytes(self.__frame_error.code, num_bytes=1) - - def _get_api_packet_spec_data_dict(self): - """ - Override method. - - .. seealso:: - | :meth:`.XBeeAPIPacket._get_api_packet_spec_data_dict` - """ - return {DictKeys.FRAME_ERROR: self.__frame_error} - - def __get_error(self): - """ - Returns the frame error of the packet. - - Returns: - :class:`.FrameError`: the frame error of the packet. - - .. seealso:: - | :class:`.FrameError` - """ - return self.__frame_error - - def __set_error(self, frame_error): - """ - Sets the frame error of the packet. - - Args: - frame_error (:class:`.FrameError`): the new frame error of the packet. - - .. seealso:: - | :class:`.FrameError` - """ - self.__frame_error = frame_error - - error = property(__get_error, __set_error) - """:class:`.FrameError`. Frame error of the packet.""" - - -class SendDataRequestPacket(XBeeAPIPacket): - """ - This class represents a send data request packet. Packet is built - using the parameters of the constructor or providing a valid API payload. - - This frame type is used to send a file of the given name and type to - Device Cloud. - - If the frame ID is non-zero, a :class:`.SendDataResponsePacket` will be - received. - - .. seealso:: - | :class:`.SendDataResponsePacket` - | :class:`.XBeeAPIPacket` - """ - - __MIN_PACKET_LENGTH = 10 - - def __init__(self, frame_id, path, content_type, options, file_data=None): - """ - Class constructor. Instantiates a new :class:`.SendDataRequestPacket` object with the provided parameters. - - Args: - frame_id (Integer): the frame ID of the packet. - path (String): path of the file to upload to Device Cloud. - content_type (String): content type of the file to upload. - options (:class:`.SendDataRequestOptions`): the action when uploading a file. - file_data (Bytearray, optional): data of the file to upload. Optional. - - Raises: - ValueError: if ``frame_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.") - - super().__init__(ApiFrameType.SEND_DATA_REQUEST) - self._frame_id = frame_id - self.__path = path - self.__content_type = content_type - self.__transport = 0x00 # Always TCP. - self.__options = options - self.__file_data = file_data - - @staticmethod - def create_packet(raw, operating_mode): - """ - Override method. - - Returns: - :class:`.SendDataRequestPacket` - - Raises: - InvalidPacketException: if the bytearray length is less than 10. (start delim. + length (2 bytes) + frame - type + frame id + path length + content type length + transport + options + checksum = 10 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 different than :attr:`.ApiFrameType.SEND_DATA_REQUEST`. - InvalidOperatingModeException: if ``operating_mode`` is not supported. - - .. seealso:: - | :meth:`.XBeePacket.create_packet` - | :meth:`.XBeeAPIPacket._check_api_packet` - """ - if operating_mode != OperatingMode.ESCAPED_API_MODE and operating_mode != OperatingMode.API_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("This packet is not a send data request packet.") - - path_length = raw[5] - content_type_length = raw[6 + path_length] - return SendDataRequestPacket(raw[4], - 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]), - raw[6 + path_length + 3 + content_type_length:-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` - """ - if self.__path is not None: - ret = utils.int_to_bytes(len(self.__path), num_bytes=1) - ret += bytearray(self.__path, "utf8") - else: - ret = utils.int_to_bytes(0x00, num_bytes=1) - if self.__content_type is not None: - ret += utils.int_to_bytes(len(self.__content_type), num_bytes=1) - ret += bytearray(self.__content_type, "utf8") - else: - ret += utils.int_to_bytes(0x00, num_bytes=1) - ret += utils.int_to_bytes(0x00, num_bytes=1) # Transport is always TCP - ret += utils.int_to_bytes(self.__options.code, num_bytes=1) - if self.__file_data is not None: - ret += self.__file_data - - return ret - - def _get_api_packet_spec_data_dict(self): - """ - Override method. - - .. seealso:: - | :meth:`.XBeeAPIPacket._get_api_packet_spec_data_dict` - """ - return {DictKeys.PATH_LENGTH: len(self.__path) if self.__path is not None else 0x00, - DictKeys.PATH: self.__path if self.__path is not None else None, - DictKeys.CONTENT_TYPE_LENGTH: len(self.__content_type) if self.__content_type is not None else 0x00, - DictKeys.CONTENT_TYPE: self.__content_type if self.__content_type is not None else None, - DictKeys.TRANSPORT: 0x00, - DictKeys.TRANSMIT_OPTIONS: self.__options, - DictKeys.RF_DATA: list(self.__file_data) if self.__file_data is not None else None} - - def __get_path(self): - """ - Returns the path of the file to upload to Device Cloud. - - Returns: - String: the path of the file to upload to Device Cloud. - """ - return self.__path - - def __set_path(self, path): - """ - Sets the path of the file to upload to Device Cloud. - - Args: - path (String): the new path of the file to upload to Device Cloud. - """ - self.__path = path - - def __get_content_type(self): - """ - Returns the content type of the file to upload. - - Returns: - String: the content type of the file to upload. - """ - return self.__content_type - - def __set_content_type(self, content_type): - """ - Sets the content type of the file to upload. - - Args: - content_type (String): the new content type of the file to upload. - """ - self.__content_type = content_type - - def __get_options(self): - """ - Returns the file upload operation options. - - Returns: - :class:`.SendDataRequestOptions`: the file upload operation options. - - .. seealso:: - | :class:`.SendDataRequestOptions` - """ - return self.__options - - def __set_options(self, options): - """ - Sets the file upload operation options. - - Args: - options (:class:`.SendDataRequestOptions`): the new file upload operation options - - .. seealso:: - | :class:`.SendDataRequestOptions` - """ - self.__options = options - - def __get_file_data(self): - """ - Returns the data of the file to upload. - - Returns: - Bytearray: the data of the file to upload. - """ - if self.__file_data is None: - return None - return self.__file_data.copy() - - def __set_file_data(self, file_data): - """ - Sets the data of the file to upload. - - Args: - file_data (Bytearray): the new data of the file to upload. - """ - if file_data is None: - self.__file_data = None - else: - self.__file_data = file_data.copy() - - path = property(__get_path, __set_path) - """String. Path of the file to upload to Device Cloud.""" - - content_type = property(__get_content_type, __set_content_type) - """String. The content type of the file to upload.""" - - options = property(__get_options, __set_options) - """:class:`.SendDataRequestOptions`. File upload operation options.""" - - file_data = property(__get_file_data, __set_file_data) - """Bytearray. Data of the file to upload.""" - - -class SendDataResponsePacket(XBeeAPIPacket): - """ - This class represents a send data response packet. Packet is built - using the parameters of the constructor or providing a valid API payload. - - This frame type is sent out the serial port in response to the - :class:`.SendDataRequestPacket`, providing its frame ID is non-zero. - - .. seealso:: - | :class:`.SendDataRequestPacket` - | :class:`.XBeeAPIPacket` - """ - - __MIN_PACKET_LENGTH = 7 - - def __init__(self, frame_id, status): - """ - Class constructor. Instantiates a new :class:`.SendDataResponsePacket` object with the provided parameters. - - Args: - frame_id (Integer): the frame ID of the packet. - status (:class:`.DeviceCloudStatus`): the file upload status. - - Raises: - ValueError: if ``frame_id`` is less than 0 or greater than 255. - - .. seealso:: - | :class:`.DeviceCloudStatus` - | :class:`.XBeeAPIPacket` - """ - if frame_id < 0 or frame_id > 255: - raise ValueError("Frame id must be between 0 and 255.") - - super().__init__(ApiFrameType.SEND_DATA_RESPONSE) - self._frame_id = frame_id - self.__status = status - - @staticmethod - def create_packet(raw, operating_mode): - """ - Override method. - - Returns: - :class:`.SendDataResponsePacket` - - Raises: - InvalidPacketException: if the bytearray length is less than 10. (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 - 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 different than :attr:`.ApiFrameType.SEND_DATA_RESPONSE`. - InvalidOperatingModeException: if ``operating_mode`` is not supported. - - .. seealso:: - | :meth:`.XBeePacket.create_packet` - | :meth:`.XBeeAPIPacket._check_api_packet` - """ - if operating_mode != OperatingMode.ESCAPED_API_MODE and operating_mode != OperatingMode.API_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("This packet is not a send data response packet.") - - return SendDataResponsePacket(raw[4], DeviceCloudStatus.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 utils.int_to_bytes(self.__status.code, num_bytes=1) - - def _get_api_packet_spec_data_dict(self): - """ - Override method. - - .. seealso:: - | :meth:`.XBeeAPIPacket._get_api_packet_spec_data_dict` - """ - return {DictKeys.DC_STATUS: self.__status} - - def __get_status(self): - """ - Returns the file upload status. - - Returns: - :class:`.DeviceCloudStatus`: the file upload status. - - .. seealso:: - | :class:`.DeviceCloudStatus` - """ - return self.__status - - def __set_status(self, status): - """ - Sets the file upload status. - - Args: - status (:class:`.DeviceCloudStatus`): the new file upload status. - - .. seealso:: - | :class:`.DeviceCloudStatus` - """ - self.__status = status - - status = property(__get_status, __set_status) - """:class:`.DeviceCloudStatus`. The file upload status.""" diff --git a/digi/xbee/packets/factory.py b/digi/xbee/packets/factory.py deleted file mode 100644 index 3f2ce6f..0000000 --- a/digi/xbee/packets/factory.py +++ /dev/null @@ -1,218 +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. - -from digi.xbee.packets.base import * -from digi.xbee.packets.cellular import * -from digi.xbee.packets.common import * -from digi.xbee.packets.devicecloud import * -from digi.xbee.packets.network import * -from digi.xbee.packets.raw import * -from digi.xbee.packets.relay import * -from digi.xbee.packets.wifi import * -from digi.xbee.packets.aft import ApiFrameType -from digi.xbee.models.mode import OperatingMode - - -""" -This module provides functionality to build XBee packets from -bytearray returning the appropriate XBeePacket subclass. - -All the API and API2 logic is already included so all packet reads are -independent of the XBee operating mode. - -Two API modes are supported and both can be enabled using the ``AP`` -(API Enable) command:: - - API1 - API Without Escapes - The data frame structure is defined as follows: - - Start Delimiter Length Frame Data Checksum - (Byte 1) (Bytes 2-3) (Bytes 4-n) (Byte n + 1) - +----------------+ +-------------------+ +--------------------------- + +----------------+ - | 0x7E | | MSB | LSB | | API-specific Structure | | 1 Byte | - +----------------+ +-------------------+ +----------------------------+ +----------------+ - MSB = Most Significant Byte, LSB = Least Significant Byte - - -API2 - API With Escapes -The data frame structure is defined as follows:: - - Start Delimiter Length Frame Data Checksum - (Byte 1) (Bytes 2-3) (Bytes 4-n) (Byte n + 1) - +----------------+ +-------------------+ +--------------------------- + +----------------+ - | 0x7E | | MSB | LSB | | API-specific Structure | | 1 Byte | - +----------------+ +-------------------+ +----------------------------+ +----------------+ - \___________________________________ _________________________________/ - \/ - Characters Escaped If Needed - - MSB = Most Significant Byte, LSB = Least Significant Byte - - -When sending or receiving an API2 frame, specific data values must be -escaped (flagged) so they do not interfere with the data frame sequencing. -To escape an interfering data byte, the byte 0x7D is inserted before -the byte to be escaped XOR'd with 0x20. - -The data bytes that need to be escaped: - -- ``0x7E`` - Frame Delimiter - :attr:`.SpecialByte. -- ``0x7D`` - Escape -- ``0x11`` - XON -- ``0x13`` - XOFF - -The length field has a two-byte value that specifies the number of -bytes that will be contained in the frame data field. It does not include the -checksum field. - -The frame data forms an API-specific structure as follows:: - - Start Delimiter Length Frame Data Checksum - (Byte 1) (Bytes 2-3) (Bytes 4-n) (Byte n + 1) - +----------------+ +-------------------+ +--------------------------- + +----------------+ - | 0x7E | | MSB | LSB | | API-specific Structure | | 1 Byte | - +----------------+ +-------------------+ +----------------------------+ +----------------+ - / \\ - / API Identifier Identifier specific data \\ - +------------------+ +------------------------------+ - | cmdID | | cmdData | - +------------------+ +------------------------------+ - - -The cmdID frame (API-identifier) indicates which API messages -will be contained in the cmdData frame (Identifier-specific data). - -To unit_test data integrity, a checksum is calculated and verified on -non-escaped data. - -.. seealso:: - | :class:`.XBeePacket` - | :class:`.OperatingMode` -""" - - -def build_frame(packet_bytearray, operating_mode=OperatingMode.API_MODE): - """ - Creates a packet from raw data. - - Args: - packet_bytearray (Bytearray): the raw data of the packet to build. - operating_mode (:class:`.OperatingMode`): the operating mode in which the raw data has been captured. - - .. seealso:: - | :class:`.OperatingMode` - """ - frame_type = ApiFrameType.get(packet_bytearray[3]) - - if frame_type == ApiFrameType.GENERIC: - return GenericXBeePacket.create_packet(packet_bytearray, operating_mode) - - elif frame_type == ApiFrameType.AT_COMMAND: - return ATCommPacket.create_packet(packet_bytearray, operating_mode) - - elif frame_type == ApiFrameType.AT_COMMAND_QUEUE: - return ATCommQueuePacket.create_packet(packet_bytearray, operating_mode) - - elif frame_type == ApiFrameType.AT_COMMAND_RESPONSE: - return ATCommResponsePacket.create_packet(packet_bytearray, operating_mode) - - elif frame_type == ApiFrameType.RECEIVE_PACKET: - return ReceivePacket.create_packet(packet_bytearray, operating_mode) - - elif frame_type == ApiFrameType.RX_64: - return RX64Packet.create_packet(packet_bytearray, operating_mode) - - elif frame_type == ApiFrameType.RX_16: - return RX16Packet.create_packet(packet_bytearray, operating_mode) - - elif frame_type == ApiFrameType.REMOTE_AT_COMMAND_REQUEST: - return RemoteATCommandPacket.create_packet(packet_bytearray, operating_mode) - - elif frame_type == ApiFrameType.REMOTE_AT_COMMAND_RESPONSE: - return RemoteATCommandResponsePacket.create_packet(packet_bytearray, operating_mode) - - elif frame_type == ApiFrameType.TRANSMIT_REQUEST: - return TransmitPacket.create_packet(packet_bytearray, operating_mode) - - elif frame_type == ApiFrameType.TRANSMIT_STATUS: - return TransmitStatusPacket.create_packet(packet_bytearray, operating_mode) - - elif frame_type == ApiFrameType.MODEM_STATUS: - return ModemStatusPacket.create_packet(packet_bytearray, operating_mode) - - elif frame_type == ApiFrameType.TX_STATUS: - return TXStatusPacket.create_packet(packet_bytearray, operating_mode) - - elif frame_type == ApiFrameType.RX_IO_16: - return RX16IOPacket.create_packet(packet_bytearray, operating_mode) - - elif frame_type == ApiFrameType.RX_IO_64: - return RX64IOPacket.create_packet(packet_bytearray, operating_mode) - - elif frame_type == ApiFrameType.IO_DATA_SAMPLE_RX_INDICATOR: - return IODataSampleRxIndicatorPacket.create_packet(packet_bytearray, operating_mode) - - elif frame_type == ApiFrameType.EXPLICIT_ADDRESSING: - return ExplicitAddressingPacket.create_packet(packet_bytearray, operating_mode) - - elif frame_type == ApiFrameType.EXPLICIT_RX_INDICATOR: - return ExplicitRXIndicatorPacket.create_packet(packet_bytearray, operating_mode) - - elif frame_type == ApiFrameType.TX_SMS: - return TXSMSPacket.create_packet(packet_bytearray, operating_mode) - - elif frame_type == ApiFrameType.TX_IPV4: - return TXIPv4Packet.create_packet(packet_bytearray, operating_mode) - - elif frame_type == ApiFrameType.RX_SMS: - return RXSMSPacket.create_packet(packet_bytearray, operating_mode) - - elif frame_type == ApiFrameType.USER_DATA_RELAY_OUTPUT: - return UserDataRelayOutputPacket.create_packet(packet_bytearray, operating_mode) - - elif frame_type == ApiFrameType.RX_IPV4: - return RXIPv4Packet.create_packet(packet_bytearray, operating_mode) - - elif frame_type == ApiFrameType.REMOTE_AT_COMMAND_REQUEST_WIFI: - return RemoteATCommandWifiPacket.create_packet(packet_bytearray, operating_mode) - - elif frame_type == ApiFrameType.SEND_DATA_REQUEST: - return SendDataRequestPacket.create_packet(packet_bytearray, operating_mode) - - elif frame_type == ApiFrameType.DEVICE_RESPONSE: - return DeviceResponsePacket.create_packet(packet_bytearray, operating_mode) - - elif frame_type == ApiFrameType.USER_DATA_RELAY_REQUEST: - return UserDataRelayPacket.create_packet(packet_bytearray, operating_mode) - - elif frame_type == ApiFrameType.REMOTE_AT_COMMAND_RESPONSE_WIFI: - return RemoteATCommandResponseWifiPacket.create_packet(packet_bytearray, operating_mode) - - elif frame_type == ApiFrameType.IO_DATA_SAMPLE_RX_INDICATOR_WIFI: - return IODataSampleRxIndicatorWifiPacket.create_packet(packet_bytearray, operating_mode) - - elif frame_type == ApiFrameType.SEND_DATA_RESPONSE: - return SendDataResponsePacket.create_packet(packet_bytearray, operating_mode) - - elif frame_type == ApiFrameType.DEVICE_REQUEST: - return DeviceRequestPacket.create_packet(packet_bytearray, operating_mode) - - elif frame_type == ApiFrameType.DEVICE_RESPONSE_STATUS: - return DeviceResponseStatusPacket.create_packet(packet_bytearray, operating_mode) - - elif frame_type == ApiFrameType.FRAME_ERROR: - return FrameErrorPacket.create_packet(packet_bytearray, operating_mode) - - else: - return UnknownXBeePacket.create_packet(packet_bytearray, operating_mode) diff --git a/digi/xbee/packets/network.py b/digi/xbee/packets/network.py deleted file mode 100644 index 0482739..0000000 --- a/digi/xbee/packets/network.py +++ /dev/null @@ -1,528 +0,0 @@ -# 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 -# 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.models.mode import OperatingMode -from digi.xbee.models.protocol import IPProtocol -from digi.xbee.packets.aft import ApiFrameType -from digi.xbee.packets.base import XBeeAPIPacket, DictKeys -from digi.xbee.util import utils -from digi.xbee.exception import InvalidOperatingModeException, InvalidPacketException - - -class RXIPv4Packet(XBeeAPIPacket): - """ - This class represents an RX (Receive) IPv4 packet. Packet is built - using the parameters of the constructor or providing a valid byte array. - - .. seealso:: - | :class:`.TXIPv4Packet` - | :class:`.XBeeAPIPacket` - """ - __MIN_PACKET_LENGTH = 15 - - def __init__(self, source_address, dest_port, source_port, ip_protocol, data=None): - """ - Class constructor. Instantiates a new :class:`.RXIPv4Packet` object with the provided parameters. - - Args: - source_address (:class:`.IPv4Address`): IPv4 address of the source device. - dest_port (Integer): destination port number. - source_port (Integer): source port number. - ip_protocol (:class:`.IPProtocol`): IP protocol used for transmitted data. - data (Bytearray, optional): data that is sent to the destination device. Optional. - - Raises: - ValueError: if ``dest_port`` is less than 0 or greater than 65535 or - ValueError: if ``source_port`` is less than 0 or greater than 65535. - - .. seealso:: - | :class:`.IPProtocol` - """ - if dest_port < 0 or dest_port > 65535: - raise ValueError("Destination port must be between 0 and 65535") - if source_port < 0 or source_port > 65535: - raise ValueError("Source port must be between 0 and 65535") - - super().__init__(ApiFrameType.RX_IPV4) - self.__source_address = source_address - self.__dest_port = dest_port - self.__source_port = source_port - self.__ip_protocol = ip_protocol - self.__status = 0 # Reserved - self.__data = data - - @staticmethod - def create_packet(raw, operating_mode): - """ - Override method. - - Returns: - RXIPv4Packet. - - Raises: - InvalidPacketException: if the bytearray length is less than 15. (start delim + length (2 bytes) + frame - type + source address (4 bytes) + dest port (2 bytes) + source port (2 bytes) + network protocol + - status + checksum = 15 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:`.SPECIAL_BYTE`. - InvalidPacketException: if the calculated checksum is different than the checksum field value (last byte). - InvalidPacketException: if the frame type is not :attr:`ApiFrameType.RX_IPV4`. - InvalidOperatingModeException: if ``operating_mode`` is not supported. - - .. seealso:: - | :meth:`.XBeePacket.create_packet` - | :meth:`.XBeeAPIPacket._check_api_packet` - """ - if operating_mode != OperatingMode.ESCAPED_API_MODE and operating_mode != OperatingMode.API_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("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]), - raw[14:-1]) - - def needs_id(self): - """ - Override method. - - .. seealso:: - | :meth:`.XBeeAPIPacket.needs_id` - """ - return True - - 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:`.IPv4Address`): The new IPv4 source address. - """ - if source_address is not None: - self.__source_address = source_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_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 - - def __get_ip_protocol(self): - """ - Returns the IP protocol used for transmitted data. - - Returns: - :class:`.IPProtocol`: the IP protocol used for transmitted data. - """ - return self.__ip_protocol - - def __set_ip_protocol(self, ip_protocol): - """ - Sets the IP protocol used for transmitted data. - - Args: - ip_protocol (:class:`.IPProtocol`): the new IP protocol. - """ - self.__ip_protocol = ip_protocol - - def __get_data(self): - """ - Returns the data of the packet. - - Returns: - Bytearray: the data of the packet. - """ - if self.__data is None: - return self.__data - return self.__data.copy() - - def __set_data(self, data): - """ - Sets the data of the packet. - - Args: - data (Bytearray): the new data of the packet. - """ - if data is None: - self.__data = None - else: - self.__data = data.copy() - - def _get_api_packet_spec_data(self): - """ - Override method. - - .. seealso:: - | :meth:`.XBeeAPIPacket._get_API_packet_spec_data` - """ - ret = bytearray(self.__source_address.packed) - ret += utils.int_to_bytes(self.__dest_port, num_bytes=2) - ret += utils.int_to_bytes(self.__source_port, num_bytes=2) - ret += utils.int_to_bytes(self.__ip_protocol.code, num_bytes=1) - ret += utils.int_to_bytes(self.__status, num_bytes=1) - if self.__data is not None: - ret += self.__data - return ret - - def _get_api_packet_spec_data_dict(self): - """ - Override method. - - .. seealso:: - | :meth:`.XBeeAPIPacket._get_API_packet_spec_data_dict` - """ - return {DictKeys.SRC_IPV4_ADDR: "%s (%s)" % (self.__source_address.packed, self.__source_address.exploded), - DictKeys.DEST_PORT: self.__dest_port, - DictKeys.SRC_PORT: self.__source_port, - DictKeys.IP_PROTOCOL: "%s (%s)" % (self.__ip_protocol.code, self.__ip_protocol.description), - DictKeys.STATUS: self.__status, - DictKeys.RF_DATA: bytearray(self.__data)} - - source_address = property(__get_source_address, __set_source_address) - """:class:`ipaddress.IPv4Address`. IPv4 address of the source device.""" - - dest_port = property(__get_dest_port, __set_dest_port) - """Integer. Destination port.""" - - source_port = property(__get_source_port, __set_source_port) - """Integer. Source port.""" - - ip_protocol = property(__get_ip_protocol, __set_ip_protocol) - """:class:`.IPProtocol`. IP protocol used in the transmission.""" - - data = property(__get_data, __set_data) - """Bytearray. Data of the packet.""" - - -class TXIPv4Packet(XBeeAPIPacket): - """ - This class represents an TX (Transmit) IPv4 packet. Packet is built - using the parameters of the constructor or providing a valid byte array. - - .. seealso:: - | :class:`.RXIPv4Packet` - | :class:`.XBeeAPIPacket` - """ - - OPTIONS_CLOSE_SOCKET = 2 - """This option will close the socket after the transmission.""" - - OPTIONS_LEAVE_SOCKET_OPEN = 0 - """This option will leave socket open after the transmission.""" - - __MIN_PACKET_LENGTH = 16 - - def __init__(self, frame_id, dest_address, dest_port, source_port, ip_protocol, transmit_options, data=None): - """ - Class constructor. Instantiates a new :class:`.TXIPv4Packet` object with the provided parameters. - - Args: - frame_id (Integer): the frame ID. Must be between 0 and 255. - dest_address (:class:`.IPv4Address`): IPv4 address of the destination device. - dest_port (Integer): destination port number. - source_port (Integer): source port number. - ip_protocol (:class:`.IPProtocol`): IP protocol used for transmitted data. - transmit_options (Integer): the transmit options of the packet. - data (Bytearray, optional): data that is sent to the destination device. Optional. - - Raises: - ValueError: if ``frame_id`` is less than 0 or greater than 255. - ValueError: if ``dest_port`` is less than 0 or greater than 65535. - ValueError: if ``source_port`` is less than 0 or greater than 65535. - - .. seealso:: - | :class:`.IPProtocol` - """ - if frame_id < 0 or frame_id > 255: - raise ValueError("Frame 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 source_port < 0 or source_port > 65535: - raise ValueError("Source port must be between 0 and 65535") - - super().__init__(ApiFrameType.TX_IPV4) - self._frame_id = frame_id - self.__dest_address = dest_address - self.__dest_port = dest_port - self.__source_port = source_port - self.__ip_protocol = ip_protocol - self.__transmit_options = transmit_options - self.__data = data - - @staticmethod - def create_packet(raw, operating_mode): - """ - Override method. - - Returns: - TXIPv4Packet. - - Raises: - InvalidPacketException: if the bytearray length is less than 16. (start delim + length (2 bytes) + frame - type + frame id + dest address (4 bytes) + dest port (2 bytes) + source port (2 bytes) + network - protocol + transmit options + checksum = 16 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:`.SPECIAL_BYTE`. - InvalidPacketException: if the calculated checksum is different than the checksum field value (last byte). - InvalidPacketException: if the frame type is not :attr:`ApiFrameType.TX_IPV4`. - InvalidOperatingModeException: if ``operating_mode`` is not supported. - - .. seealso:: - | :meth:`.XBeePacket.create_packet` - | :meth:`.XBeeAPIPacket._check_api_packet` - """ - if operating_mode != OperatingMode.ESCAPED_API_MODE and operating_mode != OperatingMode.API_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("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], raw[15:-1]) - - def needs_id(self): - """ - Override method. - - .. seealso:: - | :meth:`.XBeeAPIPacket.needs_id` - """ - return True - - 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_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 - - def __get_ip_protocol(self): - """ - Returns the IP protocol used for transmitted data. - - Returns: - :class:`.IPProtocol`: the IP protocol used for transmitted data. - """ - return self.__ip_protocol - - def __set_ip_protocol(self, ip_protocol): - """ - Sets the network protocol used for transmitted data. - - Args: - ip_protocol (:class:`.IPProtocol`): the new IP protocol. - """ - self.__ip_protocol = ip_protocol - - def __get_transmit_options(self): - """ - Returns the transmit options of the packet. - - Returns: - Integer: the transmit options of the packet. - """ - return self.__transmit_options - - def __set_transmit_options(self, transmit_options): - """ - Sets the transmit options bitfield of the packet. - - Args: - transmit_options (Integer): the new transmit options. Can - be :attr:`OPTIONS_CLOSE_SOCKET` or :attr:`OPTIONS_LEAVE_SOCKET_OPEN`. - """ - self.__transmit_options = transmit_options - - def __get_data(self): - """ - Returns the data of the packet. - - Returns: - Bytearray: the data of the packet. - """ - return self.__data if self.__data is None else self.__data.copy() - - def __set_data(self, data): - """ - Sets the data of the packet. - - Args: - data (Bytearray): the new data of the packet. - """ - self.__data = None if data is None else data.copy() - - def _get_api_packet_spec_data(self): - """ - Override method. - - .. seealso:: - | :meth:`.XBeeAPIPacket._get_API_packet_spec_data` - """ - ret = bytearray(self.__dest_address.packed) - ret += utils.int_to_bytes(self.__dest_port, num_bytes=2) - ret += utils.int_to_bytes(self.__source_port, num_bytes=2) - ret += utils.int_to_bytes(self.__ip_protocol.code) - ret += utils.int_to_bytes(self.__transmit_options) - if self.__data is not None: - ret += self.__data - return ret - - def _get_api_packet_spec_data_dict(self): - """ - Override method. - - .. seealso:: - | :meth:`.XBeeAPIPacket._get_API_packet_spec_data_dict` - """ - return {DictKeys.DEST_IPV4_ADDR: "%s (%s)" % (self.__dest_address.packed, self.__dest_address.exploded), - DictKeys.DEST_PORT: self.dest_port, - DictKeys.SRC_PORT: self.source_port, - DictKeys.IP_PROTOCOL: "%s (%s)" % (self.__ip_protocol.code, self.__ip_protocol.description), - DictKeys.OPTIONS: self.__transmit_options, - DictKeys.RF_DATA: bytearray(self.__data)} - - 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.""" - - source_port = property(__get_source_port, __set_source_port) - """Integer. Source port.""" - - ip_protocol = property(__get_ip_protocol, __set_ip_protocol) - """:class:`.IPProtocol`. IP protocol.""" - - transmit_options = property(__get_transmit_options, __set_transmit_options) - """Integer. Transmit options.""" - - data = property(__get_data, __set_data) - """Bytearray. Data of the packet.""" diff --git a/digi/xbee/packets/raw.py b/digi/xbee/packets/raw.py deleted file mode 100644 index a454ca7..0000000 --- a/digi/xbee/packets/raw.py +++ /dev/null @@ -1,1471 +0,0 @@ -# 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 -# file, You can obtain one at http://mozilla.org/MPL/2.0/. -# -# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR -# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF -# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - -from digi.xbee.packets.base import XBeeAPIPacket, DictKeys -from digi.xbee.models.address import XBee64BitAddress, XBee16BitAddress -from digi.xbee.models.status import TransmitStatus -from digi.xbee.exception import InvalidOperatingModeException, InvalidPacketException -from digi.xbee.packets.aft import ApiFrameType -from digi.xbee.models.mode import OperatingMode -from digi.xbee.io import IOSample, IOLine -from digi.xbee.util import utils - -import copy - -class TX64Packet(XBeeAPIPacket): - """ - This class represents a TX (Transmit) 64 Request packet. Packet is built - using the parameters of the constructor or providing a valid byte array. - - A TX Request message will cause the module to transmit data as an RF - Packet. - - .. seealso:: - | :class:`.XBeeAPIPacket` - """ - - __MIN_PACKET_LENGTH = 15 - - def __init__(self, frame_id, x64bit_addr, transmit_options, rf_data): - """ - Class constructor. Instantiates a new :class:`.TX64Packet` object with the provided parameters. - - Args: - frame_id (Integer): the frame ID of the packet. - x64bit_addr (:class:`.XBee64BitAddress`): the 64-bit destination address. - transmit_options (Integer): bitfield of supported transmission options. - rf_data (Bytearray, optional): RF data that is sent to the destination device. Optional. - - .. seealso:: - | :class:`.TransmitOptions` - | :class:`.XBee64BitAddress` - | :class:`.XBeeAPIPacket` - - 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(TX64Packet, self).__init__(ApiFrameType.TX_64) - self._frame_id = frame_id - self.__x64bit_addr = x64bit_addr - self.__transmit_options = transmit_options - self.__rf_data = rf_data - - @staticmethod - def create_packet(raw, operating_mode): - """ - Override method. - - Returns: - :class:`.TX64Packet`. - - Raises: - InvalidPacketException: if the bytearray length is less than 15. (start delim. + length (2 bytes) + frame - type + frame id + 64bit addr. + transmit options + checksum = 15 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 different than :attr:`.ApiFrameType.TX_64`. - InvalidOperatingModeException: if ``operating_mode`` is not supported. - - .. seealso:: - | :meth:`.XBeePacket.create_packet` - | :meth:`.XBeeAPIPacket._check_api_packet` - """ - if operating_mode != OperatingMode.ESCAPED_API_MODE and operating_mode != OperatingMode.API_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("This packet is not a TX 64 packet.") - - return TX64Packet(raw[4], XBee64BitAddress(raw[5:13]), raw[13], raw[14:-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.__x64bit_addr.address - ret.append(self.__transmit_options) - if self.__rf_data is not None: - ret += self.__rf_data - 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: self.__x64bit_addr.address, - DictKeys.TRANSMIT_OPTIONS: self.__transmit_options, - DictKeys.RF_DATA: self.__rf_data} - - def __get_64bit_addr(self): - """ - Returns the 64-bit destination address. - - Returns: - :class:`.XBee64BitAddress`: the 64-bit destination address. - - .. seealso:: - | :class:`.XBee64BitAddress` - """ - return self.__x64bit_addr - - def __set_64bit_addr(self, x64bit_addr): - """ - Sets the 64-bit destination address. - - Args: - x64bit_addr (:class:`.XBee64BitAddress`): the new 64-bit destination address. - - .. seealso:: - | :class:`.XBee64BitAddress` - """ - self.__x64bit_addr = x64bit_addr - - def __get_transmit_options(self): - """ - Returns the transmit options bitfield. - - Returns: - Integer: the transmit options bitfield. - - .. seealso:: - | :class:`.TransmitOptions` - """ - return self.__transmit_options - - def __set_transmit_options(self, transmit_options): - """ - Sets the transmit options bitfield. - - Args: - transmit_options (Integer): the new transmit options bitfield. - - .. seealso:: - | :class:`.TransmitOptions` - """ - self.__transmit_options = transmit_options - - def __get_rf_data(self): - """ - Returns the RF data to send. - - Returns: - Bytearray: the RF data to send. - """ - if self.__rf_data is None: - return None - return copy.copy(self.__rf_data) - - def __set_rf_data(self, rf_data): - """ - Sets the RF data to send. - - Args: - rf_data (Bytearray): the new RF data to send. - """ - if rf_data is None: - self.__rf_data = None - else: - self.__rf_data = copy.copy(rf_data) - - x64bit_dest_addr = property(__get_64bit_addr, __set_64bit_addr) - """XBee64BitAddress. 64-bit destination address.""" - - transmit_options = property(__get_transmit_options, __set_transmit_options) - """Integer. Transmit options bitfield.""" - - rf_data = property(__get_rf_data, __set_rf_data) - """Bytearray. RF data to send.""" - - -class TX16Packet(XBeeAPIPacket): - """ - This class represents a TX (Transmit) 16 Request packet. Packet is built - using the parameters of the constructor or providing a valid byte array. - - A TX request message will cause the module to transmit data as an RF - packet. - - .. seealso:: - | :class:`.XBeeAPIPacket` - """ - - __MIN_PACKET_LENGTH = 9 - - def __init__(self, frame_id, x16bit_addr, transmit_options, rf_data=None): - """ - Class constructor. Instantiates a new :class:`.TX16Packet` object with the provided parameters. - - Args: - frame_id (Integer): the frame ID of the packet. - x16bit_addr (:class:`.XBee16BitAddress`): the 16-bit destination address. - transmit_options (Integer): bitfield of supported transmission options. - rf_data (Bytearray, optional): RF data that is sent to the destination device. Optional. - - .. seealso:: - | :class:`.TransmitOptions` - | :class:`.XBee16BitAddress` - | :class:`.XBeeAPIPacket` - - 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(TX16Packet, self).__init__(ApiFrameType.TX_16) - self._frame_id = frame_id - self.__x16bit_addr = x16bit_addr - self.__transmit_options = transmit_options - self.__rf_data = rf_data - - @staticmethod - def create_packet(raw, operating_mode): - """ - Override method. - - Returns: - :class:`.TX16Packet`. - - Raises: - InvalidPacketException: if the bytearray length is less than 9. (start delim. + length (2 bytes) + frame - type + frame id + 16bit addr. + transmit options + 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 different than :attr:`.ApiFrameType.TX_16`. - InvalidOperatingModeException: if ``operating_mode`` is not supported. - - .. seealso:: - | :meth:`.XBeePacket.create_packet` - | :meth:`.XBeeAPIPacket._check_api_packet` - """ - if operating_mode != OperatingMode.ESCAPED_API_MODE and operating_mode != OperatingMode.API_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("This packet is not a TX 16 packet.") - - return TX16Packet(raw[4], XBee16BitAddress(raw[5:7]), 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 = self.__x16bit_addr.address - ret.append(self.__transmit_options) - if self.__rf_data is not None: - ret += self.__rf_data - return ret - - def _get_api_packet_spec_data_dict(self): - """ - Override method. - - .. seealso:: - | :meth:`.XBeeAPIPacket._get_api_packet_spec_data_dict` - """ - return {DictKeys.X16BIT_ADDR: self.__x16bit_addr, - DictKeys.TRANSMIT_OPTIONS: self.__transmit_options, - DictKeys.RF_DATA: self.__rf_data} - - def __get_16bit_addr(self): - """ - Returns the 16-bit destination address. - - Returns: - :class:`.XBee16BitAddress`: the 16-bit destination address. - - .. seealso:: - | :class:`.XBee16BitAddress` - """ - return self.__x16bit_addr - - def __set_16bit_addr(self, x16bit_addr): - """ - Sets the 16-bit destination address. - - Args: - x16bit_addr (:class:`.XBee16BitAddress`): the new 16-bit destination address. - - .. seealso:: - | :class:`.XBee16BitAddress` - """ - self.__x16bit_addr = x16bit_addr - - def __get_transmit_options(self): - """ - Returns the transmit options bitfield. - - Returns: - Integer: the transmit options bitfield. - - .. seealso:: - | :class:`.TransmitOptions` - """ - return self.__transmit_options - - def __set_transmit_options(self, transmit_options): - """ - Sets the transmit options bitfield. - - Args: - transmit_options (Integer): the new transmit options bitfield. - - .. seealso:: - | :class:`.TransmitOptions` - """ - self.__transmit_options = transmit_options - - def __get_rf_data(self): - """ - Returns the RF data to send. - - Returns: - Bytearray: the RF data to send. - """ - if self.__rf_data is None: - return None - return copy.copy(self.__rf_data) - - def __set_rf_data(self, rf_data): - """ - Sets the RF data to send. - - Args: - rf_data (Bytearray): the new RF data to send. - """ - if rf_data is None: - self.__rf_data = None - else: - self.__rf_data = copy.copy(rf_data) - - x16bit_dest_addr = property(__get_16bit_addr, __set_16bit_addr) - """XBee64BitAddress. 16-bit destination address.""" - - transmit_options = property(__get_transmit_options, __set_transmit_options) - """Integer. Transmit options bitfield.""" - - rf_data = property(__get_rf_data, __set_rf_data) - """Bytearray. RF data to send.""" - - -class TXStatusPacket(XBeeAPIPacket): - """ - This class represents a TX (Transmit) status packet. Packet is built using - the parameters of the constructor or providing a valid API payload. - - When a TX request is completed, the module sends a TX status message. - This message will indicate if the packet was transmitted successfully or if - there was a failure. - - .. seealso:: - | :class:`.TX16Packet` - | :class:`.TX64Packet` - | :class:`.XBeeAPIPacket` - """ - - __MIN_PACKET_LENGTH = 7 - - def __init__(self, frame_id, transmit_status): - """ - Class constructor. Instantiates a new :class:`.TXStatusPacket` object with the provided parameters. - - Args: - frame_id (Integer): the frame ID of the packet. - transmit_status (:class:`.TransmitStatus`): transmit status. Default: SUCCESS. - - Raises: - ValueError: if ``frame_id`` is less than 0 or greater than 255. - - .. seealso:: - | :class:`.TransmitStatus` - | :class:`.XBeeAPIPacket` - """ - if frame_id < 0 or frame_id > 255: - raise ValueError("Frame id must be between 0 and 255.") - - super(TXStatusPacket, self).__init__(ApiFrameType.TX_STATUS) - self._frame_id = frame_id - self.__transmit_status = transmit_status - - @staticmethod - def create_packet(raw, operating_mode): - """ - Override method. - - Returns: - :class:`.TXStatusPacket`. - - Raises: - InvalidPacketException: if the bytearray length is less than 7. (start delim. + length (2 bytes) + frame - type + frame id + transmit status + 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 different than :attr:`.ApiFrameType.TX_16`. - InvalidOperatingModeException: if ``operating_mode`` is not supported. - - .. seealso:: - | :meth:`.XBeePacket.create_packet` - | :meth:`.XBeeAPIPacket._check_api_packet` - """ - if operating_mode != OperatingMode.ESCAPED_API_MODE and operating_mode != OperatingMode.API_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("This packet is not a TX status packet.") - - return TXStatusPacket(raw[4], TransmitStatus.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 utils.int_to_bytes(self.__transmit_status.code, 1) - - def _get_api_packet_spec_data_dict(self): - """ - Override method. - - .. seealso:: - | :meth:`.XBeeAPIPacket._get_api_packet_spec_data_dict` - """ - return {DictKeys.TS_STATUS: self.__transmit_status} - - def __get_transmit_status(self): - """ - Returns the transmit status. - - Returns: - :class:`.TransmitStatus`: the transmit status. - - .. seealso:: - | :class:`.TransmitStatus` - """ - return self.__transmit_status - - def __set_transmit_status(self, transmit_status): - """ - Sets the transmit status. - - Args: - transmit_status (:class:`.TransmitStatus`): the new transmit status to set. - - .. seealso:: - | :class:`.TransmitStatus` - """ - self.__transmit_status = transmit_status - - transmit_status = property(__get_transmit_status, __set_transmit_status) - """:class:`.TransmitStatus`. Transmit status.""" - - -class RX64Packet(XBeeAPIPacket): - """ - This class represents an RX (Receive) 64 request packet. Packet is built - using the parameters of the constructor or providing a valid API byte array. - - When the module receives an RF packet, it is sent out the UART using - this message type. - - This packet is the response to TX (transmit) 64 request packets. - - .. seealso:: - | :class:`.ReceiveOptions` - | :class:`.TX64Packet` - | :class:`.XBeeAPIPacket` - """ - - __MIN_PACKET_LENGTH = 15 - - def __init__(self, x64bit_addr, rssi, receive_options, rf_data=None): - """ - Class constructor. Instantiates a :class:`.RX64Packet` object with the provided parameters. - - Args: - x64bit_addr (:class:`.XBee64BitAddress`): the 64-bit source address. - rssi (Integer): received signal strength indicator. - receive_options (Integer): bitfield indicating the receive options. - rf_data (Bytearray, optional): received RF data. Optional. - - .. seealso:: - | :class:`.ReceiveOptions` - | :class:`.XBee64BitAddress` - | :class:`.XBeeAPIPacket` - """ - - super(RX64Packet, self).__init__(ApiFrameType.RX_64) - - self.__x64bit_addr = x64bit_addr - self.__rssi = rssi - self.__receive_options = receive_options - self.__rf_data = rf_data - - @staticmethod - def create_packet(raw, operating_mode): - """ - Override method. - - Returns: - :class:`.RX64Packet` - - Raises: - InvalidPacketException: if the bytearray length is less than 15. (start delim. + length (2 bytes) + frame - type + 64bit addr. + rssi + receive options + checksum = 15 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 different than :attr:`.ApiFrameType.RX_64`. - InvalidOperatingModeException: if ``operating_mode`` is not supported. - - .. seealso:: - | :meth:`.XBeePacket.create_packet` - | :meth:`.XBeeAPIPacket._check_api_packet` - """ - if operating_mode != OperatingMode.ESCAPED_API_MODE and operating_mode != OperatingMode.API_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("This packet is not an RX 64 packet.") - - return RX64Packet(XBee64BitAddress(raw[4:12]), raw[12], raw[13], raw[14:-1]) - - 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 = self.__x64bit_addr.address - ret.append(self.__rssi) - ret.append(self.__receive_options) - if self.__rf_data is not None: - ret += self.__rf_data - 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: self.__x64bit_addr, - DictKeys.RSSI: self.__rssi, - DictKeys.RECEIVE_OPTIONS: self.__receive_options, - DictKeys.RF_DATA: self.__rf_data} - - def __get_64bit_addr(self): - """ - Returns the 64-bit source address. - - Returns: - :class:`.XBee64BitAddress`: the 64-bit source address. - - .. seealso:: - | :class:`.XBee64BitAddress` - """ - return self.__x64bit_addr - - def __set_64bit_addr(self, x64bit_addr): - """ - Sets the 64-bit source address. - - Args: - x64bit_addr (:class:`.XBee64BitAddress`): the new 64-bit source address. - - .. seealso:: - | :class:`.XBee64BitAddress` - """ - self.__x64bit_addr = x64bit_addr - - def __get_rssi(self): - """ - Returns the received Signal Strength Indicator (RSSI). - - Returns: - Integer: the received Signal Strength Indicator (RSSI). - """ - return self.__rssi - - def __set_rssi(self, rssi): - """ - Sets the received Signal Strength Indicator (RSSI). - - Args: - rssi (Integer): the new received Signal Strength Indicator (RSSI). - """ - self.__rssi = rssi - - def __get_options(self): - """ - Returns the receive options bitfield. - - Returns: - Integer: the receive options bitfield. - - .. seealso:: - | :class:`.ReceiveOptions` - """ - return self.__receive_options - - def __set_options(self, receive_options): - """ - Sets the receive options bitfield. - - Args: - receive_options (Integer): the new receive options bitfield. - - .. seealso:: - | :class:`.ReceiveOptions` - """ - self.__receive_options = receive_options - - def __get_rf_data(self): - """ - Returns the received RF data. - - Returns: - Bytearray: the received RF data. - """ - if self.__rf_data is None: - return None - return copy.copy(self.__rf_data) - - def __set_rf_data(self, rf_data): - """ - Sets the received RF data. - - Args: - rf_data (Bytearray): the new received RF data. - """ - if rf_data is None: - self.__rf_data = None - else: - self.__rf_data = copy.copy(rf_data) - - x64bit_source_addr = property(__get_64bit_addr, __set_64bit_addr) - """:class:`.XBee64BitAddress`. 64-bit source address.""" - - rssi = property(__get_rssi, __set_rssi) - """Integer. Received Signal Strength Indicator (RSSI) value.""" - - receive_options = property(__get_options, __set_options) - """Integer. Receive options bitfield.""" - - rf_data = property(__get_rf_data, __set_rf_data) - """Bytearray. Received RF data.""" - - -class RX16Packet(XBeeAPIPacket): - """ - This class represents an RX (Receive) 16 Request packet. Packet is built - using the parameters of the constructor or providing a valid API byte array. - - When the module receives an RF packet, it is sent out the UART using this - message type - - This packet is the response to TX (Transmit) 16 Request packets. - - .. seealso:: - | :class:`.ReceiveOptions` - | :class:`.TX16Packet` - | :class:`.XBeeAPIPacket` - """ - - __MIN_PACKET_LENGTH = 9 - - def __init__(self, x16bit_addr, rssi, receive_options, rf_data=None): - """ - Class constructor. Instantiates a :class:`.RX16Packet` object with the provided parameters. - - Args: - x16bit_addr (:class:`.XBee16BitAddress`): the 16-bit source address. - rssi (Integer): received signal strength indicator. - receive_options (Integer): bitfield indicating the receive options. - rf_data (Bytearray, optional): received RF data. Optional. - - .. seealso:: - | :class:`.ReceiveOptions` - | :class:`.XBee16BitAddress` - | :class:`.XBeeAPIPacket` - """ - - super(RX16Packet, self).__init__(ApiFrameType.RX_16) - - self.__x16bit_addr = x16bit_addr - self.__rssi = rssi - self.__receive_options = receive_options - self.__rf_data = rf_data - - @staticmethod - def create_packet(raw, operating_mode): - """ - Override method. - - Returns: - :class:`.RX16Packet`. - - Raises: - InvalidPacketException: if the bytearray length is less than 9. (start delim. + length (2 bytes) + frame - type + 16bit addr. + rssi + receive options + 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 different than :attr:`.ApiFrameType.RX_16`. - InvalidOperatingModeException: if ``operating_mode`` is not supported. - - .. seealso:: - | :meth:`.XBeePacket.create_packet` - | :meth:`.XBeeAPIPacket._check_api_packet` - """ - if operating_mode != OperatingMode.ESCAPED_API_MODE and operating_mode != OperatingMode.API_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("This packet is not an RX 16 Packet") - - return RX16Packet(XBee16BitAddress(raw[4:6]), raw[6], raw[7], raw[8:-1]) - - 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 = self.__x16bit_addr.address - ret.append(self.__rssi) - ret.append(self.__receive_options) - if self.__rf_data is not None: - ret += self.__rf_data - return ret - - def _get_api_packet_spec_data_dict(self): - """ - Override method. - - .. seealso:: - | :meth:`.XBeeAPIPacket._get_api_packet_spec_data_dict` - """ - return {DictKeys.X16BIT_ADDR: self.__x16bit_addr, - DictKeys.RSSI: self.__rssi, - DictKeys.RECEIVE_OPTIONS: self.__receive_options, - DictKeys.RF_DATA: self.__rf_data} - - def __get_16bit_addr(self): - """ - Returns the 16-bit source address. - - Returns: - :class:`.XBee16BitAddress`: the 16-bit source address. - - .. seealso:: - | :class:`.XBee16BitAddress` - """ - return self.__x16bit_addr - - def __set_16bit_addr(self, x16bit_addr): - """ - Sets the 16-bit source address. - - Args: - x16bit_addr (:class:`.XBee16BitAddress`): the new 16-bit source address. - - .. seealso:: - | :class:`.XBee16BitAddress` - """ - self.__x16bit_addr = x16bit_addr - - def __get_rssi(self): - """ - Returns the received Signal Strength Indicator (RSSI). - - Returns: - Integer: the received Signal Strength Indicator (RSSI). - """ - return self.__rssi - - def __set_rssi(self, rssi): - """ - Sets the received Signal Strength Indicator (RSSI). - - Args: - rssi (Integer): the new received Signal Strength Indicator (RSSI). - - """ - self.__rssi = rssi - - def __get_options(self): - """ - Returns the receive options bitfield. - - Returns: - Integer: the receive options bitfield. - - .. seealso:: - | :class:`.ReceiveOptions` - """ - return self.__receive_options - - def __set_options(self, receive_options): - """ - Sets the receive options bitfield. - - Args: - receive_options (Integer): the new receive options bitfield. - - .. seealso:: - | :class:`.ReceiveOptions` - """ - self.__receive_options = receive_options - - def __get_rf_data(self): - """ - Returns the received RF data. - - Returns: - Bytearray: the received RF data. - """ - if self.__rf_data is None: - return None - return copy.copy(self.__rf_data) - - def __set_rf_data(self, rf_data): - """ - Sets the received RF data. - - Args: - rf_data (Bytearray): the new received RF data. - """ - if rf_data is None: - self.__rf_data = None - else: - self.__rf_data = copy.copy(rf_data) - - x16bit_source_addr = property(__get_16bit_addr, __set_16bit_addr) - """:class:`.XBee16BitAddress`. 16-bit source address.""" - - rssi = property(__get_rssi, __set_rssi) - """Integer. Received Signal Strength Indicator (RSSI) value.""" - - receive_options = property(__get_options, __set_options) - """Integer. Receive options bitfield.""" - - rf_data = property(__get_rf_data, __set_rf_data) - """Bytearray. Received RF data.""" - - -class RX64IOPacket(XBeeAPIPacket): - """ - This class represents an RX64 address IO packet. Packet is built using the - parameters of the constructor or providing a valid API payload. - - I/O data is sent out the UART using an API frame. - - .. seealso:: - | :class:`.XBeeAPIPacket` - """ - - __MIN_PACKET_LENGTH = 20 - - def __init__(self, x64bit_addr, rssi, receive_options, rf_data): - """ - Class constructor. Instantiates an :class:`.RX64IOPacket` object with the provided parameters. - - Args: - x64bit_addr (:class:`.XBee64BitAddress`): the 64-bit source address. - rssi (Integer): received signal strength indicator. - receive_options (Integer): bitfield indicating the receive options. - rf_data (Bytearray): received RF data. - - .. seealso:: - | :class:`.ReceiveOptions` - | :class:`.XBee64BitAddress` - | :class:`.XBeeAPIPacket` - """ - super(RX64IOPacket, self).__init__(ApiFrameType.RX_IO_64) - self.__x64bit_addr = x64bit_addr - self.__rssi = rssi - self.__receive_options = receive_options - self.__rf_data = rf_data - self.__io_sample = IOSample(rf_data) if rf_data is not None and len(rf_data) >= 5 else None - - @staticmethod - def create_packet(raw, operating_mode): - """ - Override method. - - Returns: - :class:`.RX64IOPacket`. - - Raises: - InvalidPacketException: if the bytearray length is less than 20. (start delim. + length (2 bytes) + frame - type + 64bit addr. + rssi + receive options + rf data (5 bytes) + checksum = 20 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 different than :attr:`.ApiFrameType.RX_IO_64`. - InvalidOperatingModeException: if ``operating_mode`` is not supported. - - .. seealso:: - | :meth:`.XBeePacket.create_packet` - | :meth:`.XBeeAPIPacket._check_api_packet` - """ - if operating_mode != OperatingMode.ESCAPED_API_MODE and operating_mode != OperatingMode.API_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("This packet is not an RX 64 IO packet.") - - return RX64IOPacket(XBee64BitAddress(raw[4:12]), raw[12], raw[13], raw[14:-1]) - - 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 = self.__x64bit_addr.address - ret.append(self.__rssi) - ret.append(self.__receive_options) - if self.__rf_data is not None: - ret += self.__rf_data - return ret - - def _get_api_packet_spec_data_dict(self): - """ - Override method. - - .. seealso:: - | :meth:`.XBeeAPIPacket._get_api_packet_spec_data_dict` - """ - base = {DictKeys.X16BIT_ADDR: self.__x64bit_addr.address, - DictKeys.RSSI: self.__rssi, - DictKeys.RECEIVE_OPTIONS: self.__receive_options} - - if self.__io_sample is not None: - base[DictKeys.NUM_SAMPLES] = 1 - base[DictKeys.DIGITAL_MASK] = self.__io_sample.digital_mask - base[DictKeys.ANALOG_MASK] = self.__io_sample.analog_mask - - # Digital values - for i in range(16): - if self.__io_sample.has_digital_value(IOLine.get(i)): - base[IOLine.get(i).description + "digital value"] = \ - utils.hex_to_string(self.__io_sample.get_digital_value(IOLine.get(i))) - - # Analog values - for i in range(6): - if self.__io_sample.has_analog_value(IOLine.get(i)): - base[IOLine.get(i).description + "analog value"] = \ - utils.hex_to_string(self.__io_sample.get_analog_value(IOLine.get(i))) - - # Power supply - if self.__io_sample.has_power_supply_value(): - base["Power supply value "] = "%02X" % self.__io_sample.power_supply_value - - elif self.__rf_data is not None: - base[DictKeys.RF_DATA] = utils.hex_to_string(self.__rf_data) - - return base - - def __get_64bit_addr(self): - """ - Returns the 64-bit source address. - - Returns: - :class:`XBee64BitAddress`: the 64-bit source address. - - .. seealso:: - | :class:`.XBee64BitAddress` - """ - return self.__x64bit_addr - - def __set_64bit_addr(self, x64bit_addr): - """ - Sets the 64-bit source address. - - Args: - x64bit_addr (:class:`.XBee64BitAddress`): the new 64-bit source address. - - .. seealso:: - | :class:`.XBee64BitAddress` - """ - self.__x64bit_addr = x64bit_addr - - def __get_rssi(self): - """ - Returns the received Signal Strength Indicator (RSSI). - - Returns: - Integer: the received Signal Strength Indicator (RSSI). - """ - return self.__rssi - - def __set_rssi(self, rssi): - """ - Sets the received Signal Strength Indicator (RSSI). - - Args: - rssi (Integer): the new received Signal Strength Indicator (RSSI). - """ - self.__rssi = rssi - - def __get_options(self): - """ - Returns the receive options bitfield. - - Returns: - Integer: the receive options bitfield. - - .. seealso:: - | :class:`.ReceiveOptions` - """ - return self.__receive_options - - def __set_options(self, receive_options): - """ - Sets the receive options bitfield. - - Args: - receive_options (Integer): the new receive options bitfield. - - .. seealso:: - | :class:`.ReceiveOptions` - """ - self.__receive_options = receive_options - - def __get_rf_data(self): - """ - Returns the received RF data. - - Returns: - Bytearray: the received RF data. - """ - if self.__rf_data is None: - return None - return copy.copy(self.__rf_data) - - def __set_rf_data(self, rf_data): - """ - Sets the received RF data. - - Args: - rf_data (Bytearray): the new received RF data. - """ - if rf_data is None: - self.__rf_data = None - else: - self.__rf_data = copy.copy(rf_data) - - # Modify the ioSample accordingly - if rf_data is not None and len(rf_data) >= 5: - self.__io_sample = IOSample(self.__rf_data) - else: - self.__io_sample = None - - def __get_io_sample(self): - """ - Returns the IO sample corresponding to the data contained in the packet. - - Returns: - :class:`.IOSample`: the IO sample of the packet, ``None`` if the packet has not any data or if the - sample could not be generated correctly. - - .. seealso:: - | :class:`.IOSample` - """ - return self.__io_sample - - def __set_io_sample(self, io_sample): - """ - Sets the IO sample of the packet. - - Args: - io_sample (:class:`.IOSample`): the new IO sample to set. - - .. seealso:: - | :class:`.IOSample` - """ - self.__io_sample = io_sample - - x64bit_source_addr = property(__get_64bit_addr, __set_64bit_addr) - """:class:`.XBee64BitAddress`. 64-bit source address.""" - - rssi = property(__get_rssi, __set_rssi) - """Integer. Received Signal Strength Indicator (RSSI) value.""" - - receive_options = property(__get_options, __set_options) - """Integer. Receive options bitfield.""" - - rf_data = property(__get_rf_data, __set_rf_data) - """Bytearray. Received RF data.""" - - io_sample = property(__get_io_sample, __set_io_sample) - """:class:`.IOSample`: IO sample corresponding to the data contained in the packet.""" - - -class RX16IOPacket(XBeeAPIPacket): - """ - This class represents an RX16 address IO packet. Packet is built using the - parameters of the constructor or providing a valid byte array. - - I/O data is sent out the UART using an API frame. - - .. seealso:: - | :class:`.XBeeAPIPacket` - """ - - __MIN_PACKET_LENGTH = 14 - - def __init__(self, x16bit_addr, rssi, receive_options, rf_data): - """ - Class constructor. Instantiates an :class:`.RX16IOPacket` object with the provided parameters. - - Args: - x16bit_addr (:class:`.XBee16BitAddress`): the 16-bit source address. - rssi (Integer): received signal strength indicator. - receive_options (Integer): bitfield indicating the receive options. - rf_data (Bytearray): received RF data. - - .. seealso:: - | :class:`.ReceiveOptions` - | :class:`.XBee16BitAddress` - | :class:`.XBeeAPIPacket` - """ - super(RX16IOPacket, self).__init__(ApiFrameType.RX_IO_16) - self.__x16bit_addr = x16bit_addr - self.__rssi = rssi - self.__options = receive_options - self.__rf_data = rf_data - self.__io_sample = IOSample(rf_data) if rf_data is not None and len(rf_data) >= 5 else None - - @staticmethod - def create_packet(raw, operating_mode): - """ - Override method. - - Returns: - :class:`.RX16IOPacket`. - - Raises: - InvalidPacketException: if the bytearray length is less than 14. (start delim. + length (2 bytes) + frame - type + 16bit addr. + rssi + receive options + rf data (5 bytes) + 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 different than :attr:`.ApiFrameType.RX_IO_16`. - InvalidOperatingModeException: if ``operating_mode`` is not supported. - - .. seealso:: - | :meth:`.XBeePacket.create_packet` - | :meth:`.XBeeAPIPacket._check_api_packet` - """ - if operating_mode != OperatingMode.ESCAPED_API_MODE and operating_mode != OperatingMode.API_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("This packet is not an RX 16 IO packet.") - - return RX16IOPacket(XBee16BitAddress(raw[4:6]), raw[6], raw[7], raw[8:-1]) - - 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 = self.__x16bit_addr.address - ret.append(self.__rssi) - ret.append(self.__options) - if self.__rf_data is not None: - ret += self.__rf_data - return ret - - def _get_api_packet_spec_data_dict(self): - """ - Override method. - - .. seealso:: - | :meth:`.XBeeAPIPacket._get_api_packet_spec_data_dict` - """ - base = {DictKeys.X16BIT_ADDR: self.__x16bit_addr.address, - DictKeys.RSSI: self.__rssi, - DictKeys.RECEIVE_OPTIONS: self.__options} - - if self.__io_sample is not None: - base[DictKeys.NUM_SAMPLES] = 1 - base[DictKeys.DIGITAL_MASK] = self.__io_sample.digital_mask - base[DictKeys.ANALOG_MASK] = self.__io_sample.analog_mask - - # Digital values - for i in range(16): - if self.__io_sample.has_digital_value(IOLine.get(i)): - base[IOLine.get(i).description + "digital value"] = \ - utils.hex_to_string(self.__io_sample.get_digital_value(IOLine.get(i))) - - # Analog values - for i in range(6): - if self.__io_sample.has_analog_value(IOLine.get(i)): - base[IOLine.get(i).description + "analog value"] = \ - utils.hex_to_string(self.__io_sample.get_analog_value(IOLine.get(i))) - - # Power supply - if self.__io_sample.has_power_supply_value(): - base["Power supply value "] = "%02X" % self.__io_sample.power_supply_value - - elif self.__rf_data is not None: - base[DictKeys.RF_DATA] = utils.hex_to_string(self.__rf_data) - - return base - - def __get_16bit_addr(self): - """ - Returns the 16-bit source address. - - Returns: - :class:`.XBee16BitAddress`: the 16-bit source address. - - .. seealso:: - | :class:`.XBee16BitAddress` - """ - return self.__x16bit_addr - - def __set_16bit_addr(self, x16bit_addr): - """ - Sets the 16-bit source address. - - Args: - x16bit_addr (:class:`.XBee16BitAddress`): the new 16-bit source address. - - .. seealso:: - | :class:`.XBee16BitAddress` - """ - self.__x16bit_addr = x16bit_addr - - def __get_rssi(self): - """ - Returns the received Signal Strength Indicator (RSSI). - - Returns: - Integer: the received Signal Strength Indicator (RSSI). - """ - return self.__rssi - - def __set_rssi(self, rssi): - """ - Sets the received Signal Strength Indicator (RSSI). - - Args: - rssi (Integer): the new received Signal Strength Indicator (RSSI). - - """ - self.__rssi = rssi - - def __get_options(self): - """ - Returns the receive options bitfield. - - Returns: - Integer: the receive options bitfield. - - .. seealso:: - | :class:`.ReceiveOptions` - """ - return self.__receive_options - - def __set_options(self, receive_options): - """ - Sets the receive options bitfield. - - Args: - receive_options (Integer): the new receive options bitfield. - - .. seealso:: - | :class:`.ReceiveOptions` - """ - self.__receive_options = receive_options - - def __get_rf_data(self): - """ - Returns the received RF data. - - Returns: - Bytearray: the received RF data. - """ - if self.__rf_data is None: - return None - return copy.copy(self.__rf_data) - - def __set_rf_data(self, rf_data): - """ - Sets the received RF data. - - Args: - rf_data (Bytearray): the new received RF data. - """ - if rf_data is None: - self.__rf_data = None - else: - self.__rf_data = copy.copy(rf_data) - - # Modify the ioSample accordingly - if rf_data is not None and len(rf_data) >= 5: - self.__io_sample = IOSample(self.__rf_data) - else: - self.__io_sample = None - - def __get_io_sample(self): - """ - Returns the IO sample corresponding to the data contained in the packet. - - Returns: - :class:`.IOSample`: the IO sample of the packet, ``None`` if the packet has not any data or if the - sample could not be generated correctly. - - .. seealso:: - | :class:`.IOSample` - """ - return self.__io_sample - - def __set_io_sample(self, io_sample): - """ - Sets the IO sample of the packet. - - Args: - io_sample (:class:`.IOSample`): the new IO sample to set. - - .. seealso:: - | :class:`.IOSample` - """ - self.__io_sample = io_sample - - x16bit_source_addr = property(__get_16bit_addr, __set_16bit_addr) - """:class:`.XBee16BitAddress`. 16-bit source address.""" - - rssi = property(__get_rssi, __set_rssi) - """Integer. Received Signal Strength Indicator (RSSI) value.""" - - receive_options = property(__get_options, __set_options) - """Integer. Receive options bitfield.""" - - rf_data = property(__get_rf_data, __set_rf_data) - """Bytearray. Received RF data.""" - - io_sample = property(__get_io_sample, __set_io_sample) - """:class:`.IOSample`: IO sample corresponding to the data contained in the packet.""" diff --git a/digi/xbee/packets/relay.py b/digi/xbee/packets/relay.py deleted file mode 100644 index f681c33..0000000 --- a/digi/xbee/packets/relay.py +++ /dev/null @@ -1,343 +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.mode import OperatingMode -from digi.xbee.models.options import XBeeLocalInterface -from digi.xbee.packets.aft import ApiFrameType -from digi.xbee.packets.base import XBeeAPIPacket, DictKeys -from digi.xbee.exception import InvalidOperatingModeException, InvalidPacketException - - -class UserDataRelayPacket(XBeeAPIPacket): - """ - This class represents a User Data Relay packet. Packet is built using the - parameters of the constructor. - - The User Data Relay packet allows for data to come in on an interface with - a designation of the target interface for the data to be output on. - - The destination interface must be one of the interfaces found in the - corresponding enumerator (see :class:`.XBeeLocalInterface`). - - .. seealso:: - | :class:`.UserDataRelayOutputPacket` - | :class:`.XBeeAPIPacket` - | :class:`.XBeeLocalInterface` - """ - - __MIN_PACKET_LENGTH = 7 - - def __init__(self, frame_id, local_interface, data=None): - """ - Class constructor. Instantiates a new :class:`.UserDataRelayPacket` object with the provided parameters. - - Args: - frame_id (integer): the frame ID of the packet. - local_interface (:class:`.XBeeLocalInterface`): the destination interface. - data (Bytearray, optional): Data to send to the destination interface. - - .. seealso:: - | :class:`.XBeeAPIPacket` - | :class:`.XBeeLocalInterface` - - Raises: - ValueError: if ``local_interface`` is ``None``. - ValueError: if ``frame_id`` is less than 0 or greater than 255. - """ - if local_interface is None: - raise ValueError("Destination interface cannot be None") - if frame_id > 255 or frame_id < 0: - raise ValueError("frame_id must be between 0 and 255.") - - super().__init__(ApiFrameType.USER_DATA_RELAY_REQUEST) - self._frame_id = frame_id - self.__local_interface = local_interface - self.__data = data - - @staticmethod - def create_packet(raw, operating_mode): - """ - Override method. - - Returns: - :class:`.UserDataRelayPacket`. - - Raises: - InvalidPacketException: if the bytearray length is less than 7. (start delim. + length (2 bytes) + frame - type + frame id + relay interface + 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.USER_DATA_RELAY_REQUEST`. - InvalidOperatingModeException: if ``operating_mode`` is not supported. - - .. seealso:: - | :meth:`.XBeePacket.create_packet` - | :meth:`.XBeeAPIPacket._check_api_packet` - """ - if operating_mode != OperatingMode.ESCAPED_API_MODE and operating_mode != OperatingMode.API_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("This packet is not a user data relay packet.") - - return UserDataRelayPacket(raw[4], XBeeLocalInterface.get([5]), raw[6:-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.__local_interface.code) - if self.__data is not None: - return ret + self.__data - return ret - - def _get_api_packet_spec_data_dict(self): - """ - Override method. - - .. seealso:: - | :meth:`.XBeeAPIPacket._get_api_packet_spec_data_dict` - """ - return {DictKeys.DEST_INTERFACE: self.__local_interface.description, - DictKeys.DATA: list(self.__data) if self.__data is not None else None} - - def __get_data(self): - """ - Returns the data to send. - - Returns: - Bytearray: the data to send. - """ - if self.__data is None: - return None - return self.__data.copy() - - def __set_data(self, data): - """ - Sets the data to send. - - Args: - data (Bytearray): the new data to send. - """ - if data is None: - self.__data = None - else: - self.__data = data.copy() - - def __get_dest_interface(self): - """ - Returns the the destination interface. - - Returns: - :class:`.XBeeLocalInterface`: the destination interface. - - .. seealso:: - | :class:`.XBeeLocalInterface` - """ - return self.__local_interface - - def __set_dest_interface(self, local_interface): - """ - Sets the destination interface. - - Args: - local_interface (:class:`.XBeeLocalInterface`): the new destination interface. - - .. seealso:: - | :class:`.XBeeLocalInterface` - """ - self.__local_interface = local_interface - - dest_interface = property(__get_dest_interface, __set_dest_interface) - """:class:`.XBeeLocalInterface`. Destination local interface.""" - - data = property(__get_data, __set_data) - """Bytearray. Data to send.""" - - -class UserDataRelayOutputPacket(XBeeAPIPacket): - """ - This class represents a User Data Relay Output packet. Packet is built - using the parameters of the constructor. - - The User Data Relay Output packet can be received from any relay interface. - - The source interface must be one of the interfaces found in the - corresponding enumerator (see :class:`.XBeeLocalInterface`). - - .. seealso:: - | :class:`.UserDataRelayPacket` - | :class:`.XBeeAPIPacket` - | :class:`.XBeeLocalInterface` - """ - - __MIN_PACKET_LENGTH = 6 - - def __init__(self, local_interface, data=None): - """ - Class constructor. Instantiates a new - :class:`.UserDataRelayOutputPacket` object with the provided - parameters. - - Args: - local_interface (:class:`.XBeeLocalInterface`): the source interface. - data (Bytearray, optional): Data received from the source interface. - - Raises: - ValueError: if ``local_interface`` is ``None``. - - .. seealso:: - | :class:`.XBeeAPIPacket` - | :class:`.XBeeLocalInterface` - """ - if local_interface is None: - raise ValueError("Source interface cannot be None") - - super().__init__(ApiFrameType.USER_DATA_RELAY_OUTPUT) - self.__local_interface = local_interface - self.__data = data - - @staticmethod - def create_packet(raw, operating_mode): - """ - Override method. - - Returns: - :class:`.UserDataRelayOutputPacket`. - - Raises: - InvalidPacketException: if the bytearray length is less than 6. (start delim. + length (2 bytes) + frame - type + relay interface + checksum = 6 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.USER_DATA_RELAY_OUTPUT`. - InvalidOperatingModeException: if ``operating_mode`` is not supported. - - .. seealso:: - | :meth:`.XBeePacket.create_packet` - | :meth:`.XBeeAPIPacket._check_api_packet` - """ - if operating_mode != OperatingMode.ESCAPED_API_MODE and operating_mode != OperatingMode.API_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("This packet is not a user data relay output packet.") - - return UserDataRelayOutputPacket(XBeeLocalInterface.get(raw[4]), raw[5:-1]) - - 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.__local_interface.code) - if self.__data is not None: - return ret + self.__data - return ret - - def _get_api_packet_spec_data_dict(self): - """ - Override method. - - .. seealso:: - | :meth:`.XBeeAPIPacket._get_api_packet_spec_data_dict` - """ - return {DictKeys.SOURCE_INTERFACE: self.__local_interface.description, - DictKeys.DATA: list(self.__data) if self.__data is not None else None} - - def __get_data(self): - """ - Returns the received data. - - Returns: - Bytearray: the received data. - """ - if self.__data is None: - return None - return self.__data.copy() - - def __set_data(self, data): - """ - Sets the received data. - - Args: - data (Bytearray): the new received data. - """ - if data is None: - self.__data = None - else: - self.__data = data.copy() - - def __get_src_interface(self): - """ - Returns the the source interface. - - Returns: - :class:`.XBeeLocalInterface`: the source interface. - - .. seealso:: - | :class:`.XBeeLocalInterface` - """ - return self.__local_interface - - def __set_src_interface(self, local_interface): - """ - Sets the source interface. - - Args: - local_interface (:class:`.XBeeLocalInterface`): the new source interface. - - .. seealso:: - | :class:`.XBeeLocalInterface` - """ - self.__local_interface = local_interface - - src_interface = property(__get_src_interface, __set_src_interface) - """:class:`.XBeeLocalInterface`. Source local interface.""" - - data = property(__get_data, __set_data) - """Bytearray. Received data.""" diff --git a/digi/xbee/packets/wifi.py b/digi/xbee/packets/wifi.py deleted file mode 100644 index a77d388..0000000 --- a/digi/xbee/packets/wifi.py +++ /dev/null @@ -1,746 +0,0 @@ -# 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 -# file, You can obtain one at http://mozilla.org/MPL/2.0/. -# -# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR -# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF -# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - -from digi.xbee.packets.base import XBeeAPIPacket, DictKeys -from digi.xbee.util import utils -from digi.xbee.exception import InvalidOperatingModeException, InvalidPacketException -from digi.xbee.packets.aft import ApiFrameType -from digi.xbee.models.mode import OperatingMode -from digi.xbee.io import IOSample, IOLine -from ipaddress import IPv4Address -from digi.xbee.models.status import ATCommandStatus - - -class IODataSampleRxIndicatorWifiPacket(XBeeAPIPacket): - """ - This class represents a IO data sample RX indicator (Wi-Fi) packet. Packet is - built using the parameters of the constructor or providing a valid API - payload. - - When the module receives an IO sample frame from a remote device, it sends - the sample out the UART or SPI using this frame type. Only modules running - API mode will be able to receive IO samples. - - Among received data, some options can also be received indicating - transmission parameters. - - .. seealso:: - | :class:`.XBeeAPIPacket` - """ - - __MIN_PACKET_LENGTH = 16 - - def __init__(self, source_address, rssi, receive_options, rf_data=None): - """ - Class constructor. Instantiates a new :class:`.IODataSampleRxIndicatorWifiPacket` object with the - provided parameters. - - Args: - source_address (:class:`ipaddress.IPv4Address`): the 64-bit source address. - rssi (Integer): received signal strength indicator. - receive_options (Integer): bitfield indicating the receive options. - rf_data (Bytearray, optional): received RF data. Optional. - - Raises: - ValueError: if ``rf_data`` is not ``None`` and it's not valid for create an :class:`.IOSample`. - - .. seealso:: - | :class:`.IOSample` - | :class:`ipaddress.IPv4Address` - | :class:`.ReceiveOptions` - | :class:`.XBeeAPIPacket` - """ - super().__init__(ApiFrameType.IO_DATA_SAMPLE_RX_INDICATOR_WIFI) - self.__source_address = source_address - self.__rssi = rssi - self.__receive_options = receive_options - self.__rf_data = rf_data - self.__io_sample = IOSample(rf_data) if rf_data is not None and len(rf_data) >= 5 else None - - @staticmethod - def create_packet(raw, operating_mode): - """ - Override method. - - Returns: - :class:`.IODataSampleRxIndicatorWifiPacket`. - - Raises: - InvalidPacketException: if the bytearray length is less than 16. (start delim. + length (2 bytes) + frame - type + source addr. (4 bytes) + rssi + receive options + rf data (5 bytes) + checksum = 16 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.IO_DATA_SAMPLE_RX_INDICATOR_WIFI`. - InvalidOperatingModeException: if ``operating_mode`` is not supported. - - .. seealso:: - | :meth:`.XBeePacket.create_packet` - | :meth:`.XBeeAPIPacket._check_api_packet` - """ - if operating_mode != OperatingMode.ESCAPED_API_MODE and operating_mode != OperatingMode.API_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("This packet is not an IO data sample RX indicator Wi-Fi packet.") - - return IODataSampleRxIndicatorWifiPacket(IPv4Address(bytes(raw[4:8])), raw[7], raw[8], raw[9:-1]) - - 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(self.__source_address.packed) - ret += utils.int_to_bytes(self.__rssi, num_bytes=1) - ret += utils.int_to_bytes(self.__receive_options, num_bytes=1) - if self.__rf_data is not None: - ret += self.__rf_data - return ret - - def _get_api_packet_spec_data_dict(self): - """ - Override method. - - .. seealso:: - | :meth:`.XBeeAPIPacket._get_api_packet_spec_data_dict` - """ - base = {DictKeys.SRC_IPV4_ADDR: "%s (%s)" % (self.__source_address.packed, self.__source_address.exploded), - DictKeys.RSSI: self.__rssi, - DictKeys.RECEIVE_OPTIONS: self.__receive_options} - - if self.__io_sample is not None: - base[DictKeys.NUM_SAMPLES] = 1 - base[DictKeys.DIGITAL_MASK] = self.__io_sample.digital_mask - base[DictKeys.ANALOG_MASK] = self.__io_sample.analog_mask - - # Digital values - for i in range(16): - if self.__io_sample.has_digital_value(IOLine.get(i)): - base[IOLine.get(i).description + " digital value"] = \ - self.__io_sample.get_digital_value(IOLine.get(i)).name - - # Analog values - for i in range(6): - if self.__io_sample.has_analog_value(IOLine.get(i)): - base[IOLine.get(i).description + " analog value"] = \ - self.__io_sample.get_analog_value(IOLine.get(i)) - - # Power supply - if self.__io_sample.has_power_supply_value(): - base["Power supply value "] = "%02X" % self.__io_sample.power_supply_value - - elif self.__rf_data is not None: - base[DictKeys.RF_DATA] = utils.hex_to_string(self.__rf_data) - - return base - - def __get_source_address(self): - """ - Returns the IPv4 address of the source device. - - Returns: - :class:`ipaddress.IPv4Address`: the IPv4 address of the source device. - - .. seealso:: - | :class:`ipaddress.IPv4Address` - """ - 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. - - .. seealso:: - | :class:`ipaddress.IPv4Address` - """ - if source_address is not None: - self.__source_address = source_address - - def __get_rssi(self): - """ - Returns the received Signal Strength Indicator (RSSI). - - Returns: - Integer: the received Signal Strength Indicator (RSSI). - """ - return self.__rssi - - def __set_rssi(self, rssi): - """ - Sets the received Signal Strength Indicator (RSSI). - - Args: - rssi (Integer): the new received Signal Strength Indicator (RSSI). - """ - self.__rssi = rssi - - def __get_options(self): - """ - Returns the receive options bitfield. - - Returns: - Integer: the receive options bitfield. - - .. seealso:: - | :class:`.ReceiveOptions` - """ - return self.__receive_options - - def __set_options(self, receive_options): - """ - Sets the receive options bitfield. - - Args: - receive_options (Integer): the new receive options bitfield. - - .. seealso:: - | :class:`.ReceiveOptions` - """ - self.__receive_options = receive_options - - def __get_rf_data(self): - """ - Returns the received RF data. - - Returns: - Bytearray: the received RF data. - """ - if self.__rf_data is None: - return None - return self.__rf_data.copy() - - def __set_rf_data(self, rf_data): - """ - Sets the received RF data. - - Args: - rf_data (Bytearray): the new received RF data. - """ - if rf_data is None: - self.__rf_data = None - else: - self.__rf_data = rf_data.copy() - - # Modify the IO sample accordingly - if rf_data is not None and len(rf_data) >= 5: - self.__io_sample = IOSample(self.__rf_data) - else: - self.__io_sample = None - - def __get_io_sample(self): - """ - Returns the IO sample corresponding to the data contained in the packet. - - Returns: - :class:`.IOSample`: the IO sample of the packet, ``None`` if the packet has not any data or if the - sample could not be generated correctly. - - .. seealso:: - | :class:`.IOSample` - """ - return self.__io_sample - - def __set_io_sample(self, io_sample): - """ - Sets the IO sample of the packet. - - Args: - io_sample (:class:`.IOSample`): the new IO sample to set. - - .. seealso:: - | :class:`.IOSample` - """ - self.__io_sample = io_sample - - source_address = property(__get_source_address, __set_source_address) - """:class:`ipaddress.IPv4Address`. IPv4 source address.""" - - rssi = property(__get_rssi, __set_rssi) - """Integer. Received Signal Strength Indicator (RSSI) value.""" - - receive_options = property(__get_options, __set_options) - """Integer. Receive options bitfield.""" - - rf_data = property(__get_rf_data, __set_rf_data) - """Bytearray. Received RF data.""" - - io_sample = property(__get_io_sample, __set_io_sample) - """:class:`.IOSample`: IO sample corresponding to the data contained in the packet.""" - - -class RemoteATCommandWifiPacket(XBeeAPIPacket): - """ - This class represents a remote AT command request (Wi-Fi) packet. Packet is - built using the parameters of the constructor or providing a valid API - payload. - - Used to query or set module parameters on a remote device. For parameter - changes on the remote device to take effect, changes must be applied, either - by setting the apply changes options bit, or by sending an ``AC`` command - to the remote node. - - Remote command options are set as a bitfield. - - If configured, command response is received as a :class:`.RemoteATCommandResponseWifiPacket`. - - .. seealso:: - | :class:`.RemoteATCommandResponseWifiPacket` - | :class:`.XBeeAPIPacket` - """ - - __MIN_PACKET_LENGTH = 17 - - def __init__(self, frame_id, dest_address, transmit_options, command, parameter=None): - """ - Class constructor. Instantiates a new :class:`.RemoteATCommandWifiPacket` object with the provided parameters. - - Args: - frame_id (integer): the frame ID of the packet. - dest_address (:class:`ipaddress.IPv4Address`): the IPv4 address of the destination device. - transmit_options (Integer): bitfield of supported transmission options. - command (String): AT command to send. - parameter (Bytearray, optional): AT command parameter. Optional. - - Raises: - ValueError: if ``frame_id`` is less than 0 or greater than 255. - ValueError: if length of ``command`` is different than 2. - - .. seealso:: - | :class:`ipaddress.IPv4Address` - | :class:`.RemoteATCmdOptions` - | :class:`.XBeeAPIPacket` - """ - if frame_id < 0 or frame_id > 255: - raise ValueError("Frame id must be between 0 and 255.") - if len(command) != 2: - raise ValueError("Invalid command " + command) - - super().__init__(ApiFrameType.REMOTE_AT_COMMAND_REQUEST_WIFI) - self._frame_id = frame_id - self.__dest_address = dest_address - self.__transmit_options = transmit_options - self.__command = command - self.__parameter = parameter - - @staticmethod - def create_packet(raw, operating_mode): - """ - Override method. - - Returns: - :class:`.RemoteATCommandWifiPacket` - - Raises: - InvalidPacketException: if the Bytearray length is less than 17. (start delim. + length (2 bytes) + frame - type + frame id + dest. addr. (8 bytes) + transmit options + command (2 bytes) + 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.REMOTE_AT_COMMAND_REQUEST_WIFI`. - InvalidOperatingModeException: if ``operating_mode`` is not supported. - - .. seealso:: - | :meth:`.XBeePacket.create_packet` - | :meth:`.XBeeAPIPacket._check_api_packet` - """ - if operating_mode != OperatingMode.ESCAPED_API_MODE and operating_mode != OperatingMode.API_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("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"), - 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 = bytearray(self.__dest_address.packed) - ret += utils.int_to_bytes(self.__transmit_options, num_bytes=1) - ret += bytearray(self.__command, "utf8") - if self.__parameter is not None: - ret += self.__parameter - return ret - - def _get_api_packet_spec_data_dict(self): - """ - Override method. - - See: - :meth:`.XBeeAPIPacket._get_api_packet_spec_data_dict` - """ - return {DictKeys.DEST_IPV4_ADDR: "%s (%s)" % (self.__dest_address.packed, self.__dest_address.exploded), - DictKeys.TRANSMIT_OPTIONS: self.__transmit_options, - DictKeys.COMMAND: self.__command, - DictKeys.PARAMETER: list(self.__parameter) if self.__parameter is not None else None} - - def __get_dest_address(self): - """ - Returns the IPv4 address of the destination device. - - Returns: - :class:`ipaddress.IPv4Address`: the IPv4 address of the destination device. - - .. seealso:: - | :class:`ipaddress.IPv4Address` - """ - 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. - - .. seealso:: - | :class:`ipaddress.IPv4Address` - """ - if dest_address is not None: - self.__dest_address = dest_address - - def __get_transmit_options(self): - """ - Returns the transmit options bitfield. - - Returns: - Integer: the transmit options bitfield. - - .. seealso:: - | :class:`.RemoteATCmdOptions` - """ - return self.__transmit_options - - def __set_transmit_options(self, transmit_options): - """ - Sets the transmit options bitfield. - - Args: - transmit_options (Integer): the new transmit options bitfield. - - .. seealso:: - | :class:`.RemoteATCmdOptions` - """ - self.__transmit_options = transmit_options - - def __get_command(self): - """ - Returns the AT command. - - Returns: - String: the AT command. - """ - return self.__command - - def __set_command(self, command): - """ - Sets the AT command. - - Args: - command (String): the new AT command. - """ - self.__command = command - - def __get_parameter(self): - """ - Returns the AT command parameter. - - Returns: - Bytearray: the AT command parameter. - """ - return self.__parameter - - def __set_parameter(self, parameter): - """ - Sets the AT command parameter. - - Args: - parameter (Bytearray): the new AT command parameter. - """ - self.__parameter = parameter - - dest_address = property(__get_dest_address, __set_dest_address) - """:class:`ipaddress.IPv4Address`. IPv4 destination address.""" - - transmit_options = property(__get_transmit_options, __set_transmit_options) - """Integer. Transmit options bitfield.""" - - command = property(__get_command, __set_command) - """String. AT command.""" - - parameter = property(__get_parameter, __set_parameter) - """Bytearray. AT command parameter.""" - - -class RemoteATCommandResponseWifiPacket(XBeeAPIPacket): - """ - This class represents a remote AT command response (Wi-Fi) packet. Packet is - built using the parameters of the constructor or providing a valid API - payload. - - If a module receives a remote command response RF data frame in response - to a Remote AT Command Request, the module will send a Remote AT Command - Response message out the UART. Some commands may send back multiple frames - for example, Node Discover (``ND``) command. - - This packet is received in response of a :class:`.RemoteATCommandPacket`. - - Response also includes an :class:`.ATCommandStatus` object with the status - of the AT command. - - .. seealso:: - | :class:`.RemoteATCommandWifiPacket` - | :class:`.ATCommandStatus` - | :class:`.XBeeAPIPacket` - """ - - __MIN_PACKET_LENGTH = 17 - - def __init__(self, frame_id, source_address, command, response_status, comm_value=None): - """ - Class constructor. Instantiates a new :class:`.RemoteATCommandResponseWifiPacket` object with the - provided parameters. - - Args: - frame_id (Integer): the frame ID of the packet. - source_address (:class:`ipaddress.IPv4Address`): the IPv4 address of the source device. - command (String): the AT command of the packet. Must be a string. - response_status (:class:`.ATCommandStatus`): the status of the AT command. - comm_value (Bytearray, optional): the AT command response value. - - Raises: - ValueError: if ``frame_id`` is less than 0 or greater than 255. - ValueError: if length of ``command`` is different than 2. - - .. seealso:: - | :class:`.ATCommandStatus` - | :class:`ipaddress.IPv4Address` - """ - if frame_id > 255 or frame_id < 0: - raise ValueError("frame_id must be between 0 and 255.") - if len(command) != 2: - raise ValueError("Invalid command " + command) - - super().__init__(ApiFrameType.REMOTE_AT_COMMAND_RESPONSE_WIFI) - self._frame_id = frame_id - self.__source_address = source_address - self.__command = command - self.__response_status = response_status - self.__comm_value = comm_value - - @staticmethod - def create_packet(raw, operating_mode): - """ - Override method. - - Returns: - :class:`.RemoteATCommandResponseWifiPacket`. - - Raises: - InvalidPacketException: if the bytearray length is less than 17. (start delim. + length (2 bytes) + frame - type + frame id + source addr. (8 bytes) + command (2 bytes) + receive 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.REMOTE_AT_COMMAND_RESPONSE_WIFI`. - InvalidOperatingModeException: if ``operating_mode`` is not supported. - - .. seealso:: - | :meth:`.XBeePacket.create_packet` - | :meth:`.XBeeAPIPacket._check_api_packet` - """ - if operating_mode != OperatingMode.ESCAPED_API_MODE and operating_mode != OperatingMode.API_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("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]), - 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 = bytearray(self.__source_address.packed) - ret += bytearray(self.__command, "utf8") - ret += utils.int_to_bytes(self.__response_status.code, num_bytes=1) - if self.__comm_value is not None: - ret += self.__comm_value - return ret - - def _get_api_packet_spec_data_dict(self): - return {DictKeys.SRC_IPV4_ADDR: "%s (%s)" % (self.__source_address.packed, self.__source_address.exploded), - DictKeys.COMMAND: self.__command, - DictKeys.AT_CMD_STATUS: self.__response_status, - DictKeys.RF_DATA: list(self.__comm_value) if self.__comm_value is not None else None} - - def __get_source_address(self): - """ - Returns the IPv4 address of the source device. - - Returns: - :class:`ipaddress.IPv4Address`: the IPv4 address of the source device. - - .. seealso:: - | :class:`ipaddress.IPv4Address` - """ - 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. - - .. seealso:: - | :class:`ipaddress.IPv4Address` - """ - if source_address is not None: - self.__source_address = source_address - - def __get_command(self): - """ - Returns the AT command of the packet. - - Returns: - String: the AT command of the packet. - """ - return self.__command - - def __set_command(self, command): - """ - Sets the AT command of the packet. - - Args: - command (String): the new AT command of the packet. Must have length = 2. - - Raises: - ValueError: if length of ``command`` is different than 2. - """ - if len(command) != 2: - raise ValueError("Invalid command " + command) - self.__command = command - - 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 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. - - .. seealso:: - | :class:`.ATCommandStatus` - """ - self.__response_status = response_status - - def __get_value(self): - """ - Returns the AT command response value. - - Returns: - Bytearray: the AT command response value. - """ - return self.__comm_value - - def __set_value(self, comm_value): - """ - Sets the AT command response value. - - Args: - comm_value (Bytearray): the new AT command response value. - """ - self.__comm_value = comm_value - - source_address = property(__get_source_address, __set_source_address) - """:class:`ipaddress.IPv4Address`. IPv4 source address.""" - - command = property(__get_command, __set_command) - """String. AT command.""" - - status = property(__get_response_status, __set_response_status) - """:class:`.ATCommandStatus`. AT command response status.""" - - command_value = property(__get_value, __set_value) - """Bytearray. AT command value.""" diff --git a/digi/xbee/reader.py b/digi/xbee/reader.py deleted file mode 100644 index b1f5dbd..0000000 --- a/digi/xbee/reader.py +++ /dev/null @@ -1,1245 +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. - -from concurrent.futures import ThreadPoolExecutor -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.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.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 -from digi.xbee.io import IOSample - - -# Maximum number of parallel callbacks. -MAX_PARALLEL_CALLBACKS = 50 - -executor = ThreadPoolExecutor(max_workers=MAX_PARALLEL_CALLBACKS) - - -class XBeeEvent(list): - """ - This class represents a generic XBee event. - - New event callbacks can be added here following this prototype: - - :: - - def callback_prototype(*args, **kwargs): - #do something... - - All of them will be executed when the event is fired. - - .. seealso:: - | list (Python standard class) - """ - def __call__(self, *args, **kwargs): - for f in self: - future = executor.submit(f, *args, **kwargs) - future.add_done_callback(self.__execution_finished) - - def __repr__(self): - return "Event(%s)" % list.__repr__(self) - - def __iadd__(self, other): - self.append(other) - return self - - def __isub__(self, other): - self.remove(other) - return self - - def __execution_finished(self, future): - """ - Called when the execution of the callable has finished. - - Args: - future (:class:`.Future`): Future associated to the execution of the callable. - - Raises: - Exception: if the execution of the callable raised any exception. - """ - if future.exception(): - raise future.exception() - - -class PacketReceived(XBeeEvent): - """ - This event is fired when an XBee receives any packet, independent of - its frame type. - - The callbacks for handle this events will receive the following arguments: - 1. received_packet (:class:`.XBeeAPIPacket`): the received packet. - - .. seealso:: - | :class:`.XBeeAPIPacket` - | :class:`.XBeeEvent` - """ - pass - - -class DataReceived(XBeeEvent): - """ - This event is fired when an XBee receives data. - - The callbacks for handle this events will receive the following arguments: - 1. message (:class:`.XBeeMessage`): message containing the data received, the sender and the time. - - .. seealso:: - | :class:`.XBeeEvent` - | :class:`.XBeeMessage` - """ - pass - - -class ModemStatusReceived(XBeeEvent): - """ - This event is fired when a XBee receives a modem status packet. - - The callbacks for handle this events will receive the following arguments: - 1. modem_status (:class:`.ModemStatus`): the modem status received. - - .. seealso:: - | :class:`.XBeeEvent` - | :class:`.ModemStatus` - """ - pass - - -class IOSampleReceived(XBeeEvent): - """ - This event is fired when a XBee receives an IO packet. - - This includes: - - 1. IO data sample RX indicator packet. - 2. RX IO 16 packet. - 3. RX IO 64 packet. - - The callbacks that handle this event will receive the following arguments: - 1. io_sample (:class:`.IOSample`): the received IO sample. - 2. sender (:class:`.RemoteXBeeDevice`): the remote XBee device who has sent the packet. - 3. time (Integer): the time in which the packet was received. - - .. seealso:: - | :class:`.IOSample` - | :class:`.RemoteXBeeDevice` - | :class:`.XBeeEvent` - """ - pass - - -class DeviceDiscovered(XBeeEvent): - """ - This event is fired when an XBee discovers another remote XBee - during a discovering operation. - - The callbacks that handle this event will receive the following arguments: - 1. discovered_device (:class:`.RemoteXBeeDevice`): the discovered remote XBee device. - - .. seealso:: - | :class:`.RemoteXBeeDevice` - | :class:`.XBeeEvent` - """ - pass - - -class DiscoveryProcessFinished(XBeeEvent): - """ - This event is fired when the discovery process finishes, either - successfully or due to an error. - - The callbacks that handle this event will receive the following arguments: - 1. status (:class:`.NetworkDiscoveryStatus`): the network discovery status. - - .. seealso:: - | :class:`.NetworkDiscoveryStatus` - | :class:`.XBeeEvent` - """ - pass - - -class ExplicitDataReceived(XBeeEvent): - """ - This event is fired when an XBee receives an explicit data packet. - - The callbacks for handle this events will receive the following arguments: - 1. message (:class:`.ExplicitXBeeMessage`): message containing the data received, the sender, the time - and explicit data message parameters. - - .. seealso:: - | :class:`.XBeeEvent` - | :class:`.XBeeMessage` - """ - pass - - -class IPDataReceived(XBeeEvent): - """ - This event is fired when an XBee receives IP data. - - The callbacks for handle this events will receive the following arguments: - 1. message (:class:`.IPMessage`): message containing containing the IP address the message - belongs to, the source and destination ports, the IP protocol, and the content (data) of the message. - - .. seealso:: - | :class:`.XBeeEvent` - | :class:`.IPMessage` - """ - pass - - -class SMSReceived(XBeeEvent): - """ - This event is fired when an XBee receives an SMS. - - The callbacks for handle this events will receive the following arguments: - 1. message (:class:`.SMSMessage`): message containing the phone number that sent - the message and the content (data) of the message. - - .. seealso:: - | :class:`.XBeeEvent` - | :class:`.SMSMessage` - """ - pass - - -class RelayDataReceived(XBeeEvent): - """ - This event is fired when an XBee receives a user data relay output packet. - - The callbacks to handle these events will receive the following arguments: - 1. message (:class:`.UserDataRelayMessage`): message containing the source interface - and the content (data) of the message. - - .. seealso:: - | :class:`.XBeeEvent` - | :class:`.UserDataRelayMessage` - """ - pass - - -class BluetoothDataReceived(XBeeEvent): - """ - This event is fired when an XBee receives data from the Bluetooth interface. - - The callbacks to handle these events will receive the following arguments: - 1. data (Bytearray): received Bluetooth data. - - .. seealso:: - | :class:`.XBeeEvent` - """ - pass - - -class MicroPythonDataReceived(XBeeEvent): - """ - This event is fired when an XBee receives data from the MicroPython interface. - - The callbacks to handle these events will receive the following arguments: - 1. data (Bytearray): received MicroPython data. - - .. seealso:: - | :class:`.XBeeEvent` - """ - pass - - -class PacketListener(threading.Thread): - """ - This class represents a packet listener, which is a thread that's always - listening for incoming packets to the XBee. - - When it receives a packet, this class throws an event depending on which - packet it is. You can add your own callbacks for this events via certain - class methods. This callbacks must have a certain header, see each event - documentation. - - This class has fields that are events. Its recommended to use only the - append() and remove() method on them, or -= and += operators. - If you do something more with them, it's for your own risk. - - Here are the parameters which will be received by the event callbacks, - depending on which event it is in each case: - - The following parameters are passed via \*\*kwargs to event callbacks of: - - 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 - 3.1 modem_status (:class:`.ModemStatus`): the modem status received. - """ - - __DEFAULT_QUEUE_MAX_SIZE = 40 - """ - Default max. size that the queue has. - """ - - _LOG_PATTERN = "{port:<6s}{event:<12s}{fr_type:<10s}{sender:<18s}{more_data:<50s}" - """ - Generic pattern for display received messages (high-level) with logger. - """ - - _log = logging.getLogger(__name__) - """ - Logger. - """ - - def __init__(self, serial_port, xbee_device, queue_max_size=None): - """ - Class constructor. Instantiates a new :class:`.PacketListener` object with the provided parameters. - - Args: - 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) - - # User callbacks: - self.__packet_received = PacketReceived() - self.__data_received = DataReceived() - self.__modem_status_received = ModemStatusReceived() - self.__io_sample_received = IOSampleReceived() - self.__explicit_packet_received = ExplicitDataReceived() - self.__ip_data_received = IPDataReceived() - self.__sms_received = SMSReceived() - self.__relay_data_received = RelayDataReceived() - self.__bluetooth_data_received = BluetoothDataReceived() - self.__micropython_data_received = MicroPythonDataReceived() - - # API internal callbacks: - self.__packet_received_API = xbee_device.get_xbee_device_callbacks() - - self.__xbee_device = xbee_device - self.__serial_port = serial_port - self.__stop = True - - 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) - self.__data_xbee_queue = XBeeQueue(self.__queue_max_size) - self.__explicit_xbee_queue = XBeeQueue(self.__queue_max_size) - self.__ip_xbee_queue = XBeeQueue(self.__queue_max_size) - - self._log_handler = logging.StreamHandler() - self._log.addHandler(self._log_handler) - - def __del__(self): - self._log.removeHandler(self._log_handler) - - def run(self): - """ - This is the method that will be executing for listening packets. - - For each packet, it will execute the proper callbacks. - """ - try: - self.__stop = False - while not self.__stop: - # Try to read a packet. Read packet is unescaped. - 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. - if (self.__xbee_device.get_protocol() == XBeeProtocol.RAW_802_15_4 and - not self.__check_packet_802_15_4(raw_packet)): - continue - - # Build the packet. - try: - read_packet = factory.build_frame(raw_packet, self.__xbee_device.operating_mode) - except InvalidPacketException as e: - 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(port=self.__xbee_device.serial_port.port, - event="RECEIVED", - opmode=self.__xbee_device.operating_mode, - content=utils.hex_to_string(raw_packet))) - - # Add the packet to the queue. - self.__add_packet_queue(read_packet) - - # If the packet has information about a remote device, extract it - # and add/update this remote device to/in this XBee's network. - remote = self.__try_add_remote_device(read_packet) - - # Execute API internal callbacks. - self.__packet_received_API(read_packet) - - # Execute all user callbacks. - self.__execute_user_callbacks(read_packet, remote) - except Exception as e: - if not self.__stop: - self._log.exception(e) - finally: - if not self.__stop: - self.__stop = True - if self.__serial_port.isOpen(): - self.__serial_port.close() - - def stop(self): - """ - Stops listening. - """ - self.__stop = True - - def is_running(self): - """ - Returns whether this instance is running or not. - - Returns: - Boolean: ``True`` if this instance is running, ``False`` otherwise. - """ - return not self.__stop - - def get_queue(self): - """ - Returns the packets queue. - - Returns: - :class:`.XBeeQueue`: the packets queue. - """ - return self.__xbee_queue - - def get_data_queue(self): - """ - Returns the data packets queue. - - Returns: - :class:`.XBeeQueue`: the data packets queue. - """ - return self.__data_xbee_queue - - def get_explicit_queue(self): - """ - Returns the explicit packets queue. - - Returns: - :class:`.XBeeQueue`: the explicit packets queue. - """ - return self.__explicit_xbee_queue - - def get_ip_queue(self): - """ - Returns the IP packets queue. - - Returns: - :class:`.XBeeQueue`: the IP packets queue. - """ - return self.__ip_xbee_queue - - def add_packet_received_callback(self, callback): - """ - Adds a callback for the event :class:`.PacketReceived`. - - Args: - callback (Function): the callback. Receives two arguments. - - * The received packet as a :class:`.XBeeAPIPacket` - * The sender as a :class:`.RemoteXBeeDevice` - """ - self.__packet_received += 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.__data_received += 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.__modem_status_received += 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.__io_sample_received += callback - - def add_explicit_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.__explicit_packet_received += callback - - def add_ip_data_received_callback(self, callback): - """ - Adds a callback for the event :class:`.IPDataReceived`. - - Args: - callback (Function): the callback. Receives one argument. - - * The data received as an :class:`.IPMessage` - """ - self.__ip_data_received += callback - - def add_sms_received_callback(self, callback): - """ - Adds a callback for the event :class:`.SMSReceived`. - - Args: - callback (Function): the callback. Receives one argument. - - * The data received as an :class:`.SMSMessage` - """ - self.__sms_received += 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 data received as a :class:`.UserDataRelayMessage` - """ - self.__relay_data_received += 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 data received as a Bytearray - """ - self.__bluetooth_data_received += 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 data received as a Bytearray - """ - self.__micropython_data_received += callback - - def del_packet_received_callback(self, callback): - """ - 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:`.PacketReceived` event. - """ - self.__packet_received -= callback - - 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.__data_received -= 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.__modem_status_received -= 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.__io_sample_received -= callback - - def del_explicit_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.__explicit_packet_received -= callback - - def del_ip_data_received_callback(self, callback): - """ - 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:`.IPDataReceived` event. - """ - self.__ip_data_received -= callback - - def del_sms_received_callback(self, callback): - """ - 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:`.SMSReceived` event. - """ - self.__sms_received -= 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.__relay_data_received -= 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.__bluetooth_data_received -= 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.__micropython_data_received -= callback - - def __execute_user_callbacks(self, xbee_packet, remote=None): - """ - Executes callbacks corresponding to the received packet. - - Args: - xbee_packet (:class:`.XBeeAPIPacket`): the received packet. - remote (:class:`.RemoteXBeeDevice`): the XBee device that sent the packet. - """ - # All packets callback. - self.__packet_received(xbee_packet) - - # Data reception callbacks - 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.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(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(port=self.__xbee_device.serial_port.port, - event="RECEIVED", - fr_type="MODEM STATUS", - sender=str(remote.get_64bit_addr()) if remote is not None - else "None", - more_data=xbee_packet.modem_status)) - - # IO_sample callbacks - elif (xbee_packet.get_frame_type() == ApiFrameType.RX_IO_16 or - 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(port=self.__xbee_device.serial_port.port, - event="RECEIVED", - fr_type="IOSAMPLE", - sender=str(remote.get_64bit_addr()) if remote is not None - else "None", - more_data=str(xbee_packet.io_sample))) - - # Explicit packet callbacks - elif xbee_packet.get_frame_type() == ApiFrameType.EXPLICIT_RX_INDICATOR: - is_broadcast = False - # If it's 'special' packet, notify the data_received callbacks too: - 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(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(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(port=self.__xbee_device.serial_port.port, - event="RECEIVED", - fr_type="IP DATA", - sender=str(xbee_packet.source_address), - more_data=utils.hex_to_string(xbee_packet.data))) - - # 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(port=self.__xbee_device.serial_port.port, - event="RECEIVED", - fr_type="SMS", - sender=str(xbee_packet.phone_number), - more_data=xbee_packet.data)) - - # Relay - elif xbee_packet.get_frame_type() == ApiFrameType.USER_DATA_RELAY_OUTPUT: - # Notify generic callbacks. - self.__relay_data_received(UserDataRelayMessage(xbee_packet.src_interface, xbee_packet.data)) - # Notify specific callbacks. - if xbee_packet.src_interface == XBeeLocalInterface.BLUETOOTH: - 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(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))) - - 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): - """ - Creates a :class:`.RemoteXBeeDevice` that represents the device that - has sent the ``xbee_packet``. - - Returns: - :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, x16bit_addr) - - @staticmethod - def __get_remote_device_data_from_packet(xbee_packet): - """ - Extracts the 64 bit-address and the 16 bit-address from ``xbee_packet`` if is - possible. - """ - x64bit_addr = None - x16bit_addr = None - if hasattr(xbee_packet, "x64bit_source_addr"): - x64bit_addr = xbee_packet.x64bit_source_addr - if hasattr(xbee_packet, "x16bit_source_addr"): - x16bit_addr = xbee_packet.x16bit_source_addr - return x64bit_addr, x16bit_addr - - @staticmethod - def __check_packet_802_15_4(raw_data): - """ - If the current XBee's protocol is 802.15.4 and - the user sends many 'ND' commands, the device could return - an RX 64 IO packet with an invalid payload (length < 5). - - In this case the packet must be discarded, or an exception - must be raised. - - This method checks a received raw_data and returns False if - the packet mustn't be processed. - - Args: - raw_data (Bytearray): received data. - - Returns: - Boolean: ``True`` if the packet must be processed, ``False`` otherwise. - """ - if raw_data[3] == ApiFrameType.RX_IO_64 and len(raw_data[14:-1]) < IOSample.min_io_sample_payload(): - return False - return True - - def __try_add_remote_device(self, xbee_packet): - """ - If the packet has information about a remote device, this method - extracts that information from the packet, creates a remote device, and - adds it (if not exist yet) to the network. - - Returns: - :class:`.RemoteXBeeDevice`: the remote device extracted from the packet, `None`` if the packet has - not information about a remote device. - """ - 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().add_if_not_exist(x64, x16) - return remote - - @staticmethod - def __is_special_explicit_packet(xbee_packet): - """ - Checks if an explicit data packet is 'special'. - - '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 - 2. Destination endpoint = 0xE8 - 3. Cluster ID = 0x0011 - 4. 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): - """ - Creates a non-explicit data 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). - """ - 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_packet = RX64Packet(x64addr, 0, xbee_packet.receive_options, xbee_packet.rf_data) - elif x16addr != XBee16BitAddress.UNKNOWN_ADDRESS: - new_packet = RX16Packet(x16addr, 0, xbee_packet.receive_options, xbee_packet.rf_data) - else: # both address UNKNOWN - new_packet = RX64Packet(x64addr, 0, xbee_packet.receive_options, xbee_packet.rf_data) - else: - 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): - """ - Adds a packet to the queue. If the queue is full, - the first packet of the queue is removed and the given - packet is added. - - Args: - xbee_packet (:class:`.XBeeAPIPacket`): the packet to be added. - """ - # Data packets. - if xbee_packet.get_frame_type() in [ApiFrameType.RECEIVE_PACKET, ApiFrameType.RX_64, ApiFrameType.RX_16]: - if self.__data_xbee_queue.full(): - self.__data_xbee_queue.get() - self.__data_xbee_queue.put_nowait(xbee_packet) - # Explicit packets. - if xbee_packet.get_frame_type() == ApiFrameType.EXPLICIT_RX_INDICATOR: - if self.__explicit_xbee_queue.full(): - self.__explicit_xbee_queue.get() - self.__explicit_xbee_queue.put_nowait(xbee_packet) - # Check if the explicit packet is 'special'. - 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)) - # IP packets. - elif xbee_packet.get_frame_type() == ApiFrameType.RX_IPV4: - if self.__ip_xbee_queue.full(): - self.__ip_xbee_queue.get() - self.__ip_xbee_queue.put_nowait(xbee_packet) - # Rest of packets. - else: - if self.__xbee_queue.full(): - self.__xbee_queue.get() - self.__xbee_queue.put_nowait(xbee_packet) - - @staticmethod - def __expl_to_message(remote, broadcast, xbee_packet): - """ - Converts an explicit packet in an explicit message. - - Args: - remote (:class:`.RemoteXBeeDevice`): the remote XBee device that sent the packet. - broadcast (Boolean, optional, default=``False``): flag indicating whether the message is - broadcast (``True``) or not (``False``). Optional. - xbee_packet (:class:`.XBeeAPIPacket`): the packet to be converted. - - Returns: - :class:`.ExplicitXBeeMessage`: the explicit message generated from the provided parameters. - """ - 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) - - -class XBeeQueue(Queue): - """ - This class represents an XBee queue. - """ - - def __init__(self, maxsize=10): - """ - Class constructor. Instantiates a new :class:`.XBeeQueue` with the provided parameters. - - Args: - maxsize (Integer, default: 10) the maximum size of the queue. - """ - Queue.__init__(self, maxsize) - - def get(self, block=True, timeout=None): - """ - Returns the first element of the queue if there is some - element ready before timeout expires, in case of the timeout is not - ``None``. - - If timeout is ``None``, this method is non-blocking. In this case, if there - isn't any element available, it returns ``None``, otherwise it returns - an :class:`.XBeeAPIPacket`. - - Args: - block (Boolean): ``True`` to block during ``timeout`` waiting for a packet, ``False`` to not block. - timeout (Integer, optional): timeout in seconds. - - Returns: - :class:`.XBeeAPIPacket`: a packet if there is any packet available before ``timeout`` expires. - If ``timeout`` is ``None``, the returned value may be ``None``. - - Raises: - TimeoutException: if ``timeout`` is not ``None`` and there isn't any packet available - before the timeout expires. - """ - if timeout is None: - try: - xbee_packet = Queue.get(self, block=False) - except (Empty, ValueError): - xbee_packet = None - return xbee_packet - else: - try: - return Queue.get(self, True, timeout) - except Empty: - raise TimeoutException() - - def get_by_remote(self, remote_xbee_device, timeout=None): - """ - Returns the first element of the queue that had been sent - by ``remote_xbee_device``, if there is some in the specified timeout. - - If timeout is ``None``, this method is non-blocking. In this case, if there isn't - any packet sent by ``remote_xbee_device`` in the queue, it returns ``None``, - otherwise it returns an :class:`.XBeeAPIPacket`. - - Args: - remote_xbee_device (:class:`.RemoteXBeeDevice`): the remote XBee device to get its firs element from queue. - timeout (Integer, optional): timeout in seconds. - - Returns: - :class:`.XBeeAPIPacket`: if there is any packet available before the timeout expires. If timeout is - ``None``, the returned value may be ``None``. - - Raises: - TimeoutException: if timeout is not ``None`` and there isn't any packet available that has - been sent by ``remote_xbee_device`` before the timeout expires. - """ - if timeout is None: - with self.mutex: - for xbee_packet in self.queue: - if self.__remote_device_match(xbee_packet, remote_xbee_device): - self.queue.remove(xbee_packet) - return xbee_packet - return None - else: - 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, None) - if xbee_packet is None: - raise TimeoutException() - return xbee_packet - - def get_by_ip(self, ip_addr, timeout=None): - """ - Returns the first IP data packet from the queue whose IP address - matches the provided address. - - If timeout is ``None``, this method is non-blocking. In this case, if there isn't - any packet sent by ``remote_xbee_device`` in the queue, it returns ``None``, - otherwise it returns an :class:`.XBeeAPIPacket`. - - Args: - ip_addr (:class:`ipaddress.IPv4Address`): The IP address to look for in the list of packets. - timeout (Integer, optional): Timeout in seconds. - - Returns: - :class:`.XBeeAPIPacket`: if there is any packet available before the timeout expires. If timeout is - ``None``, the returned value may be ``None``. - - Raises: - TimeoutException: if timeout is not ``None`` and there isn't any packet available that has - been sent by ``remote_xbee_device`` before the timeout expires. - """ - if timeout is None: - with self.mutex: - for xbee_packet in self.queue: - if self.__ip_addr_match(xbee_packet, ip_addr): - self.queue.remove(xbee_packet) - return xbee_packet - return None - else: - 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, None) - if xbee_packet is None: - raise TimeoutException() - return xbee_packet - - def get_by_id(self, frame_id, timeout=None): - """ - Returns the first packet from the queue whose frame ID - matches the provided one. - - If timeout is ``None``, this method is non-blocking. In this case, if there isn't - any received packet with the provided frame ID in the queue, it returns ``None``, - otherwise it returns an :class:`.XBeeAPIPacket`. - - Args: - frame_id (Integer): The frame ID to look for in the list of packets. - timeout (Integer, optional): Timeout in seconds. - - Returns: - :class:`.XBeeAPIPacket`: if there is any packet available before the timeout expires. If timeout is - ``None``, the returned value may be ``None``. - - Raises: - TimeoutException: if timeout is not ``None`` and there isn't any packet available that matches - the provided frame ID before the timeout expires. - """ - if timeout is None: - with self.mutex: - for xbee_packet in self.queue: - if xbee_packet.needs_id() and xbee_packet.frame_id == frame_id: - self.queue.remove(xbee_packet) - return xbee_packet - return None - else: - 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, None) - if xbee_packet is None: - raise TimeoutException() - return xbee_packet - - def flush(self): - """ - Clears the queue. - """ - with self.mutex: - self.queue.clear() - - @staticmethod - def __remote_device_match(xbee_packet, remote_xbee_device): - """ - Returns whether or not the source address of the provided XBee packet - matches the address of the given remote XBee device. - - Args: - xbee_packet (:class:`.XBeePacket`): The XBee packet to get the address to compare. - remote_xbee_device (:class:`.RemoteXBeeDevice`): The remote XBee device to get the address to compare. - - Returns: - Boolean: ``True`` if the remote device matches, ``False`` otherwise. - """ - if xbee_packet.get_frame_type() == ApiFrameType.RECEIVE_PACKET: - if xbee_packet.x64bit_source_addr == remote_xbee_device.get_64bit_addr(): - return True - return xbee_packet.x16bit_source_addr == remote_xbee_device.get_16bit_addr() - - elif xbee_packet.get_frame_type() == ApiFrameType.REMOTE_AT_COMMAND_RESPONSE: - if xbee_packet.x64bit_source_addr == remote_xbee_device.get_64bit_addr(): - return True - return xbee_packet.x16bit_source_addr == remote_xbee_device.get_16bit_addr() - - elif xbee_packet.get_frame_type() == ApiFrameType.RX_16: - return xbee_packet.x16bit_source_addr == remote_xbee_device.get_16bit_addr() - - elif xbee_packet.get_frame_type() == ApiFrameType.RX_64: - return xbee_packet.x64bit_source_addr == remote_xbee_device.get_64bit_addr() - - elif xbee_packet.get_frame_type() == ApiFrameType.RX_IO_16: - return xbee_packet.x16bit_source_addr == remote_xbee_device.get_16bit_addr() - - elif xbee_packet.get_frame_type() == ApiFrameType.RX_IO_64: - return xbee_packet.x64bit_source_addr == remote_xbee_device.get_64bit_addr() - - elif xbee_packet.get_frame_type() == ApiFrameType.EXPLICIT_RX_INDICATOR: - return xbee_packet.x64bit_source_addr == remote_xbee_device.get_64bit_addr() - - else: - return False - - @staticmethod - def __ip_addr_match(xbee_packet, ip_addr): - """ - Returns whether or not the IP address of the XBee packet matches the - provided one. - - Args: - xbee_packet (:class:`.XBeePacket`): The XBee packet to get the address to compare. - ip_addr (:class:`ipaddress.IPv4Address`): The IP address to be compared with the XBee packet's one. - - Returns: - Boolean: ``True`` if the IP address matches, ``False`` otherwise. - """ - return xbee_packet.get_frame_type() == ApiFrameType.RX_IPV4 and xbee_packet.source_address == ip_addr diff --git a/digi/xbee/util/__init__.py b/digi/xbee/util/__init__.py deleted file mode 100644 index 8ea10d3..0000000 --- a/digi/xbee/util/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -# 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. diff --git a/digi/xbee/util/utils.py b/digi/xbee/util/utils.py deleted file mode 100644 index 3983855..0000000 --- a/digi/xbee/util/utils.py +++ /dev/null @@ -1,307 +0,0 @@ -# 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. - -import logging - - -# Number of bits to extract with the mask (__MASK) -__MASK_NUM_BITS = 8 - -# Bit mask to extract the less important __MAS_NUM_BITS bits of a number. -__MASK = 0xFF - - -def is_bit_enabled(number, position): - """ - Returns whether the bit located at ``position`` within ``number`` is enabled or not. - - Args: - number (Integer): the number to check if a bit is enabled. - position (Integer): the position of the bit to check if is enabled in ``number``. - - Returns: - Boolean: ``True`` if the bit located at ``position`` within ``number`` is enabled, ``False`` otherwise. - """ - return ((number & 0xFFFFFFFF) >> position) & 0x01 == 0x01 - - -def hex_string_to_bytes(hex_string): - """ - Converts a String (composed by hex. digits) into a bytearray with same digits. - - Args: - hex_string (String): String (made by hex. digits) with "0x" header or not. - - Returns: - Bytearray: bytearray containing the numeric value of the hexadecimal digits. - - Raises: - ValueError: if invalid literal for int() with base 16 is provided. - - Example: - >>> a = "0xFFFE" - >>> for i in hex_string_to_bytes(a): print(i) - 255 - 254 - >>> print(type(hex_string_to_bytes(a))) - - - >>> b = "FFFE" - >>> for i in hex_string_to_bytes(b): print(i) - 255 - 254 - >>> print(type(hex_string_to_bytes(b))) - - - """ - aux = int(hex_string, 16) - return int_to_bytes(aux) - - -def int_to_bytes(number, num_bytes=None): - """ - Converts the provided integer into a bytearray. - - If ``number`` has less bytes than ``num_bytes``, the resultant bytearray - is filled with zeros (0x00) starting at the beginning. - - If ``number`` has more bytes than ``num_bytes``, the resultant bytearray - is returned without changes. - - Args: - number (Integer): the number to convert to a bytearray. - num_bytes (Integer): the number of bytes that the resultant bytearray will have. - - Returns: - Bytearray: the bytearray corresponding to the provided number. - - Example: - >>> a=0xFFFE - >>> print([i for i in int_to_bytes(a)]) - [255,254] - >>> print(type(int_to_bytes(a))) - - - """ - byte_array = bytearray() - byte_array.insert(0, number & __MASK) - number >>= __MASK_NUM_BITS - while number != 0: - byte_array.insert(0, number & __MASK) - number >>= __MASK_NUM_BITS - - if num_bytes is not None: - while len(byte_array) < num_bytes: - byte_array.insert(0, 0x00) - - return byte_array - - -def length_to_int(byte_array): - """ - Calculates the length value for the given length field of a packet. - Length field are bytes 1 and 2 of any packet. - - Args: - byte_array (Bytearray): length field of a packet. - - Returns: - Integer: the length value. - - Raises: - ValueError: if ``byte_array`` is not a valid length field (it has length distinct than 0). - - Example: - >>> b = bytearray([13,14]) - >>> c = length_to_int(b) - >>> print("0x%02X" % c) - 0x1314 - >>> print(c) - 4884 - """ - if len(byte_array) != 2: - raise ValueError("bArray must have length 2") - return (byte_array[0] << 8) + byte_array[1] - - -def bytes_to_int(byte_array): - """ - Converts the provided bytearray in an Integer. - This integer is result of concatenate all components of ``byte_array`` - and convert that hex number to a decimal number. - - Args: - byte_array (Bytearray): bytearray to convert in integer. - - Returns: - Integer: the integer corresponding to the provided bytearray. - - Example: - >>> x = bytearray([0xA,0x0A,0x0A]) #this is 0xA0A0A - >>> print(bytes_to_int(x)) - 657930 - >>> b = bytearray([0x0A,0xAA]) #this is 0xAAA - >>> print(bytes_to_int(b)) - 2730 - """ - if len(byte_array) == 0: - return 0 - return int("".join(["%02X" % i for i in byte_array]), 16) - - -def ascii_to_int(ni): - """ - Converts a bytearray containing the ASCII code of each number digit in an Integer. - This integer is result of the number formed by all ASCII codes of the bytearray. - - Example: - >>> x = bytearray( [0x31,0x30,0x30] ) #0x31 => ASCII code for number 1. - #0x31,0x30,0x30 <==> 1,0,0 - >>> print(ascii_to_int(x)) - 100 - """ - return int("".join([str(i - 0x30) for i in ni])) - - -def int_to_ascii(number): - """ - Converts an integer number to a bytearray. Each element of the bytearray is the ASCII - code that corresponds to the digit of its position. - - Args: - number (Integer): the number to convert to an ASCII bytearray. - - Returns: - Bytearray: the bytearray containing the ASCII value of each digit of the number. - - Example: - >>> x = int_to_ascii(100) - >>> print(x) - 100 - >>> print([i for i in x]) - [49, 48, 48] - """ - return bytearray([ord(i) for i in str(number)]) - - -def int_to_length(number): - """ - Converts am integer into a bytearray of 2 bytes corresponding to the length field of a - packet. If this bytearray has length 1, a byte with value 0 is added at the beginning. - - Args: - number (Integer): the number to convert to a length field. - - Returns: - - - Raises: - ValueError: if ``number`` is less than 0 or greater than 0xFFFF. - - Example: - >>> a = 0 - >>> print(hex_to_string(int_to_length(a))) - 00 00 - - >>> a = 8 - >>> print(hex_to_string(int_to_length(a))) - 00 08 - - >>> a = 200 - >>> print(hex_to_string(int_to_length(a))) - 00 C8 - - >>> a = 0xFF00 - >>> print(hex_to_string(int_to_length(a))) - FF 00 - - >>> a = 0xFF - >>> print(hex_to_string(int_to_length(a))) - 00 FF - """ - if number < 0 or number > 0xFFFF: - raise ValueError("The number must be between 0 and 0xFFFF.") - length = int_to_bytes(number) - if len(length) < 2: - length.insert(0, 0) - return length - - -def hex_to_string(byte_array, pretty=True): - """ - Returns the provided bytearray in a pretty string format. All bytes are separated by blank spaces and - printed in hex format. - - Args: - byte_array (Bytearray): the bytearray to print in pretty string. - pretty (Boolean, optional): ``True`` for pretty string format, ``False`` for plain string format. - Default to ``True``. - - Returns: - String: the bytearray formatted in a string format. - """ - separator = " " if pretty else "" - return separator.join(["%02X" % i for i in byte_array]) - - -def doc_enum(enum_class, descriptions=None): - """ - Returns a string with the description of each value of an enumeration. - - Args: - enum_class (Enumeration): the Enumeration to get its values documentation. - descriptions (dictionary): each enumeration's item description. The key is the enumeration element name - and the value is the description. - - Returns: - String: the string listing all the enumeration values and their descriptions. - """ - tab = " "*4 - data = "\n| Values:\n" - for x in enum_class: - data += """| {:s}**{:s}**{:s} {:s}\n""".format(tab, x, - ":" if descriptions is not None else " =", - str(x.value) if descriptions is None else descriptions[x]) - return data + "| \n" - - -def enable_logger(name, level=logging.DEBUG): - """ - Enables a logger with the given name and level. - - Args: - name (String): name of the logger to enable. - level (Integer): logging level value. - - Assigns a default formatter and a default handler (for console). - """ - log = logging.getLogger(name) - log.disabled = False - ch = logging.StreamHandler() - ch.setLevel(level) - formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)-7s - %(message)s') - ch.setFormatter(formatter) - log.addHandler(ch) - log.setLevel(level) - - -def disable_logger(name): - """ - Disables the logger with the give name. - - Args: - name (String): the name of the logger to disable. - """ - log = logging.getLogger(name) - log.disabled = True diff --git a/digi/xbee/xbeeserial.py b/digi/xbee/xbeeserial.py deleted file mode 100644 index f2a657c..0000000 --- a/digi/xbee/xbeeserial.py +++ /dev/null @@ -1,138 +0,0 @@ -# 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 From 282b8697659d7f3f5c4f706f634bfcc63979a837 Mon Sep 17 00:00:00 2001 From: alexglzg Date: Sat, 21 Dec 2019 17:14:17 -0600 Subject: [PATCH 3/7] python 2 library --- digi/__init__.py | 13 + digi/xbee/__init__.py | 13 + digi/xbee/devices.py | 6351 ++++++++++++++++++++++++++++++ digi/xbee/exception.py | 162 + digi/xbee/io.py | 647 +++ digi/xbee/models/__init__.py | 13 + digi/xbee/models/accesspoint.py | 224 ++ digi/xbee/models/address.py | 442 +++ digi/xbee/models/atcomm.py | 273 ++ digi/xbee/models/hw.py | 145 + digi/xbee/models/message.py | 457 +++ digi/xbee/models/mode.py | 204 + digi/xbee/models/options.py | 470 +++ digi/xbee/models/protocol.py | 331 ++ digi/xbee/models/status.py | 805 ++++ digi/xbee/packets/__init__.py | 13 + digi/xbee/packets/aft.py | 114 + digi/xbee/packets/base.py | 648 +++ digi/xbee/packets/cellular.py | 353 ++ digi/xbee/packets/common.py | 2895 ++++++++++++++ digi/xbee/packets/devicecloud.py | 996 +++++ digi/xbee/packets/factory.py | 218 + digi/xbee/packets/network.py | 528 +++ digi/xbee/packets/raw.py | 1471 +++++++ digi/xbee/packets/relay.py | 343 ++ digi/xbee/packets/wifi.py | 746 ++++ digi/xbee/reader.py | 1245 ++++++ digi/xbee/util/__init__.py | 13 + digi/xbee/util/utils.py | 307 ++ digi/xbee/xbeeserial.py | 138 + 30 files changed, 20578 insertions(+) create mode 100644 digi/__init__.py create mode 100644 digi/xbee/__init__.py create mode 100644 digi/xbee/devices.py create mode 100644 digi/xbee/exception.py create mode 100644 digi/xbee/io.py create mode 100644 digi/xbee/models/__init__.py create mode 100644 digi/xbee/models/accesspoint.py create mode 100644 digi/xbee/models/address.py create mode 100644 digi/xbee/models/atcomm.py create mode 100644 digi/xbee/models/hw.py create mode 100644 digi/xbee/models/message.py create mode 100644 digi/xbee/models/mode.py create mode 100644 digi/xbee/models/options.py create mode 100644 digi/xbee/models/protocol.py create mode 100644 digi/xbee/models/status.py create mode 100644 digi/xbee/packets/__init__.py create mode 100644 digi/xbee/packets/aft.py create mode 100644 digi/xbee/packets/base.py create mode 100644 digi/xbee/packets/cellular.py create mode 100644 digi/xbee/packets/common.py create mode 100644 digi/xbee/packets/devicecloud.py create mode 100644 digi/xbee/packets/factory.py create mode 100644 digi/xbee/packets/network.py create mode 100644 digi/xbee/packets/raw.py create mode 100644 digi/xbee/packets/relay.py create mode 100644 digi/xbee/packets/wifi.py create mode 100644 digi/xbee/reader.py create mode 100644 digi/xbee/util/__init__.py create mode 100644 digi/xbee/util/utils.py create mode 100644 digi/xbee/xbeeserial.py diff --git a/digi/__init__.py b/digi/__init__.py new file mode 100644 index 0000000..8ea10d3 --- /dev/null +++ b/digi/__init__.py @@ -0,0 +1,13 @@ +# 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. diff --git a/digi/xbee/__init__.py b/digi/xbee/__init__.py new file mode 100644 index 0000000..8ea10d3 --- /dev/null +++ b/digi/xbee/__init__.py @@ -0,0 +1,13 @@ +# 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. diff --git a/digi/xbee/devices.py b/digi/xbee/devices.py new file mode 100644 index 0000000..94f727c --- /dev/null +++ b/digi/xbee/devices.py @@ -0,0 +1,6351 @@ +# 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. + +from abc import ABCMeta, abstractmethod +import logging +from ipaddress import IPv4Address +import threading +import time + +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 +from digi.xbee.models.hw import HardwareVersion +from digi.xbee.models.mode import OperatingMode, APIOutputMode, IPAddressingMode +from digi.xbee.models.address import XBee64BitAddress, XBee16BitAddress, XBeeIMEIAddress +from digi.xbee.models.message import XBeeMessage, ExplicitXBeeMessage, IPMessage +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 +from digi.xbee.packets.aft import ApiFrameType +from digi.xbee.packets.base import XBeeAPIPacket +from digi.xbee.packets.common import ATCommPacket, TransmitPacket, RemoteATCommandPacket, ExplicitAddressingPacket, \ + ATCommQueuePacket, ATCommResponsePacket, RemoteATCommandResponsePacket +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.util import utils +from digi.xbee.exception import XBeeException, TimeoutException, InvalidOperatingModeException, \ + ATCommandException, OperationNotSupportedException +from digi.xbee.io import IOSample, IOMode +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 + + +class AbstractXBeeDevice(object): + """ + This class provides common functionality for all XBee devices. + """ + __metaclass__ = ABCMeta + + _DEFAULT_TIMEOUT_SYNC_OPERATIONS = 4 + """ + The default timeout for all synchronous operations, in seconds. + """ + + _BLE_API_USERNAME = "apiservice" + """ + The Bluetooth Low Energy API username. + """ + + LOG_PATTERN = "{port:<6s}{event:<12s}{opmode:<20s}{content:<50s}" + """ + Pattern used to log packet events. + """ + + _log = logging.getLogger(__name__) + """ + Logger. + """ + + 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. + + Args: + local_xbee_device (:class:`.XBeeDevice`, optional): only necessary if XBee device is remote. The local + XBee device that will behave as connection interface to communicate with the remote XBee one. + serial_port (:class:`.XBeeSerialPort`, optional): only necessary if the XBee device is local. The serial + 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. + + .. seealso:: + | :class:`.XBeeDevice` + | :class:`.XBeeSerialPort` + """ + self.__current_frame_id = 0x00 + + self._16bit_addr = None + self._64bit_addr = None + self._apply_changes_flag = True + + self._is_open = False + self._operating_mode = None + + self._local_xbee_device = local_xbee_device + self._serial_port = serial_port + self._timeout = sync_ops_timeout + + self.__io_packet_received = False + self.__io_packet_payload = None + + self._hardware_version = None + self._firmware_version = None + self._protocol = None + self._node_id = None + + self._packet_listener = None + + 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. + + Returns: + If at least one XBee device has 64 bit address (not ``None``), this method returns ``True`` if both + XBee device's addresses are equal, ``False`` otherwise. + + If at least one XBee device has 16 bit address (not ``None``), this method returns ``True`` if both + XBee device addresses are equal, ``False`` otherwise. + + If at least one XBee device has node id (not ``None``), this method returns ``True`` if both + XBee device IDs are equal, ``False`` otherwise. + + Else (all parameters of both devices are ``None``) returns ``True``. + """ + if other is None: + return False + if not isinstance(self, AbstractXBeeDevice) or not isinstance(other, AbstractXBeeDevice): + 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 update_device_data_from(self, device): + """ + Updates the current device reference with the data provided for the given device. + + This is only for internal use. + + 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() + + 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. + + 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. + """ + value = self.__send_parameter(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("Could not get the %s value." % parameter) + + return value + + def set_parameter(self, parameter, value): + """ + Sets the value of a parameter via an AT Command. + + If you send parameter to a local XBee device, all changes + will be applied automatically, except for non-volatile memory, + in which case you will need to execute the parameter "WR" via + :meth:`.AbstractXBeeDevice.execute_command` method, or + :meth:`.AbstractXBeeDevice.apply_changes` method. + + If you are sending parameters to a remote XBee device, + the changes will be not applied automatically, unless the "apply_changes" + flag is activated. + + You can set this flag via the method :meth:`.AbstractXBeeDevice.enable_apply_changes`. + + This flag only works for volatile memory, if you want to save + changed parameters in non-volatile memory, even for remote devices, + you must execute "WR" command by one of the 2 ways mentioned above. + + Args: + parameter (String): parameter to set. + value (Bytearray): value of the parameter. + + 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 ``parameter`` is ``None`` or ``value`` is ``None``. + """ + if value is None: + raise ValueError("Value of the parameter cannot be None.") + + self.__send_parameter(parameter, value) + + # Refresh cached parameters if this method modifies some of them. + self._refresh_if_cached(parameter, value) + + def execute_command(self, parameter): + """ + Executes the provided command. + + 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. + """ + self.__send_parameter(parameter, None) + + def __send_parameter(self, parameter, parameter_value=None): + """ + Sends the given AT parameter to this XBee device with an optional + argument or value and returns the response (likely the value) of that + parameter in a byte array format. + + Args: + parameter (String): The name of the AT command to be executed. + parameter_value (bytearray, optional): The value of the parameter to set (if any). + + Returns: + Bytearray: A byte array containing the value of the parameter. + + Raises: + ValueError: if ``parameter`` is ``None`` or if ``len(parameter) != 2``. + """ + if parameter is None: + raise ValueError("Parameter cannot be None.") + if len(parameter) != 2: + raise ValueError("Parameter must contain exactly 2 characters.") + + at_command = ATCommand(parameter, parameter_value) + + # Send the AT command. + response = self._send_at_command(at_command) + + self._check_at_cmd_response_is_valid(response) + + return response.response + + def _check_at_cmd_response_is_valid(self, response): + """ + Checks if the provided ``ATCommandResponse`` is valid throwing an + :class:`.ATCommandException` in case it is not. + + Args: + response: The AT command response to check. + + Raises: + ATCommandException: if ``response`` is ``None`` or + if ``response.response != OK``. + """ + if response is None or not isinstance(response, ATCommandResponse) or response.status is None: + raise ATCommandException(None) + elif response.status != ATCommandStatus.OK: + raise ATCommandException(response.status) + + def _send_at_command(self, command): + """ + Sends the given AT command and waits for answer or until the configured + receive timeout expires. + + Args: + command (:class:`.ATCommand`): AT command to be sent. + + Returns: + :class:`.ATCommandResponse`: object containing the response of the command + or ``None`` if there is no response. + + Raises: + ValueError: if ``command`` is ``None``. + InvalidOperatingModeException: if the operating mode is different than ``API`` or ``ESCAPED_API_MODE``. + + """ + if command is None: + raise ValueError("AT command cannot be None.") + + operating_mode = self._get_operating_mode() + if operating_mode != OperatingMode.API_MODE and operating_mode != OperatingMode.ESCAPED_API_MODE: + raise InvalidOperatingModeException.from_operating_mode(operating_mode) + + if self.is_remote(): + remote_at_cmd_opts = RemoteATCmdOptions.NONE.value + if self.is_apply_changes_enabled(): + remote_at_cmd_opts |= RemoteATCmdOptions.APPLY_CHANGES.value + + remote_16bit_addr = self.get_16bit_addr() + if remote_16bit_addr is None: + 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, command.parameter) + else: + if self.is_apply_changes_enabled(): + packet = ATCommPacket(self._get_next_frame_id(), command.command, command.parameter) + else: + 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) + else: + answer_packet = self._send_packet_sync_and_get_response(packet) + + response = None + + if isinstance(answer_packet, ATCommResponsePacket) or isinstance(answer_packet, RemoteATCommandResponsePacket): + response = ATCommandResponse(command, answer_packet.command_value, answer_packet.status) + + return response + + def apply_changes(self): + """ + Applies changes via ``AC`` command. + + 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. + """ + self.execute_command("AC") + + def write_changes(self): + """ + Writes configurable parameter values to the non-volatile memory of the + XBee device so that parameter modifications persist through subsequent + resets. + + Parameters values remain in this device's memory until overwritten by + subsequent use of this method. + + If changes are made without writing them to non-volatile memory, the + module reverts back to previously saved parameters the next time the + module is powered-on. + + Writing the parameter modifications does not mean those values are + immediately applied, this depends on the status of the 'apply + configuration changes' option. Use method + :meth:`is_apply_configuration_changes_enabled` to get its status and + :meth:`enable_apply_configuration_changes` to enable/disable the + option. If it is disabled, method :meth:`apply_changes` can be used in + order to manually apply the changes. + + 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. + """ + self.execute_command("WR") + + @abstractmethod + def reset(self): + """ + Performs a software reset on this XBee device and blocks until the process is completed. + + 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. + """ + pass + + def read_device_info(self): + """ + Updates all instance parameters reading them from the XBee device. + + 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.is_remote(): + 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("Not supported operating mode: " + str(self._operating_mode)) + + if not self._serial_port.is_open: + raise XBeeException("XBee device's serial port closed") + + # Hardware version: + self._hardware_version = HardwareVersion.get(self.get_parameter("HV")[0]) + # Firmware version: + 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: + sh = self.get_parameter("SH") + sl = self.get_parameter("SL") + self._64bit_addr = XBee64BitAddress(sh + sl) + # Node ID: + 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]: + r = self.get_parameter("MY") + self._16bit_addr = XBee16BitAddress(r) + + def get_node_id(self): + """ + Returns the Node Identifier (``NI``) value of the XBee device. + + Returns: + String: the Node Identifier (``NI``) of the XBee device. + """ + return self._node_id + + def set_node_id(self, node_id): + """ + Sets the Node Identifier (``NI``) value of the XBee device.. + + Args: + node_id (String): the new Node Identifier (``NI``) of the XBee device. + + Raises: + ValueError: if ``node_id`` is ``None`` or its length is greater than 20. + TimeoutException: if the response is not received before the read timeout expires. + """ + if node_id is None: + raise ValueError("Node ID cannot be None") + if len(node_id) > 20: + raise ValueError("Node ID length must be less than 21") + + self.set_parameter("NI", bytearray(node_id, 'utf8')) + self._node_id = node_id + + def get_hardware_version(self): + """ + Returns the hardware version of the XBee device. + + Returns: + :class:`.HardwareVersion`: the hardware version of the XBee device. + + .. seealso:: + | :class:`.HardwareVersion` + """ + return self._hardware_version + + def get_firmware_version(self): + """ + Returns the firmware version of the XBee device. + + Returns: + Bytearray: the hardware version of the XBee device. + """ + return self._firmware_version + + def get_protocol(self): + """ + Returns the current protocol of the XBee device. + + Returns: + :class:`.XBeeProtocol`: the current protocol of the XBee device. + + .. seealso:: + | :class:`.XBeeProtocol` + """ + return self._protocol + + def get_16bit_addr(self): + """ + Returns the 16-bit address of the XBee device. + + Returns: + :class:`.XBee16BitAddress`: the 16-bit address of the XBee device. + + .. seealso:: + | :class:`.XBee16BitAddress` + """ + return self._16bit_addr + + def set_16bit_addr(self, value): + """ + Sets the 16-bit address of the XBee device. + + Args: + value (:class:`.XBee16BitAddress`): the new 16-bit address of the XBee device. + + 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 the current protocol is not 802.15.4. + """ + if self.get_protocol() != XBeeProtocol.RAW_802_15_4: + raise OperationNotSupportedException("16-bit address can only be set in 802.15.4 protocol") + + self.set_parameter("MY", value.address) + self._16bit_addr = value + + def get_64bit_addr(self): + """ + Returns the 64-bit address of the XBee device. + + Returns: + :class:`.XBee64BitAddress`: the 64-bit address of the XBee device. + + .. seealso:: + | :class:`.XBee64BitAddress` + """ + return self._64bit_addr + + def get_current_frame_id(self): + """ + Returns the last used frame ID. + + Returns: + Integer: the last used frame ID. + """ + return self.__current_frame_id + + def enable_apply_changes(self, value): + """ + Sets the apply_changes flag. + + Args: + value (Boolean): ``True`` to enable the apply changes flag, ``False`` to disable it. + """ + self._apply_changes_flag = value + + def is_apply_changes_enabled(self): + """ + Returns whether the apply_changes flag is enabled or not. + + Returns: + Boolean: ``True`` if the apply_changes flag is enabled, ``False`` otherwise. + """ + return self._apply_changes_flag + + @abstractmethod + def is_remote(self): + """ + Determines whether the XBee device is remote or not. + + Returns: + Boolean: ``True`` if the XBee device is remote, ``False`` otherwise. + """ + pass + + def set_sync_ops_timeout(self, sync_ops_timeout): + """ + Sets the serial port read timeout. + + Args: + sync_ops_timeout (Integer): the read timeout, expressed in seconds. + """ + self._timeout = sync_ops_timeout + if self.is_remote(): + self._local_xbee_device.serial_port.timeout = self._timeout + else: + self._serial_port.timeout = self._timeout + + def get_sync_ops_timeout(self): + """ + Returns the serial port read timeout. + + Returns: + Integer: the serial port read timeout in seconds. + """ + return self._timeout + + def get_dest_address(self): + """ + Returns the 64-bit address of the XBee device that data will be reported to. + + Returns: + :class:`.XBee64BitAddress`: the address. + + Raises: + TimeoutException: if the response is not received before the read timeout expires. + + .. seealso:: + | :class:`.XBee64BitAddress` + """ + dh = self.get_parameter("DH") + dl = self.get_parameter("DL") + return XBee64BitAddress(dh + dl) + + 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. + + Raises: + 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() + + apply_changes = None + with self.__generic_lock: + try: + apply_changes = self.is_apply_changes_enabled() + self.enable_apply_changes(False) + 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 + finally: + if apply_changes: + self.enable_apply_changes(True) + self.apply_changes() + + def get_pan_id(self): + """ + Returns the operating PAN ID of the XBee device. + + Returns: + Bytearray: operating PAN ID of the XBee device. + + Raises: + TimeoutException: if the response is not received before the read timeout expires. + """ + if self.get_protocol() == XBeeProtocol.ZIGBEE: + return self.get_parameter("OP") + return self.get_parameter("ID") + + def set_pan_id(self, value): + """ + Sets the operating PAN ID of the XBee device. + + Args: + value (Bytearray): the new operating PAN ID of the XBee device.. Must have only 1 or 2 bytes. + + Raises: + TimeoutException: if the response is not received before the read timeout expires. + """ + self.set_parameter("ID", value) + + def get_power_level(self): + """ + Returns the power level of the XBee device. + + Returns: + :class:`.PowerLevel`: the power level of the XBee device. + + Raises: + TimeoutException: if the response is not received before the read timeout expires. + + .. seealso:: + | :class:`.PowerLevel` + """ + return PowerLevel.get(self.get_parameter("PL")[0]) + + def set_power_level(self, power_level): + """ + Sets the power level of the XBee device. + + Args: + power_level (:class:`.PowerLevel`): the new power level of the XBee device. + + Raises: + TimeoutException: if the response is not received before the read timeout expires. + + .. seealso:: + | :class:`.PowerLevel` + """ + self.set_parameter("PL", bytearray([power_level.code])) + + def set_io_configuration(self, io_line, io_mode): + """ + Sets the configuration of the provided IO line. + + Args: + io_line (:class:`.IOLine`): the IO line to configure. + io_mode (:class:`.IOMode`): the IO mode to set to the IO line. + + 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. + + .. seealso:: + | :class:`.IOLine` + | :class:`.IOMode` + """ + self.set_parameter(io_line.at_command, bytearray([io_mode.value])) + + def get_io_configuration(self, io_line): + """ + Returns the configuration of the provided IO line. + + Args: + io_line (:class:`.IOLine`): the io line to configure. + + Returns: + :class:`.IOMode`: the IO mode of the IO line provided. + + 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 the received data is not an IO mode. + """ + try: + mode = IOMode.get(self.get_parameter(io_line.at_command)[0]) + except ValueError: + raise OperationNotSupportedException("The received value is not an IO mode.") + return mode + + def get_io_sampling_rate(self): + """ + Returns the IO sampling rate of the XBee device. + + Returns: + Integer: the IO sampling rate of XBee device. + + 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. + """ + resp = self.get_parameter("IR") + return utils.bytes_to_int(resp) / 1000.00 + + def set_io_sampling_rate(self, rate): + """ + Sets the IO sampling rate of the XBee device in seconds. A sample rate of 0 means the IO sampling feature is + disabled. + + Args: + rate (Integer): the new IO sampling rate of the XBee device in seconds. + + 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. + """ + self.set_parameter("IR", utils.int_to_bytes(int(rate * 1000))) + + def read_io_sample(self): + """ + Returns an IO sample from the XBee device containing the value of all enabled digital IO and + analog input channels. + + Returns: + :class:`.IOSample`: the IO sample read from the XBee device. + + 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. + + .. seealso:: + | :class:`.IOSample` + """ + # The response to the IS command in local 802.15.4 devices is empty, + # so we have to use callbacks to read the packet. + if not self.is_remote() and self.get_protocol() == XBeeProtocol.RAW_802_15_4: + lock = threading.Condition() + self.__io_packet_received = False + self.__io_packet_payload = None + + def io_sample_callback(received_packet): + # Discard non API packets. + if not isinstance(received_packet, XBeeAPIPacket): + return + # If we already have received an IO packet, ignore this packet. + if self.__io_packet_received: + return + frame_type = received_packet.get_frame_type() + # Save the packet value (IO sample payload). + if (frame_type == ApiFrameType.IO_DATA_SAMPLE_RX_INDICATOR + or frame_type == ApiFrameType.RX_IO_16 + or frame_type == ApiFrameType.RX_IO_64): + self.__io_packet_payload = received_packet.rf_data + else: + return + # Set the IO packet received flag. + self.__io_packet_received = True + # Continue execution by notifying the lock object. + lock.acquire() + lock.notify() + lock.release() + + self._add_packet_received_callback(io_sample_callback) + + try: + # Execute 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("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("IS") + + try: + return IOSample(sample_payload) + except Exception as e: + raise XBeeException("Could not create the IO sample.", e) + + def get_adc_value(self, io_line): + """ + Returns the analog value of the provided IO line. + + The provided IO line must be previously configured as ADC. To do so, + use :meth:`.AbstractXBeeDevice.set_io_configuration` and :attr:`.IOMode.ADC`. + + Args: + io_line (:class:`.IOLine`): the IO line to get its ADC value. + + Returns: + Integer: the analog value corresponding to the provided IO line. + + 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 the response does not contain the value for the given IO line. + + .. seealso:: + | :class:`.IOLine` + """ + 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 values for the given IO line.") + return io_sample.analog_values[io_line] + + def set_pwm_duty_cycle(self, io_line, cycle): + """ + Sets the duty cycle in % of the provided IO line. + + The provided IO line must be PWM-capable, previously configured as PWM output. + + Args: + io_line (:class:`.IOLine`): the IO Line to be assigned. + cycle (Integer): duty cycle in % to be assigned. Must be between 0 and 100. + + 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 the given IO line does not have PWM capability or ``cycle`` is not between 0 and 100. + + .. seealso:: + | :class:`.IOLine` + | :attr:`.IOMode.PWM` + """ + if not io_line.has_pwm_capability(): + raise ValueError("%s has no PWM capability." % io_line) + if cycle < 0 or cycle > 100: + raise ValueError("Cycle must be between 0% and 100%.") + + duty_cycle = int(round(cycle * 1023.00 / 100.00)) + + self.set_parameter(io_line.pwm_at_command, bytearray(utils.int_to_bytes(duty_cycle))) + + def get_pwm_duty_cycle(self, io_line): + """ + Returns the PWM duty cycle in % corresponding to the provided IO line. + + Args: + io_line (:class:`.IOLine`): the IO line to get its PWM duty cycle. + + Returns: + Integer: the PWM duty cycle of the given IO line or ``None`` if the response is empty. + + 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 the passed :class:`.IO_LINE` has no PWM capability. + + .. seealso:: + | :class:`.IOLine` + """ + if not io_line.has_pwm_capability(): + raise ValueError("%s has no PWM capability." % io_line) + + value = utils.bytes_to_int(self.get_parameter(io_line.pwm_at_command)) + return round(((value * 100.0 / 1023.0) * 100.0) / 100.0) + + def get_dio_value(self, io_line): + """ + Returns the digital value of the provided IO line. + + The provided IO line must be previously configured as digital I/O. + To do so, use :meth:`.AbstractXBeeDevice.set_io_configuration`. + + Args: + io_line (:class:`.IOLine`): the DIO line to gets its digital value. + + Returns: + :class:`.IOValue`: current value of the provided IO line. + + 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 the response does not contain the value for the given IO line. + + .. seealso:: + | :class:`.IOLine` + | :class:`.IOValue` + """ + 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 values for the given IO_LINE") + return sample.digital_values[io_line] + + def set_dio_value(self, io_line, io_value): + """ + Sets the digital value (high or low) to the provided IO line. + + Args: + io_line (:class:`.IOLine`): the digital IO line to sets its value. + io_value (:class:`.IOValue`): the IO value to set to the IO line. + + 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. + + .. seealso:: + | :class:`.IOLine` + | :class:`.IOValue` + """ + self.set_parameter(io_line.at_command, bytearray([io_value.value])) + + def set_dio_change_detection(self, io_lines_set): + """ + Sets the digital IO lines to be monitored and sampled whenever their status changes. + + A ``None`` set of lines disables this feature. + + Args: + io_lines_set: set of :class:`.IOLine`. + + 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. + + .. seealso:: + | :class:`.IOLine` + """ + flags = bytearray(2) + if io_lines_set is not None: + for io_line in io_lines_set: + i = io_line.index + if i < 8: + flags[1] = flags[1] | (1 << i) + else: + flags[0] = flags[0] | ((1 << i) - 8) + self.set_parameter("IC", flags) + + def get_api_output_mode(self): + """ + Returns the API output mode of the XBee device. + + The API output mode determines the format that the received data is + output through the serial interface of the XBee device. + + Returns: + :class:`.APIOutputMode`: the API output mode of the XBee device. + + 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. + + .. seealso:: + | :class:`.APIOutputMode` + """ + return APIOutputMode.get(self.get_parameter("AO")[0]) + + def set_api_output_mode(self, api_output_mode): + """ + Sets the API output mode of the XBee device. + + Args: + api_output_mode (:class:`.APIOutputMode`): the new API output mode of the XBee device. + + 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 the current protocol is ZigBee + + .. seealso:: + | :class:`.APIOutputMode` + """ + self.set_parameter("AO", bytearray([api_output_mode.code])) + + def enable_bluetooth(self): + """ + Enables the Bluetooth interface of this XBee device. + + To work with this interface, you must also configure the Bluetooth password if not done previously. + You can use the :meth:`.AbstractXBeeDevice.update_bluetooth_password` method for that purpose. + + Note that your device must have Bluetooth Low Energy support to use this method. + + 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. + """ + self._enable_bluetooth(True) + + def disable_bluetooth(self): + """ + Disables the Bluetooth interface of this XBee device. + + Note that your device must have Bluetooth Low Energy support to use this method. + + 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. + """ + self._enable_bluetooth(False) + + def _enable_bluetooth(self, enable): + """ + Enables or disables the Bluetooth interface of this XBee device. + + Args: + enable (Boolean): ``True`` to enable the Bluetooth interface, ``False`` to disable it. + + 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. + """ + self.set_parameter("BT", b'\x01' if enable else b'\x00') + self.write_changes() + self.apply_changes() + + def get_bluetooth_mac_addr(self): + """ + Reads and returns the EUI-48 Bluetooth MAC address of this XBee device in a format such as ``00112233AABB``. + + Note that your device must have Bluetooth Low Energy support to use this method. + + Returns: + String: The Bluetooth MAC 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. + """ + return utils.hex_to_string(self.get_parameter("BL"), False) + + def update_bluetooth_password(self, new_password): + """ + Changes the password of this Bluetooth device with the new one provided. + + Note that your device must have Bluetooth Low Energy support to use this method. + + Args: + new_password (String): New Bluetooth password. + + 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. + """ + # 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) + + # Ensure the verifier is 128 bytes. + verifier = (128 - len(verifier)) * b'\x00' + verifier + + # Set the salt. + self.set_parameter("$S", salt) + + # Set the verifier (split in 4 settings) + index = 0 + at_length = int(len(verifier) / 4) + + self.set_parameter("$V", verifier[index:(index + at_length)]) + index += at_length + self.set_parameter("$W", verifier[index:(index + at_length)]) + index += at_length + self.set_parameter("$X", verifier[index:(index + at_length)]) + index += at_length + self.set_parameter("$Y", verifier[index:(index + at_length)]) + + # Write and apply changes. + self.write_changes() + self.apply_changes() + + def _get_ai_status(self): + """ + Returns the current association status of this XBee device. + + It indicates occurrences of errors during the modem initialization + and connection. + + Returns: + :class:`.AssociationIndicationStatus`: The association indication status of the XBee device. + + 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. + """ + value = self.get_parameter("AI") + return AssociationIndicationStatus.get(utils.bytes_to_int(value)) + + def _force_disassociate(self): + """ + Forces this XBee device to immediately disassociate from the network and + re-attempt to associate. + + Only valid for End Devices. + + 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. + """ + self.execute_command("DA") + + def _refresh_if_cached(self, parameter, value): + """ + Refreshes the proper cached parameter depending on ``parameter`` value. + + If ``parameter`` is not a cached parameter, this method does nothing. + + Args: + parameter (String): the parameter to refresh its value. + value (Bytearray): the new value of the parameter. + """ + if parameter == "NI": + self._node_id = value.decode() + elif parameter == "MY": + self._16bit_addr = XBee16BitAddress(value) + elif parameter == "AP": + self._operating_mode = OperatingMode.get(utils.bytes_to_int(value)) + + def _get_next_frame_id(self): + """ + Returns the next frame ID of the XBee device. + + Returns: + Integer: The next frame ID of the XBee device. + """ + if self.__current_frame_id == 0xFF: + self.__current_frame_id = 1 + else: + self.__current_frame_id += 1 + return self.__current_frame_id + + def _get_operating_mode(self): + """ + Returns the Operating mode (AT, API or API escaped) of this XBee device + for a local device, and the operating mode of the local device used as + communication interface for a remote device. + + Returns: + :class:`.OperatingMode`: The operating mode of the local XBee device. + """ + if self.is_remote(): + return self._local_xbee_device.operating_mode + return self._operating_mode + + @staticmethod + def _before_send_method(func): + """ + Decorator. Used to check the operating mode and the COM port's state before a sending operation. + """ + @wraps(func) + def dec_function(self, *args, **kwargs): + 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("Not supported operating mode: " + + str(args[0].operating_mode.description)) + return func(self, *args, **kwargs) + return dec_function + + @staticmethod + def _after_send_method(func): + """ + Decorator. Used to check if the response's transmit status is success after a sending operation. + """ + @wraps(func) + def dec_function(*args, **kwargs): + response = func(*args, **kwargs) + if response.transmit_status != TransmitStatus.SUCCESS: + raise XBeeException("Transmit status: %s" % response.transmit_status.description) + return response + return dec_function + + def _get_packet_by_id(self, frame_id): + """ + Reads packets until there is one packet found with the provided frame ID. + + Args: + frame_id (Integer): frame ID to use for. Must be between 0 and 255. + + Returns: + :class:XBeePacket: the first XBee packet read whose frame ID matches the provided one. + + Raises: + ValueError: if ``frame_id`` is less than 0 or greater than 255. + TimeoutException: if there was not any XBee packet matching the provided frame ID that could be read. + """ + if not (0 <= frame_id <= 255): + raise ValueError("Frame ID must be between 0 and 255.") + + queue = self._packet_listener.get_queue() + + packet = queue.get_by_id(frame_id, XBeeDevice.TIMEOUT_READ_PACKET) + + return packet + + @staticmethod + def __is_api_packet(xbee_packet): + """ + Determines whether the provided XBee packet is an API packet or not. + + Returns: + Boolean: ``True`` if the provided XBee packet is an API packet (its frame type is inside + :class:`.ApiFrameType` enum), ``False`` otherwise. + """ + aft = xbee_packet.get_frame_type() + try: + ApiFrameType.get(aft) + except ValueError: + return False + return True + + def _add_packet_received_callback(self, callback): + """ + Adds a callback for the event :class:`.PacketReceived`. + + Args: + callback (Function): the callback. Receives two arguments. + + * 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:`.PacketReceived` event. + + Args: + callback (Function): the callback to delete. + + Raises: + ValueError: if ``callback`` is not in the callback list of :class:`.PacketReceived` event. + """ + self._packet_listener.del_packet_received_callback(callback) + + 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: + + 1. Puts "_sync_packet" to ``None``, to discard the last sync. packet read. + 2. Refresh "_sync_packet" to be used by the thread in charge of the synchronous read. + 3. Tells the packet listener that this XBee device is waiting for a packet with a determined frame ID. + 4. Sends the ``packet_to_send``. + 5. Waits the configured timeout for synchronous operations. + 6. Returns all attributes to a consistent state (except _sync_packet) + | 6.1. _sync_packet to ``None``. + | 6.2. notify the listener that we are no longer waiting for any packet. + 7. Returns the received packet if it has arrived, ``None`` otherwise. + + This method must be only used when the packet listener is online. + + At the end of this method, the class attribute ``_sync_packet`` will be + the packet read by this method, or ``None`` if the previous was not possible. + Note that ``_sync_packet`` will remain being "the last packet read in a + synchronous operation" until you call this method again. + Then, ``_sync_packet`` will be refreshed. + + Args: + packet_to_send (:class:`.XBeePacket`): the packet to send. + + Returns: + :class:`.XBeePacket`: the response packet obtained after sending the provided one. + + Raises: + TimeoutException: if the response is not received in the configured timeout. + + .. seealso:: + | :class:`.XBeePacket` + """ + lock = threading.Condition() + response_list = list() + + # Create a packet received callback for the packet to be sent. + def packet_received_callback(received_packet): + # Check if it is the packet we are waiting for. + if received_packet.needs_id() and received_packet.frame_id == packet_to_send.frame_id: + if not isinstance(packet_to_send, XBeeAPIPacket) or not isinstance(received_packet, XBeeAPIPacket): + 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: + 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: + 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 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) + + try: + # Send the packet. + self._send_packet(packet_to_send) + # Wait for response or timeout. + lock.acquire() + 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("Response not received in the configured timeout.") + # Return the received packet. + return response_list[0] + finally: + # Always remove the packet listener from the list. + self._del_packet_received_callback(packet_received_callback) + + def _send_packet(self, packet, sync=False): + """ + Sends a packet to the XBee device and waits for the response. + The packet to send will be escaped or not depending on the current + operating mode. + + This method can be synchronous or asynchronous. + + If is synchronous, this method will discard all response + packets until it finds the one that has the appropriate frame ID, + that is, the sent packet's frame ID. + + If is asynchronous, this method does not wait for any packet. Returns ``None``. + + Args: + packet (:class:`.XBeePacket`): The packet to send. + sync (Boolean): ``True`` to wait for the response of the sent packet and return it, ``False`` otherwise. + + Returns: + :class:`.XBeePacket`: The response packet if ``sync`` is ``True``, ``None`` otherwise. + + Raises: + TimeoutException: if ``sync`` is ``True`` and the response packet for the sent one cannot be read. + 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 packet listener is not running. + XBeeException: if the XBee device's serial port is closed. + + .. seealso:: + | :class:`.XBeePacket` + """ + if not self._packet_listener.is_running(): + raise XBeeException("Packet listener is not running.") + + escape = self._operating_mode == OperatingMode.ESCAPED_API_MODE + 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_log(self): + """ + Returns the XBee device log. + + Returns: + :class:`.Logger`: the XBee device logger. + """ + return self._log + + log = property(__get_log) + """:class:`.Logger`. The XBee device logger.""" + + +class XBeeDevice(AbstractXBeeDevice): + """ + This class represents a non-remote generic XBee device. + + This class has fields that are events. Its recommended to use only the + append() and remove() method on them, or -= and += operators. + If you do something more with them, it's for your own risk. + """ + + __TIMEOUT_BEFORE_COMMAND_MODE = 1.2 # 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 transparent mode). + """ + + __TIMEOUT_ENTER_COMMAND_MODE = 1.5 # 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 transparent mode). + """ + + __TIMEOUT_RESET = 5 # seconds + """ + Timeout to wait when resetting the module. + """ + + TIMEOUT_READ_PACKET = 3 # seconds + """ + Timeout to read packets. + """ + + __COMMAND_MODE_CHAR = "+" + """ + Character you have to send to enter AT command mode + """ + + __COMMAND_MODE_OK = "OK\r" + """ + Response that will be receive if the attempt to enter in at command mode goes well. + """ + + 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): + """ + Class constructor. Instantiates a new :class:`.XBeeDevice` 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. + 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): comm port read timeout. + + Raises: + All exceptions raised by PySerial's Serial class constructor. + + .. seealso:: + | PySerial documentation: http://pyserial.sourceforge.net + """ + 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), + sync_ops_timeout=_sync_ops_timeout + ) + 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 + self.__explicit_queue = None + + self.__modem_status_received = False + + @classmethod + def create_xbee_device(cls, comm_port_data): + """ + Creates and returns an :class:`.XBeeDevice` from data of the port to which is connected. + + Args: + comm_port_data (Dictionary): dictionary with all comm port data needed. + The dictionary keys are: + | "baudRate" --> Baud rate. + | "port" --> Port number. + | "bitSize" --> Bit size. + | "stopBits" --> Stop bits. + | "parity" --> Parity. + | "flowControl" --> Flow control. + | "timeout" for --> Timeout for synchronous operations (in seconds). + + Returns: + :class:`.XBeeDevice`: the XBee device created. + + Raises: + SerialException: if the port you want to open does not exist or is already opened. + + .. seealso:: + | :class:`.XBeeDevice` + """ + return XBeeDevice(comm_port_data["port"], + comm_port_data["baudRate"], + comm_port_data["bitSize"], + comm_port_data["stopBits"], + comm_port_data["parity"], + comm_port_data["flowControl"], + comm_port_data["timeout"]) + + def 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. + """ + + if self._is_open: + raise XBeeException("XBee device already open.") + + 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 = 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() + self._packet_listener.start() + + # 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("Could not determine operating mode") + if self._operating_mode == OperatingMode.AT_MODE: + self.close() + raise InvalidOperatingModeException.from_operating_mode(self._operating_mode) + + # Read the device info (obtain its parameters and protocol). + self.read_device_info() + + self._is_open = True + + def close(self): + """ + Closes the communication with the XBee device. + + This method guarantees that all threads running are stopped and + the serial port is closed. + """ + if self._network is not None: + self._network.stop_discovery_process() + + if self._packet_listener is not None: + self._packet_listener.stop() + + 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. + + Returns: + :class:`.XBeeSerialPort`: the serial port associated to the XBee device. + + .. seealso:: + | :class:`.XBeeSerialPort` + """ + return self._serial_port + + @AbstractXBeeDevice._before_send_method + def get_parameter(self, param): + """ + Override. + + .. seealso:: + | :meth:`.AbstractXBeeDevice.get_parameter` + """ + return super(XBeeDevice, self).get_parameter(param) + + @AbstractXBeeDevice._before_send_method + def set_parameter(self, param, value): + """ + Override. + + See: + :meth:`.AbstractXBeeDevice.set_parameter` + """ + super(XBeeDevice, self).set_parameter(param, value) + + @AbstractXBeeDevice._before_send_method + @AbstractXBeeDevice._after_send_method + def _send_data_64_16(self, x64addr, x16addr, data, transmit_options=TransmitOptions.NONE.value): + """ + Blocking method. This method sends data to a remote XBee device corresponding to the given + 64-bit/16-bit address. + + This method will wait for the packet response. + + The default timeout for this method is :attr:`.XBeeDevice._DEFAULT_TIMEOUT_SYNC_OPERATIONS`. + + Args: + x64addr (:class:`.XBee64BitAddress`): The 64-bit address of the XBee that will receive the data. + x16addr (:class:`.XBee16BitAddress`): The 16-bit address of the XBee that will receive the data, + :attr:`.XBee16BitAddress.UNKNOWN_ADDRESS` if it is unknown. + data (String or Bytearray): the raw data to send. + transmit_options (Integer, optional): transmit options, bitfield of :class:`.TransmitOptions`. Default to + ``TransmitOptions.NONE.value``. + + Returns: + :class:`.XBeePacket` the response. + + Raises: + ValueError: if ``x64addr`` is ``None`` + ValueError: if ``x16addr`` is ``None`` + ValueError: if ``data`` is ``None``. + TimeoutException: if this method can't read a response packet in + :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. + XBeeException: if the XBee device's serial port is closed. + XBeeException: if the status of the response received is not OK. + + .. seealso:: + | :class:`.XBee64BitAddress` + | :class:`.XBee16BitAddress` + | :class:`.XBeePacket` + """ + if x64addr is None: + raise ValueError("64-bit address cannot be None") + if x16addr is None: + raise ValueError("16-bit address cannot be None") + if data is None: + raise ValueError("Data cannot be None") + + if self.is_remote(): + raise OperationNotSupportedException("Cannot send data to a remote device from a remote device") + + if isinstance(data, str): + data = data.encode("utf8") + + packet = TransmitPacket(self.get_next_frame_id(), + x64addr, + x16addr, + 0, + transmit_options, + data) + return self.send_packet_sync_and_get_response(packet) + + @AbstractXBeeDevice._before_send_method + @AbstractXBeeDevice._after_send_method + def _send_data_64(self, x64addr, data, transmit_options=TransmitOptions.NONE.value): + """ + Blocking method. This method sends data to a remote XBee device corresponding to the given + 64-bit address. + + This method will wait for the packet response. + + The default timeout for this method is :attr:`.XBeeDevice._DEFAULT_TIMEOUT_SYNC_OPERATIONS`. + + Args: + x64addr (:class:`.XBee64BitAddress`): The 64-bit address of the XBee that will receive the data. + data (String or Bytearray): the raw data to send. + transmit_options (Integer, optional): transmit options, bitfield of :class:`.TransmitOptions`. Default to + ``TransmitOptions.NONE.value``. + + Returns: + :class:`.XBeePacket` the response. + + Raises: + ValueError: if ``x64addr`` is ``None`` + ValueError: if ``data`` is ``None``. + TimeoutException: if this method can't read a response packet in + :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. + XBeeException: if the XBee device's serial port is closed. + XBeeException: if the status of the response received is not OK. + + .. seealso:: + | :class:`.XBee64BitAddress` + | :class:`.XBeePacket` + """ + if x64addr is None: + raise ValueError("64-bit address cannot be None") + if data is None: + raise ValueError("Data cannot be None") + + if self.is_remote(): + raise OperationNotSupportedException("Cannot send data to a remote device from a remote device") + + if isinstance(data, str): + data = data.encode("utf8") + + if self.get_protocol() == XBeeProtocol.RAW_802_15_4: + packet = TX64Packet(self.get_next_frame_id(), + x64addr, + transmit_options, + data) + else: + packet = TransmitPacket(self.get_next_frame_id(), + x64addr, + XBee16BitAddress.UNKNOWN_ADDRESS, + 0, + transmit_options, + data) + return self.send_packet_sync_and_get_response(packet) + + @AbstractXBeeDevice._before_send_method + @AbstractXBeeDevice._after_send_method + def _send_data_16(self, x16addr, data, transmit_options=TransmitOptions.NONE.value): + """ + Blocking method. This method sends data to a remote XBee device corresponding to the given + 16-bit address. + + This method will wait for the packet response. + + The default timeout for this method is :attr:`.XBeeDevice._DEFAULT_TIMEOUT_SYNC_OPERATIONS`. + + Args: + x16addr (:class:`.XBee16BitAddress`): The 16-bit address of the XBee that will receive the data. + data (String or Bytearray): the raw data to send. + transmit_options (Integer, optional): transmit options, bitfield of :class:`.TransmitOptions`. Default to + ``TransmitOptions.NONE.value``. + + Returns: + :class:`.XBeePacket` the response. + + Raises: + ValueError: if ``x16addr`` is ``None`` + ValueError: if ``data`` is ``None``. + TimeoutException: if this method can't read a response packet in + :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. + XBeeException: if the XBee device's serial port is closed. + XBeeException: if the status of the response received is not OK. + + .. seealso:: + | :class:`.XBee16BitAddress` + | :class:`.XBeePacket` + """ + if x16addr is None: + raise ValueError("16-bit address cannot be None") + if data is None: + raise ValueError("Data cannot be None") + + if self.is_remote(): + raise OperationNotSupportedException("Cannot send data to a remote device from a remote device") + + if isinstance(data, str): + data = data.encode("utf8") + + packet = TX16Packet(self.get_next_frame_id(), + x16addr, + transmit_options, + data) + return self.send_packet_sync_and_get_response(packet) + + def send_data(self, remote_xbee_device, data, transmit_options=TransmitOptions.NONE.value): + """ + Blocking method. This method sends data to a remote XBee device synchronously. + + This method will wait for the packet response. + + The default timeout for this method is :attr:`.XBeeDevice._DEFAULT_TIMEOUT_SYNC_OPERATIONS`. + + Args: + remote_xbee_device (:class:`.RemoteXBeeDevice`): the remote XBee device to send data to. + data (String or Bytearray): the raw data to send. + transmit_options (Integer, optional): transmit options, bitfield of :class:`.TransmitOptions`. Default to + ``TransmitOptions.NONE.value``. + + Returns: + :class:`.XBeePacket` the response. + + Raises: + ValueError: if ``remote_xbee_device`` is ``None``. + TimeoutException: if this method can't read a response packet in + :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. + XBeeException: if the XBee device's serial port is closed. + XBeeException: if the status of the response received is not OK. + + .. seealso:: + | :class:`.RemoteXBeeDevice` + | :class:`.XBeePacket` + """ + if remote_xbee_device is None: + raise ValueError("Remote XBee device cannot be None") + + protocol = self.get_protocol() + 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) + elif remote_xbee_device.get_64bit_addr() is not None: + 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) + 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) + else: + 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) + + @AbstractXBeeDevice._before_send_method + def _send_data_async_64_16(self, x64addr, x16addr, data, transmit_options=TransmitOptions.NONE.value): + """ + Non-blocking method. This method sends data to a remote XBee device corresponding to the given + 64-bit/16-bit address. + + This method won't wait for the response. + + Args: + x64addr (:class:`.XBee64BitAddress`): The 64-bit address of the XBee that will receive the data. + x16addr (:class:`.XBee16BitAddress`): The 16-bit address of the XBee that will receive the data, + :attr:`.XBee16BitAddress.UNKNOWN_ADDRESS` if it is unknown. + data (String or Bytearray): the raw data to send. + transmit_options (Integer, optional): transmit options, bitfield of :class:`.TransmitOptions`. Default to + ``TransmitOptions.NONE.value``. + + Returns: + :class:`.XBeePacket` the response. + + Raises: + ValueError: if ``x64addr`` is ``None`` + ValueError: if ``x16addr`` is ``None`` + ValueError: if ``data`` is ``None``. + 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. + + .. seealso:: + | :class:`.XBee64BitAddress` + | :class:`.XBee16BitAddress` + | :class:`.XBeePacket` + """ + if x64addr is None: + raise ValueError("64-bit address cannot be None") + if x16addr is None: + raise ValueError("16-bit address cannot be None") + if data is None: + raise ValueError("Data cannot be None") + + if self.is_remote(): + raise OperationNotSupportedException("Cannot send data to a remote device from a remote device") + + if isinstance(data, str): + data = data.encode("utf8") + + packet = TransmitPacket(self.get_next_frame_id(), + x64addr, + x16addr, + 0, + transmit_options, + data) + self.send_packet(packet) + + @AbstractXBeeDevice._before_send_method + def _send_data_async_64(self, x64addr, data, transmit_options=TransmitOptions.NONE.value): + """ + Non-blocking method. This method sends data to a remote XBee device corresponding to the given + 64-bit address. + + This method won't wait for the response. + + Args: + x64addr (:class:`.XBee64BitAddress`): The 64-bit address of the XBee that will receive the data. + data (String or Bytearray): the raw data to send. + transmit_options (Integer, optional): transmit options, bitfield of :class:`.TransmitOptions`. Default to + ``TransmitOptions.NONE.value``. + + Returns: + :class:`.XBeePacket` the response. + + Raises: + ValueError: if ``x64addr`` is ``None`` + ValueError: if ``data`` is ``None``. + 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. + + .. seealso:: + | :class:`.XBee64BitAddress` + | :class:`.XBeePacket` + """ + if x64addr is None: + raise ValueError("64-bit address cannot be None") + if data is None: + raise ValueError("Data cannot be None") + + if self.is_remote(): + raise OperationNotSupportedException("Cannot send data to a remote device from a remote device") + + if isinstance(data, str): + data = data.encode("utf8") + + if self.get_protocol() == XBeeProtocol.RAW_802_15_4: + packet = TX64Packet(self.get_next_frame_id(), + x64addr, + transmit_options, + data) + else: + packet = TransmitPacket(self.get_next_frame_id(), + x64addr, + XBee16BitAddress.UNKNOWN_ADDRESS, + 0, + transmit_options, + data) + self.send_packet(packet) + + @AbstractXBeeDevice._before_send_method + def _send_data_async_16(self, x16addr, data, transmit_options=TransmitOptions.NONE.value): + """ + Non-blocking method. This method sends data to a remote XBee device corresponding to the given + 16-bit address. + + This method won't wait for the response. + + Args: + x16addr (:class:`.XBee16BitAddress`): The 16-bit address of the XBee that will receive the data. + data (String or Bytearray): the raw data to send. + transmit_options (Integer, optional): transmit options, bitfield of :class:`.TransmitOptions`. Default to + ``TransmitOptions.NONE.value``. + + Returns: + :class:`.XBeePacket` the response. + + Raises: + ValueError: if ``x16addr`` is ``None`` + ValueError: if ``data`` is ``None``. + 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. + + .. seealso:: + | :class:`.XBee16BitAddress` + | :class:`.XBeePacket` + """ + if x16addr is None: + raise ValueError("16-bit address cannot be None") + if data is None: + raise ValueError("Data cannot be None") + + if self.is_remote(): + raise OperationNotSupportedException("Cannot send data to a remote device from a remote device") + + if isinstance(data, str): + data = data.encode("utf8") + + packet = TX16Packet(self.get_next_frame_id(), + x16addr, + transmit_options, + data) + self.send_packet(packet) + + def send_data_async(self, remote_xbee_device, data, transmit_options=TransmitOptions.NONE.value): + """ + Non-blocking method. This method sends data to a remote XBee device. + + This method won't wait for the response. + + Args: + remote_xbee_device (:class:`.RemoteXBeeDevice`): the remote XBee device to send data to. + data (String or Bytearray): the raw data to send. + transmit_options (Integer, optional): transmit options, bitfield of :class:`.TransmitOptions`. Default to + ``TransmitOptions.NONE.value``. + + 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. + + .. seealso:: + | :class:`.RemoteXBeeDevice` + """ + if remote_xbee_device is None: + raise ValueError("Remote XBee device cannot be None") + + protocol = self.get_protocol() + 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) + elif remote_xbee_device.get_64bit_addr() is not None: + 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) + 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) + else: + 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) + + def send_data_broadcast(self, data, transmit_options=TransmitOptions.NONE.value): + """ + Sends the provided data to all the XBee nodes of the network (broadcast). + + This method blocks till a success or error transmit status arrives or + the configured receive timeout expires. + + The received timeout is configured using the :meth:`.AbstractXBeeDevice.set_sync_ops_timeout` + method and can be consulted with :meth:`.AbstractXBeeDevice.get_sync_ops_timeout` method. + + Args: + data (String or Bytearray): data to send. + transmit_options (Integer, optional): transmit options, bitfield of :class:`.TransmitOptions`. Default to + ``TransmitOptions.NONE.value``. + + Raises: + TimeoutException: if this method can't read a response packet in + :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. + 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) + + @AbstractXBeeDevice._before_send_method + def send_user_data_relay(self, local_interface, data): + """ + Sends the given data to the given XBee local interface. + + Args: + local_interface (:class:`.XBeeLocalInterface`): Destination XBee local interface. + data (Bytearray): Data to send. + + 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. + ValueError: if ``local_interface`` is ``None``. + XBeeException: if there is any problem sending the User Data Relay. + + .. seealso:: + | :class:`.XBeeLocalInterface` + """ + if local_interface is None: + 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)) + + def send_bluetooth_data(self, data): + """ + Sends the given data to the Bluetooth interface using a User Data Relay frame. + + Args: + data (Bytearray): Data to send. + + 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 there is any problem sending the data. + + .. seealso:: + | :meth:`.XBeeDevice.send_micropython_data` + | :meth:`.XBeeDevice.send_user_data_relay` + """ + self.send_user_data_relay(XBeeLocalInterface.BLUETOOTH, data) + + def send_micropython_data(self, data): + """ + Sends the given data to the MicroPython interface using a User Data Relay frame. + + Args: + data (Bytearray): Data to send. + + 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 there is any problem sending the data. + + .. seealso:: + | :meth:`.XBeeDevice.send_bluetooth_data` + | :meth:`.XBeeDevice.send_user_data_relay` + """ + self.send_user_data_relay(XBeeLocalInterface.MICROPYTHON, data) + + def read_data(self, timeout=None): + """ + Reads new data received by this XBee device. + + If a ``timeout`` is specified, this method blocks until new data is received or the timeout expires, + throwing in that case a :class:`.TimeoutException`. + + Args: + timeout (Integer, optional): read timeout in seconds. If it's ``None``, this method is non-blocking + and will return ``None`` if there is no data available. + + Returns: + :class:`.XBeeMessage`: the read message or ``None`` if this XBee did not receive new data. + + Raises: + ValueError: if a timeout is specified and is less than 0. + TimeoutException: if a timeout is specified and no data was received during that time. + 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. + + .. seealso:: + | :class:`.XBeeMessage` + """ + return self.__read_data_packet(None, timeout, False) + + def read_data_from(self, remote_xbee_device, timeout=None): + """ + Reads new data received from the given remote XBee device. + + If a ``timeout`` is specified, this method blocks until new data is received or the timeout expires, + throwing in that case a :class:`.TimeoutException`. + + Args: + remote_xbee_device (:class:`.RemoteXBeeDevice`): the remote XBee device that sent the data. + timeout (Integer, optional): read timeout in seconds. If it's ``None``, this method is non-blocking + and will return ``None`` if there is no data available. + + Returns: + :class:`.XBeeMessage`: the read message sent by ``remote_xbee_device`` or ``None`` if this XBee did + not receive new data. + + Raises: + ValueError: if a timeout is specified and is less than 0. + TimeoutException: if a timeout is specified and no data was received during that time. + 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. + + .. seealso:: + | :class:`.XBeeMessage` + | :class:`.RemoteXBeeDevice` + """ + return self.__read_data_packet(remote_xbee_device, timeout, False) + + def has_packets(self): + """ + Returns whether the XBee device's queue has packets or not. + This do not include explicit packets. + + Return: + Boolean: ``True`` if this XBee device's queue has packets, ``False`` otherwise. + + .. seealso:: + | :meth:`.XBeeDevice.has_explicit_packets` + """ + return not self.__packet_queue.empty() + + def has_explicit_packets(self): + """ + Returns whether the XBee device's queue has explicit packets or not. + This do not include non-explicit packets. + + Return: + Boolean: ``True`` if this XBee device's queue has explicit packets, ``False`` otherwise. + + .. seealso:: + | :meth:`.XBeeDevice.has_packets` + """ + return not self.__explicit_queue.empty() + + def flush_queues(self): + """ + Flushes the packets queue. + """ + self.__packet_queue.flush() + self.__data_queue.flush() + self.__explicit_queue.flush() + + def reset(self): + """ + Override method. + + .. seealso:: + | :meth:`.AbstractXBeeDevice.reset` + """ + # Send reset command. + response = self._send_at_command(ATCommand("FR")) + + # Check if AT Command response is valid. + self._check_at_cmd_response_is_valid(response) + + lock = threading.Condition() + self.__modem_status_received = False + + def ms_callback(modem_status): + if modem_status == ModemStatus.HARDWARE_RESET or modem_status == ModemStatus.WATCHDOG_TIMER_RESET: + self.__modem_status_received = True + lock.acquire() + lock.notify() + lock.release() + + self.add_modem_status_received_callback(ms_callback) + lock.acquire() + lock.wait(self.__TIMEOUT_RESET) + lock.release() + self.del_modem_status_received_callback(ms_callback) + + if self.__modem_status_received is False: + raise XBeeException("Invalid modem status.") + + def add_packet_received_callback(self, callback): + """ + Override. + """ + super(XBeeDevice, self)._add_packet_received_callback(callback) + + def add_data_received_callback(self, callback): + """ + Override. + """ + super(XBeeDevice, self)._add_data_received_callback(callback) + + def add_modem_status_received_callback(self, callback): + """ + Override. + """ + super(XBeeDevice, self)._add_modem_status_received_callback(callback) + + def add_io_sample_received_callback(self, callback): + """ + Override. + """ + super(XBeeDevice, self)._add_io_sample_received_callback(callback) + + def add_expl_data_received_callback(self, callback): + """ + Override. + """ + super(XBeeDevice, self)._add_expl_data_received_callback(callback) + + def add_user_data_relay_received_callback(self, callback): + """ + Override. + """ + super(XBeeDevice, self)._add_user_data_relay_received_callback(callback) + + def add_bluetooth_data_received_callback(self, callback): + """ + Override. + """ + super(XBeeDevice, self)._add_bluetooth_data_received_callback(callback) + + def add_micropython_data_received_callback(self, callback): + """ + Override. + """ + super(XBeeDevice, self)._add_micropython_data_received_callback(callback) + + def del_packet_received_callback(self, callback): + """ + Override. + """ + super(XBeeDevice, self)._del_packet_received_callback(callback) + + def del_data_received_callback(self, callback): + """ + Override. + """ + super(XBeeDevice, self)._del_data_received_callback(callback) + + def del_modem_status_received_callback(self, callback): + """ + Override. + """ + super(XBeeDevice, self)._del_modem_status_received_callback(callback) + + def del_io_sample_received_callback(self, callback): + """ + Override. + """ + super(XBeeDevice, self)._del_io_sample_received_callback(callback) + + def del_expl_data_received_callback(self, callback): + """ + Override. + """ + super(XBeeDevice, self)._del_expl_data_received_callback(callback) + + def del_user_data_relay_received_callback(self, callback): + """ + Override. + """ + super(XBeeDevice, self)._del_user_data_relay_received_callback(callback) + + def del_bluetooth_data_received_callback(self, callback): + """ + Override. + """ + super(XBeeDevice, self)._del_bluetooth_data_received_callback(callback) + + def del_micropython_data_received_callback(self, callback): + """ + Override. + """ + 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() + + for i in self._network.get_discovery_callbacks(): + api_callbacks.append(i) + return api_callbacks + + def __get_operating_mode(self): + """ + Returns this XBee device's operating mode. + + Returns: + :class:`.OperatingMode`. This XBee device's operating mode. + """ + return super(XBeeDevice, self)._get_operating_mode() + + def is_open(self): + """ + Returns whether this XBee device is open or not. + + Returns: + Boolean. ``True`` if this XBee device is open, ``False`` otherwise. + """ + return self._is_open + + def is_remote(self): + """ + Override method. + + .. seealso:: + | :meth:`.AbstractXBeeDevice.is_remote` + """ + return False + + def get_network(self): + """ + Returns this XBee device's current network. + + Returns: + :class:`.XBeeDevice.XBeeNetwork` + """ + return self._network + + @AbstractXBeeDevice._before_send_method + @AbstractXBeeDevice._after_send_method + def _send_expl_data(self, remote_xbee_device, data, src_endpoint, dest_endpoint, + cluster_id, profile_id, transmit_options=TransmitOptions.NONE.value): + """ + Blocking method. Sends the provided data to the given XBee device in + application layer mode. Application layer mode means that you need to + specify the application layer fields to be sent with the data. + + This method blocks till a success or error response arrives or the + configured receive timeout expires. + + The default timeout for this method is :attr:`.XBeeDevice._DEFAULT_TIMEOUT_SYNC_OPERATIONS`. + + Args: + remote_xbee_device (:class:`.RemoteXBeeDevice`): the remote XBee device to send data to. + data (String or Bytearray): the raw data to send. + src_endpoint (Integer): source endpoint of the transmission. 1 byte. + dest_endpoint (Integer): destination endpoint of the transmission. 1 byte. + cluster_id (Integer): Cluster ID of the transmission. Must be between 0x0 and 0xFFFF. + profile_id (Integer): Profile ID of the transmission. Must be between 0x0 and 0xFFFF. + transmit_options (Integer, optional): transmit options, bitfield of :class:`.TransmitOptions`. Default to + ``TransmitOptions.NONE.value``. + + Returns: + :class:`.XBeePacket`: the response packet obtained after sending the provided one. + + Raises: + TimeoutException: if this method can't read a response packet in + :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. + 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. + + .. seealso:: + | :class:`.RemoteXBeeDevice` + | :class:`.XBeePacket` + """ + return self.send_packet_sync_and_get_response(self.__build_expldata_packet(remote_xbee_device, data, + src_endpoint, dest_endpoint, + cluster_id, profile_id, + False, transmit_options)) + + @AbstractXBeeDevice._before_send_method + def _send_expl_data_async(self, remote_xbee_device, data, src_endpoint, dest_endpoint, + cluster_id, profile_id, transmit_options=TransmitOptions.NONE.value): + """ + Non-blocking method. Sends the provided data to the given XBee device in + application layer mode. Application layer mode means that you need to + specify the application layer fields to be sent with the data. + + Args: + remote_xbee_device (:class:`.RemoteXBeeDevice`): the remote XBee device to send data to. + data (String or Bytearray): the raw data to send. + src_endpoint (Integer): source endpoint of the transmission. 1 byte. + dest_endpoint (Integer): destination endpoint of the transmission. 1 byte. + cluster_id (Integer): Cluster ID of the transmission. Must be between 0x0 and 0xFFFF. + profile_id (Integer): Profile ID of the transmission. Must be between 0x0 and 0xFFFF. + transmit_options (Integer, optional): transmit options, bitfield of :class:`.TransmitOptions`. Default to + ``TransmitOptions.NONE.value``. + + 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 ``cluster_id`` is less than 0x0 or greater than 0xFFFF. + ValueError: if ``profile_id`` is less than 0x0 or greater than 0xFFFF. + + .. seealso:: + | :class:`.RemoteXBeeDevice` + """ + self.send_packet(self.__build_expldata_packet(remote_xbee_device, data, src_endpoint, + dest_endpoint, cluster_id, + 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): + """ + Sends the provided data to all the XBee nodes of the network (broadcast) + in application layer mode. Application layer mode means that you need to + specify the application layer fields to be sent with the data. + + This method blocks till a success or error transmit status arrives or + the configured receive timeout expires. + + The received timeout is configured using the :meth:`.AbstractXBeeDevice.set_sync_ops_timeout` + method and can be consulted with :meth:`.AbstractXBeeDevice.get_sync_ops_timeout` method. + + Args: + data (String or Bytearray): the raw data to send. + src_endpoint (Integer): source endpoint of the transmission. 1 byte. + dest_endpoint (Integer): destination endpoint of the transmission. 1 byte. + cluster_id (Integer): Cluster ID of the transmission. Must be between 0x0 and 0xFFFF. + profile_id (Integer): Profile ID of the transmission. Must be between 0x0 and 0xFFFF. + transmit_options (Integer, optional): transmit options, bitfield of :class:`.TransmitOptions`. Default to + ``TransmitOptions.NONE.value``. + + .. seealso:: + | :meth:`.XBeeDevice._send_expl_data` + """ + return self.send_packet_sync_and_get_response(self.__build_expldata_packet(None, data, src_endpoint, + dest_endpoint, cluster_id, + profile_id, True, transmit_options)) + + def _read_expl_data(self, timeout=None): + """ + Reads new explicit data received by this XBee device. + + If a ``timeout`` is specified, this method blocks until new data is received or the timeout expires, + throwing in that case a :class:`.TimeoutException`. + + Args: + timeout (Integer, optional): read timeout in seconds. If it's ``None``, this method is non-blocking + and will return ``None`` if there is no explicit data available. + + Returns: + :class:`.ExplicitXBeeMessage`: the read message or ``None`` if this XBee did not receive new data. + + Raises: + ValueError: if a timeout is specified and is less than 0. + TimeoutException: if a timeout is specified and no explicit data was received during that time. + InvalidOperatingModeException: if the XBee 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. + + .. seealso:: + | :class:`.ExplicitXBeeMessage` + """ + return self.__read_data_packet(None, timeout, True) + + def _read_expl_data_from(self, remote_xbee_device, timeout=None): + """ + Reads new explicit data received from the given remote XBee device. + + If a ``timeout`` is specified, this method blocks until new data is received or the timeout expires, + throwing in that case a :class:`.TimeoutException`. + + Args: + remote_xbee_device (:class:`.RemoteXBeeDevice`): the remote XBee device that sent the explicit data. + timeout (Integer, optional): read timeout in seconds. If it's ``None``, this method is non-blocking + and will return ``None`` if there is no data available. + + Returns: + :class:`.ExplicitXBeeMessage`: the read message sent by ``remote_xbee_device`` or ``None`` if this + XBee did not receive new data. + + Raises: + ValueError: if a timeout is specified and is less than 0. + TimeoutException: if a timeout is specified and no explicit data was received during that time. + InvalidOperatingModeException: if the XBee 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. + + .. seealso:: + | :class:`.ExplicitXBeeMessage` + | :class:`.RemoteXBeeDevice` + """ + return self.__read_data_packet(remote_xbee_device, timeout, True) + + @AbstractXBeeDevice._before_send_method + def __read_data_packet(self, remote, timeout, explicit): + """ + Reads a new data packet received by this XBee device during the provided timeout. + + If a ``timeout`` is specified, this method blocks until new data is received or the timeout expires, + throwing in that case a :class:`.TimeoutException`. + + Args: + remote (:class:`.RemoteXBeeDevice`): The remote device to get a data packet from. ``None`` to read a + data packet sent by any device. + timeout (Integer): The time to wait for a data packet in seconds. + explicit (Boolean): ``True`` to read an explicit data packet, ``False`` to read an standard data packet. + + Returns: + :class:`.XBeeMessage` or :class:`.ExplicitXBeeMessage`: the XBee message received by this device. + + Raises: + ValueError: if a timeout is specified and is less than 0. + TimeoutException: if a timeout is specified and no data was received during that time. + 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. + + .. seealso:: + | :class:`.XBeeMessage` + | :class:`.ExplicitXBeeMessage` + | :class:`.RemoteXBeeDevice` + """ + if timeout is not None and timeout < 0: + raise ValueError("Read timeout must be 0 or greater") + + if not explicit: + if remote is None: + packet = self.__data_queue.get(timeout=timeout) + else: + 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) + + 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, False) + elif frame_type == ApiFrameType.EXPLICIT_RX_INDICATOR: + return self.__build_xbee_message(packet, True) + else: + return None + + def _enter_at_command_mode(self): + """ + Attempts to put this device in AT Command mode. Only valid if device is + working in AT mode. + + Returns: + Boolean: ``True`` if the XBee device has entered in AT command mode, ``False`` otherwise. + + Raises: + SerialTimeoutException: if there is any error trying to write within the serial port. + """ + 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.__TIMEOUT_BEFORE_COMMAND_MODE) + # Send the command mode sequence. + b = bytearray(self.__COMMAND_MODE_CHAR, "utf8") + self._serial_port.write(b) + self._serial_port.write(b) + self._serial_port.write(b) + # Wait some time to let the module generate a response. + time.sleep(self.__TIMEOUT_ENTER_COMMAND_MODE) + # Read data from the device (it should answer with 'OK\r'). + data = self._serial_port.read_existing().decode() + + return data and data in self.__COMMAND_MODE_OK + + def _determine_operating_mode(self): + """ + Determines and returns the operating mode of the XBee device. + + If the XBee device is not in AT command mode, this method attempts + to enter on it. + + Returns: + :class:`.OperatingMode` + + .. seealso:: + | :class:`.OperatingMode` + """ + try: + self._operating_mode = OperatingMode.API_MODE + response = self.get_parameter("AP") + return OperatingMode.get(response[0]) + except TimeoutException: + self._operating_mode = OperatingMode.AT_MODE + try: + # If there is timeout exception and is possible to enter + # in AT command mode, the current operating mode is AT. + if self._enter_at_command_mode(): + return OperatingMode.AT_MODE + except SerialTimeoutException as ste: + self._log.exception(ste) + return OperatingMode.UNKNOWN + + def send_packet_sync_and_get_response(self, packet_to_send): + """ + Override method. + + .. seealso:: + | :meth:`.AbstractXBeeDevice._send_packet_sync_and_get_response` + """ + return super(XBeeDevice, self)._send_packet_sync_and_get_response(packet_to_send) + + def send_packet(self, packet, sync=False): + """ + Override method. + + .. seealso:: + | :meth:`.AbstractXBeeDevice._send_packet` + """ + return super(XBeeDevice, self)._send_packet(packet, sync) + + def __build_xbee_message(self, packet, explicit=False): + """ + Builds and returns the XBee message corresponding to the provided ``packet``. The result is an + :class:`.XBeeMessage` or :class:`.ExplicitXBeeMessage` depending on the packet. + + Args: + packet (:class:`.XBeePacket`): the packet to get its corresponding XBee message. + explicit (Boolean): ``True`` if the packet is an explicit packet, ``False`` otherwise. + + Returns: + :class:`.XBeeMessage` or :class:`.ExplicitXBeeMessage`: the resulting XBee message. + + .. seealso:: + | :class:`.ExplicitXBeeMessage` + | :class:`.XBeeMessage` + | :class:`.XBeePacket` + """ + x64addr = None + x16addr = None + remote = None + + if hasattr(packet, "x16bit_source_addr"): + x16addr = packet.x16bit_source_addr + if hasattr(packet, "x64bit_source_addr"): + x64addr = packet.x64bit_source_addr + if x64addr is not None or x16addr is not None: + 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, packet.is_broadcast()) + else: + msg = XBeeMessage(packet.rf_data, remote, time.time(), packet.is_broadcast()) + + return msg + + def __build_expldata_packet(self, remote_xbee_device, data, src_endpoint, dest_endpoint, + cluster_id, profile_id, broadcast=False, transmit_options=TransmitOptions.NONE.value): + """ + Builds and returns an explicit data packet with the provided parameters. + + Args: + remote_xbee_device (:class:`.RemoteXBeeDevice`): the remote XBee device to send data to. + data (String or Bytearray): the raw data to send. + src_endpoint (Integer): source endpoint of the transmission. 1 byte. + dest_endpoint (Integer): destination endpoint of the transmission. 1 byte. + cluster_id (Integer): Cluster ID of the transmission. Must be between 0x0 and 0xFFFF. + profile_id (Integer): Profile ID of the transmission. Must be between 0x0 and 0xFFFF. + broadcast (Boolean, optional): ``True`` to send data in broadcast mode (``remote_xbee_device`` is ignored), + ``False`` to send data to the specified ``remote_xbee_device``. + transmit_options (Integer, optional): transmit options, bitfield of :class:`.TransmitOptions`. Default to + ``TransmitOptions.NONE.value``. + + Returns: + :class:`.ExplicitAddressingPacket`: the explicit packet generated with the provided parameters. + + Raises: + All exceptions raised by :meth:`.ExplicitAddressingPacket.__init__` + + .. seealso:: + | :class:`.ExplicitAddressingPacket` + | :meth:`.ExplicitAddressingPacket.__init__` + | :class:`.RemoteXBeeDevice` + """ + if broadcast: + x64addr = XBee64BitAddress.BROADCAST_ADDRESS + x16addr = XBee16BitAddress.UNKNOWN_ADDRESS + else: + x64addr = remote_xbee_device.get_64bit_addr() + x16addr = remote_xbee_device.get_16bit_addr() + + # If the device does not have 16-bit address, set it to Unknown. + if x16addr is None: + x16addr = XBee16BitAddress.UNKNOWN_ADDRESS + + if isinstance(data, str): + data = data.encode("utf8") + + return ExplicitAddressingPacket(self._get_next_frame_id(), x64addr, + x16addr, src_endpoint, dest_endpoint, + cluster_id, profile_id, 0, transmit_options, data) + + def get_next_frame_id(self): + """ + Returns the next frame ID of the XBee device. + + Returns: + Integer: The next frame ID of the XBee device. + """ + return self._get_next_frame_id() + + serial_port = property(__get_serial_port) + """:class:`.XBeeSerialPort`. The serial port associated to the XBee device.""" + + operating_mode = property(__get_operating_mode) + """:class:`.OperatingMode`. The operating mode of the XBee device.""" + + +class Raw802Device(XBeeDevice): + """ + This class represents a local 802.15.4 XBee device. + """ + + def __init__(self, port, baud_rate): + """ + 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. + baud_rate (Integer): the serial port baud rate. + + Raises: + All exceptions raised by :meth:`.XBeeDevice.__init__` constructor. + + .. seealso:: + | :class:`.XBeeDevice` + | :meth:`.XBeeDevice.__init__` + """ + super(Raw802Device, self).__init__(port, baud_rate) + + def open(self): + """ + Override. + + Raises: + XBeeException: if the protocol is invalid. + All exceptions raised by :meth:`.XBeeDevice.open`. + + .. seealso:: + | :meth:`.XBeeDevice.open` + """ + super(Raw802Device, self).open() + if not self.is_remote() and self.get_protocol() != XBeeProtocol.RAW_802_15_4: + raise XBeeException("Invalid protocol.") + + def get_network(self): + """ + Override. + + .. seealso:: + | :meth:`.XBeeDevice.get_network` + """ + if self._network is None: + self._network = Raw802Network(self) + return self._network + + def get_protocol(self): + """ + Override. + + .. seealso:: + | :meth:`.XBeeDevice.get_protocol` + """ + return XBeeProtocol.RAW_802_15_4 + + def get_ai_status(self): + """ + Override. + + .. seealso:: + | :meth:`.AbstractXBeeDevice._get_ai_status` + """ + return super(Raw802Device, self)._get_ai_status() + + def send_data_64(self, x64addr, data, transmit_options=TransmitOptions.NONE.value): + """ + Override. + + .. seealso:: + | :meth:`.XBeeDevice.send_data_64` + """ + return super(Raw802Device, self)._send_data_64(x64addr, data, transmit_options) + + def send_data_async_64(self, x64addr, data, transmit_options=TransmitOptions.NONE.value): + """ + Override. + + .. seealso:: + | :meth:`.XBeeDevice.send_data_async_64` + """ + super(Raw802Device, self)._send_data_async_64(x64addr, data, transmit_options) + + def send_data_16(self, x16addr, data, transmit_options=TransmitOptions.NONE.value): + """ + Override. + + .. seealso:: + | :meth:`.XBeeDevice._send_data_16` + """ + return super(Raw802Device, self)._send_data_16(x16addr, data, transmit_options) + + def send_data_async_16(self, x16addr, data, transmit_options=TransmitOptions.NONE.value): + """ + Override. + + .. seealso:: + | :meth:`.XBeeDevice._send_data_async_16` + """ + super(Raw802Device, self)._send_data_async_16(x16addr, data, transmit_options) + + +class DigiMeshDevice(XBeeDevice): + """ + This class represents a local DigiMesh XBee device. + """ + + def __init__(self, port, baud_rate): + """ + 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. + baud_rate (Integer): the serial port baud rate. + + Raises: + All exceptions raised by :meth:`.XBeeDevice.__init__` constructor. + + .. seealso:: + | :class:`.XBeeDevice` + | :meth:`.XBeeDevice.__init__` + """ + super(DigiMeshDevice, self).__init__(port, baud_rate) + + def open(self): + """ + Override. + + Raises: + XBeeException: if the protocol is invalid. + All exceptions raised by :meth:`.XBeeDevice.open`. + + .. seealso:: + | :meth:`.XBeeDevice.open` + """ + super(DigiMeshDevice, self).open() + if self.get_protocol() != XBeeProtocol.DIGI_MESH: + raise XBeeException("Invalid protocol.") + + def get_network(self): + """ + Override. + + .. seealso:: + | :meth:`.XBeeDevice.get_network` + """ + if self._network is None: + self._network = DigiMeshNetwork(self) + return self._network + + def get_protocol(self): + """ + Override. + + .. seealso:: + | :meth:`.XBeeDevice.get_protocol` + """ + return XBeeProtocol.DIGI_MESH + + def send_data_64(self, x64addr, data, transmit_options=TransmitOptions.NONE.value): + """ + Override. + + .. seealso:: + | :meth:`.XBeeDevice.send_data_64` + """ + return super(DigiMeshDevice, self)._send_data_64(x64addr, data, transmit_options) + + def send_data_async_64(self, x64addr, data, transmit_options=TransmitOptions.NONE.value): + """ + Override. + + .. seealso:: + | :meth:`.XBeeDevice.send_data_async_64` + """ + super(DigiMeshDevice, self)._send_data_async_64(x64addr, data, transmit_options) + + def read_expl_data(self, timeout=None): + """ + Override. + + .. seealso:: + | :meth:`.XBeeDevice.read_expl_data` + """ + return super(DigiMeshDevice, self)._read_expl_data(timeout=timeout) + + def read_expl_data_from(self, remote_xbee_device, timeout=None): + """ + Override. + + .. seealso:: + | :meth:`.XBeeDevice.read_expl_data_from` + """ + 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): + """ + Override. + + .. seealso:: + | :meth:`.XBeeDevice.send_expl_data` + """ + 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): + """ + Override. + + .. seealso:: + | :meth:`.XBeeDevice._send_expl_data_broadcast` + """ + 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): + """ + Override. + + .. seealso:: + | :meth:`.XBeeDevice.send_expl_data_async` + """ + super(DigiMeshDevice, self)._send_expl_data_async(remote_xbee_device, data, src_endpoint, + dest_endpoint, cluster_id, profile_id, transmit_options) + + +class DigiPointDevice(XBeeDevice): + """ + This class represents a local DigiPoint XBee device. + """ + + def __init__(self, port, baud_rate): + """ + 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. + baud_rate (Integer): the serial port baud rate. + + Raises: + All exceptions raised by :meth:`XBeeDevice.__init__` constructor. + + .. seealso:: + | :class:`.XBeeDevice` + | :meth:`.XBeeDevice.__init__` + """ + super(DigiPointDevice, self).__init__(port, baud_rate) + + def open(self): + """ + Override. + + Raises: + XBeeException: if the protocol is invalid. + All exceptions raised by :meth:`.XBeeDevice.open`. + + .. seealso:: + | :meth:`.XBeeDevice.open` + """ + super(DigiPointDevice, self).open() + if self.get_protocol() != XBeeProtocol.DIGI_POINT: + raise XBeeException("Invalid protocol.") + + def get_network(self): + """ + Override. + + .. seealso:: + | :meth:`.XBeeDevice.get_network` + """ + if self._network is None: + self._network = DigiPointNetwork(self) + return self._network + + def get_protocol(self): + """ + Override. + + .. seealso:: + | :meth:`.XBeeDevice.get_protocol` + """ + return XBeeProtocol.DIGI_POINT + + def send_data_64_16(self, x64addr, x16addr, data, transmit_options=TransmitOptions.NONE.value): + """ + Override. + + .. seealso:: + | :meth:`.XBeeDevice.send_data_64_16` + """ + 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): + """ + Override. + + .. seealso:: + | :meth:`.XBeeDevice.send_data_async_64_16` + """ + super(DigiPointDevice, self)._send_data_async_64_16(x64addr, x16addr, data, transmit_options) + + def read_expl_data(self, timeout=None): + """ + Override. + + .. seealso:: + | :meth:`.XBeeDevice.read_expl_data` + """ + return super(DigiPointDevice, self)._read_expl_data(timeout=timeout) + + def read_expl_data_from(self, remote_xbee_device, timeout=None): + """ + Override. + + .. seealso:: + | :meth:`.XBeeDevice.read_expl_data_from` + """ + 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): + """ + Override. + + .. seealso:: + | :meth:`.XBeeDevice.send_expl_data` + """ + 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): + """ + Override. + + .. seealso:: + | :meth:`.XBeeDevice._send_expl_data_broadcast` + """ + 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): + """ + Override. + + .. seealso:: + | :meth:`.XBeeDevice.send_expl_data_async` + """ + super(DigiPointDevice, self)._send_expl_data_async(remote_xbee_device, data, src_endpoint, + dest_endpoint, cluster_id, profile_id, transmit_options) + + +class ZigBeeDevice(XBeeDevice): + """ + This class represents a local ZigBee XBee device. + """ + + def __init__(self, port, baud_rate): + """ + 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. + baud_rate (Integer): the serial port baud rate. + + Raises: + All exceptions raised by :func:`.XBeeDevice.__init__` constructor. + + .. seealso:: + | :class:`.XBeeDevice` + | :meth:`XBeeDevice.__init__` + """ + super(ZigBeeDevice, self).__init__(port, baud_rate) + + def open(self): + """ + Override. + + Raises: + XBeeException: if the protocol is invalid. + All exceptions raised by :meth:`.XBeeDevice.open`. + + .. seealso:: + | :meth:`.XBeeDevice.open` + """ + super(ZigBeeDevice, self).open() + if self.get_protocol() != XBeeProtocol.ZIGBEE: + raise XBeeException("Invalid protocol.") + + def get_network(self): + """ + Override. + + .. seealso:: + | :meth:`.XBeeDevice.get_network` + """ + if self._network is None: + self._network = ZigBeeNetwork(self) + return self._network + + def get_protocol(self): + """ + Override. + + .. seealso:: + | :meth:`.XBeeDevice.get_protocol` + """ + return XBeeProtocol.ZIGBEE + + def get_ai_status(self): + """ + Override. + + .. seealso:: + | :meth:`.AbstractXBeeDevice._get_ai_status` + """ + return super(ZigBeeDevice, self)._get_ai_status() + + def force_disassociate(self): + """ + Override. + + .. seealso:: + | :meth:`.AbstractXBeeDevice._force_disassociate` + """ + super(ZigBeeDevice, self)._force_disassociate() + + def send_data_64_16(self, x64addr, x16addr, data, transmit_options=TransmitOptions.NONE.value): + """ + Override. + + .. seealso:: + | :meth:`.XBeeDevice.send_data_64_16` + """ + 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): + """ + Override. + + .. seealso:: + | :meth:`.XBeeDevice.send_data_async_64_16` + """ + super(ZigBeeDevice, self)._send_data_async_64_16(x64addr, x16addr, data, transmit_options) + + def read_expl_data(self, timeout=None): + """ + Override. + + .. seealso:: + | :meth:`.XBeeDevice._read_expl_data` + """ + return super(ZigBeeDevice, self)._read_expl_data(timeout=timeout) + + def read_expl_data_from(self, remote_xbee_device, timeout=None): + """ + Override. + + .. seealso:: + | :meth:`.XBeeDevice._read_expl_data_from` + """ + 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): + """ + Override. + + .. seealso:: + | :meth:`.XBeeDevice._send_expl_data` + """ + 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): + """ + Override. + + .. seealso:: + | :meth:`.XBeeDevice._send_expl_data_broadcast` + """ + 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): + """ + Override. + + .. seealso:: + | :meth:`.XBeeDevice.send_expl_data_async` + """ + 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 + def send_multicast_data(self, group_id, data, src_endpoint, dest_endpoint, + cluster_id, profile_id): + """ + Blocking method. This method sends multicast data to the provided group ID + synchronously. + + This method will wait for the packet response. + + The default timeout for this method is :attr:`.XBeeDevice._DEFAULT_TIMEOUT_SYNC_OPERATIONS`. + + Args: + group_id (:class:`.XBee16BitAddress`): the 16 bit address of the multicast group. + data (Bytearray): the raw data to send. + src_endpoint (Integer): source endpoint of the transmission. 1 byte. + dest_endpoint (Integer): destination endpoint of the transmission. 1 byte. + cluster_id (Integer): Cluster ID of the transmission. Must be between 0x0 and 0xFFFF. + profile_id (Integer): Profile ID of the transmission. Must be between 0x0 and 0xFFFF. + + Returns: + :class:`.XBeePacket`: the response packet. + + Raises: + TimeoutException: if this method can't read a response packet in + :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. + XBeeException: if the XBee device's serial port is closed. + XBeeException: if the status of the response received is not OK. + + .. seealso:: + | :class:`XBee16BitAddress` + | :class:`XBeePacket` + """ + 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, data) + + return self.send_packet_sync_and_get_response(packet_to_send) + + @AbstractXBeeDevice._before_send_method + def send_multicast_data_async(self, group_id, data, src_endpoint, dest_endpoint, cluster_id, profile_id): + """ + Non-blocking method. This method sends multicast data to the provided group ID. + + This method won't wait for the response. + + Args: + group_id (:class:`.XBee16BitAddress`): the 16 bit address of the multicast group. + data (Bytearray): the raw data to send. + src_endpoint (Integer): source endpoint of the transmission. 1 byte. + dest_endpoint (Integer): destination endpoint of the transmission. 1 byte. + cluster_id (Integer): Cluster ID of the transmission. Must be between 0x0 and 0xFFFF. + profile_id (Integer): Profile ID of the transmission. Must be between 0x0 and 0xFFFF. + + Raises: + TimeoutException: if this method can't read a response packet in + :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. + XBeeException: if the XBee device's serial port is closed. + + .. seealso:: + | :class:`XBee16BitAddress` + """ + 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, data) + + self.send_packet(packet_to_send) + + +class IPDevice(XBeeDevice): + """ + This class provides common functionality for XBee IP devices. + """ + + BROADCAST_IP = "255.255.255.255" + + __DEFAULT_SOURCE_PORT = 9750 + + __DEFAULT_PROTOCOL = IPProtocol.TCP + + __OPERATION_EXCEPTION = "Operation not supported in this module." + + def __init__(self, port, baud_rate): + """ + 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. + baud_rate (Integer): the serial port baud rate. + + Raises: + All exceptions raised by :func:`.XBeeDevice.__init__` constructor. + + .. seealso:: + | :class:`.XBeeDevice` + | :meth:`XBeeDevice.__init__` + """ + super(IPDevice, self).__init__(port, baud_rate) + + self._ip_addr = None + self._source_port = self.__DEFAULT_SOURCE_PORT + + def read_device_info(self): + """ + Override. + + .. seealso:: + | :meth:`.AbstractXBeeDevice.read_device_info` + """ + super(IPDevice, self).read_device_info() + + # Read the module's IP address. + resp = self.get_parameter("MY") + self._ip_addr = IPv4Address(utils.bytes_to_int(resp)) + + # Read the source port. + 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): + """ + Returns the IP address of this IP device. + + To refresh this value use the method :meth:`.IPDevice.read_device_info`. + + Returns: + :class:`ipaddress.IPv4Address`: The IP address of this IP device. + + .. seealso:: + | :class:`ipaddress.IPv4Address` + """ + return self._ip_addr + + def set_dest_ip_addr(self, address): + """ + Sets the destination IP address. + + Args: + address (:class:`ipaddress.IPv4Address`): Destination IP address. + + Raises: + ValueError: if ``address`` is ``None``. + TimeoutException: if there is a timeout setting the destination IP address. + XBeeException: if there is any other XBee related exception. + + .. seealso:: + | :class:`ipaddress.IPv4Address` + """ + if address is None: + raise ValueError("Destination IP address cannot be None") + + self.set_parameter("DL", bytearray(address.exploded, "utf8")) + + def get_dest_ip_addr(self): + """ + Returns the destination IP address. + + Returns: + :class:`ipaddress.IPv4Address`: The configured destination IP address. + + Raises: + TimeoutException: if there is a timeout getting the destination IP address. + XBeeException: if there is any other XBee related exception. + + .. seealso:: + | :class:`ipaddress.IPv4Address` + """ + 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:`.IPDataReceived`. + + Args: + callback (Function): the callback. Receives one argument. + + * 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:`.IPDataReceived` + event. + + Args: + callback (Function): the callback to delete. + + Raises: + ValueError: if ``callback`` is not in the callback list of :class:`.IPDataReceived` event. + """ + self._packet_listener.del_ip_data_received_callback(callback) + + def start_listening(self, source_port): + """ + Starts listening for incoming IP transmissions in the provided port. + + Args: + source_port (Integer): Port to listen for incoming transmissions. + + Raises: + ValueError: if ``source_port`` is less than 0 or greater than 65535. + TimeoutException: if there is a timeout setting the source port. + XBeeException: if there is any other XBee related exception. + """ + if not 0 <= source_port <= 65535: + raise ValueError("Source port must be between 0 and 65535") + + self.set_parameter("C0", utils.int_to_bytes(source_port)) + self._source_port = source_port + + def stop_listening(self): + """ + Stops listening for incoming IP transmissions. + + Raises: + TimeoutException: if there is a timeout processing the operation. + XBeeException: if there is any other XBee related exception. + """ + self.set_parameter("C0", utils.int_to_bytes(0)) + self._source_port = 0 + + @AbstractXBeeDevice._before_send_method + @AbstractXBeeDevice._after_send_method + def send_ip_data(self, ip_addr, dest_port, protocol, data, close_socket=False): + """ + Sends the provided IP data to the given IP address and port using + the specified IP protocol. For TCP and TCP SSL protocols, you can + also indicate if the socket should be closed when data is sent. + + This method blocks till a success or error response arrives or the + configured receive timeout expires. + + Args: + ip_addr (:class:`ipaddress.IPv4Address`): The IP address to send IP data to. + dest_port (Integer): The destination port of the transmission. + protocol (:class:`.IPProtocol`): The IP protocol used for the transmission. + data (String or Bytearray): The IP data to be sent. + close_socket (Boolean, optional): ``True`` to close the socket just after the + transmission. ``False`` to keep it open. Default to ``False``. + + Raises: + ValueError: if ``ip_addr`` is ``None``. + ValueError: if ``protocol`` is ``None``. + ValueError: if ``data`` is ``None``. + ValueError: if ``dest_port`` is less than 0 or greater than 65535. + OperationNotSupportedException: if the device is remote. + TimeoutException: if there is a timeout sending the data. + XBeeException: if there is any other XBee related exception. + """ + if ip_addr is None: + raise ValueError("IP address cannot be None") + if protocol is None: + raise ValueError("Protocol cannot be None") + if data is None: + raise ValueError("Data cannot be None") + + if not 0 <= dest_port <= 65535: + raise ValueError("Destination port must be between 0 and 65535") + + # Check if device is remote. + if self.is_remote(): + 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. + source_port = self._source_port + if protocol is not IPProtocol.UDP: + source_port = 0 + + if isinstance(data, str): + data = data.encode("utf8") + + 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) + + return self.send_packet_sync_and_get_response(packet) + + @AbstractXBeeDevice._before_send_method + def send_ip_data_async(self, ip_addr, dest_port, protocol, data, close_socket=False): + """ + Sends the provided IP data to the given IP address and port + asynchronously using the specified IP protocol. For TCP and TCP SSL + protocols, you can also indicate if the socket should be closed when + data is sent. + + Asynchronous transmissions do not wait for answer from the remote + device or for transmit status packet. + + Args: + ip_addr (:class:`ipaddress.IPv4Address`): The IP address to send IP data to. + dest_port (Integer): The destination port of the transmission. + protocol (:class:`.IPProtocol`): The IP protocol used for the transmission. + data (String or Bytearray): The IP data to be sent. + close_socket (Boolean, optional): ``True`` to close the socket just after the + transmission. ``False`` to keep it open. Default to ``False``. + + Raises: + ValueError: if ``ip_addr`` is ``None``. + ValueError: if ``protocol`` is ``None``. + ValueError: if ``data`` is ``None``. + ValueError: if ``dest_port`` is less than 0 or greater than 65535. + OperationNotSupportedException: if the device is remote. + XBeeException: if there is any other XBee related exception. + """ + if ip_addr is None: + raise ValueError("IP address cannot be None") + if protocol is None: + raise ValueError("Protocol cannot be None") + if data is None: + raise ValueError("Data cannot be None") + + if not 0 <= dest_port <= 65535: + raise ValueError("Destination port must be between 0 and 65535") + + # Check if device is remote. + if self.is_remote(): + 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. + source_port = self._source_port + if protocol is IPProtocol.UDP: + source_port = 0 + + if isinstance(data, str): + data = data.encode("utf8") + + 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) + + self.send_packet(packet) + + def send_ip_data_broadcast(self, dest_port, data): + """ + Sends the provided IP data to all clients. + + This method blocks till a success or error transmit status arrives or + the configured receive timeout expires. + + Args: + dest_port (Integer): The destination port of the transmission. + data (String or Bytearray): The IP data to be sent. + + Raises: + ValueError: if ``data`` is ``None``. + ValueError: if ``dest_port`` is less than 0 or greater than 65535. + TimeoutException: if there is a timeout sending the data. + XBeeException: if there is any other XBee related exception. + """ + return self.send_ip_data(IPv4Address(self.BROADCAST_IP), dest_port, IPProtocol.UDP, data) + + @AbstractXBeeDevice._before_send_method + def read_ip_data(self, timeout=XBeeDevice.TIMEOUT_READ_PACKET): + """ + Reads new IP data received by this XBee device during the + provided timeout. + + This method blocks until new IP data is received or the provided + timeout expires. + + For non-blocking operations, register a callback and use the method + :meth:`IPDevice.add_ip_data_received_callback`. + + Before reading IP data you need to start listening for incoming + IP data at a specific port. Use the method :meth:`IPDevice.start_listening` + for that purpose. When finished, you can use the method + :meth:`IPDevice.stop_listening` to stop listening for incoming IP data. + + Args: + timeout (Integer, optional): The time to wait for new IP data in seconds. + + Returns: + :class:`.IPMessage`: IP message, ``None`` if this device did not receive new data. + + Raises: + ValueError: if ``timeout`` is less than 0. + """ + if timeout < 0: + raise ValueError("Read timeout must be 0 or greater.") + + return self.__read_ip_data_packet(timeout) + + @AbstractXBeeDevice._before_send_method + def read_ip_data_from(self, ip_addr, timeout=XBeeDevice.TIMEOUT_READ_PACKET): + """ + Reads new IP data received from the given IP address during the + provided timeout. + + This method blocks until new IP data from the provided IP + address is received or the given timeout expires. + + For non-blocking operations, register a callback and use the method + :meth:`IPDevice.add_ip_data_received_callback`. + + Before reading IP data you need to start listening for incoming + IP data at a specific port. Use the method :meth:`IPDevice.start_listening` + for that purpose. When finished, you can use the method + :meth:`IPDevice.stop_listening` to stop listening for incoming IP data. + + Args: + ip_addr (:class:`ipaddress.IPv4Address`): The IP address to read data from. + timeout (Integer, optional): The time to wait for new IP data in seconds. + + Returns: + :class:`.IPMessage`: IP message, ``None`` if this device did not + receive new data from the provided IP address. + + Raises: + ValueError: if ``timeout`` is less than 0. + """ + if timeout < 0: + raise ValueError("Read timeout must be 0 or greater.") + + return self.__read_ip_data_packet(timeout, ip_addr) + + def __read_ip_data_packet(self, timeout, ip_addr=None): + """ + Reads a new IP data packet received by this IP XBee device during + the provided timeout. + + This method blocks until new IP data is received or the given + timeout expires. + + If the provided IP address is ``None`` the method returns + the first IP data packet read from any IP address. If the IP address is + not ``None`` the method returns the first data package read from + the provided IP address. + + Args: + timeout (Integer, optional): The time to wait for new IP data in seconds. Optional. + ip_addr (:class:`ipaddress.IPv4Address`, optional): The IP address to read data from. + ``None`` to read an IP data packet from any IP address. + + Returns: + :class:`.IPMessage`: IP message, ``None`` if this device did not + receive new data from the provided IP address. + """ + queue = self._packet_listener.get_ip_queue() + + if ip_addr is None: + packet = queue.get(timeout=timeout) + else: + packet = queue.get_by_ip(ip_addr, timeout) + + if packet is None: + return None + + if packet.get_frame_type() == ApiFrameType.RX_IPV4: + return IPMessage(packet.source_address, packet.source_port, + packet.dest_port, packet.ip_protocol, + packet.data) + + return None + + def get_network(self): + """ + Deprecated. + + This protocol does not support the network functionality. + """ + return None + + def get_16bit_addr(self): + """ + Deprecated. + + This protocol does not have an associated 16-bit address. + """ + return None + + def get_dest_address(self): + """ + Deprecated. + + Operation not supported in this protocol. Use :meth:`.IPDevice.get_dest_ip_addr` instead. + This method will raise an :class:`.AttributeError`. + """ + raise AttributeError(self.__OPERATION_EXCEPTION) + + def set_dest_address(self, addr): + """ + Deprecated. + + Operation not supported in this protocol. Use :meth:`.IPDevice.set_dest_ip_addr` instead. + This method will raise an :class:`.AttributeError`. + """ + raise AttributeError(self.__OPERATION_EXCEPTION) + + def get_pan_id(self): + """ + Deprecated. + + Operation not supported in this protocol. + This method will raise an :class:`.AttributeError`. + """ + raise AttributeError(self.__OPERATION_EXCEPTION) + + def set_pan_id(self, value): + """ + Deprecated. + + Operation not supported in this protocol. + This method will raise an :class:`.AttributeError`. + """ + raise AttributeError(self.__OPERATION_EXCEPTION) + + def add_data_received_callback(self, callback): + """ + Deprecated. + + Operation not supported in this protocol. + This method will raise an :class:`.AttributeError`. + """ + raise AttributeError(self.__OPERATION_EXCEPTION) + + def del_data_received_callback(self, callback): + """ + Deprecated. + + Operation not supported in this protocol. + This method will raise an :class:`.AttributeError`. + """ + raise AttributeError(self.__OPERATION_EXCEPTION) + + def add_expl_data_received_callback(self, callback): + """ + Deprecated. + + Operation not supported in this protocol. + This method will raise an :class:`.AttributeError`. + """ + raise AttributeError(self.__OPERATION_EXCEPTION) + + def del_expl_data_received_callback(self, callback): + """ + Deprecated. + + Operation not supported in this protocol. + This method will raise an :class:`.AttributeError`. + """ + raise AttributeError(self.__OPERATION_EXCEPTION) + + def read_data(self, timeout=None, explicit=False): + """ + Deprecated. + + Operation not supported in this protocol. + This method will raise an :class:`.AttributeError`. + """ + raise AttributeError(self.__OPERATION_EXCEPTION) + + def read_data_from(self, remote_xbee_device, timeout=None, explicit=False): + """ + Deprecated. + + Operation not supported in this protocol. + This method will raise an :class:`.AttributeError`. + """ + raise AttributeError(self.__OPERATION_EXCEPTION) + + def send_data_broadcast(self, data, transmit_options=TransmitOptions.NONE.value): + """ + Deprecated. + + Operation not supported in this protocol. + This method will raise an :class:`.AttributeError`. + """ + raise AttributeError(self.__OPERATION_EXCEPTION) + + def send_data(self, remote_xbee_device, data, transmit_options=TransmitOptions.NONE.value): + """ + Deprecated. + + Operation not supported in this protocol. + This method will raise an :class:`.AttributeError`. + """ + raise AttributeError(self.__OPERATION_EXCEPTION) + + def send_data_async(self, remote_xbee_device, data, transmit_options=TransmitOptions.NONE.value): + """ + Deprecated. + + Operation not supported in this protocol. + This method will raise an :class:`.AttributeError`. + """ + raise AttributeError(self.__OPERATION_EXCEPTION) + + +class CellularDevice(IPDevice): + """ + This class represents a local Cellular device. + """ + + def __init__(self, port, baud_rate): + """ + 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. + baud_rate (Integer): the serial port baud rate. + + Raises: + All exceptions raised by :func:`.XBeeDevice.__init__` constructor. + + .. seealso:: + | :class:`.XBeeDevice` + | :meth:`XBeeDevice.__init__` + """ + super(CellularDevice, self).__init__(port, baud_rate) + + self._imei_addr = None + + def open(self): + """ + Override. + + Raises: + XBeeException: if the protocol is invalid. + All exceptions raised by :meth:`.XBeeDevice.open`. + + .. seealso:: + | :meth:`.XBeeDevice.open` + """ + super(CellularDevice, self).open() + if self.get_protocol() not in [XBeeProtocol.CELLULAR, XBeeProtocol.CELLULAR_NBIOT]: + raise XBeeException("Invalid protocol.") + + def get_protocol(self): + """ + Override. + + .. seealso:: + | :meth:`.XBeeDevice.get_protocol` + """ + return XBeeProtocol.CELLULAR + + def read_device_info(self): + """ + Override. + + .. seealso:: + | :meth:`.XBeeDevice.read_device _info` + """ + super(CellularDevice, self).read_device_info() + + # Generate the IMEI address. + self._imei_addr = XBeeIMEIAddress(self._64bit_addr.address) + + def is_connected(self): + """ + Returns whether the device is connected to the Internet or not. + + Returns: + Boolean: ``True`` if the device is connected to the Internet, ``False`` otherwise. + + Raises: + TimeoutException: if there is a timeout getting the association indication status. + XBeeException: if there is any other XBee related exception. + """ + status = self.get_cellular_ai_status() + return status == CellularAssociationIndicationStatus.SUCCESSFULLY_CONNECTED + + def get_cellular_ai_status(self): + """ + Returns the current association status of this Cellular device. + + It indicates occurrences of errors during the modem initialization + and connection. + + Returns: + :class:`.CellularAssociationIndicationStatus`: The association indication status of the Cellular device. + + Raises: + TimeoutException: if there is a timeout getting the association indication status. + XBeeException: if there is any other XBee related exception. + """ + 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:`.SMSReceived`. + + Args: + callback (Function): the callback. Receives one argument. + + * 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:`.SMSReceived` + event. + + Args: + callback (Function): the callback to delete. + + Raises: + ValueError: if ``callback`` is not in the callback list of :class:`.SMSReceived` event. + """ + self._packet_listener.del_sms_received_callback(callback) + + def get_imei_addr(self): + """ + Returns the IMEI address of this Cellular device. + + To refresh this value use the method :meth:`.CellularDevice.read_device_info`. + + Returns: + :class:`.XBeeIMEIAddress`: The IMEI address of this Cellular device. + """ + return self._imei_addr + + @AbstractXBeeDevice._before_send_method + @AbstractXBeeDevice._after_send_method + def send_sms(self, phone_number, data): + """ + Sends the provided SMS message to the given phone number. + + This method blocks till a success or error response arrives or the + configured receive timeout expires. + + For non-blocking operations use the method :meth:`.CellularDevice.send_sms_async`. + + Args: + phone_number (String): The phone number to send the SMS to. + data (String): Text of the SMS. + + Raises: + ValueError: if ``phone_number`` is ``None``. + ValueError: if ``data`` is ``None``. + OperationNotSupportedException: if the device is remote. + TimeoutException: if there is a timeout sending the SMS. + XBeeException: if there is any other XBee related exception. + """ + if phone_number is None: + raise ValueError("Phone number cannot be None") + if data is None: + raise ValueError("Data cannot be None") + + # Check if device is remote. + if self.is_remote(): + raise OperationNotSupportedException("Cannot send SMS from a remote device") + + xbee_packet = TXSMSPacket(self.get_next_frame_id(), phone_number, data) + + return self.send_packet_sync_and_get_response(xbee_packet) + + @AbstractXBeeDevice._before_send_method + def send_sms_async(self, phone_number, data): + """ + Sends asynchronously the provided SMS to the given phone number. + + Asynchronous transmissions do not wait for answer or for transmit + status packet. + + Args: + phone_number (String): The phone number to send the SMS to. + data (String): Text of the SMS. + + Raises: + ValueError: if ``phone_number`` is ``None``. + ValueError: if ``data`` is ``None``. + OperationNotSupportedException: if the device is remote. + XBeeException: if there is any other XBee related exception. + """ + if phone_number is None: + raise ValueError("Phone number cannot be None") + if data is None: + raise ValueError("Data cannot be None") + + # Check if device is remote. + if self.is_remote(): + 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_64bit_addr(self): + """ + Deprecated. + + Cellular protocol does not have an associated 64-bit address. + """ + return None + + def add_io_sample_received_callback(self, callback): + """ + Deprecated. + + Operation not supported in this protocol. + This method will raise an :class:`.AttributeError`. + """ + raise AttributeError(self.__OPERATION_EXCEPTION) + + def del_io_sample_received_callback(self, callback): + """ + Deprecated. + + Operation not supported in this protocol. + This method will raise an :class:`.AttributeError`. + """ + raise AttributeError(self.__OPERATION_EXCEPTION) + + def set_dio_change_detection(self, io_lines_set): + """ + Deprecated. + + Operation not supported in this protocol. + This method will raise an :class:`.AttributeError`. + """ + raise AttributeError(self.__OPERATION_EXCEPTION) + + def get_io_sampling_rate(self): + """ + Deprecated. + + Operation not supported in this protocol. + This method will raise an :class:`.AttributeError`. + """ + raise AttributeError(self.__OPERATION_EXCEPTION) + + def set_io_sampling_rate(self, rate): + """ + Deprecated. + + Operation not supported in this protocol. + This method will raise an :class:`.AttributeError`. + """ + raise AttributeError(self.__OPERATION_EXCEPTION) + + def get_node_id(self): + """ + Deprecated. + + Operation not supported in this protocol. + This method will raise an :class:`.AttributeError`. + """ + raise AttributeError(self.__OPERATION_EXCEPTION) + + def set_node_id(self, node_id): + """ + Deprecated. + + Operation not supported in this protocol. + This method will raise an :class:`.AttributeError`. + """ + raise AttributeError(self.__OPERATION_EXCEPTION) + + def get_power_level(self): + """ + Deprecated. + + Operation not supported in this protocol. + This method will raise an :class:`.AttributeError`. + """ + raise AttributeError(self.__OPERATION_EXCEPTION) + + def set_power_level(self, power_level): + """ + Deprecated. + + Operation not supported in this protocol. + This method will raise an :class:`.AttributeError`. + """ + raise AttributeError(self.__OPERATION_EXCEPTION) + + +class LPWANDevice(CellularDevice): + """ + This class provides common functionality for XBee Low-Power Wide-Area Network + devices. + """ + + def __init__(self, port, baud_rate): + """ + 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. + baud_rate (Integer): the serial port baud rate. + + Raises: + All exceptions raised by :func:`.XBeeDevice.__init__` constructor. + + .. seealso:: + | :class:`.XBeeDevice` + | :meth:`XBeeDevice.__init__` + """ + super(LPWANDevice, self).__init__(port, baud_rate) + + def send_ip_data(self, ip_addr, dest_port, protocol, data, close_socket=False): + """ + Sends the provided IP data to the given IP address and port using + the specified IP protocol. + + This method blocks till a success or error response arrives or the + configured receive timeout expires. + + Args: + ip_addr (:class:`ipaddress.IPv4Address`): The IP address to send IP data to. + dest_port (Integer): The destination port of the transmission. + protocol (:class:`.IPProtocol`): The IP protocol used for the transmission. + data (String or Bytearray): The IP data to be sent. + close_socket (Boolean, optional): Must be ``False``. + + Raises: + ValueError: if ``protocol`` is not UDP. + """ + if protocol != IPProtocol.UDP: + raise ValueError("This protocol only supports UDP transmissions") + + 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): + """ + Sends the provided IP data to the given IP address and port + asynchronously using the specified IP protocol. + + Asynchronous transmissions do not wait for answer from the remote + device or for transmit status packet. + + Args: + ip_addr (:class:`ipaddress.IPv4Address`): The IP address to send IP data to. + dest_port (Integer): The destination port of the transmission. + protocol (:class:`.IPProtocol`): The IP protocol used for the transmission. + data (String or Bytearray): The IP data to be sent. + close_socket (Boolean, optional): Must be ``False``. + + Raises: + ValueError: if ``protocol`` is not UDP. + """ + if protocol != IPProtocol.UDP: + raise ValueError("This protocol only supports UDP transmissions") + + super(LPWANDevice, self).send_ip_data_async(ip_addr, dest_port, protocol, data) + + def add_sms_callback(self, callback): + """ + Deprecated. + + Operation not supported in this protocol. + This method will raise an :class:`.AttributeError`. + """ + raise AttributeError(self.__OPERATION_EXCEPTION) + + def del_sms_callback(self, callback): + """ + Deprecated. + + Operation not supported in this protocol. + This method will raise an :class:`.AttributeError`. + """ + raise AttributeError(self.__OPERATION_EXCEPTION) + + def send_sms(self, phone_number, data): + """ + Deprecated. + + Operation not supported in this protocol. + This method will raise an :class:`.AttributeError`. + """ + raise AttributeError(self.__OPERATION_EXCEPTION) + + def send_sms_async(self, phone_number, data): + """ + Deprecated. + + Operation not supported in this protocol. + This method will raise an :class:`.AttributeError`. + """ + raise AttributeError(self.__OPERATION_EXCEPTION) + + +class NBIoTDevice(LPWANDevice): + """ + This class represents a local NB-IoT device. + """ + + def __init__(self, port, baud_rate): + """ + 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. + baud_rate (Integer): the serial port baud rate. + + Raises: + All exceptions raised by :func:`.XBeeDevice.__init__` constructor. + + .. seealso:: + | :class:`.XBeeDevice` + | :meth:`XBeeDevice.__init__` + """ + super(NBIoTDevice, self).__init__(port, baud_rate) + + self._imei_addr = None + + def open(self): + """ + Override. + + Raises: + XBeeException: if the protocol is invalid. + All exceptions raised by :meth:`.XBeeDevice.open`. + + .. seealso:: + | :meth:`.XBeeDevice.open` + """ + super(NBIoTDevice, self).open() + if self.get_protocol() != XBeeProtocol.CELLULAR_NBIOT: + raise XBeeException("Invalid protocol.") + + def get_protocol(self): + """ + Override. + + .. seealso:: + | :meth:`.XBeeDevice.get_protocol` + """ + return XBeeProtocol.CELLULAR_NBIOT + + +class WiFiDevice(IPDevice): + """ + This class represents a local Wi-Fi XBee device. + """ + + __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, baud_rate): + """ + 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. + baud_rate (Integer): the serial port baud rate. + + Raises: + All exceptions raised by :func:`.XBeeDevice.__init__` constructor. + + .. seealso:: + | :class:`.XBeeDevice` + | :meth:`XBeeDevice.__init__` + """ + 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): + """ + Override. + + Raises: + XBeeException: if the protocol is invalid. + All exceptions raised by :meth:`.XBeeDevice.open`. + + .. seealso:: + | :meth:`.XBeeDevice.open` + """ + super(WiFiDevice, self).open() + if self.get_protocol() != XBeeProtocol.XBEE_WIFI: + raise XBeeException("Invalid protocol.") + + def get_protocol(self): + """ + Override. + + .. seealso:: + | :meth:`.XBeeDevice.get_protocol` + """ + return XBeeProtocol.XBEE_WIFI + + def get_wifi_ai_status(self): + """ + Returns the current association status of the device. + + Returns: + :class:`.WiFiAssociationIndicationStatus`: the current association status of the device. + + Raises: + TimeoutException: if there is a timeout getting the association indication status. + XBeeException: if there is any other XBee related exception. + + .. seealso:: + | :class:`.WiFiAssociationIndicationStatus` + """ + return WiFiAssociationIndicationStatus.get(utils.bytes_to_int(self.get_parameter("AI"))) + + def get_access_point(self, ssid): + """ + Finds and returns the access point that matches the supplied SSID. + + Args: + ssid (String): the SSID of the access point to get. + + Returns: + :class:`.AccessPoint`: the discovered access point with the provided SSID, or ``None`` + if the timeout expires and the access point was not found. + + Raises: + TimeoutException: if there is a timeout getting the access point. + XBeeException: if there is an error sending the discovery command. + + .. seealso:: + | :class:`.AccessPoint` + """ + ap_list = self.scan_access_points() + + for access_point in ap_list: + if access_point.ssid == ssid: + return access_point + + return None + + @AbstractXBeeDevice._before_send_method + def scan_access_points(self): + """ + Performs a scan to search for access points in the vicinity. + + This method blocks until all the access points are discovered or the + configured access point timeout expires. + + The access point timeout is configured using the :meth:`.WiFiDevice.set_access_point_timeout` + method and can be consulted with :meth:`.WiFiDevice.get_access_point_timeout` method. + + Returns: + List: the list of :class:`.AccessPoint` objects discovered. + + Raises: + TimeoutException: if there is a timeout scanning the access points. + XBeeException: if there is any other XBee related exception. + + .. seealso:: + | :class:`.AccessPoint` + """ + access_points_list = [] + + 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 != "AS": + return + + # Check for error. + if xbee_packet.status == ATCommandStatus.ERROR: + self.__scanning_aps = False + self.__scanning_aps_error = True + # Check for end of discovery. + elif xbee_packet.command_value is None or len(xbee_packet.command_value) == 0: + self.__scanning_aps = False + # Get the access point from the command value. + else: + access_point = self.__parse_access_point(xbee_packet.command_value) + if access_point is not None: + access_points_list.append(access_point) + + self.add_packet_received_callback(packet_receive_callback) + self.__scanning_aps = True + + try: + 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: + time.sleep(0.1) + + # Check if we exited because of a timeout. + if self.__scanning_aps: + raise TimeoutException + # Check if there was an error in the active scan command (device is already connected). + if self.__scanning_aps_error: + raise XBeeException("There is an SSID already configured.") + finally: + self.__scanning_aps = False + self.__scanning_aps_error = False + self.del_packet_received_callback(packet_receive_callback) + + return access_points_list + + def connect_by_ap(self, access_point, password=None): + """ + Connects to the provided access point. + + This method blocks until the connection with the access point is + established or the configured access point timeout expires. + + The access point timeout is configured using the + :meth:`.WiFiDevice.set_access_point_timeout` method and can be consulted with + :meth:`.WiFiDevice.get_access_point_timeout` method. + + Once the module is connected to the access point, you can issue + the :meth:`.WiFiDevice.write_changes` method to save the connection settings. This + way the module will try to connect to the access point every time it + is powered on. + + Args: + access_point (:class:`.AccessPoint`): The access point to connect to. + password (String, optional): The password for the access point, ``None`` if it does not have + any encryption enabled. Optional. + + Returns: + Boolean: ``True`` if the module connected to the access point successfully, ``False`` otherwise. + + Raises: + ValueError:if ``access_point`` is ``None``. + TimeoutException: if there is a timeout sending the connect commands. + XBeeException: if there is any other XBee related exception. + + .. seealso:: + | :meth:`.WiFiDevice.connect_by_ssid` + | :meth:`.WiFiDevice.disconnect` + | :meth:`.WiFiDevice.get_access_point` + | :meth:`.WiFiDevice.get_access_point_timeout` + | :meth:`.WiFiDevice.scan_access_points` + | :meth:`.WiFiDevice.set_access_point_timeout` + """ + if access_point is None: + raise ValueError("The access point to connect to cannot be None.") + + # Set connection parameters. + 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("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("AI") + if status is None or len(status) < 1: + continue + if status[0] == 0: + return True + return False + + def connect_by_ssid(self, ssid, password=None): + """ + Connects to the access point with provided SSID. + + This method blocks until the connection with the access point is + established or the configured access point timeout expires. + + The access point timeout is configured using the + :meth:`.WiFiDevice.set_access_point_timeout` method and can be consulted with + :meth:`.WiFiDevice.get_access_point_timeout` method. + + Once the module is connected to the access point, you can issue + the :meth:`.WiFiDevice.write_changes` method to save the connection settings. This + way the module will try to connect to the access point every time it + is powered on. + + Args: + ssid (String): the SSID of the access point to connect to. + password (String, optional): The password for the access point, ``None`` if it does not have + any encryption enabled. Optional. + + Returns: + Boolean: ``True`` if the module connected to the access point successfully, ``False`` otherwise. + + Raises: + ValueError: if ``ssid`` is ``None``. + TimeoutException: if there is a timeout sending the connect commands. + XBeeException: if the access point with the provided SSID cannot be found. + XBeeException: if there is any other XBee related exception. + + .. seealso:: + | :meth:`.WiFiDevice.connect_by_ap` + | :meth:`.WiFiDevice.disconnect` + | :meth:`.WiFiDevice.get_access_point` + | :meth:`.WiFiDevice.get_access_point_timeout` + | :meth:`.WiFiDevice.scan_access_points` + | :meth:`.WiFiDevice.set_access_point_timeout` + """ + if ssid is None: + raise ValueError("SSID of access point cannot be None.") + + access_point = self.get_access_point(ssid) + 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) + + def disconnect(self): + """ + Disconnects from the access point that the device is connected to. + + This method blocks until the device disconnects totally from the + access point or the configured access point timeout expires. + + The access point timeout is configured using the + :meth:`.WiFiDevice.set_access_point_timeout` method and can be consulted with + :meth:`.WiFiDevice.get_access_point_timeout` method. + + Returns: + Boolean: ``True`` if the module disconnected from the access point successfully, ``False`` otherwise. + + Raises: + TimeoutException: if there is a timeout sending the disconnect command. + XBeeException: if there is any other XBee related exception. + + .. seealso:: + | :meth:`.WiFiDevice.connect_by_ap` + | :meth:`.WiFiDevice.connect_by_ssid` + | :meth:`.WiFiDevice.get_access_point_timeout` + | :meth:`.WiFiDevice.set_access_point_timeout` + """ + 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("AI") + if status is None or len(status) < 1: + continue + if status[0] == 0x23: + return True + return False + + def is_connected(self): + """ + Returns whether the device is connected to an access point or not. + + Returns: + Boolean: ``True`` if the device is connected to an access point, ``False`` otherwise. + + Raises: + TimeoutException: if there is a timeout getting the association indication status. + + .. seealso:: + | :meth:`.WiFiDevice.get_wifi_ai_status` + | :class:`.WiFiAssociationIndicationStatus` + """ + status = self.get_wifi_ai_status() + + return status == WiFiAssociationIndicationStatus.SUCCESSFULLY_JOINED + + def __parse_access_point(self, ap_data): + """ + Parses the given active scan API data and returns an :class:`.AccessPoint`: object. + + Args: + ap_data (Bytearray): access point data to parse. + + Returns: + :class:`.AccessPoint`: access point parsed from the provided data. ``None`` if the provided data + does not correspond to an access point. + + .. seealso:: + | :class:`.AccessPoint` + """ + index = 0 + + if len(ap_data) == 0: + return None + + # Get the version. + version = ap_data[index] + index += 1 + if len(ap_data[index:]) == 0: + return None + # Get the channel. + channel = ap_data[index] + index += 1 + if len(ap_data[index:]) == 0: + return None + # Get the encryption type. + encryption_type = ap_data[index] + index += 1 + if len(ap_data[index:]) == 0: + return None + # Get the signal strength. + signal_strength = ap_data[index] + index += 1 + if len(ap_data[index:]) == 0: + return None + + signal_quality = self.__get_signal_quality(version, signal_strength) + ssid = (ap_data[index:]).decode("utf8") + + return AccessPoint(ssid, WiFiEncryptionType.get(encryption_type), channel, signal_quality) + + @staticmethod + def __get_signal_quality(wifi_version, signal_strength): + """ + Converts the signal strength value in signal quality (%) based on the + provided Wi-Fi version. + + Args: + wifi_version (Integer): Wi-Fi protocol version of the Wi-Fi XBee device. + signal_strength (Integer): signal strength value to convert to %. + + Returns: + Integer: the signal quality in %. + """ + if wifi_version == 1: + if signal_strength <= -100: + quality = 0 + elif signal_strength >= -50: + quality = 100 + else: + quality = (2 * (signal_strength + 100)) + else: + quality = 2 * signal_strength + + # Check limits. + if quality > 100: + quality = 100 + if quality < 0: + quality = 0 + + return quality + + def get_access_point_timeout(self): + """ + Returns the configured access point timeout for connecting, + disconnecting and scanning access points. + + Returns: + Integer: the current access point timeout in milliseconds. + + .. seealso:: + | :meth:`.WiFiDevice.set_access_point_timeout` + """ + return self.__ap_timeout + + def set_access_point_timeout(self, ap_timeout): + """ + Configures the access point timeout in milliseconds for connecting, + disconnecting and scanning access points. + + Args: + ap_timeout (Integer): the new access point timeout in milliseconds. + + Raises: + ValueError: if ``ap_timeout`` is less than 0. + + .. seealso:: + | :meth:`.WiFiDevice.get_access_point_timeout` + """ + if ap_timeout < 0: + raise ValueError("Access point timeout cannot be less than 0.") + self.__ap_timeout = ap_timeout + + def get_ip_addressing_mode(self): + """ + Returns the IP addressing mode of the device. + + Returns: + :class:`.IPAddressingMode`: the IP addressing mode. + + Raises: + TimeoutException: if there is a timeout reading the IP addressing mode. + + .. seealso:: + | :meth:`.WiFiDevice.set_ip_addressing_mode` + | :class:`.IPAddressingMode` + """ + return IPAddressingMode.get(utils.bytes_to_int(self.get_parameter("MA"))) + + def set_ip_addressing_mode(self, mode): + """ + Sets the IP addressing mode of the device. + + Args: + mode (:class:`.IPAddressingMode`): the new IP addressing mode to set. + + Raises: + TimeoutException: if there is a timeout setting the IP addressing mode. + + .. seealso:: + | :meth:`.WiFiDevice.get_ip_addressing_mode` + | :class:`.IPAddressingMode` + """ + self.set_parameter("MA", utils.int_to_bytes(mode.code, num_bytes=1)) + + def set_ip_address(self, ip_address): + """ + Sets the IP address of the module. + + This method can only be called if the module is configured + in :attr:`.IPAddressingMode.STATIC` mode. Otherwise an ``XBeeException`` + will be thrown. + + Args: + ip_address (:class:`ipaddress.IPv4Address`): the new IP address to set. + + Raises: + TimeoutException: if there is a timeout setting the IP address. + + .. seealso:: + | :meth:`.WiFiDevice.get_mask_address` + | :class:`ipaddress.IPv4Address` + """ + self.set_parameter("MY", ip_address.packed) + + def get_mask_address(self): + """ + Returns the subnet mask IP address. + + Returns: + :class:`ipaddress.IPv4Address`: the subnet mask IP address. + + Raises: + TimeoutException: if there is a timeout reading the subnet mask address. + + .. seealso:: + | :meth:`.WiFiDevice.set_mask_address` + | :class:`ipaddress.IPv4Address` + """ + return IPv4Address(bytes(self.get_parameter("MK"))) + + def set_mask_address(self, mask_address): + """ + Sets the subnet mask IP address. + + This method can only be called if the module is configured + in :attr:`.IPAddressingMode.STATIC` mode. Otherwise an ``XBeeException`` + will be thrown. + + Args: + mask_address (:class:`ipaddress.IPv4Address`): the new subnet mask address to set. + + Raises: + TimeoutException: if there is a timeout setting the subnet mask address. + + .. seealso:: + | :meth:`.WiFiDevice.get_mask_address` + | :class:`ipaddress.IPv4Address` + """ + self.set_parameter("MK", mask_address.packed) + + def get_gateway_address(self): + """ + Returns the IP address of the gateway. + + Returns: + :class:`ipaddress.IPv4Address`: the IP address of the gateway. + + Raises: + TimeoutException: if there is a timeout reading the gateway address. + + .. seealso:: + | :meth:`.WiFiDevice.set_dns_address` + | :class:`ipaddress.IPv4Address` + """ + return IPv4Address(bytes(self.get_parameter("GW"))) + + def set_gateway_address(self, gateway_address): + """ + Sets the IP address of the gateway. + + This method can only be called if the module is configured + in :attr:`.IPAddressingMode.STATIC` mode. Otherwise an ``XBeeException`` + will be thrown. + + Args: + gateway_address (:class:`ipaddress.IPv4Address`): the new gateway address to set. + + Raises: + TimeoutException: if there is a timeout setting the gateway address. + + .. seealso:: + | :meth:`.WiFiDevice.get_gateway_address` + | :class:`ipaddress.IPv4Address` + """ + self.set_parameter("GW", gateway_address.packed) + + def get_dns_address(self): + """ + Returns the IP address of Domain Name Server (DNS). + + Returns: + :class:`ipaddress.IPv4Address`: the DNS address configured. + + Raises: + TimeoutException: if there is a timeout reading the DNS address. + + .. seealso:: + | :meth:`.WiFiDevice.set_dns_address` + | :class:`ipaddress.IPv4Address` + """ + return IPv4Address(bytes(self.get_parameter("NS"))) + + def set_dns_address(self, dns_address): + """ + Sets the IP address of Domain Name Server (DNS). + + Args: + dns_address (:class:`ipaddress.IPv4Address`): the new DNS address to set. + + Raises: + TimeoutException: if there is a timeout setting the DNS address. + + .. seealso:: + | :meth:`.WiFiDevice.get_dns_address` + | :class:`ipaddress.IPv4Address` + """ + self.set_parameter("NS", dns_address.packed) + + +class RemoteXBeeDevice(AbstractXBeeDevice): + """ + This class represents a remote XBee device. + """ + + def __init__(self, local_xbee_device, x64bit_addr=XBee64BitAddress.UNKNOWN_ADDRESS, + x16bit_addr=XBee16BitAddress.UNKNOWN_ADDRESS, node_id=None): + """ + Class constructor. Instantiates a new :class:`.RemoteXBeeDevice` with the provided parameters. + + Args: + local_xbee_device (:class:`.XBeeDevice`): the local XBee device associated with the remote one. + x64bit_addr (:class:`.XBee64BitAddress`): the 64-bit address of the remote XBee device. + x16bit_addr (:class:`.XBee16BitAddress`): the 16-bit address of the remote XBee device. + node_id (String, optional): the node identifier of the remote XBee device. Optional. + + .. seealso:: + | :class:`XBee16BitAddress` + | :class:`XBee64BitAddress` + | :class:`XBeeDevice` + """ + 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 + self._16bit_addr = x16bit_addr + self._node_id = node_id + + def get_parameter(self, parameter): + """ + Override. + + .. seealso:: + | :meth:`.AbstractXBeeDevice.get_parameter` + """ + return super(RemoteXBeeDevice, self).get_parameter(parameter) + + def set_parameter(self, parameter, value): + """ + Override. + + .. seealso:: + | :meth:`.AbstractXBeeDevice.set_parameter` + """ + super(RemoteXBeeDevice, self).set_parameter(parameter, value) + + def is_remote(self): + """ + Override method. + + .. seealso:: + | :meth:`.AbstractXBeeDevice.is_remote` + """ + return True + + def reset(self): + """ + Override method. + + .. seealso:: + | :meth:`.AbstractXBeeDevice.reset` + """ + # Send reset command. + try: + 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: + return + else: + raise te + + # Check if AT Command response is valid. + self._check_at_cmd_response_is_valid(response) + + def get_local_xbee_device(self): + """ + Returns the local XBee device associated to the remote one. + + Returns: + :class:`.XBeeDevice` + + """ + return self._local_xbee_device + + def set_local_xbee_device(self, local_xbee_device): + """ + This methods associates a :class:`.XBeeDevice` to the remote XBee device. + + Args: + local_xbee_device (:class:`.XBeeDevice`): the new local XBee device associated to the remote one. + + .. seealso:: + | :class:`.XBeeDevice` + """ + self._local_xbee_device = local_xbee_device + + def get_serial_port(self): + """ + Returns the serial port of the local XBee device associated to the remote one. + + Returns: + :class:`XBeeSerialPort`: the serial port of the local XBee device associated to the remote one. + + .. seealso:: + | :class:`XBeeSerialPort` + """ + return self._local_xbee_device.serial_port + + 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): + """ + This class represents a remote 802.15.4 XBee device. + """ + + def __init__(self, local_xbee_device, x64bit_addr=None, x16bit_addr=None, node_id=None): + """ + Class constructor. Instantiates a new :class:`.RemoteXBeeDevice` with the provided parameters. + + Args: + local_xbee_device (:class:`.XBeeDevice`): the local XBee device associated with the remote one. + x64bit_addr (:class:`.XBee64BitAddress`): the 64-bit address of the remote XBee device. + x16bit_addr (:class:`.XBee16BitAddress`): the 16-bit address of the remote XBee device. + node_id (String, optional): the node identifier of the remote XBee device. Optional. + + Raises: + XBeeException: if the protocol of ``local_xbee_device`` is invalid. + All exceptions raised by :class:`.RemoteXBeeDevice` constructor. + + .. seealso:: + | :class:`RemoteXBeeDevice` + | :class:`XBee16BitAddress` + | :class:`XBee64BitAddress` + | :class:`XBeeDevice` + """ + if local_xbee_device.get_protocol() != XBeeProtocol.RAW_802_15_4: + raise XBeeException("Invalid protocol.") + + super(RemoteRaw802Device, self).__init__(local_xbee_device, x64bit_addr, x16bit_addr, node_id=node_id) + + def get_protocol(self): + """ + Override. + + .. seealso:: + | :meth:`.RemoteXBeeDevice.get_protocol` + """ + return XBeeProtocol.RAW_802_15_4 + + def set_64bit_addr(self, address): + """ + Sets the 64-bit address of this remote 802.15.4 device. + + Args: + address (:class:`.XBee64BitAddress`): The 64-bit address to be set to the device. + + Raises: + ValueError: if ``address`` is ``None``. + """ + if address is None: + raise ValueError("64-bit address cannot be None") + + self._64bit_addr = address + + def get_ai_status(self): + """ + Override. + + .. seealso:: + | :meth:`.AbstractXBeeDevice._get_ai_status` + """ + return super(RemoteRaw802Device, self)._get_ai_status() + + +class RemoteDigiMeshDevice(RemoteXBeeDevice): + """ + This class represents a remote DigiMesh XBee device. + """ + + def __init__(self, local_xbee_device, x64bit_addr=None, node_id=None): + """ + Class constructor. Instantiates a new :class:`.RemoteDigiMeshDevice` with the provided parameters. + + Args: + local_xbee_device (:class:`.XBeeDevice`): the local XBee device associated with the remote one. + x64bit_addr (:class:`.XBee64BitAddress`): the 64-bit address of the remote XBee device. + node_id (String, optional): the node identifier of the remote XBee device. Optional. + + Raises: + XBeeException: if the protocol of ``local_xbee_device`` is invalid. + All exceptions raised by :class:`.RemoteXBeeDevice` constructor. + + .. seealso:: + | :class:`RemoteXBeeDevice` + | :class:`XBee64BitAddress` + | :class:`XBeeDevice` + """ + if local_xbee_device.get_protocol() != XBeeProtocol.DIGI_MESH: + raise XBeeException("Invalid protocol.") + + super(RemoteDigiMeshDevice, self).__init__(local_xbee_device, x64bit_addr, None, node_id) + + def get_protocol(self): + """ + Override. + + .. seealso:: + | :meth:`.RemoteXBeeDevice.get_protocol` + """ + return XBeeProtocol.DIGI_MESH + + +class RemoteDigiPointDevice(RemoteXBeeDevice): + """ + This class represents a remote DigiPoint XBee device. + """ + + def __init__(self, local_xbee_device, x64bit_addr=None, node_id=None): + """ + Class constructor. Instantiates a new :class:`.RemoteDigiMeshDevice` with the provided parameters. + + Args: + local_xbee_device (:class:`.XBeeDevice`): the local XBee device associated with the remote one. + x64bit_addr (:class:`.XBee64BitAddress`): the 64-bit address of the remote XBee device. + node_id (String, optional): the node identifier of the remote XBee device. Optional. + + Raises: + XBeeException: if the protocol of ``local_xbee_device`` is invalid. + All exceptions raised by :class:`.RemoteXBeeDevice` constructor. + + .. seealso:: + | :class:`RemoteXBeeDevice` + | :class:`XBee64BitAddress` + | :class:`XBeeDevice` + """ + if local_xbee_device.get_protocol() != XBeeProtocol.DIGI_POINT: + raise XBeeException("Invalid protocol.") + + super(RemoteDigiPointDevice, self).__init__(local_xbee_device, x64bit_addr, None, node_id) + + def get_protocol(self): + """ + Override. + + .. seealso:: + | :meth:`.RemoteXBeeDevice.get_protocol` + """ + return XBeeProtocol.DIGI_POINT + + +class RemoteZigBeeDevice(RemoteXBeeDevice): + """ + This class represents a remote ZigBee XBee device. + """ + + def __init__(self, local_xbee_device, x64bit_addr=None, x16bit_addr=None, node_id=None): + """ + Class constructor. Instantiates a new :class:`.RemoteDigiMeshDevice` with the provided parameters. + + Args: + local_xbee_device (:class:`.XBeeDevice`): the local XBee device associated with the remote one. + x64bit_addr (:class:`.XBee64BitAddress`): the 64-bit address of the remote XBee device. + x16bit_addr (:class:`.XBee16BitAddress`): the 16-bit address of the remote XBee device. + node_id (String, optional): the node identifier of the remote XBee device. Optional. + + Raises: + XBeeException: if the protocol of ``local_xbee_device`` is invalid. + All exceptions raised by :class:`.RemoteXBeeDevice` constructor. + + .. seealso:: + | :class:`RemoteXBeeDevice` + | :class:`XBee16BitAddress` + | :class:`XBee64BitAddress` + | :class:`XBeeDevice` + """ + if local_xbee_device.get_protocol() != XBeeProtocol.ZIGBEE: + raise XBeeException("Invalid protocol.") + + super(RemoteZigBeeDevice, self).__init__(local_xbee_device, x64bit_addr, x16bit_addr, node_id) + + def get_protocol(self): + """ + Override. + + .. seealso:: + | :meth:`.RemoteXBeeDevice.get_protocol` + """ + return XBeeProtocol.ZIGBEE + + def get_ai_status(self): + """ + Override. + + .. seealso:: + | :meth:`.AbstractXBeeDevice._get_ai_status` + """ + return super(RemoteZigBeeDevice, self)._get_ai_status() + + def force_disassociate(self): + """ + Override. + + .. seealso:: + | :meth:`.AbstractXBeeDevice._force_disassociate` + """ + super(RemoteZigBeeDevice, self)._force_disassociate() + + +class XBeeNetwork(object): + """ + This class represents an XBee Network. + + The network allows the discovery of remote devices in the same network + as the local one and stores them. + """ + + ND_PACKET_FINISH = 0x01 + """ + Flag that indicates a "discovery process finish" packet. + """ + + ND_PACKET_REMOTE = 0x02 + """ + Flag that indicates a discovery process packet with info about a remote XBee device. + """ + + # Default timeout for discovering process in case of + # the real timeout can't be determined. + __DEFAULT_DISCOVERY_TIMEOUT = 20 + + # Correction values for the timeout for determined devices. + # It has been tested and work 'fine' + __DIGI_MESH_TIMEOUT_CORRECTION = 3 + __DIGI_MESH_SLEEP_TIMEOUT_CORRECTION = 0.1 # DigiMesh with sleep support. + __DIGI_POINT_TIMEOUT_CORRECTION = 8 + + __NODE_DISCOVERY_COMMAND = "ND" + + def __init__(self, xbee_device): + """ + Class constructor. Instantiates a new ``XBeeNetwork``. + + Args: + xbee_device (:class:`.XBeeDevice`): the local XBee device to get the network from. + + Raises: + ValueError: if ``xbee_device`` is ``None``. + """ + if xbee_device is None: + raise ValueError("Local XBee device cannot be None") + + self.__xbee_device = xbee_device + self.__devices_list = [] + self.__last_search_dev_list = [] + self.__lock = threading.Lock() + self.__discovering = False + self.__device_discovered = DeviceDiscovered() + self.__device_discovery_finished = DiscoveryProcessFinished() + self.__discovery_thread = None + self.__sought_device_id = None + self.__discovered_device = None + + def start_discovery_process(self): + """ + Starts the discovery process. This method is not blocking. + + 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` + """ + with self.__lock: + if self.__discovering: + return + + self.__discovery_thread = threading.Thread(target=self.__discover_devices_and_notify_callbacks) + self.__discovering = True + self.__discovery_thread.start() + + def stop_discovery_process(self): + """ + Stops the discovery process if it is running. + + Note that DigiMesh/DigiPoint devices are blocked until the discovery + time configured (NT parameter) has elapsed, so if you try to get/set + any parameter during the discovery process you will receive a timeout + exception. + """ + if self.__discovering: + with self.__lock: + self.__discovering = False + + def discover_device(self, node_id): + """ + Blocking method. Discovers and reports the first remote XBee device that matches the + supplied identifier. + + Args: + node_id (String): the node identifier of the device to be discovered. + + Returns: + :class:`.RemoteXBeeDevice`: the discovered remote XBee device with the given identifier, + ``None`` if the timeout expires and the device was not found. + """ + try: + with self.__lock: + self.__sought_device_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) + return remote + + def discover_devices(self, device_id_list): + """ + Blocking method. Attempts to discover a list of devices and add them to the + current network. + + This method does not guarantee that all devices of ``device_id_list`` + will be found, even if they exist physically. This will depend on the node + discovery operation (``ND``) and timeout. + + Args: + device_id_list (List): list of device IDs to discover. + + Returns: + List: a list with the discovered devices. It may not contain all devices specified in ``device_id_list`` + """ + self.start_discovery_process() + while self.is_discovery_running(): + time.sleep(0.1) + return list(filter(lambda x: x.get_node_id() in device_id_list, self.__last_search_dev_list)) + + def is_discovery_running(self): + """ + Returns whether the discovery process is running or not. + + Returns: + Boolean: ``True`` if the discovery process is running, ``False`` otherwise. + """ + return self.__discovering + + def get_devices(self): + """ + Returns a copy of the XBee devices list of the network. + + If another XBee device is added to the list before the execution + of this method, this XBee device will not be added to the list returned + by this method. + + Returns: + List: a copy of the XBee devices list of the network. + """ + with self.__lock: + dl_copy = [len(self.__devices_list)] + dl_copy[:] = self.__devices_list[:] + return dl_copy + + def has_devices(self): + """ + Returns whether there is any device in the network or not. + + Returns: + Boolean: ``True`` if there is at least one device in the network, ``False`` otherwise. + """ + return len(self.__devices_list) > 0 + + def get_number_devices(self): + """ + Returns the number of devices in the network. + + Returns: + Integer: the number of devices in the network. + """ + return len(self.__devices_list) + + def add_device_discovered_callback(self, callback): + """ + Adds a callback for the event :class:`.DeviceDiscovered`. + + Args: + callback (Function): the callback. Receives one argument. + + * The discovered remote XBee device as a :class:`.RemoteXBeeDevice` + + .. seealso:: + | :meth:`.XBeeNetwork.del_device_discovered_callback` + | :meth:`.XBeeNetwork.add_discovery_process_finished_callback` + | :meth:`.XBeeNetwork.del_discovery_process_finished_callback` + """ + self.__device_discovered += callback + + def add_discovery_process_finished_callback(self, callback): + """ + Adds a callback for the event :class:`.DiscoveryProcessFinished`. + + Args: + callback (Function): the callback. Receives one argument. + + * The event code as an :class:`.Integer` + + .. seealso:: + | :meth:`.XBeeNetwork.del_discovery_process_finished_callback` + | :meth:`.XBeeNetwork.add_device_discovered_callback` + | :meth:`.XBeeNetwork.del_device_discovered_callback` + """ + self.__device_discovery_finished += callback + + def del_device_discovered_callback(self, callback): + """ + Deletes a callback for the callback list of :class:`.DeviceDiscovered` event. + + Args: + callback (Function): the callback to delete. + + Raises: + ValueError: if ``callback`` is not in the callback list of :class:`.DeviceDiscovered` event. + + .. seealso:: + | :meth:`.XBeeNetwork.add_device_discovered_callback` + | :meth:`.XBeeNetwork.add_discovery_process_finished_callback` + | :meth:`.XBeeNetwork.del_discovery_process_finished_callback` + """ + self.__device_discovered -= callback + + def del_discovery_process_finished_callback(self, callback): + """ + Deletes a callback for the callback list of :class:`.DiscoveryProcessFinished` event. + + Args: + callback (Function): the callback to delete. + + Raises: + ValueError: if ``callback`` is not in the callback list of :class:`.DiscoveryProcessFinished` event. + + .. seealso:: + | :meth:`.XBeeNetwork.add_discovery_process_finished_callback` + | :meth:`.XBeeNetwork.add_device_discovered_callback` + | :meth:`.XBeeNetwork.del_device_discovered_callback` + """ + self.__device_discovery_finished -= callback + + def clear(self): + """ + Removes all the remote XBee devices from the network. + """ + with self.__lock: + self.__devices_list = [] + + def get_discovery_options(self): + """ + Returns the network discovery process options. + + 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. + """ + return self.__xbee_device.get_parameter("NO") + + def set_discovery_options(self, options): + """ + Configures the discovery options (``NO`` parameter) with the given value. + + Args: + options (Set of :class:`.DiscoveryOptions`): new discovery options, empty set to clear the options. + + Raises: + ValueError: if ``options`` is ``None``. + 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. + + .. seealso:: + | :class:`.DiscoveryOptions` + """ + if options is None: + raise ValueError("Options cannot be None") + + 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): + """ + Returns the network discovery timeout. + + Returns: + Float: the network discovery timeout. + + 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. + """ + 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. + 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 ``discovery_timeout`` is not between 0x20 and 0xFF + """ + 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.__xbee_device.set_parameter("NT", timeout) + + def get_device_by_64(self, x64bit_addr): + """ + 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:`.RemoteXBeeDevice`: the remote XBee device in the network or ``None`` if it is not found. + + Raises: + ValueError: if ``x64bit_addr`` is ``None`` or unknown. + """ + if x64bit_addr is None: + raise ValueError("64-bit address cannot be None") + if x64bit_addr == XBee64BitAddress.UNKNOWN_ADDRESS: + raise ValueError("64-bit address cannot be unknown") + + with self.__lock: + for device in self.__devices_list: + if device.get_64bit_addr() is not None and device.get_64bit_addr() == x64bit_addr: + return device + + return None + + def get_device_by_16(self, x16bit_addr): + """ + 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:`.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.__xbee_device.get_protocol() == XBeeProtocol.DIGI_MESH: + raise ValueError("DigiMesh protocol does not support 16-bit addressing") + 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") + + with self.__lock: + for device in self.__devices_list: + if device.get_16bit_addr() is not None and device.get_16bit_addr() == x16bit_addr: + return device + + return None + + def get_device_by_node_id(self, node_id): + """ + 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:`.RemoteXBeeDevice`: the remote XBee device in the network or ``None`` if it is not found. + + Raises: + ValueError: if ``node_id`` is ``None``. + """ + if node_id is None: + raise ValueError("Node ID cannot be None") + + with self.__lock: + for device in self.__devices_list: + if device.get_node_id() is not None and device.get_node_id() == node_id: + return device + + return None + + def add_if_not_exist(self, x64bit_addr=None, x16bit_addr=None, node_id=None): + """ + Adds an XBee device with the provided parameters if it does not exist in the current network. + + If the XBee device already exists, its data will be updated with the provided parameters that are not ``None``. + + Args: + x64bit_addr (:class:`XBee64BitAddress`, optional): XBee device's 64bit address. Optional. + x16bit_addr (:class:`XBee16BitAddress`, optional): XBee device's 16bit address. Optional. + node_id (String, optional): the node identifier of the XBee device. Optional. + + Returns: + :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. + """ + remote = RemoteXBeeDevice(self.__xbee_device, x64bit_addr, x16bit_addr, node_id) + return self.add_remote(remote) + + def add_remote(self, remote_xbee_device): + """ + 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_device (:class:`.RemoteXBeeDevice`): the remote XBee device to add to the network. + + Returns: + :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. + """ + 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): + """ + Adds a list of remote XBee devices to the network. + + If any XBee device of the list 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_devices (List): the list of :class:`.RemoteXBeeDevice` to add to the network. + """ + for rem in remote_xbee_devices: + self.add_remote(rem) + + 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. + + Raises: + ValueError: if the provided :class:`.RemoteXBeeDevice` is not in the network. + """ + self.__devices_list.remove(remote_xbee_device) + + def get_discovery_callbacks(self): + """ + Returns the API callbacks that are used in the device discovery process. + + This callbacks notify the user callbacks for each XBee device discovered. + + Returns: + Tuple (Function, Function): callback for generic devices discovery process, + callback for discovery specific XBee device ops. + """ + def discovery_gen_callback(xbee_packet): + """ + Callback for generic devices discovery process. + """ + # if the discovering process is not running, stop. + if not self.__discovering: + return + # Check the 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 + with self.__lock: + self.__discovering = xbee_packet.status != ATCommandStatus.OK + elif nd_id == XBeeNetwork.ND_PACKET_REMOTE: + 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 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): + """ + This callback is used for discovery specific XBee device ops. + """ + # if __sought_device_id is None, exit (not searching XBee device). + if self.__sought_device_id is None: + return + # Check the 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 + if xbee_packet.status == ATCommandStatus.OK: + with self.__lock: + self.__sought_device_id = None + elif nd_id == XBeeNetwork.ND_PACKET_REMOTE: + # if it is not a finish signal, it contains info about a remote XBee device. + 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 + + return discovery_gen_callback, discovery_spec_callback + + def _get_discovery_thread(self): + """ + Returns the network discovery thread. + + Used to determine whether the discovery thread is alive or not. + + Returns: + :class:`.Thread`: the network discovery thread. + """ + return self.__discovery_thread + + @staticmethod + def __check_nd_packet(xbee_packet): + """ + Checks if the provided XBee packet is an ND response or not. If so, checks if is the 'end' signal + of the discovery process or if it has information about a remote XBee device. + + Returns: + Integer: the ID that indicates if the packet is a finish discovery signal or if it contains information + about a remote XBee device, or ``None`` if the ``xbee_packet`` is not a response for an ``ND`` command. + + * :attr:`.XBeeNetwork.ND_PACKET_FINISH`: if ``xbee_packet`` is an end signal. + * :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 == XBeeNetwork.__NODE_DISCOVERY_COMMAND): + if xbee_packet.command_value is None or len(xbee_packet.command_value) == 0: + return XBeeNetwork.ND_PACKET_FINISH + else: + return XBeeNetwork.ND_PACKET_REMOTE + else: + return None + + 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. + """ + self.__discover_devices() + self.__device_discovery_finished(NetworkDiscoveryStatus.SUCCESS) + + 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. Optional. + """ + try: + 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.__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) + + else: + # 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) + except Exception as e: + self.__xbee_device.log.exception(e) + finally: + with self.__lock: + self.__discovering = False + + def __is_802_compatible(self): + """ + Checks if the device performing the node discovery is a legacy + 802.15.4 device or a S1B device working in compatibility mode. + + Returns: + Boolean: ``True`` if the device performing the node discovery is a legacy + 802.15.4 device or S1B in compatibility mode, ``False`` otherwise. + + """ + if self.__xbee_device.get_protocol() != XBeeProtocol.RAW_802_15_4: + return False + param = None + try: + param = self.__xbee_device.get_parameter("C8") + except ATCommandException: + pass + if param is None or param[0] & 0x2 == 2: + return True + return False + + def __calculate_timeout(self): + """ + Determines the discovery timeout. + + Gets timeout information from the device and applies the proper + corrections to it. + + If the timeout cannot be determined getting it from the device, this + method returns the default timeout for discovery operations. + + Returns: + Float: discovery timeout in seconds. + """ + # Read the maximum discovery timeout (N?) + try: + discovery_timeout = utils.bytes_to_int(self.__xbee_device.get_parameter("N?")) / 1000 + except XBeeException: + discovery_timeout = None + + # If N? does not exist, read the NT parameter. + if discovery_timeout is None: + # Read the XBee device timeout (NT). + try: + discovery_timeout = utils.bytes_to_int(self.__xbee_device.get_parameter("NT")) / 10 + except XBeeException as xe: + discovery_timeout = XBeeNetwork.__DEFAULT_DISCOVERY_TIMEOUT + 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.__xbee_device.get_protocol() == XBeeProtocol.DIGI_MESH: + discovery_timeout += XBeeNetwork.__DIGI_MESH_TIMEOUT_CORRECTION + elif self.__xbee_device.get_protocol() == XBeeProtocol.DIGI_POINT: + discovery_timeout += XBeeNetwork.__DIGI_POINT_TIMEOUT_CORRECTION + + 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.__xbee_device.get_parameter("SM")) == 7: + discovery_timeout += discovery_timeout + \ + (discovery_timeout * XBeeNetwork.__DIGI_MESH_SLEEP_TIMEOUT_CORRECTION) + except XBeeException as xe: + self.__xbee_device.log.exception(xe) + + return discovery_timeout + + 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. + + 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:: + | :meth:`.XBeeNetwork.__get_data_for_remote` + """ + if discovery_data is None: + return None + p = self.__xbee_device.get_protocol() + x16bit_addr, x64bit_addr, node_id = self.__get_data_for_remote(discovery_data) + + if p == XBeeProtocol.ZIGBEE: + return RemoteZigBeeDevice(self.__xbee_device, x64bit_addr, x16bit_addr, node_id) + elif p == XBeeProtocol.DIGI_MESH: + return RemoteDigiMeshDevice(self.__xbee_device, x64bit_addr, node_id) + elif p == XBeeProtocol.DIGI_POINT: + return RemoteDigiPointDevice(self.__xbee_device, x64bit_addr, node_id) + elif p == XBeeProtocol.RAW_802_15_4: + return RemoteRaw802Device(self.__xbee_device, x64bit_addr, x16bit_addr, node_id) + else: + return RemoteXBeeDevice(self.__xbee_device, x64bit_addr, x16bit_addr, node_id) + + def __get_data_for_remote(self, data): + """ + Extracts the :class:`.XBee16BitAddress` (bytes 0 and 1), the + :class:`.XBee64BitAddress` (bytes 2 to 9) and the node identifier + from the provided data. + + Args: + data (Bytearray): the data to extract information from. + + Returns: + Tuple (:class:`.XBee16BitAddress`, :class:`.XBee64BitAddress`, Bytearray): remote device information + """ + 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 + # node ID goes from 11 to the next 0x00. + while data[i] != 0x00: + i += 1 + node_id = data[11:i] + else: + # node ID starts at 10 if protocol is not 802.15.4 + i = 10 + # node id goes from 'i' to the next 0x00. + while data[i] != 0x00: + i += 1 + node_id = data[10:i] + return XBee16BitAddress(data[0:2]), XBee64BitAddress(data[2:10]), node_id.decode() + + +class ZigBeeNetwork(XBeeNetwork): + """ + This class represents a ZigBee 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 ``ZigBeeNetwork``. + + Args: + device (:class:`.ZigBeeDevice`): the local ZigBee device to get the network from. + + Raises: + ValueError: if ``device`` is ``None``. + """ + super(ZigBeeNetwork, self).__init__(device) + + +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(Raw802Network, self).__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(DigiMeshNetwork, self).__init__(device) + + +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``. + + Args: + device (:class:`.DigiPointDevice`): the local DigiPoint device to get the network from. + + Raises: + ValueError: if ``device`` is ``None``. + """ + super(DigiPointNetwork, self).__init__(device) diff --git a/digi/xbee/exception.py b/digi/xbee/exception.py new file mode 100644 index 0000000..1130d25 --- /dev/null +++ b/digi/xbee/exception.py @@ -0,0 +1,162 @@ +# 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. + + +class XBeeException(Exception): + """ + Generic XBee API exception. This class and its subclasses indicate + conditions that an application might want to catch. + + All functionality of this class is the inherited of `Exception + `_. + """ + pass + + +class CommunicationException(XBeeException): + """ + This exception will be thrown when any problem related to the communication + with the XBee device occurs. + + All functionality of this class is the inherited of `Exception + `_. + """ + pass + + +class ATCommandException(CommunicationException): + """ + This exception will be thrown when a response of a packet is not success or OK. + + All functionality of this class is the inherited of `Exception + `_. + """ + pass + + +class ConnectionException(XBeeException): + """ + This exception will be thrown when any problem related to the connection + with the XBee device occurs. + + All functionality of this class is the inherited of `Exception + `_. + """ + pass + + +class XBeeDeviceException(XBeeException): + """ + This exception will be thrown when any problem related to the XBee device + occurs. + + All functionality of this class is the inherited of `Exception + `_. + """ + pass + + +class InvalidConfigurationException(ConnectionException): + """ + 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 + `_. + """ + __DEFAULT_MESSAGE = "The configuration used to open the interface is invalid." + + def __init__(self, message=__DEFAULT_MESSAGE): + ConnectionException.__init__(self, message) + + +class InvalidOperatingModeException(ConnectionException): + """ + This exception will be thrown if the operating mode is different than + *OperatingMode.API_MODE* and *OperatingMode.API_MODE* + + All functionality of this class is the inherited of `Exception + `_. + """ + __DEFAULT_MESSAGE = "The operating mode of the XBee device is not supported by the library." + + def __init__(self, message=__DEFAULT_MESSAGE): + ConnectionException.__init__(self, message) + + @classmethod + def from_operating_mode(cls, operating_mode): + """ + Class constructor. + + Args: + operating_mode (:class:`.OperatingMode`): the operating mode that generates the exceptions. + """ + return cls("Unsupported operating mode: " + operating_mode.description) + + +class InvalidPacketException(CommunicationException): + """ + This exception will be thrown when there is an error parsing an API packet + from the input stream. + + All functionality of this class is the inherited of `Exception + `_. + """ + __DEFAULT_MESSAGE = "The XBee API packet is not properly formed." + + def __init__(self, message=__DEFAULT_MESSAGE): + CommunicationException.__init__(self, message) + + +class OperationNotSupportedException(XBeeDeviceException): + """ + This exception will be thrown when the operation performed is not supported + by the XBee device. + + 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." + + def __init__(self, message=__DEFAULT_MESSAGE): + XBeeDeviceException.__init__(self, message) + + +class TimeoutException(CommunicationException): + """ + This exception will be thrown when performing synchronous operations and + the configured time expires. + + All functionality of this class is the inherited of `Exception + `_. + """ + __DEFAULT_MESSAGE = "There was a timeout while executing the requested operation." + + def __init__(self, _message=__DEFAULT_MESSAGE): + CommunicationException.__init__(self) + + +class TransmitException(CommunicationException): + """ + This exception will be thrown when receiving a transmit status different + than *TransmitStatus.SUCCESS* after sending an XBee API packet. + + All functionality of this class is the inherited of `Exception + `_. + """ + __DEFAULT_MESSAGE = "There was a problem with a transmitted packet response (status not ok)" + + def __init__(self, _message=__DEFAULT_MESSAGE): + CommunicationException.__init__(self, _message) diff --git a/digi/xbee/io.py b/digi/xbee/io.py new file mode 100644 index 0000000..966d0c3 --- /dev/null +++ b/digi/xbee/io.py @@ -0,0 +1,647 @@ +# 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 +# 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.util import utils +from enum import Enum, unique +from digi.xbee.exception import OperationNotSupportedException + + +@unique +class IOLine(Enum): + """ + Enumerates the different IO lines that can be found in the XBee devices. + + Depending on the hardware and firmware of the device, the number of lines + that can be used as well as their functionality may vary. Refer to the + product manual to learn more about the IO lines of your XBee device. + """ + + DIO0_AD0 = ("DIO0/AD0", 0, "D0") + DIO1_AD1 = ("DIO1/AD1", 1, "D1") + DIO2_AD2 = ("DIO2/AD2", 2, "D2") + DIO3_AD3 = ("DIO3/AD3", 3, "D3") + DIO4_AD4 = ("DIO4/AD4", 4, "D4") + DIO5_AD5 = ("DIO5/AD5", 5, "D5") + DIO6 = ("DIO6", 6, "D6") + DIO7 = ("DIO7", 7, "D7") + DIO8 = ("DIO8", 8, "D8") + DIO9 = ("DIO9", 9, "D9") + DIO10_PWM0 = ("DIO10/PWM0", 10, "P0", "M0") + DIO11_PWM1 = ("DIO11/PWM1", 11, "P1", "M1") + DIO12 = ("DIO12", 12, "P2") + DIO13 = ("DIO13", 13, "P3") + DIO14 = ("DIO14", 14, "P4") + DIO15 = ("DIO15", 15, "P5") + DIO16 = ("DIO16", 16, "P6") + DIO17 = ("DIO17", 17, "P7") + DIO18 = ("DIO18", 18, "P8") + DIO19 = ("DIO19", 19, "P9") + + def __init__(self, description, index, at_command, pwm_command=None): + self.__description = description + self.__index = index + self.__at_command = at_command + self.__pwm_command = pwm_command + + def __get_description(self): + """ + Returns the description of the IOLine element. + + Returns: + String: the description of the IOLine element. + """ + return self.__description + + def __get_index(self): + """ + Returns the index of the IOLine element. + + Returns: + Integer: the index of the IOLine element. + """ + return self.__index + + def __get_at_command(self): + """ + Returns the AT command of the IOLine element. + + Returns: + String: the AT command of the IOLine element. + """ + return self.__at_command + + def __get_pwm_command(self): + """ + Returns the PWM AT command associated to the IOLine element. + + Returns: + String: the PWM AT command associated to the IO line, ``None`` if the IO line does not have a PWM + AT command associated. + """ + return self.__pwm_command + + def has_pwm_capability(self): + """ + Returns whether the IO line has PWM capability or not. + + Returns: + Boolean: ``True`` if the IO line has PWM capability, ``False`` otherwise. + """ + return self.__pwm_command is not None + + @classmethod + def get(cls, index): + """ + Returns the :class:`.IOLine` for the given index. + + Args: + index (Integer): Returns the :class:`.IOLine` for the given index. + + Returns: + :class:`.IOLine`: :class:`.IOLine` with the given code, ``None`` if there is not any line with that index. + """ + try: + return cls.lookupTable[index] + except KeyError: + return None + + description = property(__get_description) + """String. The IO line description.""" + + index = property(__get_index) + """Integer. The IO line index.""" + + at_command = property(__get_at_command) + """String. The IO line AT command.""" + + pwm_at_command = property(__get_pwm_command) + """String. The IO line PWM AT command.""" + + +IOLine.lookupTable = {x.index: x for x in IOLine} +IOLine.__doc__ += utils.doc_enum(IOLine) + + +@unique +class IOValue(Enum): + """ + Enumerates the possible values of a :class:`.IOLine` configured as digital I/O. + """ + + LOW = 4 + HIGH = 5 + + def __init__(self, code): + self.__code = code + + def __get_code(self): + """ + Returns the code of the IOValue element. + + Returns: + String: the code of the IOValue element. + """ + return self.__code + + @classmethod + def get(cls, code): + """ + Returns the IOValue for the given code. + + Args: + code (Integer): the code corresponding to the IOValue to get. + + Returns: + :class:`.IOValue`: the IOValue with the given code, ``None`` if there is not any IOValue with that code. + """ + try: + return cls.lookupTable[code] + except KeyError: + return None + + code = property(__get_code) + """Integer. The IO value code.""" + + +IOValue.lookupTable = {x.code: x for x in IOValue} + + +class IOSample(object): + """ + This class represents an IO Data Sample. The sample is built using the + the constructor. The sample contains an analog and digital mask indicating + which IO lines are configured with that functionality. + + Depending on the protocol the XBee device is executing, the digital and + analog masks are retrieved in separated bytes (2 bytes for the digital mask + and 1 for the analog mask) or merged contained (digital and analog masks + are contained in 2 bytes). + + Digital and analog channels masks + Indicates which digital and ADC IO lines are configured in the module. Each + bit corresponds to one digital or ADC IO line on the module: + :: + + bit 0 = DIO01 + bit 1 = DIO10 + bit 2 = DIO20 + bit 3 = DIO31 + bit 4 = DIO40 + bit 5 = DIO51 + bit 6 = DIO60 + bit 7 = DIO70 + bit 8 = DIO80 + bit 9 = AD00 + bit 10 = AD11 + bit 11 = AD21 + bit 12 = AD30 + bit 13 = AD40 + bit 14 = AD50 + bit 15 = NA0 + + Example: mask of 0x0C29 means DIO0, DIO3, DIO5, AD1 and AD2 enabled. + 0 0 0 0 1 1 0 0 0 0 1 0 1 0 0 1 + + Digital Channel Mask + Indicates which digital IO lines are configured in the module. Each bit + corresponds to one digital IO line on the module: + :: + + bit 0 = DIO0AD0 + bit 1 = DIO1AD1 + bit 2 = DIO2AD2 + bit 3 = DIO3AD3 + bit 4 = DIO4AD4 + bit 5 = DIO5AD5ASSOC + bit 6 = DIO6RTS + bit 7 = DIO7CTS + bit 8 = DIO8DTRSLEEP_RQ + bit 9 = DIO9ON_SLEEP + bit 10 = DIO10PWM0RSSI + bit 11 = DIO11PWM1 + bit 12 = DIO12CD + bit 13 = DIO13 + bit 14 = DIO14 + bit 15 = NA + + Example: mask of 0x040B means DIO0, DIO1, DIO2, DIO3 and DIO10 enabled. + 0 0 0 0 0 1 0 0 0 0 0 0 1 0 1 1 + + Analog Channel Mask + Indicates which lines are configured as ADC. Each bit in the analog + channel mask corresponds to one ADC line on the module. + :: + + bit 0 = AD0DIO0 + bit 1 = AD1DIO1 + bit 2 = AD2DIO2 + bit 3 = AD3DIO3 + bit 4 = AD4DIO4 + bit 5 = AD5DIO5ASSOC + bit 6 = NA + bit 7 = Supply Voltage Value + + Example: mask of 0x83 means AD0, and AD1 enabled. + 0 0 0 0 0 0 1 1 + """ + + __pattern = "[{key}: {value}], " + """Pattern for digital and analog values in __str__ method.""" + + __pattern2 = "[Power supply voltage: {value}], " + """Pattern for power supply voltage in __str__ method.""" + + __MIN_IO_SAMPLE_PAYLOAD_LENGTH = 5 + + def __init__(self, io_sample_payload): + """ + Class constructor. Instantiates a new :class:`.IOSample` object with the provided parameters. + + Args: + io_sample_payload (Bytearray): The payload corresponding to an IO sample. + + Raises: + ValueError: if io_sample_payload length is less than 5. + """ + # dictionaries + self.__digital_values_map = {} # {IOLine : IOValue} + self.__analog_values_map = {} # {IOLine : Integer} + + # Integers: + self.__digital_hsb_mask = None + self.__digital_lsb_mask = None + self.__digital_mask = None + self.__analog_mask = None + self.__digital_hsb_values = None + self.__digital_lsb_values = None + self.__digital_values = None + self.__power_supply_voltage = None + + if len(io_sample_payload) < IOSample.__MIN_IO_SAMPLE_PAYLOAD_LENGTH: + raise ValueError("IO sample payload must be longer than 4.") + + self.__io_sample_payload = io_sample_payload + + if len(self.__io_sample_payload) % 2 != 0: + self.__parse_raw_io_sample() + else: + self.__parse_io_sample() + + def __str__(self): + s = "{" + if self.has_digital_values(): + s += (''.join([self.__pattern.format(key=x, value=self.__digital_values_map[x]) for x in + self.__digital_values_map.keys()])) + if self.has_analog_values(): + s += (''.join([self.__pattern.format(key=x, value=self.__analog_values_map[x]) for x in + self.__analog_values_map.keys()])) + if self.has_power_supply_value(): + try: + s += self.__pattern2.format(value=self.__power_supply_voltage) + except OperationNotSupportedException: + pass + s += "}" + aux = s.replace(", }", "}") + return aux + + @staticmethod + def min_io_sample_payload(): + """ + Returns the minimum IO sample payload length. + + Returns: + Integer: the minimum IO sample payload length. + """ + return IOSample.__MIN_IO_SAMPLE_PAYLOAD_LENGTH + + def __parse_raw_io_sample(self): + """ + Parses the information contained in the IO sample bytes reading the + value of each configured DIO and ADC. + """ + data_index = 3 + + # Obtain the digital mask. # Available digital IOs in 802.15.4 + self.__digital_hsb_mask = self.__io_sample_payload[1] & 0x01 # 0 0 0 0 0 0 0 1 + self.__digital_lsb_mask = self.__io_sample_payload[2] & 0xFF # 1 1 1 1 1 1 1 1 + # Combine the masks. + self.__digital_mask = (self.__digital_hsb_mask << 8) + self.__digital_lsb_mask + # Obtain the analog mask. + self.__analog_mask = ((self.__io_sample_payload[1] << 8) # Available analog IOs in 802.15.4 + + self.__io_sample_payload[2]) & 0x7E00 # 0 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 + + # Read the digital values (if any). There are 9 possible digital lines in + # 802.15.4 protocol. The digital mask indicates if there is any digital + # line enabled to read its value. If 0, no digital values are received. + if self.__digital_mask > 0: + # Obtain the digital values. + self.__digital_hsb_values = self.__io_sample_payload[3] & 0x7F + self.__digital_lsb_values = self.__io_sample_payload[4] & 0xFF + # Combine the values. + self.__digital_values = (self.__digital_hsb_values << 8) + self.__digital_lsb_values + + for i in range(16): + if not utils.is_bit_enabled(self.__digital_mask, i): + continue + if utils.is_bit_enabled(self.__digital_values, i): + self.__digital_values_map[IOLine.get(i)] = IOValue.HIGH + else: + self.__digital_values_map[IOLine.get(i)] = IOValue.LOW + + # Increase the data index to read the analog values. + data_index += 2 + + # Read the analog values (if any). There are 6 possible analog lines. + # The analog mask indicates if there is any analog line enabled to read + # its value. If 0, no analog values are received. + adc_index = 9 + while (len(self.__io_sample_payload) - data_index) > 1 and adc_index < 16: + if not (utils.is_bit_enabled(self.__analog_mask, adc_index)): + adc_index += 1 + continue + + # 802.15.4 protocol does not provide power supply value, so get just the ADC data. + self.__analog_values_map[IOLine.get(adc_index - 9)] = \ + ((self.__io_sample_payload[data_index] & 0xFF) << 8) + (self.__io_sample_payload[data_index + 1] & 0xFF) + # Increase the data index to read the next analog values. + data_index += 2 + adc_index += 1 + + def __parse_io_sample(self): + """ + Parses the information contained in the IO sample bytes reading the + value of each configured DIO and ADC. + """ + data_index = 4 + + # Obtain the digital masks. # Available digital IOs + self.__digital_hsb_mask = self.__io_sample_payload[1] & 0x7F # 0 1 1 1 1 1 1 1 + self.__digital_lsb_mask = self.__io_sample_payload[2] & 0xFF # 1 1 1 1 1 1 1 1 + # Combine the masks. + self.__digital_mask = (self.__digital_hsb_mask << 8) + self.__digital_lsb_mask + # Obtain the analog mask. # Available analog IOs + self.__analog_mask = self.__io_sample_payload[3] & 0xBF # 1 0 1 1 1 1 1 1 + + # Read the digital values (if any). There are 16 possible digital lines. + # The digital mask indicates if there is any digital line enabled to read + # its value. If 0, no digital values are received. + if self.__digital_mask > 0: + # Obtain the digital values. + self.__digital_hsb_values = self.__io_sample_payload[4] & 0x7F + self.__digital_lsb_values = self.__io_sample_payload[5] & 0xFF + # Combine the values. + self.__digital_values = (self.__digital_hsb_values << 8) + self.__digital_lsb_values + + for i in range(16): + if not utils.is_bit_enabled(self.__digital_mask, i): + continue + if utils.is_bit_enabled(self.__digital_values, i): + self.__digital_values_map[IOLine.get(i)] = IOValue.HIGH + else: + self.__digital_values_map[IOLine.get(i)] = IOValue.LOW + # Increase the data index to read the analog values. + data_index += 2 + + # Read the analog values (if any). There are 6 possible analog lines. + # The analog mask indicates if there is any analog line enabled to read + # its value. If 0, no analog values are received. + adc_index = 0 + while (len(self.__io_sample_payload) - data_index) > 1 and adc_index < 8: + if not utils.is_bit_enabled(self.__analog_mask, adc_index): + adc_index += 1 + continue + # When analog index is 7, it means that the analog value corresponds to the power + # supply voltage, therefore this value should be stored in a different value. + if adc_index == 7: + self.__power_supply_voltage = ((self.__io_sample_payload[data_index] & 0xFF) << 8) + \ + (self.__io_sample_payload[data_index + 1] & 0xFF) + else: + self.__analog_values_map[IOLine.get(adc_index)] = \ + ((self.__io_sample_payload[data_index] & 0xFF) << 8) + \ + (self.__io_sample_payload[data_index + 1] & 0xFF) + # Increase the data index to read the next analog values. + data_index += 2 + adc_index += 1 + + def __get_digital_hsb_mask(self): + """ + Returns the High Significant Byte (HSB) of the digital mask. + + Returns: + Integer: the HSB of the digital mask. + """ + return self.__digital_hsb_mask + + def __get_digital_lsb_mask(self): + """ + Returns the Low Significant Byte (HSB) of the digital mask. + + Returns: + Integer: the LSB of the digital mask. + """ + return self.__digital_lsb_mask + + def __get_digital_mask(self): + """ + Returns the combined (HSB + LSB) of the digital mask. + + Returns: + Integer: the digital mask. + """ + return self.__digital_mask + + def __get_digital_values(self): + """ + Returns the digital values map. + + To verify if this sample contains a valid digital values, use the + method :meth:`.IOSample.has_digital_values`. + + Returns: + Dictionary: the digital values map. + """ + return self.__digital_values_map.copy() + + def __get_analog_mask(self): + """ + Returns the analog mas. + + Returns: + Integer: the analog mask. + """ + return self.__analog_mask + + def __get_analog_values(self): + """ + Returns the analog values map. + + To verify if this sample contains a valid analog values, use the + method :meth:`.IOSample.has_analog_values`. + + Returns: + Dictionary: the analog values map. + """ + return self.__analog_values_map.copy() + + def __get_power_supply_value(self): + """ + Returns the value of the power supply voltage. + + To verify if this sample contains the power supply voltage, use the + method :meth:`.IOSample.has_power_supply_value`. + + Returns: + Integer: the power supply value, ``None`` if the sample does not contain power supply value. + """ + return self.__power_supply_voltage if self.has_power_supply_value() else None + + def has_digital_values(self): + """ + Checks whether the IOSample has digital values or not. + + Returns: + Boolean: ``True`` if the sample has digital values, ``False`` otherwise. + """ + return len(self.__digital_values_map) > 0 + + def has_digital_value(self, io_line): + """ + Returns whether th IO sample contains a digital value for the provided IO line or not. + + Args: + io_line (:class:`IOLine`): The IO line to check if it has a digital value. + + Returns: + Boolean: ``True`` if the given IO line has a digital value, ``False`` otherwise. + """ + return io_line in self.__digital_values_map.keys() + + def has_analog_value(self, io_line): + """ + Returns whether the given IOLine has an analog value or not. + + Returns: + Boolean: ``True`` if the given IOLine has an analog value, ``False`` otherwise. + """ + return io_line in self.__analog_values_map.keys() + + def has_analog_values(self): + """ + Returns whether the {@code IOSample} has analog values or not. + + Returns: + Boolean. ``True`` if there are analog values, ``False`` otherwise. + """ + return len(self.__analog_values_map) > 0 + + def has_power_supply_value(self): + """ + Returns whether the IOSample has power supply value or not. + + Returns: + Boolean. ``True`` if the given IOLine has a power supply value, ``False`` otherwise. + """ + return utils.is_bit_enabled(self.__analog_mask, 7) + + def get_digital_value(self, io_line): + """ + Returns the digital value of the provided IO line. + + To verify if this sample contains a digital value for the given :class:`.IOLine`, + use the method :meth:`.IOSample.has_digital_value`. + + Args: + io_line (:class:`.IOLine`): The IO line to get its digital value. + + Returns: + :class:`.IOValue`: The :class:`.IOValue` of the given IO line or ``None`` if the + IO sample does not contain a digital value for the given IO line. + + .. seealso:: + | :class:`.IOLine` + | :class:`.IOValue` + """ + if io_line in self.__digital_values_map: + return self.__digital_values_map[io_line] + return None + + def get_analog_value(self, io_line): + """ + Returns the analog value of the provided IO line. + + To verify if this sample contains an analog value for the given :class:`.IOLine`, + use the method :meth:`.IOSample.has_analog_value`. + + Args: + io_line (:class:`.IOLine`): The IO line to get its analog value. + + Returns: + Integer: The analog value of the given IO line or ``None`` if the IO sample does not + contain an analog value for the given IO line. + + .. seealso:: + | :class:`.IOLine` + """ + if io_line in self.__analog_values_map: + return self.__analog_values_map[io_line] + return None + + digital_hsb_mask = property(__get_digital_hsb_mask) + """Integer. High Significant Byte (HSB) of the digital mask.""" + + digital_lsb_mask = property(__get_digital_lsb_mask) + """Integer. Low Significant Byte (LSB) of the digital mask.""" + + digital_mask = property(__get_digital_mask) + """Integer. Digital mask of the IO sample.""" + + analog_mask = property(__get_analog_mask) + """Integer. Analog mask of the IO sample.""" + + digital_values = property(__get_digital_values) + """Dictionary. Digital values map.""" + + analog_values = property(__get_analog_values) + """Dictionary. Analog values map.""" + + power_supply_value = property(__get_power_supply_value) + """Integer. Power supply value, ``None`` if the sample does not contain power supply value.""" + + +class IOMode(Enum): + """ + Enumerates the different Input/Output modes that an IO line can be + configured with. + """ + + DISABLED = 0 + """Disabled""" + + SPECIAL_FUNCTIONALITY = 1 + """Firmware special functionality""" + + PWM = 2 + """PWM output""" + + ADC = 2 + """Analog to Digital Converter""" + + DIGITAL_IN = 3 + """Digital input""" + + DIGITAL_OUT_LOW = 4 + """Digital output, Low""" + + DIGITAL_OUT_HIGH = 5 + """Digital output, High""" diff --git a/digi/xbee/models/__init__.py b/digi/xbee/models/__init__.py new file mode 100644 index 0000000..8ea10d3 --- /dev/null +++ b/digi/xbee/models/__init__.py @@ -0,0 +1,13 @@ +# 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. diff --git a/digi/xbee/models/accesspoint.py b/digi/xbee/models/accesspoint.py new file mode 100644 index 0000000..60a7fee --- /dev/null +++ b/digi/xbee/models/accesspoint.py @@ -0,0 +1,224 @@ +# 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 enum import Enum, unique +from digi.xbee.util import utils + + +class AccessPoint(object): + """ + This class represents an Access Point for the Wi-Fi protocol. It contains + SSID, the encryption type and the link quality between the Wi-Fi module and + the access point. + + This class is used within the library to list the access points + and connect to a specific one in the Wi-Fi protocol. + + .. seealso:: + | :class:`.WiFiEncryptionType` + """ + + __ERROR_CHANNEL = "Channel cannot be negative." + __ERROR_SIGNAL_QUALITY = "Signal quality must be between 0 and 100." + + def __init__(self, ssid, encryption_type, channel=0, signal_quality=0): + """ + Class constructor. Instantiates a new :class:`.AccessPoint` object with the provided parameters. + + Args: + ssid (String): the SSID of the access point. + encryption_type (:class:`.WiFiEncryptionType`): the encryption type configured in the access point. + channel (Integer, optional): operating channel of the access point. Optional. + signal_quality (Integer, optional): signal quality with the access point in %. Optional. + + Raises: + ValueError: if length of ``ssid`` is 0. + ValueError: if ``channel`` is less than 0. + ValueError: if ``signal_quality`` is less than 0 or greater than 100. + + .. seealso:: + | :class:`.WiFiEncryptionType` + """ + if len(ssid) == 0: + raise ValueError("SSID cannot be empty.") + if channel < 0: + raise ValueError(self.__ERROR_CHANNEL) + if signal_quality < 0 or signal_quality > 100: + raise ValueError(self.__ERROR_SIGNAL_QUALITY) + + self.__ssid = ssid + self.__encryption_type = encryption_type + self.__channel = channel + self.__signal_quality = signal_quality + + def __str__(self): + """ + Returns the string representation of the access point. + + Returns: + String: representation of the access point. + """ + return "%s (%s) - CH: %s - Signal: %s%%" % (self.__ssid, self.__encryption_type.description, + self.__channel, self.__signal_quality) + + def __get_ssid(self): + """ + Returns the SSID of the access point. + + Returns: + String: the SSID of the access point. + """ + return self.__ssid + + def __get_encryption_type(self): + """ + Returns the encryption type of the access point. + + Returns: + :class:`.WiFiEncryptionType`: the encryption type of the access point. + + .. seealso:: + | :class:`.WiFiEncryptionType` + """ + return self.__encryption_type + + def __get_channel(self): + """ + Returns the channel of the access point. + + Returns: + Integer: the channel of the access point. + + .. seealso:: + | :func:`.AccessPoint.set_channel` + """ + return self.__channel + + def __set_channel(self, channel): + """ + Sets the channel of the access point. + + Args: + channel (Integer): the new channel of the access point + + Raises: + ValueError: if ``channel`` is less than 0. + + .. seealso:: + | :func:`.AccessPoint.get_channel` + """ + if channel < 0: + raise ValueError(self.__ERROR_CHANNEL) + self.__channel = channel + + def __get_signal_quality(self): + """ + Returns the signal quality with the access point in %. + + Returns: + Integer: the signal quality with the access point in %. + + .. seealso:: + | :func:`.AccessPoint.__set_signal_quality` + """ + return self.__signal_quality + + def __set_signal_quality(self, signal_quality): + """ + Sets the channel of the access point. + + Args: + signal_quality (Integer): the new signal quality with the access point. + + Raises: + ValueError: if ``signal_quality`` is less than 0 or greater than 100. + + .. seealso:: + | :func:`.AccessPoint.__get_signal_quality` + """ + if signal_quality < 0 or signal_quality > 100: + raise ValueError(self.__ERROR_SIGNAL_QUALITY) + self.__signal_quality = signal_quality + + ssid = property(__get_ssid) + """String. SSID of the access point.""" + + encryption_type = property(__get_encryption_type) + """:class:`.WiFiEncryptionType`. Encryption type of the access point.""" + + channel = property(__get_channel, __set_channel) + """String. Channel of the access point.""" + + signal_quality = property(__get_signal_quality, __set_signal_quality) + """String. The signal quality with the access point in %.""" + + +@unique +class WiFiEncryptionType(Enum): + """ + Enumerates the different Wi-Fi encryption types. + """ + NONE = (0, "No security") + WPA = (1, "WPA (TKIP) security") + WPA2 = (2, "WPA2 (AES) security") + WEP = (3, "WEP security") + + def __init__(self, code, description): + self.__code = code + self.__description = description + + def __get_code(self): + """ + Returns the code of the WiFiEncryptionType element. + + Returns: + Integer: the code of the WiFiEncryptionType element. + """ + return self.__code + + def __get_description(self): + """ + Returns the description of the WiFiEncryptionType element. + + Returns: + String: the description of the WiFiEncryptionType element. + """ + return self.__description + + @classmethod + def get(cls, code): + """ + Returns the Wi-Fi encryption type for the given code. + + Args: + code (Integer): the code of the Wi-Fi encryption type to get. + + Returns: + :class:`.WiFiEncryptionType`: the WiFiEncryptionType with the given code, ``None`` if there is + not any Wi-Fi encryption type with the provided code. + """ + try: + return cls.lookupTable[code] + except KeyError: + return None + + code = property(__get_code) + """Integer. The Wi-Fi encryption type code.""" + + description = property(__get_description) + """String. The Wi-Fi encryption type description.""" + + +WiFiEncryptionType.lookupTable = {x.code: x for x in WiFiEncryptionType} +WiFiEncryptionType.__doc__ += utils.doc_enum(WiFiEncryptionType) diff --git a/digi/xbee/models/address.py b/digi/xbee/models/address.py new file mode 100644 index 0000000..628a423 --- /dev/null +++ b/digi/xbee/models/address.py @@ -0,0 +1,442 @@ +# 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. + +import re +from digi.xbee.util import utils + + +class XBee16BitAddress(object): + """ + This class represent a 16-bit network address. + + This address is only applicable for: + + 1. 802.15.4 + 2. ZigBee + 3. ZNet 2.5 + 4. XTend (Legacy) + + DigiMesh and Point-to-multipoint does not support 16-bit addressing. + + Each device has its own 16-bit address which is unique in the network. + It is automatically assigned when the radio joins the network for ZigBee + and Znet 2.5, and manually configured in 802.15.4 radios. + + | Attributes: + | **COORDINATOR_ADDRESS** (XBee16BitAddress): 16-bit address reserved for the coordinator. + | **BROADCAST_ADDRESS** (XBee16BitAddress): 16-bit broadcast address. + | **UNKNOWN_ADDRESS** (XBee16BitAddress): 16-bit unknown address. + | **PATTERN** (String): Pattern for the 16-bit address string: ``(0[xX])?[0-9a-fA-F]{1,4}`` + + """ + + PATTERN = "(0[xX])?[0-9a-fA-F]{1,4}" + __REGEXP = re.compile(PATTERN) + + COORDINATOR_ADDRESS = None + """ + 16-bit address reserved for the coordinator (value: 0000). + """ + + BROADCAST_ADDRESS = None + """ + 16-bit broadcast address (value: FFFF). + """ + + UNKNOWN_ADDRESS = None + """ + 16-bit unknown address (value: FFFE). + """ + + def __init__(self, address): + """ + Class constructor. Instantiates a new :class:`.XBee16BitAddress` object with the provided parameters. + + Args: + address (Bytearray): address as byte array. Must be 1-2 digits. + + Raises: + TypeError: if ``address`` is ``None``. + ValueError: if ``address`` has less than 1 byte or more than 2. + """ + 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") + + if len(address) == 1: + address.insert(0, 0) + self.__address = address + + @classmethod + def from_hex_string(cls, address): + """ + Class constructor. Instantiates a new :`.XBee16BitAddress` object from the provided hex string. + + Args: + address (String): String containing the address. Must be made by + hex. digits without blanks. Minimum 1 character, maximum 4 (16-bit). + + Raises: + ValueError: if ``address`` has less than 1 character. + ValueError: if ``address`` contains non-hexadecimal characters. + """ + if len(address) < 1: + raise ValueError("Address must contain at least 1 digit") + if not XBee16BitAddress.__REGEXP.match(address): + raise ValueError("Address must match with PATTERN") + return cls(utils.hex_string_to_bytes(address)) + + @classmethod + def from_bytes(cls, hsb, lsb): + """ + Class constructor. Instantiates a new :`.XBee16BitAddress` object from the provided high significant byte and + low significant byte. + + Args: + hsb (Integer): high significant byte of the address. + lsb (Integer): low significant byte of the address. + + Raises: + ValueError: if ``lsb`` is less than 0 or greater than 255. + ValueError: if ``hsb`` is less than 0 or greater than 255. + """ + if hsb > 255 or hsb < 0: + 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])) + + def __get_item__(self, index): + """ + Operator [] + + Args: + index (Integer): index to be accessed. + + Returns: + Integer. 'index' component of the address bytearray. + """ + return self.__address.__get_item__(index) + + def __str__(self): + """ + Called by the str() built-in function and by the print statement to compute the "informal" string + representation of an object. This differs from __repr__() in that it does not have to be a valid Python + expression: a more convenient or concise representation may be used instead. + + Returns: + String: "informal" representation of this XBee16BitAddress. + """ + return utils.hex_to_string(self.__address) + + def __eq__(self, other): + """ + Operator == + + Args: + other (:class`.XBee16BitAddress`): another XBee16BitAddress object. + + 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): + """ + Gets an iterator class of this instance address. + + Returns: + Iterator: iterator of this address. + """ + return self.__address.__iter__() + + def get_hsb(self): + """ + Returns the high part of the bytearray (component 0). + + Returns: + Integer: high part of the bytearray. + """ + return self.__address[0] + + def get_lsb(self): + """ + Returns the low part of the bytearray (component 1). + + Returns: + Integer: low part of the bytearray. + """ + return self.__address[1] + + def __get_value(self): + """ + Returns a bytearray representation of this XBee16BitAddress. + + Returns: + Bytearray: bytearray representation of this XBee16BitAddress. + """ + return bytearray(self.__address) + + address = property(__get_value) + """Bytearray. Bytearray representation of this XBee16BitAddress.""" + + +XBee16BitAddress.COORDINATOR_ADDRESS = XBee16BitAddress.from_hex_string("0000") +XBee16BitAddress.BROADCAST_ADDRESS = XBee16BitAddress.from_hex_string("FFFF") +XBee16BitAddress.UNKNOWN_ADDRESS = XBee16BitAddress.from_hex_string("FFFE") + + +class XBee64BitAddress(object): + """ + This class represents a 64-bit address (also known as MAC address). + + The 64-bit address is a unique device address assigned during manufacturing. + This address is unique to each physical device. + """ + __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 + """ + 64-bit address reserved for the coordinator (value: 0000000000000000). + """ + + BROADCAST_ADDRESS = None + """ + 64-bit broadcast address (value: 000000000000FFFF). + """ + + UNKNOWN_ADDRESS = None + """ + 64-bit unknown address (value: FFFFFFFFFFFFFFFF). + """ + + def __init__(self, address): + """ + Class constructor. Instantiates a new :class:`.XBee64BitAddress` object with the provided parameters. + + Args: + address (Bytearray): the XBee 64-bit address as byte array. + + Raise: + ValueError: if length of ``address`` is less than 1 or greater than 8. + """ + 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") + + self.__address = bytearray(8) + diff = 8 - len(address) + for i in range(diff): + self.__address[i] = 0 + for i in range(diff, 8): + self.__address[i] = address[i - diff] + + @classmethod + def from_hex_string(cls, address): + """ + Class constructor. Instantiates a new :class:`.XBee64BitAddress` object from the provided hex string. + + Args: + address (String): The XBee 64-bit address as a string. + + Raises: + ValueError: if the address' length is less than 1 or does not match + with the pattern: ``(0[xX])?[0-9a-fA-F]{1,16}``. + """ + 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.__XBEE_64_BIT_ADDRESS_PATTERN) + + return cls(utils.hex_string_to_bytes(address)) + + @classmethod + def from_bytes(cls, *args): + """ + Class constructor. Instantiates a new :class:`.XBee64BitAddress` object from the provided bytes. + + Args: + args (8 Integers): 8 integers that represent the bytes 1 to 8 of this XBee64BitAddress. + + Raises: + ValueError: if the amount of arguments is not 8 or if any of the arguments is not between 0 and 255. + """ + if len(args) != 8: + raise ValueError("Number of bytes given as arguments must be 8.") + 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)) + + def __str__(self): + """ + Called by the str() built-in function and by the print statement to compute the "informal" string + representation of an object. This differs from __repr__() in that it does not have to be a valid Python + expression: a more convenient or concise representation may be used instead. + + Returns: + String: "informal" representation of this XBee64BitAddress. + """ + return "".join(["%02X" % i for i in self.__address]) + + def __eq__(self, other): + """ + Operator == + + Args: + other: another XBee64BitAddress. + + 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, XBee64BitAddress): + return False + return self.__address.__eq__(other.__address) + + def __iter__(self): + """ + Gets an iterator class of this instance address. + + Returns: + Iterator: iterator of this address. + """ + return self.__address.__iter__() + + def __get_value(self): + """ + Returns a bytearray representation of this XBee64BitAddress. + + Returns: + Bytearray: bytearray representation of this XBee64BitAddress. + """ + return bytearray(self.__address) + + address = property(__get_value) + """Bytearray. Bytearray representation of this XBee64BitAddress.""" + + +XBee64BitAddress.COORDINATOR_ADDRESS = XBee64BitAddress.from_hex_string("0000") +XBee64BitAddress.BROADCAST_ADDRESS = XBee64BitAddress.from_hex_string("FFFF") +XBee64BitAddress.UNKNOWN_ADDRESS = XBee64BitAddress.from_hex_string("F"*16) + + +class XBeeIMEIAddress(object): + """ + This class represents an IMEI address used by cellular devices. + + This address is only applicable for Cellular protocol. + """ + + __IMEI_PATTERN = "^\d{0,15}$" + __REGEXP = re.compile(__IMEI_PATTERN) + + def __init__(self, address): + """ + Class constructor. Instantiates a new :`.XBeeIMEIAddress` object with the provided parameters. + + Args: + address (Bytearray): The XBee IMEI address as byte array. + + Raises: + ValueError: if ``address`` is ``None``. + ValueError: if length of ``address`` greater than 8. + """ + if address is None: + raise ValueError("IMEI address cannot be None") + if len(address) > 8: + raise ValueError("IMEI address cannot be longer than 8 bytes") + + self.__address = self.__generate_byte_array(address) + + @classmethod + def from_string(cls, address): + """ + Class constructor. Instantiates a new :`.XBeeIMEIAddress` object from the provided string. + + Args: + address (String): The XBee IMEI address as a string. + + Raises: + ValueError: if ``address`` is ``None``. + ValueError: if ``address`` does not match the pattern: ``^\d{0,15}$``. + """ + 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.__IMEI_PATTERN) + + return cls(utils.hex_string_to_bytes(address)) + + @staticmethod + def __generate_byte_array(byte_address): + """ + Generates the IMEI byte address based on the given byte array. + + Args: + byte_address (Bytearray): the byte array used to generate the final IMEI byte address. + + Returns: + Bytearray: the IMEI in byte array format. + """ + return bytearray(8 - len(byte_address)) + byte_address # Pad zeros in the MSB of the address + + def __get_value(self): + """ + Returns a string representation of this XBeeIMEIAddress. + + Returns: + String: the IMEI address in string format. + """ + return "".join(["%02X" % i for i in self.__address])[1:] + + def __str__(self): + """ + Called by the str() built-in function and by the print statement to compute the "informal" string + representation of an object. This differs from __repr__() in that it does not have to be a valid Python + expression: a more convenient or concise representation may be used instead. + + Returns: + String: "informal" representation of this XBeeIMEIAddress. + """ + return self.__get_value() + + def __eq__(self, other): + """ + Operator == + + Args: + other (:class:`.XBeeIMEIAddress`): another XBeeIMEIAddress. + + 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, XBeeIMEIAddress): + return False + return self.__address.__eq__(other.__address) + + address = property(__get_value) + """String. String representation of this XBeeIMEIAddress.""" diff --git a/digi/xbee/models/atcomm.py b/digi/xbee/models/atcomm.py new file mode 100644 index 0000000..6f5a9c1 --- /dev/null +++ b/digi/xbee/models/atcomm.py @@ -0,0 +1,273 @@ +# 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 digi.xbee.models.status import ATCommandStatus +from enum import Enum, unique +from digi.xbee.util import utils + + +@unique +class ATStringCommand(Enum): + """ + This class represents basic AT commands. + + | Inherited properties: + | **name** (String): name (ID) of this ATStringCommand. + | **value** (String): value of this ATStringCommand. + """ + + NI = "NI" + KY = "KY" + NK = "NK" + ZU = "ZU" + ZV = "ZV" + CC = "CC" + + def __init__(self, command): + self.__command = command + + def __get_command(self): + return self.__command + + command = property(__get_command) + """String. AT Command alias.""" + + +ATStringCommand.__doc__ += utils.doc_enum(ATStringCommand) + + +@unique +class SpecialByte(Enum): + """ + Enumerates all the special bytes of the XBee protocol that must be escaped + when working on API 2 mode. + + | Inherited properties: + | **name** (String): name (ID) of this SpecialByte. + | **value** (String): the value of this SpecialByte. + """ + + ESCAPE_BYTE = 0x7D + HEADER_BYTE = 0x7E + XON_BYTE = 0x11 + XOFF_BYTE = 0x13 + + def __init__(self, code): + self.__code = code + + def __get_code(self): + """ + Returns the code of the SpecialByte element. + + Returns: + Integer: the code of the SpecialByte element. + """ + return self.__code + + @classmethod + def get(cls, value): + """ + Returns the special byte for the given value. + + Args: + value (Integer): value associated to the special byte. + + Returns: + SpecialByte: SpecialByte with the given value. + """ + return SpecialByte.lookupTable[value] + + @staticmethod + def escape(value): + """ + Escapes the byte by performing a XOR operation with 0x20 value. + + Args: + value (Integer): value to escape. + + Returns: + Integer: value ^ 0x20 (escaped). + """ + return value ^ 0x20 + + @staticmethod + def is_special_byte(value): + """ + Checks whether the given byte is special or not. + + Args: + value (Integer): byte to check. + + Returns: + Boolean: ``True`` if value is a special byte, ``False`` in other case. + """ + return True if value in [i.value for i in SpecialByte] else False + + code = property(__get_code) + """Integer. The special byte code.""" + + +SpecialByte.lookupTable = {x.code: x for x in SpecialByte} +SpecialByte.__doc__ += utils.doc_enum(SpecialByte) + + +class ATCommand(object): + """ + This class represents an AT command used to read or set different properties + of the XBee device. + + AT commands can be sent directly to the connected device or to remote + devices and may have parameters. + + After executing an AT Command, an AT Response is received from the device. + """ + + def __init__(self, command, parameter=None): + """ + Class constructor. Instantiates a new :class:`.ATCommand` object with the provided parameters. + + Args: + command (String): AT Command, must have length 2. + parameter (String or Bytearray, optional): The AT parameter value. Defaults to ``None``. Optional. + + Raises: + ValueError: if command length is not 2. + """ + if len(command) != 2: + raise ValueError("Command length must be 2.") + + self.__command = command + self.__set_parameter(parameter) + + def __str__(self): + """ + Returns a string representation of this ATCommand. + + Returns: + String: representation of this ATCommand. + """ + return "Command: " + self.__command + "\n" + "Parameter: " + str(self.__parameter) + + def __len__(self): + """ + Returns the length of this ATCommand. + + Returns: + Integer: length of command + length of parameter. + """ + if self.__parameter: + return len(self.__command) + len(self.__parameter) + else: + return len(self.__command) + + def __get_command(self): + """ + Returns the AT command. + + Returns: + ATCommand: the AT command. + """ + return self.__command + + def __get_parameter(self): + """ + Returns the AT command parameter. + + Returns: + Bytearray: the AT command parameter. ``None`` if there is no parameter. + """ + return self.__parameter + + def get_parameter_string(self): + """ + Returns this ATCommand parameter as a String. + + Returns: + String: this ATCommand parameter. ``None`` if there is no parameter. + """ + return self.__parameter.decode() if self.__parameter else None + + def __set_parameter(self, parameter): + """ + Sets the AT command parameter. + + Args: + parameter (Bytearray): the parameter to be set. + """ + if isinstance(parameter, str): + self.__parameter = bytearray(parameter, 'utf8') + else: + self.__parameter = parameter + + command = property(__get_command) + """String. The AT command""" + + parameter = property(__get_parameter, __set_parameter) + """Bytearray. The AT command parameter""" + + +class ATCommandResponse(object): + """ + This class represents the response of an AT Command sent by the connected + XBee device or by a remote device after executing an AT Command. + """ + + def __init__(self, command, response=None, status=ATCommandStatus.OK): + """ + Class constructor. + + Args: + command (ATCommand): The AT command that generated the response. + response (bytearray, optional): The command response. Default to ``None``. + status (ATCommandStatus, optional): The AT command status. Default to ATCommandStatus.OK + """ + self.__atCommand = command + self.__response = response + self.__comm_status = status + + def __get_command(self): + """ + Returns the AT command. + + Returns: + ATCommand: the AT command. + """ + return self.__atCommand + + def __get_response(self): + """ + Returns the AT command response. + + Returns: + Bytearray: the AT command response. + """ + return self.__response + + def __get_status(self): + """ + Returns the AT command response status. + + Returns: + ATCommandStatus: The AT command response status. + """ + return self.__comm_status + + command = property(__get_command) + """String. The AT command.""" + + response = property(__get_response) + """Bytearray. The AT command response data.""" + + status = property(__get_status) + """ATCommandStatus. The AT command response status.""" diff --git a/digi/xbee/models/hw.py b/digi/xbee/models/hw.py new file mode 100644 index 0000000..9d75562 --- /dev/null +++ b/digi/xbee/models/hw.py @@ -0,0 +1,145 @@ +# 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 enum import Enum, unique +from digi.xbee.util import utils + + +@unique +class HardwareVersion(Enum): + """ + This class lists all hardware versions. + + | Inherited properties: + | **name** (String): The name of this HardwareVersion. + | **value** (Integer): The ID of this HardwareVersion. + """ + X09_009 = (0x01, "X09-009") + X09_019 = (0x02, "X09-019") + XH9_009 = (0x03, "XH9-009") + XH9_019 = (0x04, "XH9-019") + X24_009 = (0x05, "X24-009") + X24_019 = (0x06, "X24-019") + X09_001 = (0x07, "X09-001") + XH9_001 = (0x08, "XH9-001") + X08_004 = (0x09, "X08-004") + XC09_009 = (0x0A, "XC09-009") + XC09_038 = (0x0B, "XC09-038") + X24_038 = (0x0C, "X24-038") + X09_009_TX = (0x0D, "X09-009-TX") + X09_019_TX = (0x0E, "X09-019-TX") + XH9_009_TX = (0x0F, "XH9-009-TX") + XH9_019_TX = (0x10, "XH9-019-TX") + X09_001_TX = (0x11, "X09-001-TX") + XH9_001_TX = (0x12, "XH9-001-TX") + XT09B_XXX = (0x13, "XT09B-xxx (Attenuator version)") + XT09_XXX = (0x14, "XT09-xxx") + XC08_009 = (0x15, "XC08-009") + XC08_038 = (0x16, "XC08-038") + XB24_AXX_XX = (0x17, "XB24-Axx-xx") + XBP24_AXX_XX = (0x18, "XBP24-Axx-xx") + XB24_BXIX_XXX = (0x19, "XB24-BxIx-xxx and XB24-Z7xx-xxx") + XBP24_BXIX_XXX = (0x1A, "XBP24-BxIx-xxx and XBP24-Z7xx-xxx") + XBP09_DXIX_XXX = (0x1B, "XBP09-DxIx-xxx Digi Mesh") + XBP09_XCXX_XXX = (0x1C, "XBP09-XCxx-xxx: S3 XSC Compatibility") + XBP08_DXXX_XXX = (0x1D, "XBP08-Dxx-xxx 868MHz") + XBP24B = (0x1E, "XBP24B: Low cost ZB PRO and PLUS S2B") + XB24_WF = (0x1F, "XB24-WF: XBee 802.11 (Redpine module)") + AMBER_MBUS = (0x20, "??????: M-Bus module made by Amber") + XBP24C = (0x21, "XBP24C: XBee PRO SMT Ember 357 S2C PRO") + XB24C = (0x22, "XB24C: XBee SMT Ember 357 S2C") + XSC_GEN3 = (0x23, "XSC_GEN3: XBP9 XSC 24 dBm") + SRD_868_GEN3 = (0x24, "SDR_868_GEN3: XB8 12 dBm") + ABANDONATED = (0x25, "Abandonated") + SMT_900LP = (0x26, "900LP (SMT): 900LP on 'S8 HW'") + WIFI_ATHEROS = (0x27, "WiFi Atheros (TH-DIP) XB2S-WF") + SMT_WIFI_ATHEROS = (0x28, "WiFi Atheros (SMT) XB2B-WF") + SMT_475LP = (0x29, "475LP (SMT): Beta 475MHz") + XBEE_CELL_TH = (0x2A, "XBee-Cell (TH): XBee Cellular") + XLR_MODULE = (0x2B, "XLR Module") + XB900HP_NZ = (0x2C, "XB900HP (New Zealand): XB9 NZ HW/SW") + XBP24C_TH_DIP = (0x2D, "XBP24C (TH-DIP): XBee PRO DIP") + XB24C_TH_DIP = (0x2E, "XB24C (TH-DIP): XBee DIP") + XLR_BASEBOARD = (0x2F, "XLR Baseboard") + XBP24C_S2C_SMT = (0x30, "XBee PRO SMT") + SX_PRO = (0x31, "SX Pro") + S2D_SMT_PRO = (0x32, "XBP24D: S2D SMT PRO") + S2D_SMT_REG = (0x33, "XB24D: S2D SMT Reg") + S2D_TH_PRO = (0x34, "XBP24D: S2D TH PRO") + S2D_TH_REG = (0x35, "XB24D: S2D TH Reg") + SX = (0x3E, "SX") + XTR = (0x3F, "XTR") + CELLULAR_CAT1_LTE_VERIZON = (0x40, "XBee Cellular Cat 1 LTE Verizon") + XBEE3 = (0x41, "XBEE3") + XBEE3_SMT = (0x42, "XBEE3 SMT") + XBEE3_TH = (0x43, "XBEE3 TH") + CELLULAR_3G = (0x44, "XBee Cellular 3G") + XB8X = (0x45, "XB8X") + CELLULAR_LTE_VERIZON = (0x46, "XBee Cellular LTE-M Verizon") + CELLULAR_LTE_ATT = (0x47, "XBee Cellular LTE-M AT&T") + CELLULAR_NBIOT_EUROPE = (0x48, "XBee Cellular NBIoT Europe") + CELLULAR_3_CAT1_LTE_ATT = (0x49, "XBee Cellular 3 Cat 1 LTE AT&T") + CELLULAR_3_LTE_M_VERIZON = (0x4A, "XBee Cellular 3 LTE-M Verizon") + CELLULAR_3_LTE_M_ATT = (0x4B, "XBee Cellular 3 LTE-M AT&T") + + def __init__(self, code, description): + self.__code = code + self.__description = description + + def __get_code(self): + """ + Returns the code of the HardwareVersion element. + + Returns: + Integer: the code of the HardwareVersion element. + """ + return self.__code + + def __get_description(self): + """ + Returns the description of the HardwareVersion element. + + Returns: + String: the description of the HardwareVersion element. + """ + return self.__description + + @classmethod + def get(cls, code): + """ + Returns the HardwareVersion for the given code. + + Args: + code (Integer): the code of the hardware version to get. + + Returns: + :class:`HardwareVersion`: the HardwareVersion with the given code, ``None`` if there is not a + HardwareVersion with that code. + """ + try: + return cls.lookupTable[code] + except KeyError: + return None + + code = property(__get_code) + """Integer. The hardware version code.""" + + description = property(__get_description) + """String. The hardware version description.""" + + +# Class variable of HardwareVersion. Dictionary for +# search HardwareVersion by code. +HardwareVersion.lookupTable = {x.code: x for x in HardwareVersion} +HardwareVersion.__doc__ += utils.doc_enum(HardwareVersion) diff --git a/digi/xbee/models/message.py b/digi/xbee/models/message.py new file mode 100644 index 0000000..ff6d198 --- /dev/null +++ b/digi/xbee/models/message.py @@ -0,0 +1,457 @@ +# 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 re + + +class XBeeMessage(object): + """ + This class represents a XBee message, which is formed by a :class:`.RemoteXBeeDevice` + (the sender) and some data (the data sent) as a bytearray. + """ + + def __init__(self, data, remote_device, timestamp, broadcast=False): + """ + Class constructor. + + Args: + data (Bytearray): the data sent. + remote_device (:class:`.RemoteXBeeDevice`): the sender. + broadcast (Boolean, optional, default=``False``): flag indicating whether the message is + broadcast (``True``) or not (``False``). Optional. + timestamp: instant of time when the message was received. + """ + self.__data = data + self.__remote_device = remote_device + self.__is_broadcast = broadcast + self.__timestamp = timestamp + + def __get_data(self): + """ + Returns the data of the message. + + Returns: + Bytearray: the data of the message. + """ + return self.__data + + def __get_remote_device(self): + """ + Returns the device which has sent the message. + + Returns: + :class:`.RemoteXBeeDevice`: the device which has sent the message. + """ + return self.__remote_device + + def __is_broadcast(self): + """ + Returns whether the message is broadcast or not. + + Returns: + Boolean: ``True`` if the message is broadcast, ``False`` otherwise. + """ + return self.__is_broadcast + + def __get_timestamp(self): + """ + Returns the moment when the message was received as a ``time.time()`` + function returned value. + + Returns: + Float: the returned value of using :meth:`time.time()` function when the message was received. + """ + return self.__timestamp + + def to_dict(self): + """ + Returns the message information as a dictionary. + """ + return {"Data: ": self.__data, + "Sender: ": str(self.__remote_device.get_64bit_addr()), + "Broadcast: ": self.__is_broadcast, + "Received at: ": self.__timestamp} + + data = property(__get_data) + """Bytearray. Bytearray containing the data of the message.""" + + remote_device = property(__get_remote_device) + """:class:`.RemoteXBeeDevice`. The device that has sent the message.""" + + is_broadcast = property(__is_broadcast) + """Boolean. ``True`` to indicate that the message is broadcast, ``False`` otherwise.""" + + timestamp = property(__get_timestamp) + """Integer. Instant of time when the message was received.""" + + +class ExplicitXBeeMessage(XBeeMessage): + """ + This class represents an Explicit XBee message, which is formed by all parameters of a common XBee message and: + Source endpoint, destination endpoint, cluster ID, profile ID. + """ + + def __init__(self, data, remote_device, timestamp, source_endpoint, + dest_endpoint, cluster_id, profile_id, broadcast=False): + """ + Class constructor. + + Args: + data (Bytearray): the data sent. + remote_device (:class:`.RemoteXBeeDevice`): the sender device. + timestamp: instant of time when the message was received. + source_endpoint (Integer): source endpoint of the message. 1 byte. + dest_endpoint (Integer): destination endpoint of the message. 1 byte. + cluster_id (Integer): cluster id of the message. 2 bytes. + profile_id (Integer): profile id of the message. 2 bytes. + broadcast (Boolean, optional, default=``False``): flag indicating whether the message is + broadcast (``True``) or not (``False``). Optional. + """ + XBeeMessage.__init__(self, data, remote_device, timestamp, broadcast) + self.__source_endpoint = source_endpoint + self.__dest_endpoint = dest_endpoint + self.__cluster_id = cluster_id + self.__profile_id = profile_id + + def __get_source_endpoint(self): + """ + Returns the source endpoint of the message. + + Returns: + Integer: the source endpoint of the message. 1 byte. + """ + return self.__source_endpoint + + def __get_dest_endpoint(self): + """ + Returns the destination endpoint of the message. + + Returns: + Integer: the destination endpoint of the message. 1 byte. + """ + return self.__dest_endpoint + + def __get_cluster_id(self): + """ + Returns the cluster ID of the message. + + Returns: + Integer: the cluster ID of the message. 2 bytes. + """ + return self.__cluster_id + + def __get_profile_id(self): + """ + Returns the profile ID of the message. + + Returns: + Integer: the profile ID of the message. 2 bytes. + """ + return self.__profile_id + + def __set_source_endpoint(self, source_endpoint): + """ + Sets the source endpoint of the message. + + Args: + source_endpoint (Integer): the new source endpoint of the message. + """ + self.__source_endpoint = source_endpoint + + def __set_dest_endpoint(self, dest_endpoint): + """ + Sets the destination endpoint of the message. + + Args: + dest_endpoint (Integer): the new destination endpoint of the message. + """ + self.__dest_endpoint = dest_endpoint + + def __set_cluster_id(self, cluster_id): + """ + Sets the cluster ID of the message. + + Args: + cluster_id (Integer): the new cluster ID of the message. + """ + self.__cluster_id = cluster_id + + def __set_profile_id(self, profile_id): + """ + Sets the profile ID of the message. + + Args: + profile_id (Integer): the new profile ID of the message. + """ + self.__profile_id = profile_id + + def to_dict(self): + dc = XBeeMessage.to_dict(self) + dc.update({"Src_endpoint": self.__source_endpoint, + "Dest_endpoint": self.__dest_endpoint, + "Cluster_id": self.__cluster_id, + "Profile_id": self.__profile_id}) + return dc + + source_endpoint = property(__get_source_endpoint, __set_source_endpoint) + """Integer. The source endpoint of the message""" + + dest_endpoint = property(__get_dest_endpoint, __set_dest_endpoint) + """Integer. The destination endpoint of the message""" + + cluster_id = property(__get_cluster_id, __set_cluster_id) + """Integer. The Cluster ID of the message.""" + + profile_id = property(__get_profile_id, __set_profile_id) + """Integer. The profile ID of the message.""" + + +class IPMessage(object): + """ + This class represents an IP message containing the IP address the message belongs to, the source and destination + ports, the IP protocol, and the content (data) of the message. + """ + + def __init__(self, ip_addr, source_port, dest_port, protocol, data): + """ + Class constructor. + + Args: + ip_addr (:class:`ipaddress.IPv4Address`): The IP address the message comes from. + source_port (Integer): TCP or UDP source port of the transmission. + dest_port (Integer): TCP or UDP destination port of the transmission. + protocol (:class:`.IPProtocol`): IP protocol used in the transmission. + data (Bytearray): the data sent. + + Raises: + ValueError: if ``ip_addr`` is ``None``. + ValueError: if ``protocol`` is ``None``. + ValueError: if ``data`` is ``None``. + ValueError: if ``source_port`` is less than 0 or greater than 65535. + ValueError: if ``dest_port`` is less than 0 or greater than 65535. + """ + if ip_addr is None: + raise ValueError("IP address cannot be None") + if protocol is None: + raise ValueError("Protocol cannot be None") + if data is None: + raise ValueError("Data cannot be None") + + if not 0 <= source_port <= 65535: + raise ValueError("Source port must be between 0 and 65535") + if not 0 <= dest_port <= 65535: + raise ValueError("Destination port must be between 0 and 65535") + + self.__ip_addr = ip_addr + self.__source_port = source_port + self.__dest_port = dest_port + self.__protocol = protocol + self.__data = data + + def __get_ip_addr(self): + """ + Returns the IPv4 address this message is associated to. + + Returns: + :class:`ipaddress.IPv4Address`: The IPv4 address this message is associated to. + """ + return self.__ip_addr + + def __get_source_port(self): + """ + Returns the source port of the transmission. + + Returns: + Integer: The source port of the transmission. + """ + return self.__source_port + + def __get_dest_port(self): + """ + Returns the destination port of the transmission. + + Returns: + Integer: The destination port of the transmission. + """ + return self.__dest_port + + def __get_protocol(self): + """ + Returns the protocol used in the transmission. + + Returns: + :class:`.IPProtocol`: The protocol used in the transmission. + """ + return self.__protocol + + def __get_data(self): + """ + Returns the data of the message. + + Returns: + Bytearray: the data of the message. + """ + return self.__data + + def to_dict(self): + """ + Returns the message information as a dictionary. + """ + return {"IP address: ": self.__ip_addr, + "Source port: ": self.__source_port, + "Destination port: ": self.__dest_port, + "Protocol: ": self.__protocol, + "Data: ": self.__data} + + ip_addr = property(__get_ip_addr) + """:class:`ipaddress.IPv4Address`. The IPv4 address this message is associated to.""" + + source_port = property(__get_source_port) + """Integer. The source port of the transmission.""" + + dest_port = property(__get_dest_port) + """Integer. The destination port of the transmission.""" + + protocol = property(__get_protocol) + """:class:`.IPProtocol`. The protocol used in the transmission.""" + + data = property(__get_data) + """Bytearray. Bytearray containing the data of the message.""" + + +class SMSMessage(object): + """ + This class represents an SMS message containing the phone number that sent + the message and the content (data) of the message. + + This class is used within the library to read SMS sent to Cellular devices. + """ + + __PHONE_NUMBER_PATTERN = "^\+?\d+$" + + def __init__(self, phone_number, data): + """ + Class constructor. Instantiates a new :class:`.SMSMessage` object with the provided parameters. + + Args: + phone_number (String): The phone number that sent the message. + data (String): The message text. + + Raises: + ValueError: if ``phone_number`` is ``None``. + ValueError: if ``data`` is ``None``. + ValueError: if ``phone_number`` is not a valid phone number. + """ + if phone_number is None: + raise ValueError("Phone number cannot be None") + if data is None: + raise ValueError("Data cannot be None") + if not re.compile(SMSMessage.__PHONE_NUMBER_PATTERN).match(phone_number): + raise ValueError("Invalid phone number") + + self.__phone_number = phone_number + self.__data = data + + def __get_phone_number(self): + """ + Returns the phone number that sent the message. + + Returns: + String: The phone number that sent the message. + """ + return self.__phone_number + + def __get_data(self): + """ + Returns the data of the message. + + Returns: + String: The data of the message. + """ + return self.__data + + def to_dict(self): + """ + Returns the message information as a dictionary. + """ + return {"Phone number: ": self.__phone_number, + "Data: ": self.__data} + + phone_number = property(__get_phone_number) + """String. The phone number that sent the message.""" + + data = property(__get_data) + """String. The data of the message.""" + + +class UserDataRelayMessage(object): + """ + This class represents a user data relay message containing the source + interface and the content (data) of the message. + + .. seealso:: + | :class:`.XBeeLocalInterface` + """ + + def __init__(self, local_interface, data): + """ + Class constructor. Instantiates a new :class:`.UserDataRelayMessage` object with + the provided parameters. + + Args: + local_interface (:class:`.XBeeLocalInterface`): The source XBee local interface. + data (Bytearray): Byte array containing the data of the message. + + Raises: + ValueError: if ``relay_interface`` is ``None``. + + .. seealso:: + | :class:`.XBeeLocalInterface` + """ + if local_interface is None: + raise ValueError("XBee local interface cannot be None") + + self.__local_interface = local_interface + self.__data = data + + def __get_src_interface(self): + """ + Returns the source interface that sent the message. + + Returns: + :class:`.XBeeLocalInterface`: The source interface that sent the message. + """ + return self.__local_interface + + def __get_data(self): + """ + Returns the data of the message. + + Returns: + Bytearray: The data of the message. + """ + return self.__data + + def to_dict(self): + """ + Returns the message information as a dictionary. + """ + return {"XBee local interface: ": self.__local_interface, + "Data: ": self.__data} + + local_interface = property(__get_src_interface) + """:class:`.XBeeLocalInterface`. Source interface that sent the message.""" + + data = property(__get_data) + """Bytearray. The data of the message.""" diff --git a/digi/xbee/models/mode.py b/digi/xbee/models/mode.py new file mode 100644 index 0000000..8c46bab --- /dev/null +++ b/digi/xbee/models/mode.py @@ -0,0 +1,204 @@ +# 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 enum import Enum, unique +from digi.xbee.util import utils + + +@unique +class OperatingMode(Enum): + """ + This class represents all operating modes available. + + | Inherited properties: + | **name** (String): the name (id) of this OperatingMode. + | **value** (String): the value of this OperatingMode. + """ + + AT_MODE = (0, "AT mode") + API_MODE = (1, "API mode") + ESCAPED_API_MODE = (2, "API mode with escaped characters") + UNKNOWN = (99, "Unknown") + + def __init__(self, code, description): + self.__code = code + self.__description = description + + def __get_code(self): + """ + Returns the code of the OperatingMode element. + + Returns: + String: the code of the OperatingMode element. + """ + return self.__code + + def __get_description(self): + """ + Returns the description of the OperatingMode element. + + Returns: + String: the description of the OperatingMode element. + """ + return self.__description + + @classmethod + def get(cls, code): + """ + Returns the OperatingMode for the given code. + + Args: + code (Integer): the code corresponding to the operating mode to get. + + Returns: + :class:`.OperatingMode`: the OperatingMode with the given code. + """ + try: + return cls.lookupTable[code] + except KeyError: + return OperatingMode.UNKNOWN + + code = property(__get_code) + """Integer. The operating mode code.""" + + description = property(__get_description) + """String: The operating mode description.""" + + +OperatingMode.lookupTable = {x.code: x for x in OperatingMode} +OperatingMode.__doc__ += utils.doc_enum(OperatingMode) + + +@unique +class APIOutputMode(Enum): + """ + Enumerates the different API output modes. The API output mode establishes + the way data will be output through the serial interface of an XBee device. + + | Inherited properties: + | **name** (String): the name (id) of this OperatingMode. + | **value** (String): the value of this OperatingMode. + """ + + NATIVE = (0x00, "Native") + EXPLICIT = (0x01, "Explicit") + EXPLICIT_ZDO_PASSTHRU = (0x03, "Explicit with ZDO Passthru") + + def __init__(self, code, description): + self.__code = code + self.__description = description + + def __get_code(self): + """ + Returns the code of the APIOutputMode element. + + Returns: + String: the code of the APIOutputMode element. + """ + return self.__code + + def __get_description(self): + """ + Returns the description of the APIOutputMode element. + + Returns: + String: the description of the APIOutputMode element. + """ + return self.__description + + @classmethod + def get(cls, code): + """ + Returns the APIOutputMode for the given code. + + Args: + code (Integer): the code corresponding to the API output mode to get. + + Returns: + :class:`.OperatingMode`: the APIOutputMode with the given code, ``None`` if there is not an + APIOutputMode with that code. + """ + try: + return cls.lookupTable[code] + except KeyError: + return None + + code = property(__get_code) + """Integer. The API output mode code.""" + + description = property(__get_description) + """String: The API output mode description.""" + + +APIOutputMode.lookupTable = {x.code: x for x in APIOutputMode} +APIOutputMode.__doc__ += utils.doc_enum(APIOutputMode) + + +@unique +class IPAddressingMode(Enum): + """ + Enumerates the different IP addressing modes. + """ + + DHCP = (0x00, "DHCP") + STATIC = (0x01, "Static") + + def __init__(self, code, description): + self.__code = code + self.__description = description + + def __get_code(self): + """ + Returns the code of the IPAddressingMode element. + + Returns: + String: the code of the IPAddressingMode element. + """ + return self.__code + + def __get_description(self): + """ + Returns the description of the IPAddressingMode element. + + Returns: + String: the description of the IPAddressingMode element. + """ + return self.__description + + @classmethod + def get(cls, code): + """ + Returns the IPAddressingMode for the given code. + + Args: + code (Integer): the code corresponding to the IP addressing mode to get. + + Returns: + :class:`.IPAddressingMode`: the IPAddressingMode with the given code, ``None`` if there is not an + IPAddressingMode with that code. + """ + try: + return cls.lookupTable[code] + except KeyError: + return None + + code = property(__get_code) + """Integer. The IP addressing mode code.""" + + description = property(__get_description) + """String. The IP addressing mode description.""" + + +IPAddressingMode.lookupTable = {x.code: x for x in IPAddressingMode} +IPAddressingMode.__doc__ += utils.doc_enum(IPAddressingMode) diff --git a/digi/xbee/models/options.py b/digi/xbee/models/options.py new file mode 100644 index 0000000..92c1d0e --- /dev/null +++ b/digi/xbee/models/options.py @@ -0,0 +1,470 @@ +# 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. + +from enum import Enum, unique + +from digi.xbee.models.protocol import XBeeProtocol +from digi.xbee.util import utils + + +class ReceiveOptions(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. + """ + + NONE = 0x00 + """ + No special receive options. + """ + + PACKET_ACKNOWLEDGED = 0x01 + """ + Packet was acknowledged. + + Not valid for WiFi protocol. + """ + + BROADCAST_PACKET = 0x02 + """ + Packet was a broadcast packet. + + Not valid for WiFi protocol. + """ + + APS_ENCRYPTED = 0x20 + """ + Packet encrypted with APS encryption. + + Only valid for ZigBee XBee protocol. + """ + + SENT_FROM_END_DEVICE = 0x40 + """ + Packet was sent from an end device (if known). + + Only valid for ZigBee XBee protocol. + """ + + +ReceiveOptions.__doc__ += utils.doc_enum(ReceiveOptions) + + +class TransmitOptions(Enum): + """ + This class lists all the possible options that can be set while + transmitting an XBee packet. + + The transmit options are usually set as a bitfield meaning that the options + can be combined using the '|' operand. + + Not all options are available for all cases, that's why there are different + names with same values. In each moment, you must be sure that the option + your are going to use, is a valid option in your context. + """ + + NONE = 0x00 + """ + No special transmit options. + """ + + DISABLE_ACK = 0x01 + """ + Disables acknowledgments on all unicasts . + + Only valid for DigiMesh, 802.15.4 and Point-to-multipoint + protocols. + """ + + DISABLE_RETRIES_AND_REPAIR = 0x01 + """ + Disables the retries and router repair in the frame . + + Only valid for ZigBee protocol. + """ + + DONT_ATTEMPT_RD = 0x02 + """ + Doesn't attempt Route Discovery . + + Disables Route Discovery on all DigiMesh unicasts. + + Only valid for DigiMesh protocol. + """ + + USE_BROADCAST_PAN_ID = 0x04 + """ + Sends packet with broadcast {@code PAN ID}. Packet will be sent to all + devices in the same channel ignoring the {@code PAN ID}. + + It cannot be combined with other options. + + Only valid for 802.15.4 XBee protocol. + """ + + ENABLE_UNICAST_NACK = 0x04 + """ + Enables unicast NACK messages . + + NACK message is enabled on the packet. + + Only valid for DigiMesh 868/900 protocol. + """ + + ENABLE_UNICAST_TRACE_ROUTE = 0x04 + """ + Enables unicast trace route messages . + + Trace route is enabled on the packets. + + Only valid for DigiMesh 868/900 protocol. + """ + + ENABLE_MULTICAST = 0x08 + """ + Enables multicast transmission request. + + Only valid for ZigBee XBee protocol. + """ + + ENABLE_APS_ENCRYPTION = 0x20 + """ + Enables APS encryption, only if {@code EE=1} . + + Enabling APS encryption decreases the maximum number of RF payload + bytes by 4 (below the value reported by {@code NP}). + + Only valid for ZigBee XBee protocol. + """ + + USE_EXTENDED_TIMEOUT = 0x40 + """ + Uses the extended transmission timeout . + + Setting the extended timeout bit causes the stack to set the + extended transmission timeout for the destination address. + + Only valid for ZigBee XBee protocol. + """ + + POINT_MULTIPOINT_MODE = 0x40 + """ + Transmission is performed using point-to-Multipoint mode. + + Only valid for DigiMesh 868/900 and Point-to-Multipoint 868/900 + protocols. + """ + + REPEATER_MODE = 0x80 + """ + Transmission is performed using repeater mode . + + Only valid for DigiMesh 868/900 and Point-to-Multipoint 868/900 + protocols. + """ + + DIGIMESH_MODE = 0xC0 + """ + Transmission is performed using DigiMesh mode . + + Only valid for DigiMesh 868/900 and Point-to-Multipoint 868/900 + protocols. + """ + + +TransmitOptions.__doc__ += utils.doc_enum(TransmitOptions) + + +class RemoteATCmdOptions(Enum): + """ + This class lists all the possible options that can be set while transmitting + a remote AT Command. + + These options are usually set as a bitfield meaning that the options + can be combined using the '|' operand. + """ + + NONE = 0x00 + """ + No special transmit options + """ + + DISABLE_ACK = 0x01 + """ + Disables ACK + """ + + APPLY_CHANGES = 0x02 + """ + Applies changes in the remote device. + + If this option is not set, AC command must be sent before changes + will take effect. + """ + + EXTENDED_TIMEOUT = 0x40 + """ + Uses the extended transmission timeout + + Setting the extended timeout bit causes the stack to set the extended + transmission timeout for the destination address. + + Only valid for ZigBee XBee protocol. + """ + + +RemoteATCmdOptions.__doc__ += utils.doc_enum(RemoteATCmdOptions) + + +@unique +class SendDataRequestOptions(Enum): + """ + Enumerates the different options for the :class:`.SendDataRequestPacket`. + """ + OVERWRITE = (0, "Overwrite") + ARCHIVE = (1, "Archive") + APPEND = (2, "Append") + TRANSIENT = (3, "Transient data (do not store)") + + def __init__(self, code, description): + self.__code = code + self.__description = description + + def __get_code(self): + """ + Returns the code of the SendDataRequestOptions element. + + Returns: + Integer: the code of the SendDataRequestOptions element. + """ + return self.__code + + def __get_description(self): + """ + Returns the description of the SendDataRequestOptions element. + + Returns: + String: the description of the SendDataRequestOptions element. + """ + return self.__description + + @classmethod + def get(cls, code): + """ + Returns the send data request option for the given code. + + Args: + code (Integer): the code of the send data request option to get. + + Returns: + :class:`.FrameError`: the SendDataRequestOptions with the given code, ``None`` if there is not + any send data request option with the provided code. + """ + try: + return cls.lookupTable[code] + except KeyError: + return None + + code = property(__get_code) + """Integer. The send data request option code.""" + + description = property(__get_description) + """String. The send data request option description.""" + + +SendDataRequestOptions.lookupTable = {x.code: x for x in SendDataRequestOptions} +SendDataRequestOptions.__doc__ += utils.doc_enum(SendDataRequestOptions) + + +@unique +class DiscoveryOptions(Enum): + """ + Enumerates the different options used in the discovery process. + """ + + APPEND_DD = (0x01, "Append device type identifier (DD)") + """ + Append device type identifier (DD) to the discovery response. + + Valid for the following protocols: + * DigiMesh + * Point-to-multipoint (Digi Point) + * ZigBee + """ + + DISCOVER_MYSELF = (0x02, "Local device sends response frame") + """ + Local device sends response frame when discovery is issued. + + Valid for the following protocols: + * DigiMesh + * Point-to-multipoint (Digi Point) + * ZigBee + * 802.15.4 + """ + + APPEND_RSSI = (0x04, "Append RSSI (of the last hop)") + """ + Append RSSI of the last hop to the discovery response. + + Valid for the following protocols: + * DigiMesh + * Point-to-multipoint (Digi Point) + """ + + def __init__(self, code, description): + self.__code = code + self.__description = description + + def __get_code(self): + """ + Returns the code of the ``DiscoveryOptions`` element. + + Returns: + Integer: the code of the ``DiscoveryOptions`` element. + """ + return self.__code + + def __get_description(self): + """ + Returns the description of the ``DiscoveryOptions`` element. + + Returns: + String: the description of the ``DiscoveryOptions`` element. + """ + return self.__description + + @classmethod + def get(cls, code): + """ + Returns the ``DiscoveryOptions`` for the given code. + + Args: + code (Integer): the code of the ``DiscoveryOptions`` to get. + + Returns: + :class:`.FrameError`: the ``DiscoveryOptions`` with the given code, ``None`` if there is not + any discovery option with the provided code. + """ + try: + return cls.lookupTable[code] + except KeyError: + return None + + @staticmethod + def calculate_discovery_value(protocol, options): + """ + Calculates the total value of a combination of several options for the + given protocol. + + Args: + protocol (:class:`.XBeeProtocol`): the ``XBeeProtocol`` to calculate the value of all the given + discovery options. + options: collection of options to get the final value. + + Returns: + Integer: The value to be configured in the module depending on the given + collection of options and the protocol. + """ + value = 0 + if protocol in [XBeeProtocol.ZIGBEE, XBeeProtocol.ZNET]: + for op in options: + if op == DiscoveryOptions.APPEND_RSSI: + continue + value = value + op.code + elif protocol in [XBeeProtocol.DIGI_MESH, XBeeProtocol.DIGI_POINT, XBeeProtocol.XLR, XBeeProtocol.XLR_DM]: + for op in options: + value = value + op.code + else: + if DiscoveryOptions.DISCOVER_MYSELF in options: + value = 1 # This is different for 802.15.4. + return value + + code = property(__get_code) + """Integer. The discovery option code.""" + + description = property(__get_description) + """String. The discovery option description.""" + + +DiscoveryOptions.lookupTable = {x.code: x for x in DiscoveryOptions} +DiscoveryOptions.__doc__ += utils.doc_enum(DiscoveryOptions) + + +@unique +class XBeeLocalInterface(Enum): + """ + Enumerates the different interfaces for the :class:`.UserDataRelayPacket` + and :class:`.UserDataRelayOutputPacket`. + + | Inherited properties: + | **name** (String): the name (id) of the XBee local interface. + | **value** (String): the value of the XBee local interface. + """ + SERIAL = (0, "Serial port (UART when in API mode, or SPI interface)") + BLUETOOTH = (1, "BLE API interface (on XBee devices which support BLE)") + MICROPYTHON = (2, "MicroPython") + UNKNOWN = (255, "Unknown interface") + + def __init__(self, code, description): + self.__code = code + self.__description = description + + def __get_code(self): + """ + Returns the code of the ``XBeeLocalInterface`` element. + + Returns: + Integer: the code of the ``XBeeLocalInterface`` element. + """ + return self.__code + + def __get_description(self): + """ + Returns the description of the ``XBeeLocalInterface`` element. + + Returns: + String: the description of the ``XBeeLocalInterface`` element. + """ + return self.__description + + @classmethod + def get(cls, code): + """ + Returns the XBee local interface option for the given code. + + Args: + code (Integer): the code of the XBee local interface to get. + + Returns: + :class:`.XBeeLocalInterface`: the ``XBeeLocalInterface`` with the given code, + ``UNKNOWN`` if there is not any ``XBeeLocalInterface`` with the provided code. + """ + try: + return cls.lookupTable[code] + except KeyError: + return XBeeLocalInterface.UNKNOWN + + code = property(__get_code) + """Integer. The XBee local interface code.""" + + description = property(__get_description) + """String. The XBee local interface description.""" + + +XBeeLocalInterface.lookupTable = {x.code: x for x in XBeeLocalInterface} +XBeeLocalInterface.__doc__ += utils.doc_enum(XBeeLocalInterface) diff --git a/digi/xbee/models/protocol.py b/digi/xbee/models/protocol.py new file mode 100644 index 0000000..4751afe --- /dev/null +++ b/digi/xbee/models/protocol.py @@ -0,0 +1,331 @@ +# 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 +# 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 enum import Enum, unique +from digi.xbee.models.hw import HardwareVersion +from digi.xbee.util import utils + + +@unique +class XBeeProtocol(Enum): + """ + Enumerates the available XBee protocols. The XBee protocol is determined + by the combination of hardware and firmware of an XBee device. + + | Inherited properties: + | **name** (String): the name (id) of this XBeeProtocol. + | **value** (String): the value of this XBeeProtocol. + """ + + ZIGBEE = (0, "ZigBee") + RAW_802_15_4 = (1, "802.15.4") + XBEE_WIFI = (2, "Wi-Fi") + DIGI_MESH = (3, "DigiMesh") + XCITE = (4, "XCite") + XTEND = (5, "XTend (Legacy)") + XTEND_DM = (6, "XTend (DigiMesh)") + SMART_ENERGY = (7, "Smart Energy") + DIGI_POINT = (8, "Point-to-multipoint") + ZNET = (9, "ZNet 2.5") + XC = (10, "XSC") + XLR = (11, "XLR") + XLR_DM = (12, "XLR") + SX = (13, "XBee SX") + XLR_MODULE = (14, "XLR Module") + CELLULAR = (15, "Cellular") + CELLULAR_NBIOT = (16, "Cellular NB-IoT") + UNKNOWN = (99, "Unknown") + + def __init__(self, code, description): + self.__code = code + self.__description = description + + def __get_code(self): + """ + Returns the code of the XBeeProtocol element. + + Returns: + Integer: the code of the XBeeProtocol element. + """ + return self.__code + + def __get_description(self): + """ + Returns the description of the XBeeProtocol element. + + Returns: + String: the description of the XBeeProtocol element. + """ + return self.__description + + @classmethod + def get(cls, code): + """ + Returns the XBeeProtocol for the given code. + + Args: + code (Integer): code of the XBeeProtocol to get. + + Returns: + XBeeProtocol: XBeeProtocol for the given code. + """ + try: + return cls.lookupTable[code] + except KeyError: + return XBeeProtocol.UNKNOWN + + @staticmethod + def determine_protocol(hardware_version, firmware_version): + """ + Determines the XBee protocol based on the given hardware and firmware + versions. + + Args: + hardware_version (Integer): hardware version of the protocol to determine. + firmware_version (String): firmware version of the protocol to determine. + + Returns: + The XBee protocol corresponding to the given hardware and firmware versions. + """ + firmware_version = "".join(["%02X" % i for i in firmware_version]) + + if hardware_version is None or firmware_version is None or hardware_version < 0x09 or \ + HardwareVersion.get(hardware_version) is None: + return XBeeProtocol.UNKNOWN + + elif hardware_version in [HardwareVersion.XC09_009.code, + HardwareVersion.XC09_038.code]: + return XBeeProtocol.XCITE + + elif hardware_version in [HardwareVersion.XT09_XXX.code, + HardwareVersion.XT09B_XXX.code]: + if ((len(firmware_version) == 4 and firmware_version.startswith("8")) or + (len(firmware_version) == 5 and firmware_version[1] == '8')): + return XBeeProtocol.XTEND_DM + return XBeeProtocol.XTEND + + elif hardware_version in [HardwareVersion.XB24_AXX_XX.code, + HardwareVersion.XBP24_AXX_XX.code]: + if len(firmware_version) == 4 and firmware_version.startswith("8"): + return XBeeProtocol.DIGI_MESH + return XBeeProtocol.RAW_802_15_4 + + elif hardware_version in [HardwareVersion.XB24_BXIX_XXX.code, + HardwareVersion.XBP24_BXIX_XXX.code]: + if ((len(firmware_version) == 4 and firmware_version.startswith("1") and firmware_version.endswith("20")) + or (len(firmware_version) == 4 and firmware_version.startswith("2"))): + return XBeeProtocol.ZIGBEE + elif len(firmware_version) == 4 and firmware_version.startswith("3"): + return XBeeProtocol.SMART_ENERGY + return XBeeProtocol.ZNET + + elif hardware_version == HardwareVersion.XBP09_DXIX_XXX.code: + if ((len(firmware_version) == 4 and firmware_version.startswith("8") or + (len(firmware_version) == 4 and firmware_version[1] == '8')) or + (len(firmware_version) == 5 and firmware_version[1] == '8')): + return XBeeProtocol.DIGI_MESH + return XBeeProtocol.DIGI_POINT + + elif hardware_version == HardwareVersion.XBP09_XCXX_XXX.code: + return XBeeProtocol.XC + + elif hardware_version == HardwareVersion.XBP08_DXXX_XXX.code: + return XBeeProtocol.DIGI_POINT + + elif hardware_version == HardwareVersion.XBP24B.code: + if len(firmware_version) == 4 and firmware_version.startswith("3"): + return XBeeProtocol.SMART_ENERGY + return XBeeProtocol.ZIGBEE + + elif hardware_version in [HardwareVersion.XB24_WF.code, + HardwareVersion.WIFI_ATHEROS.code, + HardwareVersion.SMT_WIFI_ATHEROS.code]: + return XBeeProtocol.XBEE_WIFI + + elif hardware_version in [HardwareVersion.XBP24C.code, + HardwareVersion.XB24C.code]: + if (len(firmware_version) == 4 and (firmware_version.startswith("5")) or + (firmware_version.startswith("6"))): + return XBeeProtocol.SMART_ENERGY + elif firmware_version.startswith("2"): + return XBeeProtocol.RAW_802_15_4 + elif firmware_version.startswith("9"): + return XBeeProtocol.DIGI_MESH + return XBeeProtocol.ZIGBEE + + elif hardware_version in [HardwareVersion.XSC_GEN3.code, + HardwareVersion.SRD_868_GEN3.code]: + if len(firmware_version) == 4 and firmware_version.startswith("8"): + return XBeeProtocol.DIGI_MESH + elif len(firmware_version) == 4 and firmware_version.startswith("1"): + return XBeeProtocol.DIGI_POINT + return XBeeProtocol.XC + + elif hardware_version == HardwareVersion.XBEE_CELL_TH.code: + return XBeeProtocol.UNKNOWN + + elif hardware_version == HardwareVersion.XLR_MODULE.code: + # This is for the old version of the XLR we have (K60), and it is + # reporting the firmware of the module (8001), this will change in + # future (after K64 integration) reporting the hardware and firmware + # version of the baseboard (see the case HardwareVersion.XLR_BASEBOARD). + # TODO maybe this should be removed in future, since this case will never be released. + if firmware_version.startswith("1"): + return XBeeProtocol.XLR + else: + return XBeeProtocol.XLR_MODULE + + elif hardware_version == HardwareVersion.XLR_BASEBOARD.code: + # XLR devices with K64 will report the baseboard hardware version, + # and also firmware version (the one we have here is 1002, but this value + # is not being reported since is an old K60 version, the module fw version + # is reported instead). + + # TODO [XLR_DM] The next version of the XLR will add DigiMesh support should be added. + # Probably this XLR_DM and XLR will depend on the firmware version. + if firmware_version.startswith("1"): + return XBeeProtocol.XLR + else: + return XBeeProtocol.XLR_MODULE + + elif hardware_version == HardwareVersion.XB900HP_NZ.code: + return XBeeProtocol.DIGI_POINT + + elif hardware_version in [HardwareVersion.XBP24C_TH_DIP.code, + HardwareVersion.XB24C_TH_DIP.code, + HardwareVersion.XBP24C_S2C_SMT.code]: + if (len(firmware_version) == 4 and + (firmware_version.startswith("5") or firmware_version.startswith("6"))): + return XBeeProtocol.SMART_ENERGY + elif firmware_version.startswith("2"): + return XBeeProtocol.RAW_802_15_4 + elif firmware_version.startswith("9"): + return XBeeProtocol.DIGI_MESH + return XBeeProtocol.ZIGBEE + + elif hardware_version in [HardwareVersion.SX_PRO.code, + HardwareVersion.SX.code, + HardwareVersion.XTR.code]: + if firmware_version.startswith("2"): + return XBeeProtocol.XTEND + elif firmware_version.startswith("8"): + return XBeeProtocol.XTEND_DM + return XBeeProtocol.DIGI_MESH + + elif hardware_version in [HardwareVersion.S2D_SMT_PRO.code, + HardwareVersion.S2D_SMT_REG.code, + HardwareVersion.S2D_TH_PRO.code, + HardwareVersion.S2D_TH_REG.code]: + return XBeeProtocol.ZIGBEE + + elif hardware_version in [HardwareVersion.CELLULAR_CAT1_LTE_VERIZON.code, + HardwareVersion.CELLULAR_3G.code, + HardwareVersion.CELLULAR_LTE_ATT.code, + HardwareVersion.CELLULAR_LTE_VERIZON.code, + HardwareVersion.CELLULAR_3_CAT1_LTE_ATT.code, + HardwareVersion.CELLULAR_3_LTE_M_VERIZON.code, + HardwareVersion.CELLULAR_3_LTE_M_ATT.code]: + return XBeeProtocol.CELLULAR + + elif hardware_version == HardwareVersion.CELLULAR_NBIOT_EUROPE.code: + return XBeeProtocol.CELLULAR_NBIOT + + elif hardware_version in [HardwareVersion.XBEE3.code, + HardwareVersion.XBEE3_SMT.code, + HardwareVersion.XBEE3_TH.code]: + if firmware_version.startswith("2"): + return XBeeProtocol.RAW_802_15_4 + elif firmware_version.startswith("3"): + return XBeeProtocol.DIGI_MESH + else: + return XBeeProtocol.ZIGBEE + + elif hardware_version == HardwareVersion.XB8X.code: + return XBeeProtocol.DIGI_MESH + + return XBeeProtocol.ZIGBEE + + code = property(__get_code) + """Integer. XBee protocol code.""" + + description = property(__get_description) + """String. XBee protocol description.""" + + +XBeeProtocol.lookupTable = {x.code: x for x in XBeeProtocol} +XBeeProtocol.__doc__ += utils.doc_enum(XBeeProtocol) + + +@unique +class IPProtocol(Enum): + """ + Enumerates the available network protocols. + + | Inherited properties: + | **name** (String): the name (id) of this IPProtocol. + | **value** (String): the value of this IPProtocol. + """ + + UDP = (0, "UDP") + TCP = (1, "TCP") + TCP_SSL = (4, "TCP SSL") + + def __init__(self, code, description): + self.__code = code + self.__description = description + + def __get_code(self): + """ + Returns the code of the IP protocol. + + Returns: + Integer: code of the IP protocol. + """ + return self.__code + + def __get_description(self): + """ + Returns the description of the IP protocol. + + Returns: + String: description of the IP protocol. + """ + return self.__description + + @classmethod + def get(cls, code): + """ + Returns the IPProtocol for the given code. + + Args: + code (Integer): code associated to the IP protocol. + + Returns: + :class:`.IPProtocol`: IP protocol for the given code or ``None`` if there + is not any ``IPProtocol`` with the given code. + """ + try: + return cls.lookupTable[code] + except KeyError: + return None + + code = property(__get_code) + """Integer: IP protocol code.""" + + description = property(__get_description) + """String: IP protocol description.""" + + +IPProtocol.lookupTable = {x.code: x for x in IPProtocol} +IPProtocol.__doc__ += utils.doc_enum(IPProtocol) diff --git a/digi/xbee/models/status.py b/digi/xbee/models/status.py new file mode 100644 index 0000000..5937789 --- /dev/null +++ b/digi/xbee/models/status.py @@ -0,0 +1,805 @@ +# 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. + +from enum import Enum, unique +from digi.xbee.util import utils + + +@unique +class ATCommandStatus(Enum): + """ + This class lists all the possible states of an AT command after executing it. + + | Inherited properties: + | **name** (String): the name (id) of the ATCommandStatus. + | **value** (String): the value of the ATCommandStatus. + """ + OK = (0, "Status OK") + ERROR = (1, "Status Error") + INVALID_COMMAND = (2, "Invalid command") + INVALID_PARAMETER = (3, "Invalid parameter") + TX_FAILURE = (4, "TX failure") + UNKNOWN = (255, "Unknown status") + + def __init__(self, code, description): + self.__code = code + self.__description = description + + def __get_code(self): + """ + Returns the code of the ATCommandStatus element. + + Returns: + Integer: the code of the ATCommandStatus element. + """ + return self.__code + + def __get_description(self): + """ + Returns the description of the ATCommandStatus element. + + Returns: + String: the description of the ATCommandStatus element. + """ + return self.__description + + @classmethod + def get(cls, code): + """ + Returns the AT command status for the given code. + + Args: + code (Integer): the code of the AT command status to get. + + Returns: + :class:`.ATCommandStatus`: the AT command status with the given code. + """ + try: + return cls.lookupTable[code] + except KeyError: + return ATCommandStatus.UNKNOWN + + code = property(__get_code) + """Integer. The AT command status code.""" + + description = property(__get_description) + """String. The AT command status description.""" + + +ATCommandStatus.lookupTable = {x.code: x for x in ATCommandStatus} +ATCommandStatus.__doc__ += utils.doc_enum(ATCommandStatus) + + +@unique +class DiscoveryStatus(Enum): + """ + This class lists all the possible states of the discovery process. + + | Inherited properties: + | **name** (String): The name of the DiscoveryStatus. + | **value** (Integer): The ID of the DiscoveryStatus. + """ + NO_DISCOVERY_OVERHEAD = (0x00, "No discovery overhead") + ADDRESS_DISCOVERY = (0x01, "Address discovery") + ROUTE_DISCOVERY = (0x02, "Route discovery") + ADDRESS_AND_ROUTE = (0x03, "Address and route") + EXTENDED_TIMEOUT_DISCOVERY = (0x40, "Extended timeout discovery") + UNKNOWN = (0xFF, "Unknown") + + def __init__(self, code, description): + self.__code = code + self.__description = description + + def __get_code(self): + """ + Returns the code of the DiscoveryStatus element. + + Returns: + Integer: the code of the DiscoveryStatus element. + """ + return self.__code + + def __get_description(self): + """ + Returns the description of the DiscoveryStatus element. + + Returns: + String: The description of the DiscoveryStatus element. + + """ + return self.__description + + @classmethod + def get(cls, code): + """ + Returns the discovery status for the given code. + + Args: + code (Integer): the code of the discovery status to get. + + Returns: + :class:`.DiscoveryStatus`: the discovery status with the given code. + """ + try: + return cls.lookupTable[code] + except KeyError: + return DiscoveryStatus.UNKNOWN + + code = property(__get_code) + """Integer. The discovery status code.""" + + description = property(__get_description) + """String. The discovery status description.""" + + +DiscoveryStatus.lookupTable = {x.code: x for x in DiscoveryStatus} +DiscoveryStatus.__doc__ += utils.doc_enum(DiscoveryStatus) + + +@unique +class TransmitStatus(Enum): + """ + This class represents all available transmit status. + + | Inherited properties: + | **name** (String): the name (id) of ths TransmitStatus. + | **value** (String): the value of ths TransmitStatus. + """ + SUCCESS = (0x00, "Success.") + NO_ACK = (0x01, "No acknowledgement received.") + CCA_FAILURE = (0x02, "CCA failure.") + PURGED = (0x03, "Transmission purged, it was attempted before stack was up.") + WIFI_PHYSICAL_ERROR = (0x04, "Physical error occurred on the interface with the WiFi transceiver.") + INVALID_DESTINATION = (0x15, "Invalid destination endpoint.") + NO_BUFFERS = (0x18, "No buffers.") + NETWORK_ACK_FAILURE = (0x21, "Network ACK Failure.") + NOT_JOINED_NETWORK = (0x22, "Not joined to network.") + SELF_ADDRESSED = (0x23, "Self-addressed.") + ADDRESS_NOT_FOUND = (0x24, "Address not found.") + ROUTE_NOT_FOUND = (0x25, "Route not found.") + BROADCAST_FAILED = (0x26, "Broadcast source failed to hear a neighbor relay the message.") + INVALID_BINDING_TABLE_INDEX = (0x2B, "Invalid binding table index.") + INVALID_ENDPOINT = (0x2C, "Invalid endpoint") + BROADCAST_ERROR_APS = (0x2D, "Attempted broadcast with APS transmission.") + BROADCAST_ERROR_APS_EE0 = (0x2E, "Attempted broadcast with APS transmission, but EE=0.") + SOFTWARE_ERROR = (0x31, "A software error occurred.") + RESOURCE_ERROR = (0x32, "Resource error lack of free buffers, timers, etc.") + PAYLOAD_TOO_LARGE = (0x74, "Data payload too large.") + INDIRECT_MESSAGE_UNREQUESTED = (0x75, "Indirect message unrequested") + SOCKET_CREATION_FAILED = (0x76, "Attempt to create a client socket failed.") + IP_PORT_NOT_EXIST = (0x77, "TCP connection to given IP address and port doesn't exist. Source port is non-zero so " + "that a new connection is not attempted.") + UDP_SRC_PORT_NOT_MATCH_LISTENING_PORT = (0x78, "Source port on a UDP transmission doesn't match a listening port " + "on the transmitting module.") + TCP_SRC_PORT_NOT_MATCH_LISTENING_PORT = (0x79, "Source port on a TCP transmission doesn't match a listening port " + "on the transmitting module.") + INVALID_IP_ADDRESS = (0x7A, "Destination IPv4 address is not valid.") + INVALID_IP_PROTOCOL = (0x7B, "Protocol on an IPv4 transmission is not valid.") + RELAY_INTERFACE_INVALID = (0x7C, "Destination interface on a User Data Relay Frame " + "doesn't exist.") + RELAY_INTERFACE_REJECTED = (0x7D, "Destination interface on a User Data Relay Frame " + "exists, but the interface is not accepting data.") + SOCKET_CONNECTION_REFUSED = (0x80, "Destination server refused the connection.") + SOCKET_CONNECTION_LOST = (0x81, "The existing connection was lost before the data was sent.") + SOCKET_ERROR_NO_SERVER = (0x82, "The attempted connection timed out.") + SOCKET_ERROR_CLOSED = (0x83, "The existing connection was closed.") + SOCKET_ERROR_UNKNOWN_SERVER = (0x84, "The server could not be found.") + SOCKET_ERROR_UNKNOWN_ERROR = (0x85, "An unknown error occurred.") + INVALID_TLS_CONFIGURATION = (0x86, "TLS Profile on a 0x23 API request doesn't exist, or " + "one or more certificates is not valid.") + KEY_NOT_AUTHORIZED = (0xBB, "Key not authorized.") + UNKNOWN = (0xFF, "Unknown.") + + def __init__(self, code, description): + self.__code = code + self.__description = description + + def __get_code(self): + """ + Returns the code of the TransmitStatus element. + + Returns: + Integer: the code of the TransmitStatus element. + """ + return self.__code + + def __get_description(self): + """ + Returns the description of the TransmitStatus element. + + Returns: + String: the description of the TransmitStatus element. + """ + return self.__description + + @classmethod + def get(cls, code): + """ + Returns the transmit status for the given code. + + Args: + code (Integer): the code of the transmit status to get. + + Returns: + :class:`.TransmitStatus`: the transmit status with the given code. + """ + try: + return cls.lookupTable[code] + except KeyError: + return TransmitStatus.UNKNOWN + + code = property(__get_code) + """Integer. The transmit status code.""" + + description = property(__get_description) + """String. The transmit status description.""" + + +TransmitStatus.lookupTable = {x.code: x for x in TransmitStatus} +TransmitStatus.__doc__ += utils.doc_enum(TransmitStatus) + + +@unique +class ModemStatus(Enum): + """ + Enumerates the different modem status events. This enumeration list is + intended to be used within the :class:`.ModemStatusPacket` packet. + """ + HARDWARE_RESET = (0x00, "Device was reset") + WATCHDOG_TIMER_RESET = (0x01, "Watchdog timer was reset") + JOINED_NETWORK = (0x02, "Device joined to network") + DISASSOCIATED = (0x03, "Device disassociated") + ERROR_SYNCHRONIZATION_LOST = (0x04, "Configuration error/synchronization lost") + COORDINATOR_REALIGNMENT = (0x05, "Coordinator realignment") + COORDINATOR_STARTED = (0x06, "The coordinator started") + NETWORK_SECURITY_KEY_UPDATED = (0x07, "Network security key was updated") + NETWORK_WOKE_UP = (0x0B, "Network Woke Up") + NETWORK_WENT_TO_SLEEP = (0x0C, "Network Went To Sleep") + VOLTAGE_SUPPLY_LIMIT_EXCEEDED = (0x0D, "Voltage supply limit exceeded") + REMOTE_MANAGER_CONNECTED = (0x0E, "Remote Manager connected") + REMOTE_MANAGER_DISCONNECTED = (0x0F, "Remote Manager disconnected") + MODEM_CONFIG_CHANGED_WHILE_JOINING = (0x11, "Modem configuration changed while joining") + BLUETOOTH_CONNECTED = (0x32, "A Bluetooth connection has been made and API mode has been unlocked.") + BLUETOOTH_DISCONNECTED = (0x33, "An unlocked Bluetooth connection has been disconnected.") + BANDMASK_CONFIGURATION_ERROR = (0x34, "LTE-M/NB-IoT bandmask configuration has failed.") + ERROR_STACK = (0x80, "Stack error") + ERROR_AP_NOT_CONNECTED = (0x82, "Send/join command issued without connecting from AP") + ERROR_AP_NOT_FOUND = (0x83, "Access point not found") + ERROR_PSK_NOT_CONFIGURED = (0x84, "PSK not configured") + ERROR_SSID_NOT_FOUND = (0x87, "SSID not found") + ERROR_FAILED_JOIN_SECURITY = (0x88, "Failed to join with security enabled") + ERROR_INVALID_CHANNEL = (0x8A, "Invalid channel") + ERROR_FAILED_JOIN_AP = (0x8E, "Failed to join access point") + UNKNOWN = (0xFF, "UNKNOWN") + + def __init__(self, code, description): + self.__code = code + self.__description = description + + def __get_code(self): + """ + Returns the code of the ModemStatus element. + + Returns: + Integer: the code of the ModemStatus element. + """ + return self.__code + + def __get_description(self): + """ + Returns the description of the ModemStatus element. + + Returns: + String: the description of the ModemStatus element. + """ + return self.__description + + @classmethod + def get(cls, code): + """ + Returns the modem status for the given code. + + Args: + code (Integer): the code of the modem status to get. + + Returns: + :class:`.ModemStatus`: the ModemStatus with the given code. + """ + try: + return cls.lookupTable[code] + except KeyError: + return ModemStatus.UNKNOWN + + code = property(__get_code) + """Integer. The modem status code.""" + + description = property(__get_description) + """String. The modem status description.""" + + +ModemStatus.lookupTable = {x.code: x for x in ModemStatus} +ModemStatus.__doc__ += utils.doc_enum(ModemStatus) + + +@unique +class PowerLevel(Enum): + """ + Enumerates the different power levels. The power level indicates the output + power value of a radio when transmitting data. + """ + LEVEL_LOWEST = (0x00, "Lowest") + LEVEL_LOW = (0x01, "Low") + LEVEL_MEDIUM = (0x02, "Medium") + LEVEL_HIGH = (0x03, "High") + LEVEL_HIGHEST = (0x04, "Highest") + LEVEL_UNKNOWN = (0xFF, "Unknown") + + def __init__(self, code, description): + self.__code = code + self.__description = description + + def __get_code(self): + """ + Returns the code of the PowerLevel element. + + Returns: + Integer: the code of the PowerLevel element. + """ + return self.__code + + def __get_description(self): + """ + Returns the description of the PowerLevel element. + + Returns: + String: the description of the PowerLevel element. + """ + return self.__description + + @classmethod + def get(cls, code): + """ + Returns the power level for the given code. + + Args: + code (Integer): the code of the power level to get. + + Returns: + :class:`.PowerLevel`: the PowerLevel with the given code. + """ + try: + return cls.lookupTable[code] + except KeyError: + return PowerLevel.LEVEL_UNKNOWN + + code = property(__get_code) + """Integer. The power level code.""" + + description = property(__get_description) + """String. The power level description.""" + + +PowerLevel.lookupTable = {x.code: x for x in PowerLevel} +PowerLevel.__doc__ += utils.doc_enum(PowerLevel) + + +@unique +class AssociationIndicationStatus(Enum): + """ + Enumerates the different association indication statuses. + """ + SUCCESSFULLY_JOINED = (0x00, "Successfully formed or joined a network.") + AS_TIMEOUT = (0x01, "Active Scan Timeout.") + AS_NO_PANS_FOUND = (0x02, "Active Scan found no PANs.") + AS_ASSOCIATION_NOT_ALLOWED = (0x03, "Active Scan found PAN, but the CoordinatorAllowAssociation bit is not set.") + AS_BEACONS_NOT_SUPPORTED = (0x04, "Active Scan found PAN, but Coordinator and End Device are not configured to " + "support beacons.") + AS_ID_DOESNT_MATCH = (0x05, "Active Scan found PAN, but the Coordinator ID parameter does not match the ID " + "parameter of the End Device.") + AS_CHANNEL_DOESNT_MATCH = (0x06, "Active Scan found PAN, but the Coordinator CH parameter does not match " + "the CH parameter of the End Device.") + ENERGY_SCAN_TIMEOUT = (0x07, "Energy Scan Timeout.") + COORDINATOR_START_REQUEST_FAILED = (0x08, "Coordinator start request failed.") + COORDINATOR_INVALID_PARAMETER = (0x09, "Coordinator could not start due to invalid parameter.") + COORDINATOR_REALIGNMENT = (0x0A, "Coordinator Realignment is in progress.") + AR_NOT_SENT = (0x0B, "Association Request not sent.") + AR_TIMED_OUT = (0x0C, "Association Request timed out - no reply was received.") + AR_INVALID_PARAMETER = (0x0D, "Association Request had an Invalid Parameter.") + AR_CHANNEL_ACCESS_FAILURE = (0x0E, "Association Request Channel Access Failure. Request was not " + "transmitted - CCA failure.") + AR_COORDINATOR_ACK_WASNT_RECEIVED = (0x0F, "Remote Coordinator did not send an ACK after Association " + "Request was sent.") + AR_COORDINATOR_DIDNT_REPLY = (0x10, "Remote Coordinator did not reply to the Association Request, but an ACK " + "was received after sending the request.") + SYNCHRONIZATION_LOST = (0x12, "Sync-Loss - Lost synchronization with a Beaconing Coordinator.") + DISASSOCIATED = (0x13, " Disassociated - No longer associated to Coordinator.") + NO_PANS_FOUND = (0x21, "Scan found no PANs.") + NO_PANS_WITH_ID_FOUND = (0x22, "Scan found no valid PANs based on current SC and ID settings.") + NJ_EXPIRED = (0x23, "Valid Coordinator or Routers found, but they are not allowing joining (NJ expired).") + NO_JOINABLE_BEACONS_FOUND = (0x24, "No joinable beacons were found.") + UNEXPECTED_STATE = (0x25, "Unexpected state, node should not be attempting to join at this time.") + JOIN_FAILED = (0x27, "Node Joining attempt failed (typically due to incompatible security settings).") + COORDINATOR_START_FAILED = (0x2A, "Coordinator Start attempt failed.") + CHECKING_FOR_COORDINATOR = (0x2B, "Checking for an existing coordinator.") + NETWORK_LEAVE_FAILED = (0x2C, "Attempt to leave the network failed.") + DEVICE_DIDNT_RESPOND = (0xAB, "Attempted to join a device that did not respond.") + UNSECURED_KEY_RECEIVED = (0xAC, "Secure join error - network security key received unsecured.") + KEY_NOT_RECEIVED = (0xAD, "Secure join error - network security key not received.") + INVALID_SECURITY_KEY = (0xAF, "Secure join error - joining device does not have the right preconfigured link key.") + SCANNING_NETWORK = (0xFF, "Scanning for a network/Attempting to associate.") + + def __init__(self, code, description): + self.__code = code + self.__description = description + + def __get_code(self): + """ + Returns the code of the ``AssociationIndicationStatus`` element. + + Returns: + Integer: the code of the ``AssociationIndicationStatus`` element. + """ + return self.__code + + def __get_description(self): + """ + Returns the description of the ``AssociationIndicationStatus`` element. + + Returns: + String: the description of the ``AssociationIndicationStatus`` element. + """ + return self.__description + + @classmethod + def get(cls, code): + """ + Returns the ``AssociationIndicationStatus`` for the given code. + + Args: + code (Integer): the code of the ``AssociationIndicationStatus`` to get. + + Returns: + :class:`.AssociationIndicationStatus`: the ``AssociationIndicationStatus`` with the given code. + """ + try: + return cls.lookupTable[code] + except KeyError: + return None + + code = property(__get_code) + """Integer. The association indication status code.""" + + description = property(__get_description) + """String. The association indication status description.""" + + +AssociationIndicationStatus.lookupTable = {x.code: x for x in AssociationIndicationStatus} +AssociationIndicationStatus.__doc__ += utils.doc_enum(AssociationIndicationStatus) + + +@unique +class CellularAssociationIndicationStatus(Enum): + """ + Enumerates the different association indication statuses for the Cellular protocol. + """ + SUCCESSFULLY_CONNECTED = (0x00, "Connected to the Internet.") + REGISTERING_CELLULAR_NETWORK = (0x22, "Registering to cellular network") + CONNECTING_INTERNET = (0x23, "Connecting to the Internet") + MODEM_FIRMWARE_CORRUPT = (0x24, "The cellular component requires a new firmware image.") + REGISTRATION_DENIED = (0x25, "Cellular network registration was denied.") + AIRPLANE_MODE = (0x2A, "Airplane mode is active.") + USB_DIRECT = (0x2B, "USB Direct mode is active.") + PSM_LOW_POWER = (0x2C, "The cellular component is in the PSM low-power state.") + BYPASS_MODE = (0x2F, "Bypass mode active") + INITIALIZING = (0xFF, "Initializing") + + def __init__(self, code, description): + self.__code = code + self.__description = description + + def __get_code(self): + """ + Returns the code of the ``CellularAssociationIndicationStatus`` element. + + Returns: + Integer: the code of the ``CellularAssociationIndicationStatus`` element. + """ + return self.__code + + def __get_description(self): + """ + Returns the description of the ``CellularAssociationIndicationStatus`` element. + + Returns: + String: the description of the ``CellularAssociationIndicationStatus`` element. + """ + return self.__description + + @classmethod + def get(cls, code): + """ + Returns the ``CellularAssociationIndicationStatus`` for the given code. + + Args: + code (Integer): the code of the ``CellularAssociationIndicationStatus`` to get. + + Returns: + :class:`.CellularAssociationIndicationStatus`: the ``CellularAssociationIndicationStatus`` + with the given code. + """ + try: + return cls.lookupTable[code] + except KeyError: + return None + + code = property(__get_code) + """Integer. The cellular association indication status code.""" + + description = property(__get_description) + """String. The cellular association indication status description.""" + + +CellularAssociationIndicationStatus.lookupTable = {x.code: x for x in CellularAssociationIndicationStatus} +CellularAssociationIndicationStatus.__doc__ += utils.doc_enum(CellularAssociationIndicationStatus) + + +@unique +class DeviceCloudStatus(Enum): + """ + Enumerates the different Device Cloud statuses. + """ + SUCCESS = (0x00, "Success") + BAD_REQUEST = (0x01, "Bad request") + RESPONSE_UNAVAILABLE = (0x02, "Response unavailable") + DEVICE_CLOUD_ERROR = (0x03, "Device Cloud error") + CANCELED = (0x20, "Device Request canceled by user") + TIME_OUT = (0x21, "Session timed out") + UNKNOWN_ERROR = (0x40, "Unknown error") + + def __init__(self, code, description): + self.__code = code + self.__description = description + + def __get_code(self): + """ + Returns the code of the ``DeviceCloudStatus`` element. + + Returns: + Integer: the code of the ``DeviceCloudStatus`` element. + """ + return self.__code + + def __get_description(self): + """ + Returns the description of the ``DeviceCloudStatus`` element. + + Returns: + String: the description of the ``DeviceCloudStatus`` element. + """ + return self.__description + + @classmethod + def get(cls, code): + """ + Returns the Device Cloud status for the given code. + + Args: + code (Integer): the code of the Device Cloud status to get. + + Returns: + :class:`.DeviceCloudStatus`: the ``DeviceCloudStatus`` with the given code, ``None`` if there is not any + status with the provided code. + """ + try: + return cls.lookupTable[code] + except KeyError: + return None + + code = property(__get_code) + """Integer. The Device Cloud status code.""" + + description = property(__get_description) + """String. The Device Cloud status description.""" + + +DeviceCloudStatus.lookupTable = {x.code: x for x in DeviceCloudStatus} +DeviceCloudStatus.__doc__ += utils.doc_enum(DeviceCloudStatus) + + +@unique +class FrameError(Enum): + """ + Enumerates the different frame errors. + """ + INVALID_TYPE = (0x02, "Invalid frame type") + INVALID_LENGTH = (0x03, "Invalid frame length") + INVALID_CHECKSUM = (0x04, "Erroneous checksum on last frame") + PAYLOAD_TOO_BIG = (0x05, "Payload of last API frame was too big to fit into a buffer") + STRING_ENTRY_TOO_BIG = (0x06, "String entry was too big on last API frame sent") + WRONG_STATE = (0x07, "Wrong state to receive frame") + WRONG_REQUEST_ID = (0x08, "Device request ID of device response didn't match the number in the request") + + def __init__(self, code, description): + self.__code = code + self.__description = description + + def __get_code(self): + """ + Returns the code of the ``FrameError`` element. + + Returns: + Integer: the code of the ``FrameError`` element. + """ + return self.__code + + def __get_description(self): + """ + Returns the description of the ``FrameError`` element. + + Returns: + String: the description of the ``FrameError`` element. + """ + return self.__description + + @classmethod + def get(cls, code): + """ + Returns the frame error for the given code. + + Args: + code (Integer): the code of the frame error to get. + + Returns: + :class:`.FrameError`: the ``FrameError`` with the given code, ``None`` if there is not any frame error + with the provided code. + """ + try: + return cls.lookupTable[code] + except KeyError: + return None + + code = property(__get_code) + """Integer. The frame error code.""" + + description = property(__get_description) + """String. The frame error description.""" + + +FrameError.lookupTable = {x.code: x for x in FrameError} +FrameError.__doc__ += utils.doc_enum(FrameError) + + +@unique +class WiFiAssociationIndicationStatus(Enum): + """ + Enumerates the different Wi-Fi association indication statuses. + """ + SUCCESSFULLY_JOINED = (0x00, "Successfully joined to access point.") + INITIALIZING = (0x01, "Initialization in progress.") + INITIALIZED = (0x02, "Initialized, but not yet scanning.") + DISCONNECTING = (0x13, "Disconnecting from access point.") + SSID_NOT_CONFIGURED = (0x23, "SSID not configured") + INVALID_KEY = (0x24, "Encryption key invalid (NULL or invalid length).") + JOIN_FAILED = (0x27, "SSID found, but join failed.") + WAITING_FOR_AUTH = (0x40, "Waiting for WPA or WPA2 authentication.") + WAITING_FOR_IP = (0x41, "Joined to a network and waiting for IP address.") + SETTING_UP_SOCKETS = (0x42, "Joined to a network and IP configured. Setting up listening sockets.") + SCANNING_FOR_SSID = (0xFF, "Scanning for the configured SSID.") + + def __init__(self, code, description): + self.__code = code + self.__description = description + + def __get_code(self): + """ + Returns the code of the ``WiFiAssociationIndicationStatus`` element. + + Returns: + Integer: the code of the ``WiFiAssociationIndicationStatus`` element. + """ + return self.__code + + def __get_description(self): + """ + Returns the description of the ``WiFiAssociationIndicationStatus`` element. + + Returns: + String: the description of the ``WiFiAssociationIndicationStatus`` element. + """ + return self.__description + + @classmethod + def get(cls, code): + """ + Returns the Wi-Fi association indication status for the given code. + + Args: + code (Integer): the code of the Wi-Fi association indication status to get. + + Returns: + :class:`.WiFiAssociationIndicationStatus`: the ``WiFiAssociationIndicationStatus`` with the given code, + ``None`` if there is not any Wi-Fi association indication status with the provided code. + """ + try: + return cls.lookupTable[code] + except KeyError: + return None + + code = property(__get_code) + """Integer. The Wi-Fi association indication status code.""" + + description = property(__get_description) + """String. The Wi-Fi association indication status description.""" + + +WiFiAssociationIndicationStatus.lookupTable = {x.code: x for x in WiFiAssociationIndicationStatus} +WiFiAssociationIndicationStatus.__doc__ += utils.doc_enum(WiFiAssociationIndicationStatus) + + +@unique +class NetworkDiscoveryStatus(Enum): + """ + Enumerates the different statuses of the network discovery process. + """ + SUCCESS = (0x00, "Success") + ERROR_READ_TIMEOUT = (0x01, "Read timeout error") + + def __init__(self, code, description): + self.__code = code + self.__description = description + + def __get_code(self): + """ + Returns the code of the ``NetworkDiscoveryStatus`` element. + + Returns: + Integer: the code of the ``NetworkDiscoveryStatus`` element. + """ + return self.__code + + def __get_description(self): + """ + Returns the description of the ``NetworkDiscoveryStatus`` element. + + Returns: + String: the description of the ``NetworkDiscoveryStatus`` element. + """ + return self.__description + + @classmethod + def get(cls, code): + """ + Returns the network discovery status for the given code. + + Args: + code (Integer): the code of the network discovery status to get. + + Returns: + :class:`.NetworkDiscoveryStatus`: the ``NetworkDiscoveryStatus`` with the given code, ``None`` if + there is not any status with the provided code. + """ + try: + return cls.lookupTable[code] + except KeyError: + return None + + code = property(__get_code) + """Integer. The network discovery status code.""" + + description = property(__get_description) + """String. The network discovery status description.""" + + +NetworkDiscoveryStatus.lookupTable = {x.code: x for x in NetworkDiscoveryStatus} +NetworkDiscoveryStatus.__doc__ += utils.doc_enum(NetworkDiscoveryStatus) diff --git a/digi/xbee/packets/__init__.py b/digi/xbee/packets/__init__.py new file mode 100644 index 0000000..8ea10d3 --- /dev/null +++ b/digi/xbee/packets/__init__.py @@ -0,0 +1,13 @@ +# 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. diff --git a/digi/xbee/packets/aft.py b/digi/xbee/packets/aft.py new file mode 100644 index 0000000..79e993d --- /dev/null +++ b/digi/xbee/packets/aft.py @@ -0,0 +1,114 @@ +# 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. + +from enum import Enum, unique +from digi.xbee.util import utils + + +@unique +class ApiFrameType(Enum): + """ + This enumeration lists all the available frame types used in any XBee protocol. + + | Inherited properties: + | **name** (String): the name (id) of this ApiFrameType. + | **value** (String): the value of this ApiFrameType. + + """ + TX_64 = (0x00, "TX (Transmit) Request 64-bit address") + TX_16 = (0x01, "TX (Transmit) Request 16-bit address") + REMOTE_AT_COMMAND_REQUEST_WIFI = (0x07, "Remote AT Command Request (Wi-Fi)") + AT_COMMAND = (0x08, "AT Command") + AT_COMMAND_QUEUE = (0x09, "AT Command Queue") + TRANSMIT_REQUEST = (0x10, "Transmit Request") + EXPLICIT_ADDRESSING = (0x11, "Explicit Addressing Command Frame") + REMOTE_AT_COMMAND_REQUEST = (0x17, "Remote AT Command Request") + TX_SMS = (0x1F, "TX SMS") + TX_IPV4 = (0x20, "TX IPv4") + SEND_DATA_REQUEST = (0x28, "Send Data Request") + DEVICE_RESPONSE = (0x2A, "Device Response") + USER_DATA_RELAY_REQUEST = (0x2D, "User Data Relay Request") + 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") + RX_IO_16 = (0x83, "IO Data Sample RX 16-bit Address Indicator") + REMOTE_AT_COMMAND_RESPONSE_WIFI = (0x87, "Remote AT Command Response (Wi-Fi)") + AT_COMMAND_RESPONSE = (0x88, "AT Command Response") + TX_STATUS = (0x89, "TX (Transmit) Status") + MODEM_STATUS = (0x8A, "Modem Status") + TRANSMIT_STATUS = (0x8B, "Transmit Status") + IO_DATA_SAMPLE_RX_INDICATOR_WIFI = (0x8F, "IO Data Sample RX Indicator (Wi-Fi)") + RECEIVE_PACKET = (0x90, "Receive Packet") + EXPLICIT_RX_INDICATOR = (0x91, "Explicit RX Indicator") + IO_DATA_SAMPLE_RX_INDICATOR = (0x92, "IO Data Sample RX Indicator") + REMOTE_AT_COMMAND_RESPONSE = (0x97, "Remote Command Response") + RX_SMS = (0x9F, "RX SMS") + 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") + FRAME_ERROR = (0xFE, "Frame Error") + GENERIC = (0xFF, "Generic") + UNKNOWN = (-1, "Unknown Packet") + + def __init__(self, code, description): + self.__code = code + self.__description = description + + def __get_code(self): + """ + Returns the code of the ApiFrameType element. + + Returns: + Integer: the code of the ApiFrameType element. + """ + return self.__code + + def __get_description(self): + """ + Returns the description of the ApiFrameType element. + + Returns: + Integer: the description of the ApiFrameType element. + """ + return self.__description + + @classmethod + def get(cls, code): + """ + Retrieves the api frame type associated to the given ID. + + Args: + code (Integer): the code of the API frame type to get. + + Returns: + :class:`.ApiFrameType`: the API frame type associated to the given code or ``UNKNOWN`` if + the given code is not a valid ApiFrameType code. + """ + try: + return cls.lookupTable[code] + except KeyError: + return ApiFrameType.UNKNOWN + + code = property(__get_code) + """Integer. The API frame type code.""" + + description = property(__get_description) + """String. The API frame type description.""" + + +# Dictionary 255: + raise ValueError("Frame ID must be between 0 and 255.") + self._frame_id = frame_id + + @staticmethod + def _check_api_packet(raw, min_length=5): + """ + Checks the not escaped bytearray 'raw' meets conditions. + + Args: + raw (Bytearray): non-escaped bytearray to be checked. + min_length (Integer): the minimum length of the packet in bytes. + + Raises: + InvalidPacketException: if the bytearray length is less than 5. (start delim. + length (2 bytes) + + frame type + checksum = 5 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). + + .. seealso:: + | :mod:`.factory` + """ + if len(raw) < min_length: + 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("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("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("Wrong checksum") + + @abstractmethod + def _get_api_packet_spec_data(self): + """ + Returns the frame specific data without frame type and frame ID fields. + + Returns: + Bytearray: the frame specific data without frame type and frame ID fields. + """ + pass + + @abstractmethod + def needs_id(self): + """ + Returns whether the packet requires frame ID or not. + + Returns: + Boolean: ``True`` if the packet needs frame ID, ``False`` otherwise. + """ + pass + + @abstractmethod + def _get_api_packet_spec_data_dict(self): + """ + Similar to :meth:`XBeeAPIPacket._get_api_packet_spec_data` but returns data as dictionary or list. + + Returns: + Dictionary: data as dictionary or list. + """ + pass + + frame_id = property(__get_frame_id, __set_frame_id) + + +class GenericXBeePacket(XBeeAPIPacket): + """ + This class represents a basic and Generic XBee packet. + + .. seealso:: + | :class:`.XBeeAPIPacket` + """ + + __MIN_PACKET_LENGTH = 5 + + def __init__(self, rf_data): + """ + Class constructor. Instantiates a :class:`.GenericXBeePacket` object with the provided parameters. + + Args: + rf_data (bytearray): the frame specific data without frame type and frame ID. + + .. seealso:: + | :mod:`.factory` + | :class:`.XBeeAPIPacket` + """ + super(GenericXBeePacket, self).__init__(api_frame_type=ApiFrameType.GENERIC) + self.__rf_data = rf_data + + @staticmethod + def create_packet(raw, operating_mode=OperatingMode.API_MODE): + """ + Override method. + + Returns: + :class:`.GenericXBeePacket`: the GenericXBeePacket generated. + + Raises: + InvalidPacketException: if the bytearray length is less than 5. (start delim. + length (2 bytes) + + frame type + checksum = 5 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 different than :attr:`.ApiFrameType.GENERIC`. + InvalidOperatingModeException: if ``operating_mode`` is not supported. + + .. seealso:: + | :meth:`.XBeePacket.create_packet` + | :meth:`.XBeeAPIPacket._check_api_packet` + """ + if operating_mode != OperatingMode.ESCAPED_API_MODE and operating_mode != OperatingMode.API_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("Wrong frame type, expected: " + ApiFrameType.GENERIC.description + + ". Value: " + ApiFrameType.GENERIC.code) + + return GenericXBeePacket(raw[4:-1]) + + def _get_api_packet_spec_data(self): + """ + Override method. + + .. seealso:: + | :meth:`.XBeeAPIPacket._get_api_packet_spec_data` + """ + return bytearray(self.__rf_data) + + def needs_id(self): + """ + Override method. + + .. seealso:: + | :meth:`.XBeeAPIPacket.needs_id` + """ + return False + + def _get_api_packet_spec_data_dict(self): + """ + Override method. + + .. seealso:: + | :meth:`.XBeeAPIPacket._get_api_packet_spec_data_dict` + """ + return {DictKeys.RF_DATA: self.__rf_data} + + +class UnknownXBeePacket(XBeeAPIPacket): + """ + This class represents an unknown XBee packet. + + .. seealso:: + | :class:`.XBeeAPIPacket` + """ + + __MIN_PACKET_LENGTH = 5 + + def __init__(self, api_frame, rf_data): + """ + Class constructor. Instantiates a :class:`.UnknownXBeePacket` object with the provided parameters. + + Args: + api_frame (Integer): the API frame integer value of this packet. + rf_data (bytearray): the frame specific data without frame type and frame ID. + + .. seealso:: + | :mod:`.factory` + | :class:`.XBeeAPIPacket` + """ + super(UnknownXBeePacket, self).__init__(api_frame_type=api_frame) + self.__rf_data = rf_data + + @staticmethod + def create_packet(raw, operating_mode=OperatingMode.API_MODE): + """ + Override method. + + Returns: + :class:`.UnknownXBeePacket`: the UnknownXBeePacket generated. + + Raises: + InvalidPacketException: if the bytearray length is less than 5. (start delim. + length (2 bytes) + + frame type + checksum = 5 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). + InvalidOperatingModeException: if ``operating_mode`` is not supported. + + .. seealso:: + | :meth:`.XBeePacket.create_packet` + | :meth:`.XBeeAPIPacket._check_api_packet` + """ + if operating_mode != OperatingMode.ESCAPED_API_MODE and operating_mode != OperatingMode.API_MODE: + raise InvalidOperatingModeException(operating_mode + " is not supported.") + + XBeeAPIPacket._check_api_packet(raw, min_length=UnknownXBeePacket.__MIN_PACKET_LENGTH) + + return UnknownXBeePacket(raw[3], raw[4:-1]) + + def _get_api_packet_spec_data(self): + """ + Override method. + + .. seealso:: + | :meth:`.XBeeAPIPacket._get_api_packet_spec_data` + """ + return bytearray(self.__rf_data) + + def needs_id(self): + """ + Override method. + + .. seealso:: + | :meth:`.XBeeAPIPacket.needs_id` + """ + return False + + def _get_api_packet_spec_data_dict(self): + """ + Override method. + + .. seealso:: + | :meth:`.XBeeAPIPacket._get_api_packet_spec_data_dict` + """ + return {DictKeys.RF_DATA: self.__rf_data} diff --git a/digi/xbee/packets/cellular.py b/digi/xbee/packets/cellular.py new file mode 100644 index 0000000..c754e02 --- /dev/null +++ b/digi/xbee/packets/cellular.py @@ -0,0 +1,353 @@ +# 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 +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +from digi.xbee.packets.base import XBeeAPIPacket, DictKeys +from digi.xbee.exception import InvalidOperatingModeException, InvalidPacketException +from digi.xbee.packets.aft import ApiFrameType +from digi.xbee.models.mode import OperatingMode +from digi.xbee.models.options import TransmitOptions +from digi.xbee.util import utils +import re + + +PATTERN_PHONE_NUMBER = "^\+?\d+$" +"""Pattern used to validate the phone number parameter of SMS packets.""" + + +class RXSMSPacket(XBeeAPIPacket): + """ + This class represents an RX (Receive) SMS packet. Packet is built + using the parameters of the constructor or providing a valid byte array. + + .. seealso:: + | :class:`.TXSMSPacket` + | :class:`.XBeeAPIPacket` + """ + + __MIN_PACKET_LENGTH = 25 + + def __init__(self, phone_number, data): + """ + Class constructor. Instantiates a new :class:`.RXSMSPacket` object withe the provided parameters. + + Args: + phone_number (String): phone number of the device that sent the SMS. + data (String): packet data (text of the SMS). + + Raises: + ValueError: if length of ``phone_number`` is greater than 20. + ValueError: if ``phone_number`` is not a valid phone number. + """ + if len(phone_number) > 20: + raise ValueError("Phone number length cannot be greater than 20 bytes") + if not re.match(PATTERN_PHONE_NUMBER, phone_number): + raise ValueError("Phone number invalid, only numbers and '+' prefix allowed.") + super().__init__(ApiFrameType.RX_SMS) + + self.__phone_number = bytearray(20) + self.__phone_number[0:len(phone_number)] = phone_number.encode("utf8") + self.__data = data + + @staticmethod + def create_packet(raw, operating_mode): + """ + Override method. + + Returns: + :class:`.RXSMSPacket` + + Raises: + InvalidPacketException: if the bytearray length is less than 25. (start delim + length (2 bytes) + + frame type + phone number (20 bytes) + checksum = 25 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:`.SPECIAL_BYTE`. + InvalidPacketException: if the calculated checksum is different than the checksum field value (last byte). + InvalidPacketException: if the frame type is different than :py:attr:`.ApiFrameType.RX_SMS`. + InvalidOperatingModeException: if ``operating_mode`` is not supported. + + .. seealso:: + | :meth:`.XBeePacket.create_packet` + """ + if operating_mode != OperatingMode.ESCAPED_API_MODE and operating_mode != OperatingMode.API_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("This packet is not an RXSMSPacket") + + return RXSMSPacket(raw[4:23].decode("utf8").replace("\0", ""), raw[24:-1].decode("utf8")) + + def needs_id(self): + """ + Override method. + + .. seealso:: + | :meth:`.XBeeAPIPacket.needs_id` + """ + return False + + def get_phone_number_byte_array(self): + """ + Returns the phone number byte array. + + Returns: + Bytearray: phone number of the device that sent the SMS. + """ + return self.__phone_number + + def __get_phone_number(self): + """ + Returns the phone number of the device that sent the SMS. + + Returns: + String: phone number of the device that sent the SMS. + """ + return self.__phone_number.decode("utf8").replace("\0", "") + + def __set_phone_number(self, phone_number): + """ + Sets the phone number of the device that sent the SMS. + + Args: + phone_number (String): the new phone number. + + Raises: + ValueError: if length of ``phone_number`` is greater than 20. + ValueError: if ``phone_number`` is not a valid phone number. + """ + if len(phone_number) > 20: + raise ValueError("Phone number length cannot be greater than 20 bytes") + if not re.match(PATTERN_PHONE_NUMBER, phone_number): + raise ValueError("Phone number invalid, only numbers and '+' prefix allowed.") + + self.__phone_number = bytearray(20) + self.__phone_number[0:len(phone_number)] = phone_number.encode("utf8") + + def __get_data(self): + """ + Returns the data of the packet (SMS text). + + Returns: + String: the data of the packet. + """ + return self.__data + + def __set_data(self, data): + """ + Sets the data of the packet. + + Args: + data (String): the new data of the packet. + """ + self.__data = data + + def _get_api_packet_spec_data(self): + """ + Override method. + + .. seealso:: + | :meth:`.XBeeAPIPacket._get_API_packet_spec_data` + """ + ret = bytearray() + ret += self.__phone_number + if self.__data is not None: + ret += self.__data.encode("utf8") + return ret + + def _get_api_packet_spec_data_dict(self): + """ + Override method. + + .. seealso:: + | :meth:`.XBeeAPIPacket._get_API_packet_spec_data_dict` + """ + return {DictKeys.PHONE_NUMBER: self.__phone_number, + DictKeys.RF_DATA: self.__data} + + phone_number = property(__get_phone_number, __set_phone_number) + """String. Phone number that sent the SMS.""" + + data = property(__get_data, __set_data) + """String. Data of the SMS.""" + + +class TXSMSPacket(XBeeAPIPacket): + """ + This class represents a TX (Transmit) SMS packet. Packet is built + using the parameters of the constructor or providing a valid byte array. + + .. seealso:: + | :class:`.RXSMSPacket` + | :class:`.XBeeAPIPacket` + """ + + __MIN_PACKET_LENGTH = 27 + + def __init__(self, frame_id, phone_number, data): + """ + Class constructor. Instantiates a new :class:`.TXSMSPacket` object with the provided parameters. + + Args: + frame_id (Integer): the frame ID. Must be between 0 and 255. + phone_number (String): the phone number. + data (String): this packet's data. + + Raises: + ValueError: if ``frame_id`` is not between 0 and 255. + ValueError: if length of ``phone_number`` is greater than 20. + ValueError: if ``phone_number`` is not a valid phone number. + + .. seealso:: + | :class:`.XBeeAPIPacket` + """ + if frame_id < 0 or frame_id > 255: + raise ValueError("Frame id must be between 0 and 255") + if len(phone_number) > 20: + raise ValueError("Phone number length cannot be greater than 20 bytes") + if not re.match(PATTERN_PHONE_NUMBER, phone_number): + raise ValueError("Phone number invalid, only numbers and '+' prefix allowed.") + super().__init__(ApiFrameType.TX_SMS) + + self._frame_id = frame_id + self.__transmit_options = TransmitOptions.NONE.value + self.__phone_number = bytearray(20) + self.__phone_number[0:len(phone_number)] = phone_number.encode("utf8") + self.__data = data + + @staticmethod + def create_packet(raw, operating_mode): + """ + Override method. + + Returns: + :class:`.TXSMSPacket` + + Raises: + InvalidPacketException: if the bytearray length is less than 27. (start delim, length (2 bytes), frame type, + frame id, transmit options, phone number (20 bytes), checksum) + 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:`.SPECIAL_BYTE`. + InvalidPacketException: if the calculated checksum is different than the checksum field value (last byte). + InvalidPacketException: if the frame type is different than :py:attr:`.ApiFrameType.TX_SMS`. + InvalidOperatingModeException: if ``operating_mode`` is not supported. + + .. seealso:: + | :meth:`.XBeePacket.create_packet` + """ + if operating_mode != OperatingMode.ESCAPED_API_MODE and operating_mode != OperatingMode.API_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("This packet is not a TXSMSPacket") + + return TXSMSPacket(raw[4], raw[6:25].decode("utf8").replace("\0", ""), raw[26:-1].decode("utf8")) + + def needs_id(self): + """ + Override method. + + .. seealso:: + | :meth:`.XBeeAPIPacket.needs_id` + """ + return True + + def get_phone_number_byte_array(self): + """ + Returns the phone number byte array. + + Returns: + Bytearray: phone number of the device that sent the SMS. + """ + return self.__phone_number + + def __get_phone_number(self): + """ + Returns the phone number of the transmitter device. + + Returns: + String: the phone number of the transmitter device. + """ + return self.__phone_number.decode("utf8").replace("\0", "") + + def __set_phone_number(self, phone_number): + """ + Sets the phone number of the transmitter device. + + Args: + phone_number (String): the new phone number. + + Raises: + ValueError: if length of ``phone_number`` is greater than 20. + ValueError: if ``phone_number`` is not a valid phone number. + """ + if len(phone_number) > 20: + raise ValueError("Phone number length cannot be greater than 20 bytes") + if not re.match(PATTERN_PHONE_NUMBER, phone_number): + raise ValueError("Phone number invalid, only numbers and '+' prefix allowed.") + + self.__phone_number = bytearray(20) + self.__phone_number[0:len(phone_number)] = phone_number.encode("utf8") + + def __get_data(self): + """ + Returns the data of the packet (SMS text). + + Returns: + Bytearray: packet's data. + """ + return self.__data + + def __set_data(self, data): + """ + Sets the data of the packet. + + Args: + data (Bytearray): the new data of the packet. + """ + self.__data = data + + def _get_api_packet_spec_data(self): + """ + Override method. + + .. seealso:: + | :meth:`.XBeeAPIPacket._get_API_packet_spec_data` + """ + ret = utils.int_to_bytes(self.__transmit_options, num_bytes=1) + ret += self.__phone_number + if self.__data is not None: + ret += self.__data.encode("utf8") + return ret + + def _get_api_packet_spec_data_dict(self): + """ + Override method. + + .. seealso:: + | :meth:`.XBeeAPIPacket._get_API_packet_spec_data_dict` + """ + return {DictKeys.OPTIONS: self.__transmit_options, + DictKeys.PHONE_NUMBER: self.__phone_number, + DictKeys.RF_DATA: self.__data} + + phone_number = property(__get_phone_number, __set_phone_number) + """String. Phone number that sent the SMS.""" + + data = property(__get_data, __set_data) + """String. Data of the SMS.""" diff --git a/digi/xbee/packets/common.py b/digi/xbee/packets/common.py new file mode 100644 index 0000000..40e3a07 --- /dev/null +++ b/digi/xbee/packets/common.py @@ -0,0 +1,2895 @@ +# 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 +# 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.mode import OperatingMode +from digi.xbee.models.address import XBee16BitAddress, XBee64BitAddress +from digi.xbee.models.status import ATCommandStatus, DiscoveryStatus, TransmitStatus, ModemStatus +from digi.xbee.packets.aft import ApiFrameType +from digi.xbee.packets.base import XBeeAPIPacket, DictKeys +from digi.xbee.util import utils +from digi.xbee.exception import InvalidOperatingModeException, InvalidPacketException +from digi.xbee.io import IOSample, IOLine + +import copy + +class ATCommPacket(XBeeAPIPacket): + """ + This class represents an AT command packet. + + Used to query or set module parameters on the local device. This API + command applies changes after executing the command. (Changes made to + module parameters take effect once changes are applied.). + + Command response is received as an :class:`.ATCommResponsePacket`. + + .. seealso:: + | :class:`.ATCommResponsePacket` + | :class:`.XBeeAPIPacket` + """ + + __MIN_PACKET_LENGTH = 6 + + def __init__(self, frame_id, command, parameter=None): + """ + Class constructor. Instantiates a new :class:`.ATCommPacket` object with the provided parameters. + + Args: + frame_id (Integer): the frame ID of the packet. + command (String): the AT command of the packet. Must be a string. + parameter (Bytearray, optional): the AT command parameter. Optional. + + Raises: + ValueError: if ``frame_id`` is less than 0 or greater than 255. + ValueError: if length of ``command`` is different than 2. + + .. seealso:: + | :class:`.XBeeAPIPacket` + """ + if len(command) != 2: + raise ValueError("Invalid command " + command) + + if frame_id < 0 or frame_id > 255: + raise ValueError("Frame id must be between 0 and 255.") + + super(ATCommPacket, self).__init__(ApiFrameType.AT_COMMAND) + self.__command = command + self.__parameter = parameter + self._frame_id = frame_id + + @staticmethod + def create_packet(raw, operating_mode): + """ + Override method. + + Returns: + :class:`.ATCommPacket` + + Raises: + InvalidPacketException: if the bytearray length is less than 6. (start delim. + length (2 bytes) + frame + type + frame id + checksum = 6 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 different than :attr:`.ApiFrameType.AT_COMMAND`. + InvalidOperatingModeException: if ``operating_mode`` is not supported. + + .. seealso:: + | :meth:`.XBeePacket.create_packet` + | :meth:`.XBeeAPIPacket._check_api_packet` + """ + if operating_mode != OperatingMode.ESCAPED_API_MODE and operating_mode != OperatingMode.API_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("This packet is not an AT command packet.") + + return ATCommPacket(raw[4], raw[5:7].decode("utf8"), 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` + """ + if self.__parameter is not None: + return bytearray(self.__command, "utf8") + self.__parameter + return bytearray(self.__command, "utf8") + + def _get_api_packet_spec_data_dict(self): + """ + Override method. + + .. seealso:: + | :meth:`.XBeeAPIPacket._get_api_packet_spec_data_dict` + """ + return {DictKeys.COMMAND: self.__command, + DictKeys.PARAMETER: list(self.__parameter) if self.__parameter is not None else None} + + def __get_command(self): + """ + Returns the AT command of the packet. + + Returns: + String: the AT command of the packet. + """ + return self.__command + + def __set_command(self, command): + """ + Sets the AT command of the packet. + + Args: + command (String): the new AT command of the packet. Must have length = 2. + + Raises: + ValueError: if length of ``command`` is different than 2. + """ + if len(command) != 2: + raise ValueError("Invalid command " + command) + self.__command = command + + def __get_parameter(self): + """ + Returns the parameter of the packet. + + Returns: + Bytearray: the parameter of the packet. + """ + return self.__parameter + + def __set_parameter(self, param): + """ + Sets the parameter of the packet. + + Args: + param (Bytearray): the new parameter of the packet. + """ + self.__parameter = param + + command = property(__get_command, __set_command) + """String. AT command.""" + + parameter = property(__get_parameter, __set_parameter) + """Bytearray. AT command parameter.""" + + +class ATCommQueuePacket(XBeeAPIPacket): + """ + This class represents an AT command Queue packet. + + Used to query or set module parameters on the local device. + + In contrast to the :class:`.ATCommPacket` API packet, new parameter + values are queued and not applied until either an :class:`.ATCommPacket` + is sent or the ``applyChanges()`` method of the :class:`.XBeeDevice` + class is issued. + + Command response is received as an :class:`.ATCommResponsePacket`. + + .. seealso:: + | :class:`.ATCommResponsePacket` + | :class:`.XBeeAPIPacket` + """ + + __MIN_PACKET_LENGTH = 6 + + def __init__(self, frame_id, command, parameter=None): + """ + Class constructor. Instantiates a new :class:`.ATCommQueuePacket` object with the provided parameters. + + Args: + frame_id (Integer): the frame ID of the packet. + command (String): the AT command of the packet. Must be a string. + parameter (Bytearray, optional): the AT command parameter. Optional. + + Raises: + ValueError: if ``frame_id`` is less than 0 or greater than 255. + ValueError: if length of ``command`` is different than 2. + + .. seealso:: + | :class:`.XBeeAPIPacket` + """ + if len(command) != 2: + raise ValueError("Invalid command " + command) + + if frame_id < 0 or frame_id > 255: + raise ValueError("Frame id must be between 0 and 255.") + + super(ATCommQueuePacket, self).__init__(ApiFrameType.AT_COMMAND_QUEUE) + self.__command = command + self.__parameter = parameter + self._frame_id = frame_id + + @staticmethod + def create_packet(raw, operating_mode): + """ + Override method. + + Returns: + :class:`.ATCommQueuePacket` + + Raises: + InvalidPacketException: if the bytearray length is less than 6. (start delim. + length (2 bytes) + frame + type + frame id + checksum = 6 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 different than :attr:`.ApiFrameType.AT_COMMAND_QUEUE`. + InvalidOperatingModeException: if ``operating_mode`` is not supported. + + .. seealso:: + | :meth:`.XBeePacket.create_packet` + | :meth:`.XBeeAPIPacket._check_api_packet` + """ + if operating_mode != OperatingMode.ESCAPED_API_MODE and operating_mode != OperatingMode.API_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("This packet is not an AT command Queue packet.") + + return ATCommQueuePacket(raw[4], raw[5:7].decode("utf8"), 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` + """ + if self.__parameter is not None: + return bytearray(self.__command, "utf8") + self.__parameter + return bytearray(self.__command, "utf8") + + def _get_api_packet_spec_data_dict(self): + """ + Override method. + + .. seealso:: + | :meth:`.XBeeAPIPacket._get_api_packet_spec_data_dict` + """ + return {DictKeys.COMMAND: self.__command, + DictKeys.PARAMETER: list(self.__parameter) if self.__parameter is not None else None} + + def __get_command(self): + """ + Returns the AT command of the packet. + + Returns: + String: the AT command of the packet. + """ + return self.__command + + def __set_command(self, command): + """ + Sets the AT command of the packet. + + Args: + command (String): the new AT command of the packet. Must have length = 2. + + Raises: + ValueError: if length of ``command`` is different than 2. + """ + if len(command) != 2: + raise ValueError("Invalid command " + command) + self.__command = command + + def __get_parameter(self): + """ + Returns the parameter of the packet. + + Returns: + Bytearray: the parameter of the packet. + """ + return self.__parameter + + def __set_parameter(self, param): + """ + Sets the parameter of the packet. + + Args: + param (Bytearray): the new parameter of the packet. + """ + self.__parameter = param + + command = property(__get_command, __set_command) + """String. AT command.""" + + parameter = property(__get_parameter, __set_parameter) + """Bytearray. AT command parameter.""" + + +class ATCommResponsePacket(XBeeAPIPacket): + """ + This class represents an AT command response packet. + + In response to an AT command message, the module will send an AT command + response message. Some commands will send back multiple frames (for example, + the ``ND`` - Node Discover command). + + This packet is received in response of an :class:`.ATCommPacket`. + + Response also includes an :class:`.ATCommandStatus` object with the status + of the AT command. + + .. seealso:: + | :class:`.ATCommPacket` + | :class:`.ATCommandStatus` + | :class:`.XBeeAPIPacket` + """ + + __MIN_PACKET_LENGTH = 9 + + 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`): the status of the AT command. + comm_value (Bytearray, optional): the AT command response value. Optional. + + Raises: + ValueError: if ``frame_id`` is less than 0 or greater than 255. + ValueError: if length of ``command`` is different than 2. + + .. seealso:: + | :class:`.ATCommandStatus` + | :class:`.XBeeAPIPacket` + """ + if frame_id < 0 or frame_id > 255: + raise ValueError("Frame id must be between 0 and 255.") + if len(command) != 2: + raise ValueError("Invalid command " + command) + + super(ATCommResponsePacket, self).__init__(ApiFrameType.AT_COMMAND_RESPONSE) + self._frame_id = frame_id + self.__command = command + self.__response_status = response_status + self.__comm_value = comm_value + + @staticmethod + def create_packet(raw, operating_mode): + """ + Override method. + + Returns: + :class:`.ATCommResponsePacket` + + Raises: + InvalidPacketException: if the bytearray length is less than 9. (start delim. + length (2 bytes) + + frame type + frame id + at command (2 bytes) + command 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 different than :attr:`.ApiFrameType.AT_COMMAND_RESPONSE`. + InvalidPacketException: if the command status field is not a valid value. See :class:`.ATCommandStatus`. + InvalidOperatingModeException: if ``operating_mode`` is not supported. + + .. seealso:: + | :meth:`.XBeePacket.create_packet` + | :meth:`.XBeeAPIPacket._check_api_packet` + """ + if operating_mode != OperatingMode.ESCAPED_API_MODE and operating_mode != OperatingMode.API_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("This packet is not an AT command response packet.") + if ATCommandStatus.get(raw[7]) is None: + raise InvalidPacketException("Invalid command status.") + + return ATCommResponsePacket(raw[4], raw[5:7].decode("utf8"), ATCommandStatus.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(self.__command, "utf8") + ret.append(self.__response_status.code) + if self.__comm_value is not None: + ret += self.__comm_value + return ret + + def _get_api_packet_spec_data_dict(self): + """ + Override method. + + .. seealso:: + | :meth:`.XBeeAPIPacket._get_api_packet_spec_data_dict` + """ + return {DictKeys.COMMAND: self.__command, + DictKeys.AT_CMD_STATUS: self.__response_status, + DictKeys.RF_DATA: list(self.__comm_value) if self.__comm_value is not None else None} + + def __get_command(self): + """ + Returns the AT command of the packet. + + Returns: + String: the AT command of the packet. + """ + return self.__command + + def __set_command(self, command): + """ + Sets the AT command of the packet. + + Args: + command (String): the new AT command of the packet. Must have length = 2. + + Raises: + ValueError: if length of ``command`` is different than 2. + """ + if len(command) != 2: + raise ValueError("Invalid command " + command) + self.__command = command + + def __get_value(self): + """ + Returns the AT command response value. + + Returns: + Bytearray: the AT command response value. + """ + return self.__comm_value + + def __set_value(self, __comm_value): + """ + Sets the AT command response value. + + Args: + __comm_value (Bytearray): the new AT command response value. + """ + self.__comm_value = __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 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. + + .. seealso:: + | :class:`.ATCommandStatus` + """ + self.__response_status = response_status + + command = property(__get_command, __set_command) + """String. AT command.""" + + command_value = property(__get_value, __set_value) + """Bytearray. AT command value.""" + + status = property(__get_response_status, __set_response_status) + """:class:`.ATCommandStatus`. AT command response status.""" + + +class ReceivePacket(XBeeAPIPacket): + """ + This class represents a receive packet. Packet is built using the parameters + of the constructor or providing a valid byte array. + + When the module receives an RF packet, it is sent out the UART using this + message type. + + This packet is received when external devices send transmit request + packets to this module. + + Among received data, some options can also be received indicating + transmission parameters. + + .. seealso:: + | :class:`.TransmitPacket` + | :class:`.ReceiveOptions` + | :class:`.XBeeAPIPacket` + """ + + __MIN_PACKET_LENGTH = 16 + + def __init__(self, x64bit_addr, x16bit_addr, receive_options, rf_data=None): + """ + Class constructor. Instantiates a new :class:`.ReceivePacket` object with the provided parameters. + + Args: + x64bit_addr (:class:`.XBee64BitAddress`): the 64-bit source address. + x16bit_addr (:class:`.XBee16BitAddress`): the 16-bit source address. + receive_options (Integer): bitfield indicating the receive options. + rf_data (Bytearray, optional): received RF data. Optional. + + .. seealso:: + | :class:`.ReceiveOptions` + | :class:`.XBee16BitAddress` + | :class:`.XBee64BitAddress` + | :class:`.XBeeAPIPacket` + """ + super(ReceivePacket, self).__init__(ApiFrameType.RECEIVE_PACKET) + self.__x64bit_addr = x64bit_addr + self.__x16bit_addr = x16bit_addr + self.__receive_options = receive_options + self.__rf_data = rf_data + + @staticmethod + def create_packet(raw, operating_mode): + """ + Override method. + + Returns: + :class:`.ATCommResponsePacket` + + Raises: + InvalidPacketException: if the bytearray length is less than 16. (start delim. + length (2 bytes) + frame + type + frame id + 64bit addr. + 16bit addr. + Receive options + checksum = 16 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.RECEIVE_PACKET`. + InvalidOperatingModeException: if ``operating_mode`` is not supported. + + .. seealso:: + | :meth:`.XBeePacket.create_packet` + | :meth:`.XBeeAPIPacket._check_api_packet` + """ + if operating_mode != OperatingMode.ESCAPED_API_MODE and operating_mode != OperatingMode.API_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("This packet is not a receive packet.") + return ReceivePacket(XBee64BitAddress(raw[4:12]), + XBee16BitAddress(raw[12:14]), + raw[14], + raw[15:-1]) + + 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 = self.__x64bit_addr.address + ret += self.__x16bit_addr.address + ret.append(self.__receive_options) + if self.__rf_data is not None: + return ret + self.__rf_data + 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: self.__x64bit_addr.address, + DictKeys.X16BIT_ADDR: self.__x16bit_addr.address, + DictKeys.RECEIVE_OPTIONS: self.__receive_options, + DictKeys.RF_DATA: list(self.__rf_data) if self.__rf_data is not None else None} + + def __get_64bit_addr(self): + """ + Returns the 64-bit source address. + + Returns: + :class:`.XBee64BitAddress`: the 64-bit source address. + + .. seealso:: + | :class:`.XBee64BitAddress` + """ + return self.__x64bit_addr + + def __set_64bit_addr(self, x64bit_addr): + """ + Sets the 64-bit source address. + + Args: + x64bit_addr (:class:`.XBee64BitAddress`): the new 64-bit source address. + + .. seealso:: + | :class:`.XBee64BitAddress` + """ + self.__x64bit_addr = x64bit_addr + + def __get_16bit_addr(self): + """ + Returns the 16-bit source address. + + Returns: + :class:`.XBee16BitAddress`: the 16-bit source address. + + .. seealso:: + | :class:`.XBee16BitAddress` + """ + return self.__x16bit_addr + + def __set_16bit_addr(self, x16bit_addr): + """ + Sets the 16-bit source address. + + Args: + x16bit_addr (:class:`.XBee16BitAddress`): the new 16-bit source address. + + .. seealso:: + | :class:`.XBee16BitAddress` + """ + self.__x16bit_addr = x16bit_addr + + def __get_options(self): + """ + Returns the receive options bitfield. + + Returns: + Integer: the receive options bitfield. + + .. seealso:: + | :class:`.ReceiveOptions` + """ + return self.__receive_options + + def __set_options(self, receive_options): + """ + Sets the receive options bitfield. + + Args: + receive_options (Integer): the new receive options bitfield. + + .. seealso:: + | :class:`.ReceiveOptions` + """ + self.__receive_options = receive_options + + def __get_rf_data(self): + """ + Returns the received RF data. + + Returns: + Bytearray: the received RF data. + """ + if self.__rf_data is None: + return None + return copy.copy(self.__rf_data) + + def __set_rf_data(self, rf_data): + """ + Sets the received RF data. + + Args: + rf_data (Bytearray): the new received RF data. + """ + if rf_data is None: + self.__rf_data = None + else: + self.__rf_data = copy.copy(rf_data) + + x64bit_source_addr = property(__get_64bit_addr, __set_64bit_addr) + """:class:`.XBee64BitAddress`. 64-bit source address.""" + + x16bit_source_addr = property(__get_16bit_addr, __set_16bit_addr) + """:class:`.XBee16BitAddress`. 16-bit source address.""" + + receive_options = property(__get_options, __set_options) + """Integer. Receive options bitfield.""" + + rf_data = property(__get_rf_data, __set_rf_data) + """Bytearray. Received RF data.""" + + +class RemoteATCommandPacket(XBeeAPIPacket): + """ + This class represents a Remote AT command Request packet. Packet is built + using the parameters of the constructor or providing a valid byte array. + + Used to query or set module parameters on a remote device. For parameter + changes on the remote device to take effect, changes must be applied, either + by setting the apply changes options bit, or by sending an ``AC`` command + to the remote node. + + Remote command options are set as a bitfield. + + If configured, command response is received as a :class:`.RemoteATCommandResponsePacket`. + + .. seealso:: + | :class:`.RemoteATCommandResponsePacket` + | :class:`.XBeeAPIPacket` + """ + + __MIN_PACKET_LENGTH = 19 + + def __init__(self, frame_id, x64bit_addr, x16bit_addr, transmit_options, command, parameter=None): + """ + Class constructor. Instantiates a new :class:`.RemoteATCommandPacket` object with the provided parameters. + + Args: + frame_id (integer): the frame ID of the packet. + x64bit_addr (:class:`.XBee64BitAddress`): the 64-bit destination address. + x16bit_addr (:class:`.XBee16BitAddress`): the 16-bit destination address. + transmit_options (Integer): bitfield of supported transmission options. + command (String): AT command to send. + parameter (Bytearray, optional): AT command parameter. Optional. + + Raises: + ValueError: if ``frame_id`` is less than 0 or greater than 255. + ValueError: if length of ``command`` is different than 2. + + .. seealso:: + | :class:`.RemoteATCmdOptions` + | :class:`.XBee16BitAddress` + | :class:`.XBee64BitAddress` + | :class:`.XBeeAPIPacket` + """ + if frame_id < 0 or frame_id > 255: + raise ValueError("Frame id must be between 0 and 255.") + + if len(command) != 2: + raise ValueError("Invalid command " + command) + + super(RemoteATCommandPacket, self).__init__(ApiFrameType.REMOTE_AT_COMMAND_REQUEST) + self._frame_id = frame_id + self.__x64bit_addr = x64bit_addr + self.__x16bit_addr = x16bit_addr + self.__transmit_options = transmit_options + self.__command = command + self.__parameter = parameter + + @staticmethod + def create_packet(raw, operating_mode): + """ + Override method. + + Returns: + :class:`.RemoteATCommandPacket` + + Raises: + InvalidPacketException: if the Bytearray length is less than 19. (start delim. + length (2 bytes) + frame + type + frame id + 64bit addr. + 16bit addr. + transmit options + command (2 bytes) + checksum = + 19 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.REMOTE_AT_COMMAND_REQUEST`. + InvalidOperatingModeException: if ``operating_mode`` is not supported. + + .. seealso:: + | :meth:`.XBeePacket.create_packet` + | :meth:`.XBeeAPIPacket._check_api_packet` + """ + if operating_mode != OperatingMode.ESCAPED_API_MODE and operating_mode != OperatingMode.API_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("This packet is not a remote AT command request packet.") + + return RemoteATCommandPacket( + raw[4], + XBee64BitAddress(raw[5:13]), + XBee16BitAddress(raw[13:15]), + raw[15], + raw[16:18].decode("utf8"), + raw[18:-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.__x64bit_addr.address + ret += self.__x16bit_addr.address + ret.append(self.__transmit_options) + ret += bytearray(self.__command, "utf8") + return ret if self.__parameter is None else ret + self.__parameter + + def _get_api_packet_spec_data_dict(self): + """ + Override method. + + .. seealso:: + | :meth:`.XBeeAPIPacket._get_api_packet_spec_data_dict` + """ + return {DictKeys.X64BIT_ADDR: self.__x64bit_addr.address, + DictKeys.X16BIT_ADDR: self.__x16bit_addr.address, + DictKeys.TRANSMIT_OPTIONS: self.__transmit_options, + DictKeys.COMMAND: self.__command, + DictKeys.PARAMETER: list(self.__parameter) if self.__parameter is not None else None} + + def __get_64bit_addr(self): + """ + Returns the 64-bit destination address. + + Returns: + :class:`.XBee64BitAddress`: the 64-bit destination address. + + .. seealso:: + | :class:`.XBee64BitAddress` + """ + return self.__x64bit_addr + + def __set_64bit_addr(self, x64bit_addr): + """ + Sets the 64-bit destination address. + + Args: + x64bit_addr (:class:`.XBee64BitAddress`): the new 64-bit destination address. + + .. seealso:: + | :class:`.XBee64BitAddress` + """ + self.__x64bit_addr = x64bit_addr + + def __get_16bit_addr(self): + """ + Returns the 16-bit destination address. + + Returns: + :class:`.XBee16BitAddress`: the 16-bit destination address. + + .. seealso:: + | :class:`.XBee16BitAddress` + """ + return self.__x16bit_addr + + def __set_16bit_addr(self, x16bit_addr): + """ + Sets the 16-bit destination address. + + Args: + x16bit_addr (:class:`.XBee16BitAddress`): the new 16-bit destination address. + + .. seealso:: + | :class:`.XBee16BitAddress` + """ + self.__x16bit_addr = x16bit_addr + + def __get_transmit_options(self): + """ + Returns the transmit options bitfield. + + Returns: + Integer: the transmit options bitfield. + + .. seealso:: + | :class:`.RemoteATCmdOptions` + """ + return self.__transmit_options + + def __set_transmit_options(self, transmit_options): + """ + Sets the transmit options bitfield. + + Args: + transmit_options (Integer): the new transmit options bitfield. + + .. seealso:: + | :class:`.RemoteATCmdOptions` + """ + self.__transmit_options = transmit_options + + def __get_parameter(self): + """ + Returns the AT command parameter. + + Returns: + Bytearray: the AT command parameter. + """ + return self.__parameter + + def __set_parameter(self, parameter): + """ + Sets the AT command parameter. + + Args: + parameter (Bytearray): the new AT command parameter. + """ + self.__parameter = parameter + + def __get_command(self): + """ + Returns the AT command. + + Returns: + String: the AT command. + """ + return self.__command + + def __set_command(self, command): + """ + Sets the AT command. + + Args: + command (String): the new AT command. + """ + self.__command = command + + x64bit_dest_addr = property(__get_64bit_addr, __set_64bit_addr) + """:class:`.XBee64BitAddress`. 64-bit destination address.""" + + x16bit_dest_addr = property(__get_16bit_addr, __set_16bit_addr) + """:class:`.XBee16BitAddress`. 16-bit destination address.""" + + transmit_options = property(__get_transmit_options, __set_transmit_options) + """Integer. Transmit options bitfield.""" + + command = property(__get_command, __set_command) + """String. AT command.""" + + parameter = property(__get_parameter, __set_parameter) + """Bytearray. AT command parameter.""" + + +class RemoteATCommandResponsePacket(XBeeAPIPacket): + """ + This class represents a remote AT command response packet. Packet is built + using the parameters of the constructor or providing a valid byte array. + + If a module receives a remote command response RF data frame in response + to a remote AT command request, the module will send a remote AT command + response message out the UART. Some commands may send back multiple frames, + for example, Node Discover (``ND``) command. + + This packet is received in response of a :class:`.RemoteATCommandPacket`. + + Response also includes an object with the status of the AT command. + + .. seealso:: + | :class:`.RemoteATCommandPacket` + | :class:`.ATCommandStatus` + | :class:`.XBeeAPIPacket` + """ + + __MIN_PACKET_LENGTH = 19 + + def __init__(self, frame_id, x64bit_addr, x16bit_addr, command, response_status, comm_value=None): + """ + Class constructor. Instantiates a new :class:`.RemoteATCommandResponsePacket` object with the provided + parameters. + + Args: + frame_id (Integer): the frame ID of the packet. + 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`): the status of the AT command. + comm_value (Bytearray, optional): the AT command response value. Optional. + + Raises: + ValueError: if ``frame_id`` is less than 0 or greater than 255. + ValueError: if length of ``command`` is different than 2. + + .. seealso:: + | :class:`.ATCommandStatus` + | :class:`.XBee16BitAddress` + | :class:`.XBee64BitAddress` + | :class:`.XBeeAPIPacket` + """ + if frame_id > 255 or frame_id < 0: + raise ValueError("frame_id must be between 0 and 255.") + if len(command) != 2: + raise ValueError("Invalid command " + command) + + 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 + self.__response_status = response_status + self.__comm_value = comm_value + + @staticmethod + def create_packet(raw, operating_mode): + """ + Override method. + + Returns: + :class:`.RemoteATCommandResponsePacket`. + + Raises: + InvalidPacketException: if the bytearray length is less than 19. (start delim. + length (2 bytes) + frame + type + frame id + 64bit addr. + 16bit addr. + receive options + command (2 bytes) + checksum = + 19 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.REMOTE_AT_COMMAND_RESPONSE`. + InvalidOperatingModeException: if ``operating_mode`` is not supported. + + .. seealso:: + | :meth:`.XBeePacket.create_packet` + | :meth:`.XBeeAPIPacket._check_api_packet` + """ + if operating_mode != OperatingMode.ESCAPED_API_MODE and operating_mode != OperatingMode.API_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("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"), + ATCommandStatus.get(raw[17]), raw[18:-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.__x64bit_addr.address + ret += self.__x16bit_addr.address + ret += bytearray(self.__command, "utf8") + ret.append(self.__response_status.code) + if self.__comm_value is not None: + ret += self.__comm_value + return ret + + def _get_api_packet_spec_data_dict(self): + return {DictKeys.X64BIT_ADDR: self.__x64bit_addr.address, + DictKeys.X16BIT_ADDR: self.__x16bit_addr.address, + DictKeys.COMMAND: self.__command, + DictKeys.AT_CMD_STATUS: self.__response_status, + DictKeys.RF_DATA: list(self.__comm_value) if self.__comm_value is not None else None} + + def __get_command(self): + """ + Returns the AT command of the packet. + + Returns: + String: the AT command of the packet. + """ + return self.__command + + def __set_command(self, command): + """ + Sets the AT command of the packet. + + Args: + command (String): the new AT command of the packet. Must have length = 2. + + Raises: + ValueError: if length of ``command`` is different than 2. + """ + if len(command) != 2: + raise ValueError("Invalid command " + command) + self.__command = command + + def __get_value(self): + """ + Returns the AT command response value. + + Returns: + Bytearray: the AT command response value. + """ + return self.__comm_value + + def __set_value(self, comm_value): + """ + Sets the AT command response value. + + Args: + comm_value (Bytearray): the new AT command response value. + """ + self.__comm_value = 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 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. + + .. seealso:: + | :class:`.ATCommandStatus` + """ + self.__response_status = response_status + + def __get_64bit_addr(self): + """ + Returns the 64-bit source address. + + Returns: + :class:`.XBee64BitAddress`: the 64-bit source address. + + .. seealso:: + | :class:`.XBee64BitAddress` + """ + return self.__x64bit_addr + + def __set_64bit_addr(self, x64bit_addr): + """ + Sets the 64-bit source address. + + Args: + x64bit_addr (:class:`.XBee64BitAddress`): the new 64-bit source address + + .. seealso:: + | :class:`.XBee64BitAddress` + """ + self.__x64bit_addr = x64bit_addr + + def __get_16bit_addr(self): + """ + Returns the 16-bit source address. + + Returns: + :class:`.XBee16BitAddress`: the 16-bit source address. + + .. seealso:: + | :class:`.XBee16BitAddress` + """ + return self.__x16bit_addr + + def __set_16bit_addr(self, x16bit_addr): + """ + Sets the 16-bit source address. + + Args: + x16bit_addr (:class:`.XBee16BitAddress`): the new 16-bit source address. + + .. seealso:: + | :class:`.XBee16BitAddress` + """ + self.__x16bit_addr = x16bit_addr + + x64bit_source_addr = property(__get_64bit_addr, __set_64bit_addr) + """:class:`.XBee64BitAddress`. 64-bit source address.""" + + x16bit_source_addr = property(__get_16bit_addr, __set_16bit_addr) + """:class:`.XBee16BitAddress`. 16-bit source address.""" + + command = property(__get_command, __set_command) + """String. AT command.""" + + command_value = property(__get_value, __set_value) + """Bytearray. AT command value.""" + + status = property(__get_response_status, __set_response_status) + """:class:`.ATCommandStatus`. AT command response status.""" + + +class TransmitPacket(XBeeAPIPacket): + """ + This class represents a transmit request packet. Packet is built using the parameters + of the constructor or providing a valid API byte array. + + A transmit request API frame causes the module to send data as an RF + packet to the specified destination. + + The 64-bit destination address should be set to ``0x000000000000FFFF`` + for a broadcast transmission (to all devices). + + The coordinator can be addressed by either setting the 64-bit address to + all ``0x00``} and the 16-bit address to ``0xFFFE``, OR by setting the + 64-bit address to the coordinator's 64-bit address and the 16-bit address to + ``0x0000``. + + For all other transmissions, setting the 16-bit address to the correct + 16-bit address can help improve performance when transmitting to multiple + destinations. + + If a 16-bit address is not known, this field should be set to + ``0xFFFE`` (unknown). + + The transmit status frame ( :attr:`.ApiFrameType.TRANSMIT_STATUS`) will + indicate the discovered 16-bit address, if successful (see :class:`.TransmitStatusPacket`). + + The broadcast radius can be set from ``0`` up to ``NH``. If set + to ``0``, the value of ``NH`` specifies the broadcast radius + (recommended). This parameter is only used for broadcast transmissions. + + The maximum number of payload bytes can be read with the ``NP`` + command. + + Several transmit options can be set using the transmit options bitfield. + + .. seealso:: + | :class:`.TransmitOptions` + | :attr:`.XBee16BitAddress.COORDINATOR_ADDRESS` + | :attr:`.XBee16BitAddress.UNKNOWN_ADDRESS` + | :attr:`.XBee64BitAddress.BROADCAST_ADDRESS` + | :attr:`.XBee64BitAddress.COORDINATOR_ADDRESS` + | :class:`.XBeeAPIPacket` + """ + + __MIN_PACKET_LENGTH = 18 + + def __init__(self, frame_id, x64bit_addr, x16bit_addr, broadcast_radius, transmit_options, rf_data=None): + """ + Class constructor. Instantiates a new :class:`.TransmitPacket` object with the provided parameters. + + Args: + frame_id (integer): the frame ID of the packet. + x64bit_addr (:class:`.XBee64BitAddress`): the 64-bit destination address. + x16bit_addr (:class:`.XBee16BitAddress`): the 16-bit destination address. + broadcast_radius (Integer): maximum number of hops a broadcast transmission can occur. + transmit_options (Integer): bitfield of supported transmission options. + rf_data (Bytearray, optional): RF data that is sent to the destination device. Optional. + + .. seealso:: + | :class:`.TransmitOptions` + | :class:`.XBee16BitAddress` + | :class:`.XBee64BitAddress` + | :class:`.XBeeAPIPacket` + + Raises: + ValueError: if ``frame_id`` is less than 0 or greater than 255. + """ + if frame_id > 255 or frame_id < 0: + raise ValueError("frame_id must be between 0 and 255.") + + super(TransmitPacket, self).__init__(ApiFrameType.TRANSMIT_REQUEST) + self._frame_id = frame_id + self.__x64bit_addr = x64bit_addr + self.__x16bit_addr = x16bit_addr + self.__broadcast_radius = broadcast_radius + self.__transmit_options = transmit_options + self.__rf_data = rf_data + + @staticmethod + def create_packet(raw, operating_mode): + """ + Override method. + + Returns: + :class:`.TransmitPacket`. + + Raises: + InvalidPacketException: if the bytearray length is less than 18. (start delim. + length (2 bytes) + frame + type + frame id + 64bit addr. + 16bit addr. + Receive options + checksum = 16 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.TRANSMIT_REQUEST`. + InvalidOperatingModeException: if ``operating_mode`` is not supported. + + .. seealso:: + | :meth:`.XBeePacket.create_packet` + | :meth:`.XBeeAPIPacket._check_api_packet` + """ + if operating_mode != OperatingMode.ESCAPED_API_MODE and operating_mode != OperatingMode.API_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("This packet is not a transmit request packet.") + + return TransmitPacket(raw[4], XBee64BitAddress(raw[5:13]), + XBee16BitAddress(raw[13:15]), raw[15], + raw[16], raw[17:-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.__x64bit_addr.address + ret += self.__x16bit_addr.address + ret.append(self.__broadcast_radius) + ret.append(self.__transmit_options) + if self.__rf_data is not None: + return ret + bytes(self.__rf_data) + 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: self.__x64bit_addr.address, + DictKeys.X16BIT_ADDR: self.__x16bit_addr.address, + DictKeys.BROADCAST_RADIUS: self.__broadcast_radius, + DictKeys.TRANSMIT_OPTIONS: self.__transmit_options, + DictKeys.RF_DATA: list(self.__rf_data) if self.__rf_data is not None else None} + + def __get_rf_data(self): + """ + Returns the RF data to send. + + Returns: + Bytearray: the RF data to send. + """ + if self.__rf_data is None: + return None + return copy.copy(self.__rf_data) + + def __set_rf_data(self, rf_data): + """ + Sets the RF data to send. + + Args: + rf_data (Bytearray): the new RF data to send. + """ + if rf_data is None: + self.__rf_data = None + else: + self.__rf_data = copy.copy(rf_data) + + def __get_transmit_options(self): + """ + Returns the transmit options bitfield. + + Returns: + Integer: the transmit options bitfield. + + .. seealso:: + | :class:`.TransmitOptions` + """ + return self.__transmit_options + + def __set_transmit_options(self, transmit_options): + """ + Sets the transmit options bitfield. + + Args: + transmit_options (Integer): the new transmit options bitfield. + + .. seealso:: + | :class:`.TransmitOptions` + """ + self.__transmit_options = transmit_options + + def __get_broadcast_radius(self): + """ + Returns the broadcast radius. Broadcast radius is the maximum number of hops a broadcast transmission. + + Returns: + Integer: the broadcast radius. + """ + return self.__broadcast_radius + + def __set_broadcast_radius(self, br_radius): + """ + Sets the broadcast radius. Broadcast radius is the maximum number of hops a broadcast transmission. + + Args: + br_radius (Integer): the new broadcast radius. + """ + self.__broadcast_radius = br_radius + + def __get_64bit_addr(self): + """ + Returns the 64-bit destination address. + + Returns: + :class:`.XBee64BitAddress`: the 64-bit destination address. + + .. seealso:: + | :class:`.XBee64BitAddress` + """ + return self.__x64bit_addr + + def __set_64bit_addr(self, x64bit_addr): + """ + Sets the 64-bit destination address. + + Args: + x64bit_addr (:class:`.XBee64BitAddress`): the new 64-bit destination address. + + .. seealso:: + | :class:`.XBee64BitAddress` + """ + self.__x64bit_addr = x64bit_addr + + def __get_16bit_addr(self): + """ + Returns the 16-bit destination address. + + Returns: + :class:`XBee16BitAddress`: the 16-bit destination address. + + .. seealso:: + | :class:`.XBee16BitAddress` + """ + return self.__x16bit_addr + + def __set_16bit_addr(self, x16bit_addr): + """ + Sets the 16-bit destination address. + + Args: + x16bit_addr (:class:`.XBee16BitAddress`): the new 16-bit destination address. + + .. seealso:: + | :class:`.XBee16BitAddress` + """ + self.__x16bit_addr = x16bit_addr + + x64bit_dest_addr = property(__get_64bit_addr, __set_64bit_addr) + """:class:`.XBee64BitAddress`. 64-bit destination address.""" + + x16bit_dest_addr = property(__get_16bit_addr, __set_16bit_addr) + """:class:`.XBee16BitAddress`. 16-bit destination address.""" + + transmit_options = property(__get_transmit_options, __set_transmit_options) + """Integer. Transmit options bitfield.""" + + broadcast_radius = property(__get_broadcast_radius, __set_broadcast_radius) + """Integer. Broadcast radius.""" + + rf_data = property(__get_rf_data, __set_rf_data) + """Bytearray. RF data to send.""" + + +class TransmitStatusPacket(XBeeAPIPacket): + """ + This class represents a transmit status packet. Packet is built using the + parameters of the constructor or providing a valid raw byte array. + + When a Transmit Request is completed, the module sends a transmit status + message. This message will indicate if the packet was transmitted + successfully or if there was a failure. + + This packet is the response to standard and explicit transmit requests. + + .. seealso:: + | :class:`.TransmitPacket` + """ + + __MIN_PACKET_LENGTH = 11 + + def __init__(self, frame_id, x16bit_addr, transmit_retry_count, transmit_status=TransmitStatus.SUCCESS, + discovery_status=DiscoveryStatus.NO_DISCOVERY_OVERHEAD): + """ + Class constructor. Instantiates a new :class:`.TransmitStatusPacket` object with the provided parameters. + + Args: + frame_id (Integer): the frame ID of the packet. + x16bit_addr (:class:`.XBee16BitAddress`): 16-bit network address the packet was delivered to. + transmit_retry_count (Integer): the number of application transmission retries that took place. + transmit_status (:class:`.TransmitStatus`, optional): transmit status. Default: SUCCESS. Optional. + discovery_status (:class:`DiscoveryStatus`, optional): discovery status. Default: NO_DISCOVERY_OVERHEAD. + Optional. + + Raises: + ValueError: if ``frame_id`` is less than 0 or greater than 255. + + .. seealso:: + | :class:`.DiscoveryStatus` + | :class:`.TransmitStatus` + | :class:`.XBee16BitAddress` + | :class:`.XBeeAPIPacket` + """ + if frame_id < 0 or frame_id > 255: + raise ValueError("Frame id must be between 0 and 255.") + + super(TransmitStatusPacket, self).__init__(ApiFrameType.TRANSMIT_STATUS) + self._frame_id = frame_id + self.__x16bit_addr = x16bit_addr + self.__transmit_retry_count = transmit_retry_count + self.__transmit_status = transmit_status + self.__discovery_status = discovery_status + + @staticmethod + def create_packet(raw, operating_mode): + """ + Override method. + + Returns: + :class:`.TransmitStatusPacket` + + Raises: + InvalidPacketException: if the bytearray length is less than 11. (start delim. + length (2 bytes) + frame + type + frame id + 16bit addr. + transmit retry count + delivery status + discovery status + 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.TRANSMIT_STATUS`. + InvalidOperatingModeException: if ``operating_mode`` is not supported. + + .. seealso:: + | :meth:`.XBeePacket.create_packet` + | :meth:`.XBeeAPIPacket._check_api_packet` + """ + if operating_mode != OperatingMode.ESCAPED_API_MODE and operating_mode != OperatingMode.API_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("This packet is not a transmit status packet.") + + return TransmitStatusPacket(raw[4], XBee16BitAddress(raw[5:7]), raw[7], + TransmitStatus.get(raw[8]), DiscoveryStatus.get(raw[9])) + + 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.__x16bit_addr.address + ret.append(self.__transmit_retry_count) + ret.append(self.__transmit_status.code) + ret.append(self.__discovery_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.X16BIT_ADDR: self.__x16bit_addr.address, + DictKeys.TRANS_R_COUNT: self.__transmit_retry_count, + DictKeys.TS_STATUS: self.__transmit_status, + DictKeys.DS_STATUS: self.__discovery_status} + + def __get_16bit_addr(self): + """ + Returns the 16-bit destination address. + + Returns: + :class:`.XBee16BitAddress`: the 16-bit destination address. + + .. seealso:: + | :class:`.XBee16BitAddress` + """ + return self.__x16bit_addr + + def __set_16bit_addr(self, x16bit_addr): + """ + Sets the 16-bit destination address. + + Args: + x16bit_addr (:class:`.XBee16BitAddress`): the new 16-bit destination address. + + .. seealso:: + | :class:`.XBee16BitAddress` + """ + self.__x16bit_addr = x16bit_addr + + def __get_transmit_status(self): + """ + Returns the transmit status. + + Returns: + :class:`.TransmitStatus`: the transmit status. + + .. seealso:: + | :class:`.TransmitStatus` + """ + return self.__transmit_status + + def __set_transmit_status(self, transmit_status): + """ + Sets the transmit status. + + Args: + transmit_status (:class:`.TransmitStatus`): the new transmit status to set. + + .. seealso:: + | :class:`.TransmitStatus` + """ + self.__transmit_status = transmit_status + + def __get_transmit_retry_count(self): + """ + Returns the transmit retry count. + + Returns: + Integer: the transmit retry count. + """ + return self.__transmit_retry_count + + def __set_transmit_retry_count(self, transmit_retry_count): + """ + Sets the transmit retry count. + + Args: + transmit_retry_count (Integer): the new transmit retry count. + """ + self.__transmit_retry_count = transmit_retry_count + + def __get_discovery_status(self): + """ + Returns the discovery status. + + Returns: + :class:`.DiscoveryStatus`: the discovery status. + + .. seealso:: + | :class:`.DiscoveryStatus` + """ + return self.__discovery_status + + def __set_discovery_status(self, discovery_status): + """ + Sets the discovery status. + + Args: + discovery_status (:class:`.DiscoveryStatus`): the new discovery status to set. + + .. seealso:: + | :class:`.DiscoveryStatus` + """ + self.__discovery_status = discovery_status + + x16bit_dest_addr = property(__get_16bit_addr, __set_16bit_addr) + """:class:`.XBee16BitAddress`. 16-bit destination address.""" + + transmit_retry_count = property(__get_transmit_retry_count, __set_transmit_retry_count) + """Integer. Transmit retry count value.""" + + transmit_status = property(__get_transmit_status, __set_transmit_status) + """:class:`.TransmitStatus`. Transmit status.""" + + discovery_status = property(__get_discovery_status, __set_discovery_status) + """:class:`.DiscoveryStatus`. Discovery status.""" + + +class ModemStatusPacket(XBeeAPIPacket): + """ + This class represents a modem status packet. Packet is built using the + parameters of the constructor or providing a valid API raw byte array. + + RF module status messages are sent from the module in response to specific + conditions and indicates the state of the modem in that moment. + + .. seealso:: + | :class:`.XBeeAPIPacket` + """ + + __MIN_PACKET_LENGTH = 6 + + def __init__(self, modem_status): + """ + Class constructor. Instantiates a new :class:`.ModemStatusPacket` object with the provided parameters. + + Args: + modem_status (:class:`.ModemStatus`): the modem status event. + + .. seealso:: + | :class:`.ModemStatus` + | :class:`.XBeeAPIPacket` + """ + super(ModemStatusPacket, self).__init__(ApiFrameType.MODEM_STATUS) + self.__modem_status = modem_status + + @staticmethod + def create_packet(raw, operating_mode): + """ + Override method. + + Returns: + :class:`.ModemStatusPacket`. + + Raises: + InvalidPacketException: if the bytearray length is less than 6. (start delim. + length (2 bytes) + frame + type + modem status + checksum = 6 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.MODEM_STATUS`. + InvalidOperatingModeException: if ``operating_mode`` is not supported. + + .. seealso:: + | :meth:`.XBeePacket.create_packet` + | :meth:`.XBeeAPIPacket._check_api_packet` + """ + if operating_mode != OperatingMode.ESCAPED_API_MODE and operating_mode != OperatingMode.API_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("This packet is not a modem status packet.") + + return ModemStatusPacket(ModemStatus.get(raw[4])) + + 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_dict` + """ + return bytearray([self.__modem_status.code]) + + def _get_api_packet_spec_data_dict(self): + """ + Override method. + + .. seealso:: + | :meth:`.XBeeAPIPacket._get_api_packet_spec_data_dict` + """ + return {DictKeys.MODEM_STATUS: self.__modem_status} + + def __get_modem_status(self): + """ + Returns the modem status event. + + Returns: + :class:`.ModemStatus`: The modem status event. + + .. seealso:: + | :class:`.ModemStatus` + """ + return self.__modem_status + + def __set_modem_status(self, modem_status): + """ + Sets the modem status event. + + Args: + modem_status (:class:`.ModemStatus`): the new modem status event to set. + + .. seealso:: + | :class:`.ModemStatus` + """ + self.__modem_status = modem_status + + modem_status = property(__get_modem_status, __set_modem_status) + """:class:`.ModemStatus`. Modem status event.""" + + +class IODataSampleRxIndicatorPacket(XBeeAPIPacket): + """ + This class represents an IO data sample RX indicator packet. Packet is built + using the parameters of the constructor or providing a valid API byte array. + + When the module receives an IO sample frame from a remote device, it + sends the sample out the UART using this frame type (when ``AO=0``). Only modules + running API firmware will send IO samples out the UART. + + Among received data, some options can also be received indicating + transmission parameters. + + .. seealso:: + | :class:`.XBeeAPIPacket` + | :class:`.ReceiveOptions` + """ + + __MIN_PACKET_LENGTH = 20 + + def __init__(self, x64bit_addr, x16bit_addr, receive_options, rf_data=None): + """ + Class constructor. Instantiates a new :class:`.IODataSampleRxIndicatorPacket` object with the provided + parameters. + + Args: + x64bit_addr (:class:`.XBee64BitAddress`): the 64-bit source address. + x16bit_addr (:class:`.XBee16BitAddress`): the 16-bit source address. + receive_options (Integer): bitfield indicating the receive options. + rf_data (Bytearray, optional): received RF data. Optional. + + Raises: + ValueError: if ``rf_data`` is not ``None`` and it's not valid for create an :class:`.IOSample`. + + .. seealso:: + | :class:`.IOSample` + | :class:`.ReceiveOptions` + | :class:`.XBee16BitAddress` + | :class:`.XBee64BitAddress` + | :class:`.XBeeAPIPacket` + """ + 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 + self.__rf_data = rf_data + self.__io_sample = IOSample(rf_data) if rf_data is not None and len(rf_data) >= 5 else None + + @staticmethod + def create_packet(raw, operating_mode): + """ + Override method. + + Returns: + :class:`.IODataSampleRxIndicatorPacket`. + + Raises: + InvalidPacketException: if the bytearray length is less than 20. (start delim. + length (2 bytes) + frame + type + 64bit addr. + 16bit addr. + rf data (5 bytes) + checksum = 20 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.IO_DATA_SAMPLE_RX_INDICATOR`. + InvalidOperatingModeException: if ``operating_mode`` is not supported. + + .. seealso:: + | :meth:`.XBeePacket.create_packet` + | :meth:`.XBeeAPIPacket._check_api_packet` + """ + if operating_mode != OperatingMode.ESCAPED_API_MODE and operating_mode != OperatingMode.API_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("This packet is not an IO data sample RX indicator packet.") + + return IODataSampleRxIndicatorPacket(XBee64BitAddress(raw[4:12]), XBee16BitAddress(raw[12:14]), + raw[14], raw[15:-1]) + + 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 = self.__x64bit_addr.address + ret += self.__x16bit_addr.address + ret.append(self.__receive_options) + if self.__rf_data is not None: + ret += self.__rf_data + return ret + + def _get_api_packet_spec_data_dict(self): + """ + Override method. + + .. seealso:: + | :meth:`.XBeeAPIPacket._get_api_packet_spec_data_dict` + """ + base = {DictKeys.X64BIT_ADDR: self.__x64bit_addr.address, + DictKeys.X16BIT_ADDR: self.__x16bit_addr.address, + DictKeys.RECEIVE_OPTIONS: self.__receive_options} + + if self.__io_sample is not None: + base[DictKeys.NUM_SAMPLES] = 1 + base[DictKeys.DIGITAL_MASK] = self.__io_sample.digital_mask + base[DictKeys.ANALOG_MASK] = self.__io_sample.analog_mask + + # Digital values + for i in range(16): + if self.__io_sample.has_digital_value(IOLine.get(i)): + base[IOLine.get(i).description + " digital value"] = \ + self.__io_sample.get_digital_value(IOLine.get(i)).name + + # Analog values + for i in range(6): + if self.__io_sample.has_analog_value(IOLine.get(i)): + base[IOLine.get(i).description + " analog value"] = \ + self.__io_sample.get_analog_value(IOLine.get(i)) + + # Power supply + if self.__io_sample.has_power_supply_value(): + base["Power supply value "] = "%02X" % self.__io_sample.power_supply_value + + elif self.__rf_data is not None: + base[DictKeys.RF_DATA] = utils.hex_to_string(self.__rf_data) + + return base + + def is_broadcast(self): + """ + Override method. + + .. seealso:: + | :meth:`XBeeAPIPacket.is_broadcast` + """ + return utils.is_bit_enabled(self.__receive_options, 1) + + def __get_64bit_addr(self): + """ + Returns the 64-bit source address. + + Returns: + :class:`.XBee64BitAddress`: the 64-bit source address. + + .. seealso:: + | :class:`.XBee64BitAddress` + """ + return self.__x64bit_addr + + def __set_64bit_addr(self, x64bit_addr): + """ + Sets the 64-bit source address. + + Args: + x64bit_addr (:class:`.XBee64BitAddress`): the new 64-bit source address + + .. seealso:: + | :class:`.XBee64BitAddress` + """ + self.__x64bit_addr = x64bit_addr + + def __get_16bit_addr(self): + """ + Returns the 16-bit source address. + + Returns: + :class:`.XBee16BitAddress`: the 16-bit source address. + + .. seealso:: + | :class:`.XBee16BitAddress` + """ + return self.__x16bit_addr + + def __set_16bit_addr(self, x16bit_addr): + """ + Sets the 16-bit source address. + + Args: + x16bit_addr (:class:`.XBee16BitAddress`): the new 16-bit source address. + + .. seealso:: + | :class:`.XBee16BitAddress` + """ + self.__x16bit_addr = x16bit_addr + + def __get_options(self): + """ + Returns the receive options bitfield. + + Returns: + Integer: the receive options bitfield. + + .. seealso:: + | :class:`.ReceiveOptions` + """ + return self.__receive_options + + def __set_options(self, receive_options): + """ + Sets the receive options bitfield. + + Args: + receive_options (Integer): the new receive options bitfield. + + .. seealso:: + | :class:`.ReceiveOptions` + """ + self.__receive_options = receive_options + + def __get_rf_data(self): + """ + Returns the received RF data. + + Returns: + Bytearray: the received RF data. + """ + if self.__rf_data is None: + return None + return copy.copy(self.__rf_data) + + def __set_rf_data(self, rf_data): + """ + Sets the received RF data. + + Args: + rf_data (Bytearray): the new received RF data. + """ + if rf_data is None: + self.__rf_data = None + else: + self.__rf_data = copy.copy(rf_data) + + # Modify the ioSample accordingly + if rf_data is not None and len(rf_data) >= 5: + self.__io_sample = IOSample(self.__rf_data) + else: + self.__io_sample = None + + def __get_io_sample(self): + """ + Returns the IO sample corresponding to the data contained in the packet. + + Returns: + :class:`.IOSample`: the IO sample of the packet, ``None`` if the packet has not any data or if the + sample could not be generated correctly. + + .. seealso:: + | :class:`.IOSample` + """ + return self.__io_sample + + def __set_io_sample(self, io_sample): + """ + Sets the IO sample of the packet. + + Args: + io_sample (:class:`.IOSample`): the new IO sample to set. + + .. seealso:: + | :class:`.IOSample` + """ + self.__io_sample = io_sample + + x64bit_source_addr = property(__get_64bit_addr, __set_64bit_addr) + """:class:`.XBee64BitAddress`. 64-bit source address.""" + + x16bit_source_addr = property(__get_16bit_addr, __set_16bit_addr) + """:class:`.XBee16BitAddress`. 16-bit source address.""" + + receive_options = property(__get_options, __set_options) + """Integer. Receive options bitfield.""" + + rf_data = property(__get_rf_data, __set_rf_data) + """Bytearray. Received RF data.""" + + io_sample = property(__get_io_sample, __set_io_sample) + """:class:`.IOSample`: IO sample corresponding to the data contained in the packet.""" + + +class ExplicitAddressingPacket(XBeeAPIPacket): + """ + This class represents an explicit addressing command packet. Packet is + built using the parameters of the constructor or providing a valid API + payload. + + Allows application layer fields (endpoint and cluster ID) to be + specified for a data transmission. Similar to the transmit request, but + also requires application layer addressing fields to be specified + (endpoints, cluster ID, profile ID). An explicit addressing request API + frame causes the module to send data as an RF packet to the specified + destination, using the specified source and destination endpoints, cluster + ID, and profile ID. + + The 64-bit destination address should be set to ``0x000000000000FFFF`` for + a broadcast transmission (to all devices). + + The coordinator can be addressed by either setting the 64-bit address to all + ``0x00`` and the 16-bit address to ``0xFFFE``, OR by setting the 64-bit + address to the coordinator's 64-bit address and the 16-bit address to ``0x0000``. + + For all other transmissions, setting the 16-bit address to the correct + 16-bit address can help improve performance when transmitting to + multiple destinations. + + If a 16-bit address is not known, this field should be set to + ``0xFFFE`` (unknown). + + The transmit status frame ( :attr:`.ApiFrameType.TRANSMIT_STATUS`) will + indicate the discovered 16-bit address, if successful (see :class:`.TransmitStatusPacket`)). + + The broadcast radius can be set from ``0`` up to ``NH``. If set + to ``0``, the value of ``NH`` specifies the broadcast radius + (recommended). This parameter is only used for broadcast transmissions. + + The maximum number of payload bytes can be read with the ``NP`` + command. Note: if source routing is used, the RF payload will be reduced + by two bytes per intermediate hop in the source route. + + Several transmit options can be set using the transmit options bitfield. + + .. seealso:: + | :class:`.TransmitOptions` + | :attr:`.XBee16BitAddress.COORDINATOR_ADDRESS` + | :attr:`.XBee16BitAddress.UNKNOWN_ADDRESS` + | :attr:`.XBee64BitAddress.BROADCAST_ADDRESS` + | :attr:`.XBee64BitAddress.COORDINATOR_ADDRESS` + | :class:`.ExplicitRXIndicatorPacket` + | :class:`.XBeeAPIPacket` + """ + + __MIN_PACKET_LENGTH = 24 + + def __init__(self, frame_id, x64bit_addr, x16bit_addr, source_endpoint, dest_endpoint, cluster_id, + profile_id, broadcast_radius=0x00, transmit_options=0x00, rf_data=None): + """ + Class constructor. . Instantiates a new :class:`.ExplicitAddressingPacket` object with the provided parameters. + + Args: + frame_id (Integer): the frame ID of the packet. + x64bit_addr (:class:`.XBee64BitAddress`): the 64-bit address. + x16bit_addr (:class:`.XBee16BitAddress`): the 16-bit address. + source_endpoint (Integer): source endpoint. 1 byte. + dest_endpoint (Integer): destination endpoint. 1 byte. + cluster_id (Integer): cluster id. Must be between 0 and 0xFFFF. + profile_id (Integer): profile id. Must be between 0 and 0xFFFF. + broadcast_radius (Integer): maximum number of hops a broadcast transmission can occur. + transmit_options (Integer): bitfield of supported transmission options. + rf_data (Bytearray, optional): RF data that is sent to the destination device. Optional. + + Raises: + ValueError: if ``frame_id``, ``src_endpoint`` or ``dst_endpoint`` are less than 0 or greater than 255. + ValueError: if lengths of ``cluster_id`` or ``profile_id`` (respectively) are less than 0 or greater than + 0xFFFF. + + .. seealso:: + | :class:`.XBee16BitAddress` + | :class:`.XBee64BitAddress` + | :class:`.TransmitOptions` + | :class:`.XBeeAPIPacket` + """ + if frame_id < 0 or frame_id > 255: + raise ValueError("Frame id must be between 0 and 255.") + if source_endpoint < 0 or source_endpoint > 255: + raise ValueError("Source endpoint must be between 0 and 255.") + if dest_endpoint < 0 or dest_endpoint > 255: + raise ValueError("Destination endpoint must be between 0 and 255.") + if cluster_id < 0 or cluster_id > 0xFFFF: + raise ValueError("Cluster id must be between 0 and 0xFFFF.") + if profile_id < 0 or profile_id > 0xFFFF: + raise ValueError("Profile id must be between 0 and 0xFFFF.") + + super(ExplicitAddressingPacket, self).__init__(ApiFrameType.EXPLICIT_ADDRESSING) + self._frame_id = frame_id + self.__x64_addr = x64bit_addr + self.__x16_addr = x16bit_addr + self.__source_endpoint = source_endpoint + self.__dest_endpoint = dest_endpoint + self.__cluster_id = cluster_id + self.__profile_id = profile_id + self.__broadcast_radius = broadcast_radius + self.__transmit_options = transmit_options + self.__rf_data = rf_data + + @staticmethod + def create_packet(raw, operating_mode): + """ + Override method. + + Returns: + :class:`.ExplicitAddressingPacket`. + + Raises: + InvalidPacketException: if the bytearray length is less than 24. (start delim. + length (2 bytes) + frame + type + frame ID + 64bit addr. + 16bit addr. + source endpoint + dest. endpoint + cluster ID (2 bytes) + + profile ID (2 bytes) + broadcast radius + transmit options + checksum = 24 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 different than :attr:`.ApiFrameType.EXPLICIT_ADDRESSING`. + InvalidOperatingModeException: if ``operating_mode`` is not supported. + + .. seealso:: + | :meth:`.XBeePacket.create_packet` + | :meth:`.XBeeAPIPacket._check_api_packet` + """ + if operating_mode != OperatingMode.ESCAPED_API_MODE and operating_mode != OperatingMode.API_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("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], raw[23:-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` + """ + raw = self.__x64_addr.address + raw += self.__x16_addr.address + raw.append(self.__source_endpoint) + raw.append(self.__dest_endpoint) + 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: + raw += self.__rf_data + return raw + + def _get_api_packet_spec_data_dict(self): + """ + Override method. + + .. seealso:: + | :meth:`.XBeeAPIPacket._get_api_packet_spec_data_dict` + """ + return {DictKeys.X64BIT_ADDR: self.__x64_addr.address, + DictKeys.X16BIT_ADDR: self.__x16_addr.address, + DictKeys.SOURCE_ENDPOINT: self.__source_endpoint, + DictKeys.DEST_ENDPOINT: self.__dest_endpoint, + DictKeys.CLUSTER_ID: self.__cluster_id, + DictKeys.PROFILE_ID: self.__profile_id, + DictKeys.BROADCAST_RADIUS: self.__broadcast_radius, + DictKeys.TRANSMIT_OPTIONS: self.__transmit_options, + DictKeys.RF_DATA: self.__rf_data} + + def __get_source_endpoint(self): + """ + Returns the source endpoint of the transmission. + + Returns: + Integer: the source endpoint of the transmission. + """ + return self.__dest_endpoint + + def __set_source_endpoint(self, source_endpoint): + """ + Sets the source endpoint of the transmission. + + Args: + source_endpoint (Integer): the new source endpoint of the transmission. + """ + self.__source_endpoint = source_endpoint + + def __get_dest_endpoint(self): + """ + Returns the destination endpoint of the transmission. + + Returns: + Integer: the destination endpoint of the transmission. + """ + return self.__dest_endpoint + + def __set_dest_endpoint(self, dest_endpoint): + """ + Sets the destination endpoint of the transmission. + + Args: + dest_endpoint (Integer): the new destination endpoint of the transmission. + """ + self.__dest_endpoint = dest_endpoint + + def __get_cluster_id(self): + """ + Returns the cluster ID of the transmission. + + Returns: + Integer: the cluster ID of the transmission. + """ + return self.__cluster_id + + def __set_cluster_id(self, cluster_id): + """ + Sets the cluster ID of the transmission. + + Args: + cluster_id (Integer): the new cluster ID of the transmission. + """ + self.__cluster_id = cluster_id + + def __get_profile_id(self): + """ + Returns the profile ID of the transmission. + + Returns + Integer: the profile ID of the transmission. + """ + return self.__profile_id + + def __set_profile_id(self, profile_id): + """ + Sets the profile ID of the transmission. + + Args + profile_id (Integer): the new profile ID of the transmission. + """ + self.__profile_id = profile_id + + def __get_rf_data(self): + """ + Returns the RF data to send. + + Returns: + Bytearray: the RF data to send. + """ + if self.__rf_data is None: + return None + return copy.copy(self.__rf_data) + + def __set_rf_data(self, rf_data): + """ + Sets the RF data to send. + + Args: + rf_data (Bytearray): the new RF data to send. + """ + if rf_data is None: + self.__rf_data = None + else: + self.__rf_data = copy.copy(rf_data) + + def __get_transmit_options(self): + """ + Returns the transmit options bitfield. + + Returns: + Integer: the transmit options bitfield. + + .. seealso:: + | :class:`.TransmitOptions` + """ + return self.__transmit_options + + def __set_transmit_options(self, transmit_options): + """ + Sets the transmit options bitfield. + + Args: + transmit_options (Integer): the new transmit options bitfield. + + .. seealso:: + | :class:`.TransmitOptions` + """ + self.__transmit_options = transmit_options + + def __get_broadcast_radius(self): + """ + Returns the broadcast radius. Broadcast radius is the maximum number of hops a broadcast transmission. + + Returns: + Integer: the broadcast radius. + """ + return self.__broadcast_radius + + def __set_broadcast_radius(self, br_radius): + """ + Sets the broadcast radius. Broadcast radius is the maximum number of hops a broadcast transmission. + + Args: + br_radius (Integer): the new broadcast radius. + """ + self.__broadcast_radius = br_radius + + def __get_64bit_addr(self): + """ + Returns the 64-bit destination address. + + Returns: + :class:`.XBee64BitAddress`: the 64-bit destination address. + + .. seealso:: + | :class:`.XBee64BitAddress` + """ + return self.__x64bit_addr + + def __set_64bit_addr(self, x64bit_addr): + """ + Sets the 64-bit destination address. + + Args: + x64bit_addr (:class:`.XBee64BitAddress`): the new 64-bit destination address. + + .. seealso:: + | :class:`.XBee64BitAddress` + """ + self.__x64bit_addr = x64bit_addr + + def __get_16bit_addr(self): + """ + Returns the 16-bit destination address. + + Returns: + :class:`XBee16BitAddress`: the 16-bit destination address. + + .. seealso:: + | :class:`.XBee16BitAddress` + """ + return self.__x16bit_addr + + def __set_16bit_addr(self, x16bit_addr): + """ + Sets the 16-bit destination address. + + Args: + x16bit_addr (:class:`.XBee16BitAddress`): the new 16-bit destination address. + + .. seealso:: + | :class:`.XBee16BitAddress` + """ + self.__x16bit_addr = x16bit_addr + + x64bit_dest_addr = property(__get_64bit_addr, __set_64bit_addr) + """:class:`.XBee64BitAddress`. 64-bit destination address.""" + + x16bit_dest_addr = property(__get_16bit_addr, __set_16bit_addr) + """:class:`.XBee16BitAddress`. 16-bit destination address.""" + + transmit_options = property(__get_transmit_options, __set_transmit_options) + """Integer. Transmit options bitfield.""" + + broadcast_radius = property(__get_broadcast_radius, __set_broadcast_radius) + """Integer. Broadcast radius.""" + + source_endpoint = property(__get_source_endpoint, __set_source_endpoint) + """Integer. Source endpoint of the transmission.""" + + dest_endpoint = property(__get_dest_endpoint, __set_dest_endpoint) + """Integer. Destination endpoint of the transmission.""" + + cluster_id = property(__get_cluster_id, __set_cluster_id) + """Integer. Cluster ID of the transmission.""" + + profile_id = property(__get_profile_id, __set_profile_id) + """Integer. Profile ID of the transmission.""" + + rf_data = property(__get_rf_data, __set_rf_data) + """Bytearray. RF data to send.""" + + +class ExplicitRXIndicatorPacket(XBeeAPIPacket): + """ + This class represents an explicit RX indicator packet. Packet is + built using the parameters of the constructor or providing a valid API + payload. + + When the modem receives an RF packet it is sent out the UART using this + message type (when ``AO=1``). + + This packet is received when external devices send explicit addressing + packets to this module. + + Among received data, some options can also be received indicating + transmission parameters. + + .. seealso:: + | :class:`.XBeeReceiveOptions` + | :class:`.ExplicitAddressingPacket` + | :class:`.XBeeAPIPacket` + """ + + __MIN_PACKET_LENGTH = 22 + + def __init__(self, x64bit_addr, x16bit_addr, source_endpoint, + dest_endpoint, cluster_id, profile_id, receive_options, rf_data=None): + """ + Class constructor. Instantiates a new :class:`.ExplicitRXIndicatorPacket` object with the provided parameters. + + Args: + x64bit_addr (:class:`.XBee64BitAddress`): the 64-bit source address. + x16bit_addr (:class:`.XBee16BitAddress`): the 16-bit source address. + source_endpoint (Integer): source endpoint. 1 byte. + dest_endpoint (Integer): destination endpoint. 1 byte. + cluster_id (Integer): cluster ID. Must be between 0 and 0xFFFF. + profile_id (Integer): profile ID. Must be between 0 and 0xFFFF. + receive_options (Integer): bitfield indicating the receive options. + rf_data (Bytearray, optional): received RF data. Optional. + + Raises: + ValueError: if ``src_endpoint`` or ``dst_endpoint`` are less than 0 or greater than 255. + ValueError: if lengths of ``cluster_id`` or ``profile_id`` (respectively) are different than 2. + + .. seealso:: + | :class:`.XBee16BitAddress` + | :class:`.XBee64BitAddress` + | :class:`.XBeeReceiveOptions` + | :class:`.XBeeAPIPacket` + """ + if source_endpoint < 0 or source_endpoint > 255: + raise ValueError("Source endpoint must be between 0 and 255.") + if dest_endpoint < 0 or dest_endpoint > 255: + raise ValueError("Destination endpoint must be between 0 and 255.") + if cluster_id < 0 or cluster_id > 0xFFFF: + raise ValueError("Cluster id must be between 0 and 0xFFFF.") + if profile_id < 0 or profile_id > 0xFFFF: + raise ValueError("Profile id must be between 0 and 0xFFFF.") + + super(ExplicitRXIndicatorPacket, self).__init__(ApiFrameType.EXPLICIT_RX_INDICATOR) + self.__x64bit_addr = x64bit_addr + self.__x16bit_addr = x16bit_addr + self.__source_endpoint = source_endpoint + self.__dest_endpoint = dest_endpoint + self.__cluster_id = cluster_id + self.__profile_id = profile_id + self.__receive_options = receive_options + self.__rf_data = rf_data + + @staticmethod + def create_packet(raw, operating_mode): + """ + Override method. + + Returns: + :class:`.ExplicitRXIndicatorPacket`. + + Raises: + InvalidPacketException: if the bytearray length is less than 22. (start delim. + length (2 bytes) + frame + type + 64bit addr. + 16bit addr. + source endpoint + dest. endpoint + cluster ID (2 bytes) + + profile ID (2 bytes) + receive options + checksum = 22 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 different than :attr:`.ApiFrameType.EXPLICIT_RX_INDICATOR`. + InvalidOperatingModeException: if ``operating_mode`` is not supported. + + .. seealso:: + | :meth:`.XBeePacket.create_packet` + | :meth:`.XBeeAPIPacket._check_api_packet` + """ + if operating_mode != OperatingMode.ESCAPED_API_MODE and operating_mode != OperatingMode.API_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("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], raw[21:-1]) + + 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` + """ + raw = self.__x64bit_addr.address + raw += self.__x16bit_addr.address + raw.append(self.__source_endpoint) + raw.append(self.__dest_endpoint) + 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 + return raw + + def _get_api_packet_spec_data_dict(self): + """ + Override method. + + .. seealso:: + | :meth:`.XBeeAPIPacket._get_api_packet_spec_data_dict` + """ + return {DictKeys.X64BIT_ADDR: self.__x64bit_addr.address, + DictKeys.X16BIT_ADDR: self.__x16bit_addr.address, + DictKeys.SOURCE_ENDPOINT: self.__source_endpoint, + DictKeys.DEST_ENDPOINT: self.__dest_endpoint, + DictKeys.CLUSTER_ID: self.__cluster_id, + DictKeys.PROFILE_ID: self.__profile_id, + DictKeys.RECEIVE_OPTIONS: self.__receive_options, + DictKeys.RF_DATA: self.__rf_data} + + def __get_64bit_addr(self): + """ + Returns the 64-bit source address. + + Returns: + :class:`.XBee64BitAddress`: the 64-bit source address. + + .. seealso:: + | :class:`.XBee64BitAddress` + """ + return self.__x64bit_addr + + def __set_64bit_addr(self, x64bit_addr): + """ + Sets the 64-bit source address. + + Args: + x64bit_addr (:class:`.XBee64BitAddress`): the new 64-bit source address. + + .. seealso:: + | :class:`.XBee64BitAddress` + """ + self.__x64bit_addr = x64bit_addr + + def __get_16bit_addr(self): + """ + Returns the 16-bit source address. + + Returns: + :class:`.XBee16BitAddress`: the 16-bit source address. + + .. seealso:: + | :class:`.XBee16BitAddress` + """ + return self.__x16bit_addr + + def __set_16bit_addr(self, x16bit_addr): + """ + Sets the 16-bit source address. + + Args: + x16bit_addr (:class:`.XBee16BitAddress`): the new 16-bit source address. + + .. seealso:: + | :class:`.XBee16BitAddress` + """ + self.__x16bit_addr = x16bit_addr + + def __get_source_endpoint(self): + """ + Returns the source endpoint of the transmission. + + Returns: + Integer: the source endpoint of the transmission. + """ + return self.__source_endpoint + + def __set_source_endpoint(self, source_endpoint): + """ + Sets the source endpoint of the transmission. + + Args: + source_endpoint (Integer): the new source endpoint of the transmission. + """ + self.__source_endpoint = source_endpoint + + def __get_dest_endpoint(self): + """ + Returns the destination endpoint of the transmission. + + Returns: + Integer: the destination endpoint of the transmission. + """ + return self.__dest_endpoint + + def __set_dest_endpoint(self, dest_endpoint): + """ + Sets the destination endpoint of the transmission. + + Args: + dest_endpoint (Integer): the new destination endpoint of the transmission. + """ + self.__dest_endpoint = dest_endpoint + + def __get_cluster_id(self): + """ + Returns the cluster ID of the transmission. + + Returns: + Integer: the cluster ID of the transmission. + """ + return self.__cluster_id + + def __set_cluster_id(self, cluster_id): + """ + Sets the cluster ID of the transmission. + + Args: + cluster_id (Integer): the new cluster ID of the transmission. + """ + self.__cluster_id = cluster_id + + def __get_profile_id(self): + """ + Returns the profile ID of the transmission. + + Returns + Integer: the profile ID of the transmission. + """ + return self.__profile_id + + def __set_profile_id(self, profile_id): + """ + Sets the profile ID of the transmission. + + Args + profile_id (Integer): the new profile ID of the transmission. + """ + self.__profile_id = profile_id + + def __get_options(self): + """ + Returns the receive options bitfield. + + Returns: + Integer: the receive options bitfield. + + .. seealso:: + | :class:`.XBeeReceiveOptions` + """ + return self.__receive_options + + def __set_options(self, receive_options): + """ + Sets the receive options bitfield. + + Args: + receive_options (Integer): the new receive options bitfield. + + .. seealso:: + | :class:`.XBeeReceiveOptions` + """ + self.__receive_options = receive_options + + def __get_rf_data(self): + """ + Returns the received RF data. + + Returns: + Bytearray: the received RF data. + """ + if self.__rf_data is None: + return None + return copy.copy(self.__rf_data) + + def __set_rf_data(self, rf_data): + """ + Sets the received RF data. + + Args: + rf_data (Bytearray): the new received RF data. + """ + if rf_data is None: + self.__rf_data = None + else: + self.__rf_data = copy.copy(rf_data) + + x64bit_source_addr = property(__get_64bit_addr, __set_64bit_addr) + """:class:`.XBee64BitAddress`. 64-bit source address.""" + + x16bit_source_addr = property(__get_16bit_addr, __set_16bit_addr) + """:class:`.XBee16BitAddress`. 16-bit source address.""" + + receive_options = property(__get_options, __set_options) + """Integer. Receive options bitfield.""" + + source_endpoint = property(__get_source_endpoint, __set_source_endpoint) + """Integer. Source endpoint of the transmission.""" + + dest_endpoint = property(__get_dest_endpoint, __set_dest_endpoint) + """Integer. Destination endpoint of the transmission.""" + + cluster_id = property(__get_cluster_id, __set_cluster_id) + """Integer. Cluster ID of the transmission.""" + + profile_id = property(__get_profile_id, __set_profile_id) + """Integer. Profile ID of the transmission.""" + + rf_data = property(__get_rf_data, __set_rf_data) + """Bytearray. Received RF data.""" diff --git a/digi/xbee/packets/devicecloud.py b/digi/xbee/packets/devicecloud.py new file mode 100644 index 0000000..8fb66ce --- /dev/null +++ b/digi/xbee/packets/devicecloud.py @@ -0,0 +1,996 @@ +# 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 +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +from digi.xbee.packets.base import XBeeAPIPacket, DictKeys +from digi.xbee.util import utils +from digi.xbee.exception import InvalidOperatingModeException, InvalidPacketException +from digi.xbee.packets.aft import ApiFrameType +from digi.xbee.models.mode import OperatingMode +from digi.xbee.models.status import DeviceCloudStatus, FrameError +from digi.xbee.models.options import SendDataRequestOptions + + +class DeviceRequestPacket(XBeeAPIPacket): + """ + This class represents a device request packet. Packet is built + using the parameters of the constructor or providing a valid API payload. + + This frame type is sent out the serial port when the XBee module receives + a valid device request from Device Cloud. + + .. seealso:: + | :class:`.DeviceResponsePacket` + | :class:`.XBeeAPIPacket` + """ + + __MIN_PACKET_LENGTH = 5 + + def __init__(self, request_id, target=None, request_data=None): + """ + Class constructor. Instantiates a new :class:`.DeviceRequestPacket` object with the provided parameters. + + Args: + request_id (Integer): number that identifies the device request. (0 has no special meaning) + target (String): device request target. + request_data (Bytearray, optional): data of the request. Optional. + + Raises: + ValueError: if ``request_id`` is less than 0 or greater than 255. + ValueError: if length of ``target`` is greater than 255. + + .. seealso:: + | :class:`.XBeeAPIPacket` + """ + if request_id < 0 or request_id > 255: + raise ValueError("Device request ID must be between 0 and 255.") + + if target is not None and len(target) > 255: + raise ValueError("Target length cannot exceed 255 bytes.") + + super().__init__(ApiFrameType.DEVICE_REQUEST) + self.__request_id = request_id + self.__transport = 0x00 # Reserved. + self.__flags = 0x00 # Reserved. + self.__target = target + self.__request_data = request_data + + @staticmethod + def create_packet(raw, operating_mode): + """ + Override method. + + Returns: + :class:`.DeviceRequestPacket` + + Raises: + InvalidPacketException: if the bytearray length is less than 9. (start delim. + length (2 bytes) + frame + type + request id + transport + flags + target length + 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 different than :attr:`.ApiFrameType.DEVICE_REQUEST`. + InvalidOperatingModeException: if ``operating_mode`` is not supported. + + .. seealso:: + | :meth:`.XBeePacket.create_packet` + | :meth:`.XBeeAPIPacket._check_api_packet` + """ + if operating_mode != OperatingMode.ESCAPED_API_MODE and operating_mode != OperatingMode.API_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("This packet is not a device request packet.") + + target_length = raw[7] + return DeviceRequestPacket(raw[4], raw[8:8 + target_length].decode("utf8"), raw[8 + target_length:-1]) + + 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 = utils.int_to_bytes(self.__request_id, num_bytes=1) + ret += utils.int_to_bytes(self.__transport, num_bytes=1) + ret += utils.int_to_bytes(self.__flags, num_bytes=1) + if self.__target is not None: + ret += utils.int_to_bytes(len(self.__target), num_bytes=1) + ret += bytearray(self.__target, "utf8") + else: + ret += utils.int_to_bytes(0x00, num_bytes=1) + if self.__request_data is not None: + ret += self.__request_data + + return ret + + def _get_api_packet_spec_data_dict(self): + """ + Override method. + + See: + :meth:`.XBeeAPIPacket._get_api_packet_spec_data_dict` + """ + return {DictKeys.REQUEST_ID: self.__request_id, + DictKeys.TRANSPORT: self.__transport, + DictKeys.FLAGS: self.__flags, + DictKeys.TARGET: self.__target, + DictKeys.RF_DATA: list(self.__request_data) if self.__request_data is not None else None} + + def __get_request_id(self): + """ + Returns the request ID of the packet. + + Returns: + Integer: the request ID of the packet. + """ + return self.__request_id + + def __set_request_id(self, request_id): + """ + Sets the request ID of the packet. + + Args: + request_id (Integer): the new request ID of the packet. Must be between 0 and 255. + + Raises: + ValueError: if ``request_id`` is less than 0 or greater than 255. + """ + if request_id < 0 or request_id > 255: + raise ValueError("Device request ID must be between 0 and 255.") + self.__request_id = request_id + + def __get_transport(self): + """ + Returns the transport of the packet. + + Returns: + Integer: the transport of the packet. + """ + return self.__transport + + def __get_flags(self): + """ + Returns the flags of the packet. + + Returns: + Integer: the flags of the packet. + """ + return self.__flags + + def __get_target(self): + """ + Returns the device request target of the packet. + + Returns: + String: the device request target of the packet. + """ + return self.__target + + def __set_target(self, target): + """ + Sets the device request target of the packet. + + Args: + target (String): the new device request target of the packet. + + Raises: + ValueError: if ``target`` length is greater than 255. + """ + if target is not None and len(target) > 255: + raise ValueError("Target length cannot exceed 255 bytes.") + self.__target = target + + def __get_request_data(self): + """ + Returns the data of the device request. + + Returns: + Bytearray: the data of the device request. + """ + if self.__request_data is None: + return None + return self.__request_data.copy() + + def __set_request_data(self, request_data): + """ + Sets the data of the device request. + + Args: + request_data (Bytearray): the new data of the device request. + """ + if request_data is None: + self.__request_data = None + else: + self.__request_data = request_data.copy() + + request_id = property(__get_request_id, __set_request_id) + """Integer. Request ID of the packet.""" + + transport = property(__get_transport) + """Integer. Transport (reserved).""" + + flags = property(__get_flags) + """Integer. Flags (reserved).""" + + target = property(__get_target, __set_target) + """String. Request target of the packet.""" + + request_data = property(__get_request_data, __set_request_data) + """Bytearray. Data of the device request.""" + + +class DeviceResponsePacket(XBeeAPIPacket): + """ + This class represents a device response packet. Packet is built + using the parameters of the constructor or providing a valid API payload. + + This frame type is sent to the serial port by the host in response to the + :class:`.DeviceRequestPacket`. It should be sent within five seconds to avoid + a timeout error. + + .. seealso:: + | :class:`.DeviceRequestPacket` + | :class:`.XBeeAPIPacket` + """ + + __MIN_PACKET_LENGTH = 8 + + def __init__(self, frame_id, request_id, response_data=None): + """ + Class constructor. Instantiates a new :class:`.DeviceResponsePacket` object with the provided parameters. + + Args: + frame_id (Integer): the frame ID of the packet. + request_id (Integer): device Request ID. This number should match the device request ID in the + device request. Otherwise, an error will occur. (0 has no special meaning) + response_data (Bytearray, optional): data of the response. Optional. + + Raises: + ValueError: if ``frame_id`` is less than 0 or greater than 255. + ValueError: if ``request_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 request_id < 0 or request_id > 255: + raise ValueError("Device request ID must be between 0 and 255.") + + super().__init__(ApiFrameType.DEVICE_RESPONSE) + self._frame_id = frame_id + self.__request_id = request_id + self.__response_data = response_data + + @staticmethod + def create_packet(raw, operating_mode): + """ + Override method. + + Returns: + :class:`.DeviceResponsePacket` + + Raises: + InvalidPacketException: if the bytearray length is less than 8. (start delim. + length (2 bytes) + frame + type + frame id + request id + reserved + 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 different than :attr:`.ApiFrameType.DEVICE_RESPONSE`. + InvalidOperatingModeException: if ``operating_mode`` is not supported. + + .. seealso:: + | :meth:`.XBeePacket.create_packet` + | :meth:`.XBeeAPIPacket._check_api_packet` + """ + if operating_mode != OperatingMode.ESCAPED_API_MODE and operating_mode != OperatingMode.API_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("This packet is not a device response packet.") + + return DeviceResponsePacket(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 = utils.int_to_bytes(self.__request_id, num_bytes=1) + ret += utils.int_to_bytes(0x00, num_bytes=1) + if self.__response_data is not None: + ret += self.__response_data + + return ret + + def _get_api_packet_spec_data_dict(self): + """ + Override method. + + .. seealso:: + | :meth:`.XBeeAPIPacket._get_api_packet_spec_data_dict` + """ + return {DictKeys.REQUEST_ID: self.__request_id, + DictKeys.RESERVED: 0x00, + DictKeys.RF_DATA: list(self.__response_data) if self.__response_data is not None else None} + + def __get_request_id(self): + """ + Returns the request ID of the packet. + + Returns: + Integer: the request ID of the packet. + """ + return self.__request_id + + def __set_request_id(self, request_id): + """ + Sets the request ID of the packet. + + Args: + request_id (Integer): the new request ID of the packet. Must be between 0 and 255. + + Raises: + ValueError: if ``request_id`` is less than 0 or greater than 255. + """ + if request_id < 0 or request_id > 255: + raise ValueError("Device request ID must be between 0 and 255.") + self.__request_id = request_id + + def __get_response_data(self): + """ + Returns the data of the device response. + + Returns: + Bytearray: the data of the device response. + """ + if self.__response_data is None: + return None + return self.__response_data.copy() + + def __set_response_data(self, response_data): + """ + Sets the data of the device response. + + Args: + response_data (Bytearray): the new data of the device response. + """ + if response_data is None: + self.__response_data = None + else: + self.__response_data = response_data.copy() + + request_id = property(__get_request_id, __set_request_id) + """Integer. Request ID of the packet.""" + + request_data = property(__get_response_data, __set_response_data) + """Bytearray. Data of the device response.""" + + +class DeviceResponseStatusPacket(XBeeAPIPacket): + """ + This class represents a device response status packet. Packet is built + using the parameters of the constructor or providing a valid API payload. + + This frame type is sent to the serial port after the serial port sends a + :class:`.DeviceResponsePacket`. + + .. seealso:: + | :class:`.DeviceResponsePacket` + | :class:`.XBeeAPIPacket` + """ + + __MIN_PACKET_LENGTH = 7 + + def __init__(self, frame_id, status): + """ + Class constructor. Instantiates a new :class:`.DeviceResponseStatusPacket` object with the provided parameters. + + Args: + frame_id (Integer): the frame ID of the packet. + status (:class:`.DeviceCloudStatus`): device response status. + + Raises: + ValueError: if ``frame_id`` is less than 0 or greater than 255. + + .. seealso:: + | :class:`.DeviceCloudStatus` + | :class:`.XBeeAPIPacket` + """ + if frame_id < 0 or frame_id > 255: + raise ValueError("Frame id must be between 0 and 255.") + + super().__init__(ApiFrameType.DEVICE_RESPONSE_STATUS) + self._frame_id = frame_id + self.__status = status + + @staticmethod + def create_packet(raw, operating_mode): + """ + Override method. + + Returns: + :class:`.DeviceResponseStatusPacket` + + Raises: + InvalidPacketException: if the bytearray length is less than 7. (start delim. + length (2 bytes) + frame + type + frame id + device response status + 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 different than :attr:`.ApiFrameType.DEVICE_RESPONSE_STATUS`. + InvalidOperatingModeException: if ``operating_mode`` is not supported. + + .. seealso:: + | :meth:`.XBeePacket.create_packet` + | :meth:`.XBeeAPIPacket._check_api_packet` + """ + if operating_mode != OperatingMode.ESCAPED_API_MODE and operating_mode != OperatingMode.API_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("This packet is not a device response status packet.") + + return DeviceResponseStatusPacket(raw[4], DeviceCloudStatus.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 utils.int_to_bytes(self.__status.code, num_bytes=1) + + def _get_api_packet_spec_data_dict(self): + """ + Override method. + + See: + :meth:`.XBeeAPIPacket._get_api_packet_spec_data_dict` + """ + return {DictKeys.DC_STATUS: self.__status} + + def __get_status(self): + """ + Returns the status of the device response packet. + + Returns: + :class:`.DeviceCloudStatus`: the status of the device response packet. + + .. seealso:: + | :class:`.DeviceCloudStatus` + """ + return self.__status + + def __set_status(self, status): + """ + Sets the status of the device response packet. + + Args: + status (:class:`.DeviceCloudStatus`): the new status of the device response packet. + + .. seealso:: + | :class:`.DeviceCloudStatus` + """ + self.__status = status + + status = property(__get_status, __set_status) + """:class:`.DeviceCloudStatus`. Status of the device response.""" + + +class FrameErrorPacket(XBeeAPIPacket): + """ + This class represents a frame error packet. Packet is built + using the parameters of the constructor or providing a valid API payload. + + This frame type is sent to the serial port for any type of frame error. + + .. seealso:: + | :class:`.FrameError` + | :class:`.XBeeAPIPacket` + """ + + __MIN_PACKET_LENGTH = 6 + + def __init__(self, frame_error): + """ + Class constructor. Instantiates a new :class:`.FrameErrorPacket` object with the provided parameters. + + Args: + frame_error (:class:`.FrameError`): the frame error. + + .. seealso:: + | :class:`.FrameError` + | :class:`.XBeeAPIPacket` + """ + super().__init__(ApiFrameType.FRAME_ERROR) + self.__frame_error = frame_error + + @staticmethod + def create_packet(raw, operating_mode): + """ + Override method. + + Returns: + :class:`.FrameErrorPacket` + + Raises: + InvalidPacketException: if the bytearray length is less than 6. (start delim. + length (2 bytes) + frame + type + frame error + checksum = 6 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 different than :attr:`.ApiFrameType.FRAME_ERROR`. + InvalidOperatingModeException: if ``operating_mode`` is not supported. + + .. seealso:: + | :meth:`.XBeePacket.create_packet` + | :meth:`.XBeeAPIPacket._check_api_packet` + """ + if operating_mode != OperatingMode.ESCAPED_API_MODE and operating_mode != OperatingMode.API_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("This packet is not a frame error packet.") + + return FrameErrorPacket(FrameError.get(raw[4])) + + 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` + """ + return utils.int_to_bytes(self.__frame_error.code, num_bytes=1) + + def _get_api_packet_spec_data_dict(self): + """ + Override method. + + .. seealso:: + | :meth:`.XBeeAPIPacket._get_api_packet_spec_data_dict` + """ + return {DictKeys.FRAME_ERROR: self.__frame_error} + + def __get_error(self): + """ + Returns the frame error of the packet. + + Returns: + :class:`.FrameError`: the frame error of the packet. + + .. seealso:: + | :class:`.FrameError` + """ + return self.__frame_error + + def __set_error(self, frame_error): + """ + Sets the frame error of the packet. + + Args: + frame_error (:class:`.FrameError`): the new frame error of the packet. + + .. seealso:: + | :class:`.FrameError` + """ + self.__frame_error = frame_error + + error = property(__get_error, __set_error) + """:class:`.FrameError`. Frame error of the packet.""" + + +class SendDataRequestPacket(XBeeAPIPacket): + """ + This class represents a send data request packet. Packet is built + using the parameters of the constructor or providing a valid API payload. + + This frame type is used to send a file of the given name and type to + Device Cloud. + + If the frame ID is non-zero, a :class:`.SendDataResponsePacket` will be + received. + + .. seealso:: + | :class:`.SendDataResponsePacket` + | :class:`.XBeeAPIPacket` + """ + + __MIN_PACKET_LENGTH = 10 + + def __init__(self, frame_id, path, content_type, options, file_data=None): + """ + Class constructor. Instantiates a new :class:`.SendDataRequestPacket` object with the provided parameters. + + Args: + frame_id (Integer): the frame ID of the packet. + path (String): path of the file to upload to Device Cloud. + content_type (String): content type of the file to upload. + options (:class:`.SendDataRequestOptions`): the action when uploading a file. + file_data (Bytearray, optional): data of the file to upload. Optional. + + Raises: + ValueError: if ``frame_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.") + + super().__init__(ApiFrameType.SEND_DATA_REQUEST) + self._frame_id = frame_id + self.__path = path + self.__content_type = content_type + self.__transport = 0x00 # Always TCP. + self.__options = options + self.__file_data = file_data + + @staticmethod + def create_packet(raw, operating_mode): + """ + Override method. + + Returns: + :class:`.SendDataRequestPacket` + + Raises: + InvalidPacketException: if the bytearray length is less than 10. (start delim. + length (2 bytes) + frame + type + frame id + path length + content type length + transport + options + checksum = 10 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 different than :attr:`.ApiFrameType.SEND_DATA_REQUEST`. + InvalidOperatingModeException: if ``operating_mode`` is not supported. + + .. seealso:: + | :meth:`.XBeePacket.create_packet` + | :meth:`.XBeeAPIPacket._check_api_packet` + """ + if operating_mode != OperatingMode.ESCAPED_API_MODE and operating_mode != OperatingMode.API_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("This packet is not a send data request packet.") + + path_length = raw[5] + content_type_length = raw[6 + path_length] + return SendDataRequestPacket(raw[4], + 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]), + raw[6 + path_length + 3 + content_type_length:-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` + """ + if self.__path is not None: + ret = utils.int_to_bytes(len(self.__path), num_bytes=1) + ret += bytearray(self.__path, "utf8") + else: + ret = utils.int_to_bytes(0x00, num_bytes=1) + if self.__content_type is not None: + ret += utils.int_to_bytes(len(self.__content_type), num_bytes=1) + ret += bytearray(self.__content_type, "utf8") + else: + ret += utils.int_to_bytes(0x00, num_bytes=1) + ret += utils.int_to_bytes(0x00, num_bytes=1) # Transport is always TCP + ret += utils.int_to_bytes(self.__options.code, num_bytes=1) + if self.__file_data is not None: + ret += self.__file_data + + return ret + + def _get_api_packet_spec_data_dict(self): + """ + Override method. + + .. seealso:: + | :meth:`.XBeeAPIPacket._get_api_packet_spec_data_dict` + """ + return {DictKeys.PATH_LENGTH: len(self.__path) if self.__path is not None else 0x00, + DictKeys.PATH: self.__path if self.__path is not None else None, + DictKeys.CONTENT_TYPE_LENGTH: len(self.__content_type) if self.__content_type is not None else 0x00, + DictKeys.CONTENT_TYPE: self.__content_type if self.__content_type is not None else None, + DictKeys.TRANSPORT: 0x00, + DictKeys.TRANSMIT_OPTIONS: self.__options, + DictKeys.RF_DATA: list(self.__file_data) if self.__file_data is not None else None} + + def __get_path(self): + """ + Returns the path of the file to upload to Device Cloud. + + Returns: + String: the path of the file to upload to Device Cloud. + """ + return self.__path + + def __set_path(self, path): + """ + Sets the path of the file to upload to Device Cloud. + + Args: + path (String): the new path of the file to upload to Device Cloud. + """ + self.__path = path + + def __get_content_type(self): + """ + Returns the content type of the file to upload. + + Returns: + String: the content type of the file to upload. + """ + return self.__content_type + + def __set_content_type(self, content_type): + """ + Sets the content type of the file to upload. + + Args: + content_type (String): the new content type of the file to upload. + """ + self.__content_type = content_type + + def __get_options(self): + """ + Returns the file upload operation options. + + Returns: + :class:`.SendDataRequestOptions`: the file upload operation options. + + .. seealso:: + | :class:`.SendDataRequestOptions` + """ + return self.__options + + def __set_options(self, options): + """ + Sets the file upload operation options. + + Args: + options (:class:`.SendDataRequestOptions`): the new file upload operation options + + .. seealso:: + | :class:`.SendDataRequestOptions` + """ + self.__options = options + + def __get_file_data(self): + """ + Returns the data of the file to upload. + + Returns: + Bytearray: the data of the file to upload. + """ + if self.__file_data is None: + return None + return self.__file_data.copy() + + def __set_file_data(self, file_data): + """ + Sets the data of the file to upload. + + Args: + file_data (Bytearray): the new data of the file to upload. + """ + if file_data is None: + self.__file_data = None + else: + self.__file_data = file_data.copy() + + path = property(__get_path, __set_path) + """String. Path of the file to upload to Device Cloud.""" + + content_type = property(__get_content_type, __set_content_type) + """String. The content type of the file to upload.""" + + options = property(__get_options, __set_options) + """:class:`.SendDataRequestOptions`. File upload operation options.""" + + file_data = property(__get_file_data, __set_file_data) + """Bytearray. Data of the file to upload.""" + + +class SendDataResponsePacket(XBeeAPIPacket): + """ + This class represents a send data response packet. Packet is built + using the parameters of the constructor or providing a valid API payload. + + This frame type is sent out the serial port in response to the + :class:`.SendDataRequestPacket`, providing its frame ID is non-zero. + + .. seealso:: + | :class:`.SendDataRequestPacket` + | :class:`.XBeeAPIPacket` + """ + + __MIN_PACKET_LENGTH = 7 + + def __init__(self, frame_id, status): + """ + Class constructor. Instantiates a new :class:`.SendDataResponsePacket` object with the provided parameters. + + Args: + frame_id (Integer): the frame ID of the packet. + status (:class:`.DeviceCloudStatus`): the file upload status. + + Raises: + ValueError: if ``frame_id`` is less than 0 or greater than 255. + + .. seealso:: + | :class:`.DeviceCloudStatus` + | :class:`.XBeeAPIPacket` + """ + if frame_id < 0 or frame_id > 255: + raise ValueError("Frame id must be between 0 and 255.") + + super().__init__(ApiFrameType.SEND_DATA_RESPONSE) + self._frame_id = frame_id + self.__status = status + + @staticmethod + def create_packet(raw, operating_mode): + """ + Override method. + + Returns: + :class:`.SendDataResponsePacket` + + Raises: + InvalidPacketException: if the bytearray length is less than 10. (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 + 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 different than :attr:`.ApiFrameType.SEND_DATA_RESPONSE`. + InvalidOperatingModeException: if ``operating_mode`` is not supported. + + .. seealso:: + | :meth:`.XBeePacket.create_packet` + | :meth:`.XBeeAPIPacket._check_api_packet` + """ + if operating_mode != OperatingMode.ESCAPED_API_MODE and operating_mode != OperatingMode.API_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("This packet is not a send data response packet.") + + return SendDataResponsePacket(raw[4], DeviceCloudStatus.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 utils.int_to_bytes(self.__status.code, num_bytes=1) + + def _get_api_packet_spec_data_dict(self): + """ + Override method. + + .. seealso:: + | :meth:`.XBeeAPIPacket._get_api_packet_spec_data_dict` + """ + return {DictKeys.DC_STATUS: self.__status} + + def __get_status(self): + """ + Returns the file upload status. + + Returns: + :class:`.DeviceCloudStatus`: the file upload status. + + .. seealso:: + | :class:`.DeviceCloudStatus` + """ + return self.__status + + def __set_status(self, status): + """ + Sets the file upload status. + + Args: + status (:class:`.DeviceCloudStatus`): the new file upload status. + + .. seealso:: + | :class:`.DeviceCloudStatus` + """ + self.__status = status + + status = property(__get_status, __set_status) + """:class:`.DeviceCloudStatus`. The file upload status.""" diff --git a/digi/xbee/packets/factory.py b/digi/xbee/packets/factory.py new file mode 100644 index 0000000..3f2ce6f --- /dev/null +++ b/digi/xbee/packets/factory.py @@ -0,0 +1,218 @@ +# 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. + +from digi.xbee.packets.base import * +from digi.xbee.packets.cellular import * +from digi.xbee.packets.common import * +from digi.xbee.packets.devicecloud import * +from digi.xbee.packets.network import * +from digi.xbee.packets.raw import * +from digi.xbee.packets.relay import * +from digi.xbee.packets.wifi import * +from digi.xbee.packets.aft import ApiFrameType +from digi.xbee.models.mode import OperatingMode + + +""" +This module provides functionality to build XBee packets from +bytearray returning the appropriate XBeePacket subclass. + +All the API and API2 logic is already included so all packet reads are +independent of the XBee operating mode. + +Two API modes are supported and both can be enabled using the ``AP`` +(API Enable) command:: + + API1 - API Without Escapes + The data frame structure is defined as follows: + + Start Delimiter Length Frame Data Checksum + (Byte 1) (Bytes 2-3) (Bytes 4-n) (Byte n + 1) + +----------------+ +-------------------+ +--------------------------- + +----------------+ + | 0x7E | | MSB | LSB | | API-specific Structure | | 1 Byte | + +----------------+ +-------------------+ +----------------------------+ +----------------+ + MSB = Most Significant Byte, LSB = Least Significant Byte + + +API2 - API With Escapes +The data frame structure is defined as follows:: + + Start Delimiter Length Frame Data Checksum + (Byte 1) (Bytes 2-3) (Bytes 4-n) (Byte n + 1) + +----------------+ +-------------------+ +--------------------------- + +----------------+ + | 0x7E | | MSB | LSB | | API-specific Structure | | 1 Byte | + +----------------+ +-------------------+ +----------------------------+ +----------------+ + \___________________________________ _________________________________/ + \/ + Characters Escaped If Needed + + MSB = Most Significant Byte, LSB = Least Significant Byte + + +When sending or receiving an API2 frame, specific data values must be +escaped (flagged) so they do not interfere with the data frame sequencing. +To escape an interfering data byte, the byte 0x7D is inserted before +the byte to be escaped XOR'd with 0x20. + +The data bytes that need to be escaped: + +- ``0x7E`` - Frame Delimiter - :attr:`.SpecialByte. +- ``0x7D`` - Escape +- ``0x11`` - XON +- ``0x13`` - XOFF + +The length field has a two-byte value that specifies the number of +bytes that will be contained in the frame data field. It does not include the +checksum field. + +The frame data forms an API-specific structure as follows:: + + Start Delimiter Length Frame Data Checksum + (Byte 1) (Bytes 2-3) (Bytes 4-n) (Byte n + 1) + +----------------+ +-------------------+ +--------------------------- + +----------------+ + | 0x7E | | MSB | LSB | | API-specific Structure | | 1 Byte | + +----------------+ +-------------------+ +----------------------------+ +----------------+ + / \\ + / API Identifier Identifier specific data \\ + +------------------+ +------------------------------+ + | cmdID | | cmdData | + +------------------+ +------------------------------+ + + +The cmdID frame (API-identifier) indicates which API messages +will be contained in the cmdData frame (Identifier-specific data). + +To unit_test data integrity, a checksum is calculated and verified on +non-escaped data. + +.. seealso:: + | :class:`.XBeePacket` + | :class:`.OperatingMode` +""" + + +def build_frame(packet_bytearray, operating_mode=OperatingMode.API_MODE): + """ + Creates a packet from raw data. + + Args: + packet_bytearray (Bytearray): the raw data of the packet to build. + operating_mode (:class:`.OperatingMode`): the operating mode in which the raw data has been captured. + + .. seealso:: + | :class:`.OperatingMode` + """ + frame_type = ApiFrameType.get(packet_bytearray[3]) + + if frame_type == ApiFrameType.GENERIC: + return GenericXBeePacket.create_packet(packet_bytearray, operating_mode) + + elif frame_type == ApiFrameType.AT_COMMAND: + return ATCommPacket.create_packet(packet_bytearray, operating_mode) + + elif frame_type == ApiFrameType.AT_COMMAND_QUEUE: + return ATCommQueuePacket.create_packet(packet_bytearray, operating_mode) + + elif frame_type == ApiFrameType.AT_COMMAND_RESPONSE: + return ATCommResponsePacket.create_packet(packet_bytearray, operating_mode) + + elif frame_type == ApiFrameType.RECEIVE_PACKET: + return ReceivePacket.create_packet(packet_bytearray, operating_mode) + + elif frame_type == ApiFrameType.RX_64: + return RX64Packet.create_packet(packet_bytearray, operating_mode) + + elif frame_type == ApiFrameType.RX_16: + return RX16Packet.create_packet(packet_bytearray, operating_mode) + + elif frame_type == ApiFrameType.REMOTE_AT_COMMAND_REQUEST: + return RemoteATCommandPacket.create_packet(packet_bytearray, operating_mode) + + elif frame_type == ApiFrameType.REMOTE_AT_COMMAND_RESPONSE: + return RemoteATCommandResponsePacket.create_packet(packet_bytearray, operating_mode) + + elif frame_type == ApiFrameType.TRANSMIT_REQUEST: + return TransmitPacket.create_packet(packet_bytearray, operating_mode) + + elif frame_type == ApiFrameType.TRANSMIT_STATUS: + return TransmitStatusPacket.create_packet(packet_bytearray, operating_mode) + + elif frame_type == ApiFrameType.MODEM_STATUS: + return ModemStatusPacket.create_packet(packet_bytearray, operating_mode) + + elif frame_type == ApiFrameType.TX_STATUS: + return TXStatusPacket.create_packet(packet_bytearray, operating_mode) + + elif frame_type == ApiFrameType.RX_IO_16: + return RX16IOPacket.create_packet(packet_bytearray, operating_mode) + + elif frame_type == ApiFrameType.RX_IO_64: + return RX64IOPacket.create_packet(packet_bytearray, operating_mode) + + elif frame_type == ApiFrameType.IO_DATA_SAMPLE_RX_INDICATOR: + return IODataSampleRxIndicatorPacket.create_packet(packet_bytearray, operating_mode) + + elif frame_type == ApiFrameType.EXPLICIT_ADDRESSING: + return ExplicitAddressingPacket.create_packet(packet_bytearray, operating_mode) + + elif frame_type == ApiFrameType.EXPLICIT_RX_INDICATOR: + return ExplicitRXIndicatorPacket.create_packet(packet_bytearray, operating_mode) + + elif frame_type == ApiFrameType.TX_SMS: + return TXSMSPacket.create_packet(packet_bytearray, operating_mode) + + elif frame_type == ApiFrameType.TX_IPV4: + return TXIPv4Packet.create_packet(packet_bytearray, operating_mode) + + elif frame_type == ApiFrameType.RX_SMS: + return RXSMSPacket.create_packet(packet_bytearray, operating_mode) + + elif frame_type == ApiFrameType.USER_DATA_RELAY_OUTPUT: + return UserDataRelayOutputPacket.create_packet(packet_bytearray, operating_mode) + + elif frame_type == ApiFrameType.RX_IPV4: + return RXIPv4Packet.create_packet(packet_bytearray, operating_mode) + + elif frame_type == ApiFrameType.REMOTE_AT_COMMAND_REQUEST_WIFI: + return RemoteATCommandWifiPacket.create_packet(packet_bytearray, operating_mode) + + elif frame_type == ApiFrameType.SEND_DATA_REQUEST: + return SendDataRequestPacket.create_packet(packet_bytearray, operating_mode) + + elif frame_type == ApiFrameType.DEVICE_RESPONSE: + return DeviceResponsePacket.create_packet(packet_bytearray, operating_mode) + + elif frame_type == ApiFrameType.USER_DATA_RELAY_REQUEST: + return UserDataRelayPacket.create_packet(packet_bytearray, operating_mode) + + elif frame_type == ApiFrameType.REMOTE_AT_COMMAND_RESPONSE_WIFI: + return RemoteATCommandResponseWifiPacket.create_packet(packet_bytearray, operating_mode) + + elif frame_type == ApiFrameType.IO_DATA_SAMPLE_RX_INDICATOR_WIFI: + return IODataSampleRxIndicatorWifiPacket.create_packet(packet_bytearray, operating_mode) + + elif frame_type == ApiFrameType.SEND_DATA_RESPONSE: + return SendDataResponsePacket.create_packet(packet_bytearray, operating_mode) + + elif frame_type == ApiFrameType.DEVICE_REQUEST: + return DeviceRequestPacket.create_packet(packet_bytearray, operating_mode) + + elif frame_type == ApiFrameType.DEVICE_RESPONSE_STATUS: + return DeviceResponseStatusPacket.create_packet(packet_bytearray, operating_mode) + + elif frame_type == ApiFrameType.FRAME_ERROR: + return FrameErrorPacket.create_packet(packet_bytearray, operating_mode) + + else: + return UnknownXBeePacket.create_packet(packet_bytearray, operating_mode) diff --git a/digi/xbee/packets/network.py b/digi/xbee/packets/network.py new file mode 100644 index 0000000..0482739 --- /dev/null +++ b/digi/xbee/packets/network.py @@ -0,0 +1,528 @@ +# 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 +# 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.models.mode import OperatingMode +from digi.xbee.models.protocol import IPProtocol +from digi.xbee.packets.aft import ApiFrameType +from digi.xbee.packets.base import XBeeAPIPacket, DictKeys +from digi.xbee.util import utils +from digi.xbee.exception import InvalidOperatingModeException, InvalidPacketException + + +class RXIPv4Packet(XBeeAPIPacket): + """ + This class represents an RX (Receive) IPv4 packet. Packet is built + using the parameters of the constructor or providing a valid byte array. + + .. seealso:: + | :class:`.TXIPv4Packet` + | :class:`.XBeeAPIPacket` + """ + __MIN_PACKET_LENGTH = 15 + + def __init__(self, source_address, dest_port, source_port, ip_protocol, data=None): + """ + Class constructor. Instantiates a new :class:`.RXIPv4Packet` object with the provided parameters. + + Args: + source_address (:class:`.IPv4Address`): IPv4 address of the source device. + dest_port (Integer): destination port number. + source_port (Integer): source port number. + ip_protocol (:class:`.IPProtocol`): IP protocol used for transmitted data. + data (Bytearray, optional): data that is sent to the destination device. Optional. + + Raises: + ValueError: if ``dest_port`` is less than 0 or greater than 65535 or + ValueError: if ``source_port`` is less than 0 or greater than 65535. + + .. seealso:: + | :class:`.IPProtocol` + """ + if dest_port < 0 or dest_port > 65535: + raise ValueError("Destination port must be between 0 and 65535") + if source_port < 0 or source_port > 65535: + raise ValueError("Source port must be between 0 and 65535") + + super().__init__(ApiFrameType.RX_IPV4) + self.__source_address = source_address + self.__dest_port = dest_port + self.__source_port = source_port + self.__ip_protocol = ip_protocol + self.__status = 0 # Reserved + self.__data = data + + @staticmethod + def create_packet(raw, operating_mode): + """ + Override method. + + Returns: + RXIPv4Packet. + + Raises: + InvalidPacketException: if the bytearray length is less than 15. (start delim + length (2 bytes) + frame + type + source address (4 bytes) + dest port (2 bytes) + source port (2 bytes) + network protocol + + status + checksum = 15 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:`.SPECIAL_BYTE`. + InvalidPacketException: if the calculated checksum is different than the checksum field value (last byte). + InvalidPacketException: if the frame type is not :attr:`ApiFrameType.RX_IPV4`. + InvalidOperatingModeException: if ``operating_mode`` is not supported. + + .. seealso:: + | :meth:`.XBeePacket.create_packet` + | :meth:`.XBeeAPIPacket._check_api_packet` + """ + if operating_mode != OperatingMode.ESCAPED_API_MODE and operating_mode != OperatingMode.API_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("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]), + raw[14:-1]) + + def needs_id(self): + """ + Override method. + + .. seealso:: + | :meth:`.XBeeAPIPacket.needs_id` + """ + return True + + 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:`.IPv4Address`): The new IPv4 source address. + """ + if source_address is not None: + self.__source_address = source_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_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 + + def __get_ip_protocol(self): + """ + Returns the IP protocol used for transmitted data. + + Returns: + :class:`.IPProtocol`: the IP protocol used for transmitted data. + """ + return self.__ip_protocol + + def __set_ip_protocol(self, ip_protocol): + """ + Sets the IP protocol used for transmitted data. + + Args: + ip_protocol (:class:`.IPProtocol`): the new IP protocol. + """ + self.__ip_protocol = ip_protocol + + def __get_data(self): + """ + Returns the data of the packet. + + Returns: + Bytearray: the data of the packet. + """ + if self.__data is None: + return self.__data + return self.__data.copy() + + def __set_data(self, data): + """ + Sets the data of the packet. + + Args: + data (Bytearray): the new data of the packet. + """ + if data is None: + self.__data = None + else: + self.__data = data.copy() + + def _get_api_packet_spec_data(self): + """ + Override method. + + .. seealso:: + | :meth:`.XBeeAPIPacket._get_API_packet_spec_data` + """ + ret = bytearray(self.__source_address.packed) + ret += utils.int_to_bytes(self.__dest_port, num_bytes=2) + ret += utils.int_to_bytes(self.__source_port, num_bytes=2) + ret += utils.int_to_bytes(self.__ip_protocol.code, num_bytes=1) + ret += utils.int_to_bytes(self.__status, num_bytes=1) + if self.__data is not None: + ret += self.__data + return ret + + def _get_api_packet_spec_data_dict(self): + """ + Override method. + + .. seealso:: + | :meth:`.XBeeAPIPacket._get_API_packet_spec_data_dict` + """ + return {DictKeys.SRC_IPV4_ADDR: "%s (%s)" % (self.__source_address.packed, self.__source_address.exploded), + DictKeys.DEST_PORT: self.__dest_port, + DictKeys.SRC_PORT: self.__source_port, + DictKeys.IP_PROTOCOL: "%s (%s)" % (self.__ip_protocol.code, self.__ip_protocol.description), + DictKeys.STATUS: self.__status, + DictKeys.RF_DATA: bytearray(self.__data)} + + source_address = property(__get_source_address, __set_source_address) + """:class:`ipaddress.IPv4Address`. IPv4 address of the source device.""" + + dest_port = property(__get_dest_port, __set_dest_port) + """Integer. Destination port.""" + + source_port = property(__get_source_port, __set_source_port) + """Integer. Source port.""" + + ip_protocol = property(__get_ip_protocol, __set_ip_protocol) + """:class:`.IPProtocol`. IP protocol used in the transmission.""" + + data = property(__get_data, __set_data) + """Bytearray. Data of the packet.""" + + +class TXIPv4Packet(XBeeAPIPacket): + """ + This class represents an TX (Transmit) IPv4 packet. Packet is built + using the parameters of the constructor or providing a valid byte array. + + .. seealso:: + | :class:`.RXIPv4Packet` + | :class:`.XBeeAPIPacket` + """ + + OPTIONS_CLOSE_SOCKET = 2 + """This option will close the socket after the transmission.""" + + OPTIONS_LEAVE_SOCKET_OPEN = 0 + """This option will leave socket open after the transmission.""" + + __MIN_PACKET_LENGTH = 16 + + def __init__(self, frame_id, dest_address, dest_port, source_port, ip_protocol, transmit_options, data=None): + """ + Class constructor. Instantiates a new :class:`.TXIPv4Packet` object with the provided parameters. + + Args: + frame_id (Integer): the frame ID. Must be between 0 and 255. + dest_address (:class:`.IPv4Address`): IPv4 address of the destination device. + dest_port (Integer): destination port number. + source_port (Integer): source port number. + ip_protocol (:class:`.IPProtocol`): IP protocol used for transmitted data. + transmit_options (Integer): the transmit options of the packet. + data (Bytearray, optional): data that is sent to the destination device. Optional. + + Raises: + ValueError: if ``frame_id`` is less than 0 or greater than 255. + ValueError: if ``dest_port`` is less than 0 or greater than 65535. + ValueError: if ``source_port`` is less than 0 or greater than 65535. + + .. seealso:: + | :class:`.IPProtocol` + """ + if frame_id < 0 or frame_id > 255: + raise ValueError("Frame 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 source_port < 0 or source_port > 65535: + raise ValueError("Source port must be between 0 and 65535") + + super().__init__(ApiFrameType.TX_IPV4) + self._frame_id = frame_id + self.__dest_address = dest_address + self.__dest_port = dest_port + self.__source_port = source_port + self.__ip_protocol = ip_protocol + self.__transmit_options = transmit_options + self.__data = data + + @staticmethod + def create_packet(raw, operating_mode): + """ + Override method. + + Returns: + TXIPv4Packet. + + Raises: + InvalidPacketException: if the bytearray length is less than 16. (start delim + length (2 bytes) + frame + type + frame id + dest address (4 bytes) + dest port (2 bytes) + source port (2 bytes) + network + protocol + transmit options + checksum = 16 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:`.SPECIAL_BYTE`. + InvalidPacketException: if the calculated checksum is different than the checksum field value (last byte). + InvalidPacketException: if the frame type is not :attr:`ApiFrameType.TX_IPV4`. + InvalidOperatingModeException: if ``operating_mode`` is not supported. + + .. seealso:: + | :meth:`.XBeePacket.create_packet` + | :meth:`.XBeeAPIPacket._check_api_packet` + """ + if operating_mode != OperatingMode.ESCAPED_API_MODE and operating_mode != OperatingMode.API_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("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], raw[15:-1]) + + def needs_id(self): + """ + Override method. + + .. seealso:: + | :meth:`.XBeeAPIPacket.needs_id` + """ + return True + + 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_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 + + def __get_ip_protocol(self): + """ + Returns the IP protocol used for transmitted data. + + Returns: + :class:`.IPProtocol`: the IP protocol used for transmitted data. + """ + return self.__ip_protocol + + def __set_ip_protocol(self, ip_protocol): + """ + Sets the network protocol used for transmitted data. + + Args: + ip_protocol (:class:`.IPProtocol`): the new IP protocol. + """ + self.__ip_protocol = ip_protocol + + def __get_transmit_options(self): + """ + Returns the transmit options of the packet. + + Returns: + Integer: the transmit options of the packet. + """ + return self.__transmit_options + + def __set_transmit_options(self, transmit_options): + """ + Sets the transmit options bitfield of the packet. + + Args: + transmit_options (Integer): the new transmit options. Can + be :attr:`OPTIONS_CLOSE_SOCKET` or :attr:`OPTIONS_LEAVE_SOCKET_OPEN`. + """ + self.__transmit_options = transmit_options + + def __get_data(self): + """ + Returns the data of the packet. + + Returns: + Bytearray: the data of the packet. + """ + return self.__data if self.__data is None else self.__data.copy() + + def __set_data(self, data): + """ + Sets the data of the packet. + + Args: + data (Bytearray): the new data of the packet. + """ + self.__data = None if data is None else data.copy() + + def _get_api_packet_spec_data(self): + """ + Override method. + + .. seealso:: + | :meth:`.XBeeAPIPacket._get_API_packet_spec_data` + """ + ret = bytearray(self.__dest_address.packed) + ret += utils.int_to_bytes(self.__dest_port, num_bytes=2) + ret += utils.int_to_bytes(self.__source_port, num_bytes=2) + ret += utils.int_to_bytes(self.__ip_protocol.code) + ret += utils.int_to_bytes(self.__transmit_options) + if self.__data is not None: + ret += self.__data + return ret + + def _get_api_packet_spec_data_dict(self): + """ + Override method. + + .. seealso:: + | :meth:`.XBeeAPIPacket._get_API_packet_spec_data_dict` + """ + return {DictKeys.DEST_IPV4_ADDR: "%s (%s)" % (self.__dest_address.packed, self.__dest_address.exploded), + DictKeys.DEST_PORT: self.dest_port, + DictKeys.SRC_PORT: self.source_port, + DictKeys.IP_PROTOCOL: "%s (%s)" % (self.__ip_protocol.code, self.__ip_protocol.description), + DictKeys.OPTIONS: self.__transmit_options, + DictKeys.RF_DATA: bytearray(self.__data)} + + 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.""" + + source_port = property(__get_source_port, __set_source_port) + """Integer. Source port.""" + + ip_protocol = property(__get_ip_protocol, __set_ip_protocol) + """:class:`.IPProtocol`. IP protocol.""" + + transmit_options = property(__get_transmit_options, __set_transmit_options) + """Integer. Transmit options.""" + + data = property(__get_data, __set_data) + """Bytearray. Data of the packet.""" diff --git a/digi/xbee/packets/raw.py b/digi/xbee/packets/raw.py new file mode 100644 index 0000000..a454ca7 --- /dev/null +++ b/digi/xbee/packets/raw.py @@ -0,0 +1,1471 @@ +# 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 +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +from digi.xbee.packets.base import XBeeAPIPacket, DictKeys +from digi.xbee.models.address import XBee64BitAddress, XBee16BitAddress +from digi.xbee.models.status import TransmitStatus +from digi.xbee.exception import InvalidOperatingModeException, InvalidPacketException +from digi.xbee.packets.aft import ApiFrameType +from digi.xbee.models.mode import OperatingMode +from digi.xbee.io import IOSample, IOLine +from digi.xbee.util import utils + +import copy + +class TX64Packet(XBeeAPIPacket): + """ + This class represents a TX (Transmit) 64 Request packet. Packet is built + using the parameters of the constructor or providing a valid byte array. + + A TX Request message will cause the module to transmit data as an RF + Packet. + + .. seealso:: + | :class:`.XBeeAPIPacket` + """ + + __MIN_PACKET_LENGTH = 15 + + def __init__(self, frame_id, x64bit_addr, transmit_options, rf_data): + """ + Class constructor. Instantiates a new :class:`.TX64Packet` object with the provided parameters. + + Args: + frame_id (Integer): the frame ID of the packet. + x64bit_addr (:class:`.XBee64BitAddress`): the 64-bit destination address. + transmit_options (Integer): bitfield of supported transmission options. + rf_data (Bytearray, optional): RF data that is sent to the destination device. Optional. + + .. seealso:: + | :class:`.TransmitOptions` + | :class:`.XBee64BitAddress` + | :class:`.XBeeAPIPacket` + + 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(TX64Packet, self).__init__(ApiFrameType.TX_64) + self._frame_id = frame_id + self.__x64bit_addr = x64bit_addr + self.__transmit_options = transmit_options + self.__rf_data = rf_data + + @staticmethod + def create_packet(raw, operating_mode): + """ + Override method. + + Returns: + :class:`.TX64Packet`. + + Raises: + InvalidPacketException: if the bytearray length is less than 15. (start delim. + length (2 bytes) + frame + type + frame id + 64bit addr. + transmit options + checksum = 15 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 different than :attr:`.ApiFrameType.TX_64`. + InvalidOperatingModeException: if ``operating_mode`` is not supported. + + .. seealso:: + | :meth:`.XBeePacket.create_packet` + | :meth:`.XBeeAPIPacket._check_api_packet` + """ + if operating_mode != OperatingMode.ESCAPED_API_MODE and operating_mode != OperatingMode.API_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("This packet is not a TX 64 packet.") + + return TX64Packet(raw[4], XBee64BitAddress(raw[5:13]), raw[13], raw[14:-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.__x64bit_addr.address + ret.append(self.__transmit_options) + if self.__rf_data is not None: + ret += self.__rf_data + 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: self.__x64bit_addr.address, + DictKeys.TRANSMIT_OPTIONS: self.__transmit_options, + DictKeys.RF_DATA: self.__rf_data} + + def __get_64bit_addr(self): + """ + Returns the 64-bit destination address. + + Returns: + :class:`.XBee64BitAddress`: the 64-bit destination address. + + .. seealso:: + | :class:`.XBee64BitAddress` + """ + return self.__x64bit_addr + + def __set_64bit_addr(self, x64bit_addr): + """ + Sets the 64-bit destination address. + + Args: + x64bit_addr (:class:`.XBee64BitAddress`): the new 64-bit destination address. + + .. seealso:: + | :class:`.XBee64BitAddress` + """ + self.__x64bit_addr = x64bit_addr + + def __get_transmit_options(self): + """ + Returns the transmit options bitfield. + + Returns: + Integer: the transmit options bitfield. + + .. seealso:: + | :class:`.TransmitOptions` + """ + return self.__transmit_options + + def __set_transmit_options(self, transmit_options): + """ + Sets the transmit options bitfield. + + Args: + transmit_options (Integer): the new transmit options bitfield. + + .. seealso:: + | :class:`.TransmitOptions` + """ + self.__transmit_options = transmit_options + + def __get_rf_data(self): + """ + Returns the RF data to send. + + Returns: + Bytearray: the RF data to send. + """ + if self.__rf_data is None: + return None + return copy.copy(self.__rf_data) + + def __set_rf_data(self, rf_data): + """ + Sets the RF data to send. + + Args: + rf_data (Bytearray): the new RF data to send. + """ + if rf_data is None: + self.__rf_data = None + else: + self.__rf_data = copy.copy(rf_data) + + x64bit_dest_addr = property(__get_64bit_addr, __set_64bit_addr) + """XBee64BitAddress. 64-bit destination address.""" + + transmit_options = property(__get_transmit_options, __set_transmit_options) + """Integer. Transmit options bitfield.""" + + rf_data = property(__get_rf_data, __set_rf_data) + """Bytearray. RF data to send.""" + + +class TX16Packet(XBeeAPIPacket): + """ + This class represents a TX (Transmit) 16 Request packet. Packet is built + using the parameters of the constructor or providing a valid byte array. + + A TX request message will cause the module to transmit data as an RF + packet. + + .. seealso:: + | :class:`.XBeeAPIPacket` + """ + + __MIN_PACKET_LENGTH = 9 + + def __init__(self, frame_id, x16bit_addr, transmit_options, rf_data=None): + """ + Class constructor. Instantiates a new :class:`.TX16Packet` object with the provided parameters. + + Args: + frame_id (Integer): the frame ID of the packet. + x16bit_addr (:class:`.XBee16BitAddress`): the 16-bit destination address. + transmit_options (Integer): bitfield of supported transmission options. + rf_data (Bytearray, optional): RF data that is sent to the destination device. Optional. + + .. seealso:: + | :class:`.TransmitOptions` + | :class:`.XBee16BitAddress` + | :class:`.XBeeAPIPacket` + + 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(TX16Packet, self).__init__(ApiFrameType.TX_16) + self._frame_id = frame_id + self.__x16bit_addr = x16bit_addr + self.__transmit_options = transmit_options + self.__rf_data = rf_data + + @staticmethod + def create_packet(raw, operating_mode): + """ + Override method. + + Returns: + :class:`.TX16Packet`. + + Raises: + InvalidPacketException: if the bytearray length is less than 9. (start delim. + length (2 bytes) + frame + type + frame id + 16bit addr. + transmit options + 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 different than :attr:`.ApiFrameType.TX_16`. + InvalidOperatingModeException: if ``operating_mode`` is not supported. + + .. seealso:: + | :meth:`.XBeePacket.create_packet` + | :meth:`.XBeeAPIPacket._check_api_packet` + """ + if operating_mode != OperatingMode.ESCAPED_API_MODE and operating_mode != OperatingMode.API_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("This packet is not a TX 16 packet.") + + return TX16Packet(raw[4], XBee16BitAddress(raw[5:7]), 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 = self.__x16bit_addr.address + ret.append(self.__transmit_options) + if self.__rf_data is not None: + ret += self.__rf_data + return ret + + def _get_api_packet_spec_data_dict(self): + """ + Override method. + + .. seealso:: + | :meth:`.XBeeAPIPacket._get_api_packet_spec_data_dict` + """ + return {DictKeys.X16BIT_ADDR: self.__x16bit_addr, + DictKeys.TRANSMIT_OPTIONS: self.__transmit_options, + DictKeys.RF_DATA: self.__rf_data} + + def __get_16bit_addr(self): + """ + Returns the 16-bit destination address. + + Returns: + :class:`.XBee16BitAddress`: the 16-bit destination address. + + .. seealso:: + | :class:`.XBee16BitAddress` + """ + return self.__x16bit_addr + + def __set_16bit_addr(self, x16bit_addr): + """ + Sets the 16-bit destination address. + + Args: + x16bit_addr (:class:`.XBee16BitAddress`): the new 16-bit destination address. + + .. seealso:: + | :class:`.XBee16BitAddress` + """ + self.__x16bit_addr = x16bit_addr + + def __get_transmit_options(self): + """ + Returns the transmit options bitfield. + + Returns: + Integer: the transmit options bitfield. + + .. seealso:: + | :class:`.TransmitOptions` + """ + return self.__transmit_options + + def __set_transmit_options(self, transmit_options): + """ + Sets the transmit options bitfield. + + Args: + transmit_options (Integer): the new transmit options bitfield. + + .. seealso:: + | :class:`.TransmitOptions` + """ + self.__transmit_options = transmit_options + + def __get_rf_data(self): + """ + Returns the RF data to send. + + Returns: + Bytearray: the RF data to send. + """ + if self.__rf_data is None: + return None + return copy.copy(self.__rf_data) + + def __set_rf_data(self, rf_data): + """ + Sets the RF data to send. + + Args: + rf_data (Bytearray): the new RF data to send. + """ + if rf_data is None: + self.__rf_data = None + else: + self.__rf_data = copy.copy(rf_data) + + x16bit_dest_addr = property(__get_16bit_addr, __set_16bit_addr) + """XBee64BitAddress. 16-bit destination address.""" + + transmit_options = property(__get_transmit_options, __set_transmit_options) + """Integer. Transmit options bitfield.""" + + rf_data = property(__get_rf_data, __set_rf_data) + """Bytearray. RF data to send.""" + + +class TXStatusPacket(XBeeAPIPacket): + """ + This class represents a TX (Transmit) status packet. Packet is built using + the parameters of the constructor or providing a valid API payload. + + When a TX request is completed, the module sends a TX status message. + This message will indicate if the packet was transmitted successfully or if + there was a failure. + + .. seealso:: + | :class:`.TX16Packet` + | :class:`.TX64Packet` + | :class:`.XBeeAPIPacket` + """ + + __MIN_PACKET_LENGTH = 7 + + def __init__(self, frame_id, transmit_status): + """ + Class constructor. Instantiates a new :class:`.TXStatusPacket` object with the provided parameters. + + Args: + frame_id (Integer): the frame ID of the packet. + transmit_status (:class:`.TransmitStatus`): transmit status. Default: SUCCESS. + + Raises: + ValueError: if ``frame_id`` is less than 0 or greater than 255. + + .. seealso:: + | :class:`.TransmitStatus` + | :class:`.XBeeAPIPacket` + """ + if frame_id < 0 or frame_id > 255: + raise ValueError("Frame id must be between 0 and 255.") + + super(TXStatusPacket, self).__init__(ApiFrameType.TX_STATUS) + self._frame_id = frame_id + self.__transmit_status = transmit_status + + @staticmethod + def create_packet(raw, operating_mode): + """ + Override method. + + Returns: + :class:`.TXStatusPacket`. + + Raises: + InvalidPacketException: if the bytearray length is less than 7. (start delim. + length (2 bytes) + frame + type + frame id + transmit status + 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 different than :attr:`.ApiFrameType.TX_16`. + InvalidOperatingModeException: if ``operating_mode`` is not supported. + + .. seealso:: + | :meth:`.XBeePacket.create_packet` + | :meth:`.XBeeAPIPacket._check_api_packet` + """ + if operating_mode != OperatingMode.ESCAPED_API_MODE and operating_mode != OperatingMode.API_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("This packet is not a TX status packet.") + + return TXStatusPacket(raw[4], TransmitStatus.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 utils.int_to_bytes(self.__transmit_status.code, 1) + + def _get_api_packet_spec_data_dict(self): + """ + Override method. + + .. seealso:: + | :meth:`.XBeeAPIPacket._get_api_packet_spec_data_dict` + """ + return {DictKeys.TS_STATUS: self.__transmit_status} + + def __get_transmit_status(self): + """ + Returns the transmit status. + + Returns: + :class:`.TransmitStatus`: the transmit status. + + .. seealso:: + | :class:`.TransmitStatus` + """ + return self.__transmit_status + + def __set_transmit_status(self, transmit_status): + """ + Sets the transmit status. + + Args: + transmit_status (:class:`.TransmitStatus`): the new transmit status to set. + + .. seealso:: + | :class:`.TransmitStatus` + """ + self.__transmit_status = transmit_status + + transmit_status = property(__get_transmit_status, __set_transmit_status) + """:class:`.TransmitStatus`. Transmit status.""" + + +class RX64Packet(XBeeAPIPacket): + """ + This class represents an RX (Receive) 64 request packet. Packet is built + using the parameters of the constructor or providing a valid API byte array. + + When the module receives an RF packet, it is sent out the UART using + this message type. + + This packet is the response to TX (transmit) 64 request packets. + + .. seealso:: + | :class:`.ReceiveOptions` + | :class:`.TX64Packet` + | :class:`.XBeeAPIPacket` + """ + + __MIN_PACKET_LENGTH = 15 + + def __init__(self, x64bit_addr, rssi, receive_options, rf_data=None): + """ + Class constructor. Instantiates a :class:`.RX64Packet` object with the provided parameters. + + Args: + x64bit_addr (:class:`.XBee64BitAddress`): the 64-bit source address. + rssi (Integer): received signal strength indicator. + receive_options (Integer): bitfield indicating the receive options. + rf_data (Bytearray, optional): received RF data. Optional. + + .. seealso:: + | :class:`.ReceiveOptions` + | :class:`.XBee64BitAddress` + | :class:`.XBeeAPIPacket` + """ + + super(RX64Packet, self).__init__(ApiFrameType.RX_64) + + self.__x64bit_addr = x64bit_addr + self.__rssi = rssi + self.__receive_options = receive_options + self.__rf_data = rf_data + + @staticmethod + def create_packet(raw, operating_mode): + """ + Override method. + + Returns: + :class:`.RX64Packet` + + Raises: + InvalidPacketException: if the bytearray length is less than 15. (start delim. + length (2 bytes) + frame + type + 64bit addr. + rssi + receive options + checksum = 15 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 different than :attr:`.ApiFrameType.RX_64`. + InvalidOperatingModeException: if ``operating_mode`` is not supported. + + .. seealso:: + | :meth:`.XBeePacket.create_packet` + | :meth:`.XBeeAPIPacket._check_api_packet` + """ + if operating_mode != OperatingMode.ESCAPED_API_MODE and operating_mode != OperatingMode.API_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("This packet is not an RX 64 packet.") + + return RX64Packet(XBee64BitAddress(raw[4:12]), raw[12], raw[13], raw[14:-1]) + + 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 = self.__x64bit_addr.address + ret.append(self.__rssi) + ret.append(self.__receive_options) + if self.__rf_data is not None: + ret += self.__rf_data + 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: self.__x64bit_addr, + DictKeys.RSSI: self.__rssi, + DictKeys.RECEIVE_OPTIONS: self.__receive_options, + DictKeys.RF_DATA: self.__rf_data} + + def __get_64bit_addr(self): + """ + Returns the 64-bit source address. + + Returns: + :class:`.XBee64BitAddress`: the 64-bit source address. + + .. seealso:: + | :class:`.XBee64BitAddress` + """ + return self.__x64bit_addr + + def __set_64bit_addr(self, x64bit_addr): + """ + Sets the 64-bit source address. + + Args: + x64bit_addr (:class:`.XBee64BitAddress`): the new 64-bit source address. + + .. seealso:: + | :class:`.XBee64BitAddress` + """ + self.__x64bit_addr = x64bit_addr + + def __get_rssi(self): + """ + Returns the received Signal Strength Indicator (RSSI). + + Returns: + Integer: the received Signal Strength Indicator (RSSI). + """ + return self.__rssi + + def __set_rssi(self, rssi): + """ + Sets the received Signal Strength Indicator (RSSI). + + Args: + rssi (Integer): the new received Signal Strength Indicator (RSSI). + """ + self.__rssi = rssi + + def __get_options(self): + """ + Returns the receive options bitfield. + + Returns: + Integer: the receive options bitfield. + + .. seealso:: + | :class:`.ReceiveOptions` + """ + return self.__receive_options + + def __set_options(self, receive_options): + """ + Sets the receive options bitfield. + + Args: + receive_options (Integer): the new receive options bitfield. + + .. seealso:: + | :class:`.ReceiveOptions` + """ + self.__receive_options = receive_options + + def __get_rf_data(self): + """ + Returns the received RF data. + + Returns: + Bytearray: the received RF data. + """ + if self.__rf_data is None: + return None + return copy.copy(self.__rf_data) + + def __set_rf_data(self, rf_data): + """ + Sets the received RF data. + + Args: + rf_data (Bytearray): the new received RF data. + """ + if rf_data is None: + self.__rf_data = None + else: + self.__rf_data = copy.copy(rf_data) + + x64bit_source_addr = property(__get_64bit_addr, __set_64bit_addr) + """:class:`.XBee64BitAddress`. 64-bit source address.""" + + rssi = property(__get_rssi, __set_rssi) + """Integer. Received Signal Strength Indicator (RSSI) value.""" + + receive_options = property(__get_options, __set_options) + """Integer. Receive options bitfield.""" + + rf_data = property(__get_rf_data, __set_rf_data) + """Bytearray. Received RF data.""" + + +class RX16Packet(XBeeAPIPacket): + """ + This class represents an RX (Receive) 16 Request packet. Packet is built + using the parameters of the constructor or providing a valid API byte array. + + When the module receives an RF packet, it is sent out the UART using this + message type + + This packet is the response to TX (Transmit) 16 Request packets. + + .. seealso:: + | :class:`.ReceiveOptions` + | :class:`.TX16Packet` + | :class:`.XBeeAPIPacket` + """ + + __MIN_PACKET_LENGTH = 9 + + def __init__(self, x16bit_addr, rssi, receive_options, rf_data=None): + """ + Class constructor. Instantiates a :class:`.RX16Packet` object with the provided parameters. + + Args: + x16bit_addr (:class:`.XBee16BitAddress`): the 16-bit source address. + rssi (Integer): received signal strength indicator. + receive_options (Integer): bitfield indicating the receive options. + rf_data (Bytearray, optional): received RF data. Optional. + + .. seealso:: + | :class:`.ReceiveOptions` + | :class:`.XBee16BitAddress` + | :class:`.XBeeAPIPacket` + """ + + super(RX16Packet, self).__init__(ApiFrameType.RX_16) + + self.__x16bit_addr = x16bit_addr + self.__rssi = rssi + self.__receive_options = receive_options + self.__rf_data = rf_data + + @staticmethod + def create_packet(raw, operating_mode): + """ + Override method. + + Returns: + :class:`.RX16Packet`. + + Raises: + InvalidPacketException: if the bytearray length is less than 9. (start delim. + length (2 bytes) + frame + type + 16bit addr. + rssi + receive options + 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 different than :attr:`.ApiFrameType.RX_16`. + InvalidOperatingModeException: if ``operating_mode`` is not supported. + + .. seealso:: + | :meth:`.XBeePacket.create_packet` + | :meth:`.XBeeAPIPacket._check_api_packet` + """ + if operating_mode != OperatingMode.ESCAPED_API_MODE and operating_mode != OperatingMode.API_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("This packet is not an RX 16 Packet") + + return RX16Packet(XBee16BitAddress(raw[4:6]), raw[6], raw[7], raw[8:-1]) + + 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 = self.__x16bit_addr.address + ret.append(self.__rssi) + ret.append(self.__receive_options) + if self.__rf_data is not None: + ret += self.__rf_data + return ret + + def _get_api_packet_spec_data_dict(self): + """ + Override method. + + .. seealso:: + | :meth:`.XBeeAPIPacket._get_api_packet_spec_data_dict` + """ + return {DictKeys.X16BIT_ADDR: self.__x16bit_addr, + DictKeys.RSSI: self.__rssi, + DictKeys.RECEIVE_OPTIONS: self.__receive_options, + DictKeys.RF_DATA: self.__rf_data} + + def __get_16bit_addr(self): + """ + Returns the 16-bit source address. + + Returns: + :class:`.XBee16BitAddress`: the 16-bit source address. + + .. seealso:: + | :class:`.XBee16BitAddress` + """ + return self.__x16bit_addr + + def __set_16bit_addr(self, x16bit_addr): + """ + Sets the 16-bit source address. + + Args: + x16bit_addr (:class:`.XBee16BitAddress`): the new 16-bit source address. + + .. seealso:: + | :class:`.XBee16BitAddress` + """ + self.__x16bit_addr = x16bit_addr + + def __get_rssi(self): + """ + Returns the received Signal Strength Indicator (RSSI). + + Returns: + Integer: the received Signal Strength Indicator (RSSI). + """ + return self.__rssi + + def __set_rssi(self, rssi): + """ + Sets the received Signal Strength Indicator (RSSI). + + Args: + rssi (Integer): the new received Signal Strength Indicator (RSSI). + + """ + self.__rssi = rssi + + def __get_options(self): + """ + Returns the receive options bitfield. + + Returns: + Integer: the receive options bitfield. + + .. seealso:: + | :class:`.ReceiveOptions` + """ + return self.__receive_options + + def __set_options(self, receive_options): + """ + Sets the receive options bitfield. + + Args: + receive_options (Integer): the new receive options bitfield. + + .. seealso:: + | :class:`.ReceiveOptions` + """ + self.__receive_options = receive_options + + def __get_rf_data(self): + """ + Returns the received RF data. + + Returns: + Bytearray: the received RF data. + """ + if self.__rf_data is None: + return None + return copy.copy(self.__rf_data) + + def __set_rf_data(self, rf_data): + """ + Sets the received RF data. + + Args: + rf_data (Bytearray): the new received RF data. + """ + if rf_data is None: + self.__rf_data = None + else: + self.__rf_data = copy.copy(rf_data) + + x16bit_source_addr = property(__get_16bit_addr, __set_16bit_addr) + """:class:`.XBee16BitAddress`. 16-bit source address.""" + + rssi = property(__get_rssi, __set_rssi) + """Integer. Received Signal Strength Indicator (RSSI) value.""" + + receive_options = property(__get_options, __set_options) + """Integer. Receive options bitfield.""" + + rf_data = property(__get_rf_data, __set_rf_data) + """Bytearray. Received RF data.""" + + +class RX64IOPacket(XBeeAPIPacket): + """ + This class represents an RX64 address IO packet. Packet is built using the + parameters of the constructor or providing a valid API payload. + + I/O data is sent out the UART using an API frame. + + .. seealso:: + | :class:`.XBeeAPIPacket` + """ + + __MIN_PACKET_LENGTH = 20 + + def __init__(self, x64bit_addr, rssi, receive_options, rf_data): + """ + Class constructor. Instantiates an :class:`.RX64IOPacket` object with the provided parameters. + + Args: + x64bit_addr (:class:`.XBee64BitAddress`): the 64-bit source address. + rssi (Integer): received signal strength indicator. + receive_options (Integer): bitfield indicating the receive options. + rf_data (Bytearray): received RF data. + + .. seealso:: + | :class:`.ReceiveOptions` + | :class:`.XBee64BitAddress` + | :class:`.XBeeAPIPacket` + """ + super(RX64IOPacket, self).__init__(ApiFrameType.RX_IO_64) + self.__x64bit_addr = x64bit_addr + self.__rssi = rssi + self.__receive_options = receive_options + self.__rf_data = rf_data + self.__io_sample = IOSample(rf_data) if rf_data is not None and len(rf_data) >= 5 else None + + @staticmethod + def create_packet(raw, operating_mode): + """ + Override method. + + Returns: + :class:`.RX64IOPacket`. + + Raises: + InvalidPacketException: if the bytearray length is less than 20. (start delim. + length (2 bytes) + frame + type + 64bit addr. + rssi + receive options + rf data (5 bytes) + checksum = 20 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 different than :attr:`.ApiFrameType.RX_IO_64`. + InvalidOperatingModeException: if ``operating_mode`` is not supported. + + .. seealso:: + | :meth:`.XBeePacket.create_packet` + | :meth:`.XBeeAPIPacket._check_api_packet` + """ + if operating_mode != OperatingMode.ESCAPED_API_MODE and operating_mode != OperatingMode.API_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("This packet is not an RX 64 IO packet.") + + return RX64IOPacket(XBee64BitAddress(raw[4:12]), raw[12], raw[13], raw[14:-1]) + + 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 = self.__x64bit_addr.address + ret.append(self.__rssi) + ret.append(self.__receive_options) + if self.__rf_data is not None: + ret += self.__rf_data + return ret + + def _get_api_packet_spec_data_dict(self): + """ + Override method. + + .. seealso:: + | :meth:`.XBeeAPIPacket._get_api_packet_spec_data_dict` + """ + base = {DictKeys.X16BIT_ADDR: self.__x64bit_addr.address, + DictKeys.RSSI: self.__rssi, + DictKeys.RECEIVE_OPTIONS: self.__receive_options} + + if self.__io_sample is not None: + base[DictKeys.NUM_SAMPLES] = 1 + base[DictKeys.DIGITAL_MASK] = self.__io_sample.digital_mask + base[DictKeys.ANALOG_MASK] = self.__io_sample.analog_mask + + # Digital values + for i in range(16): + if self.__io_sample.has_digital_value(IOLine.get(i)): + base[IOLine.get(i).description + "digital value"] = \ + utils.hex_to_string(self.__io_sample.get_digital_value(IOLine.get(i))) + + # Analog values + for i in range(6): + if self.__io_sample.has_analog_value(IOLine.get(i)): + base[IOLine.get(i).description + "analog value"] = \ + utils.hex_to_string(self.__io_sample.get_analog_value(IOLine.get(i))) + + # Power supply + if self.__io_sample.has_power_supply_value(): + base["Power supply value "] = "%02X" % self.__io_sample.power_supply_value + + elif self.__rf_data is not None: + base[DictKeys.RF_DATA] = utils.hex_to_string(self.__rf_data) + + return base + + def __get_64bit_addr(self): + """ + Returns the 64-bit source address. + + Returns: + :class:`XBee64BitAddress`: the 64-bit source address. + + .. seealso:: + | :class:`.XBee64BitAddress` + """ + return self.__x64bit_addr + + def __set_64bit_addr(self, x64bit_addr): + """ + Sets the 64-bit source address. + + Args: + x64bit_addr (:class:`.XBee64BitAddress`): the new 64-bit source address. + + .. seealso:: + | :class:`.XBee64BitAddress` + """ + self.__x64bit_addr = x64bit_addr + + def __get_rssi(self): + """ + Returns the received Signal Strength Indicator (RSSI). + + Returns: + Integer: the received Signal Strength Indicator (RSSI). + """ + return self.__rssi + + def __set_rssi(self, rssi): + """ + Sets the received Signal Strength Indicator (RSSI). + + Args: + rssi (Integer): the new received Signal Strength Indicator (RSSI). + """ + self.__rssi = rssi + + def __get_options(self): + """ + Returns the receive options bitfield. + + Returns: + Integer: the receive options bitfield. + + .. seealso:: + | :class:`.ReceiveOptions` + """ + return self.__receive_options + + def __set_options(self, receive_options): + """ + Sets the receive options bitfield. + + Args: + receive_options (Integer): the new receive options bitfield. + + .. seealso:: + | :class:`.ReceiveOptions` + """ + self.__receive_options = receive_options + + def __get_rf_data(self): + """ + Returns the received RF data. + + Returns: + Bytearray: the received RF data. + """ + if self.__rf_data is None: + return None + return copy.copy(self.__rf_data) + + def __set_rf_data(self, rf_data): + """ + Sets the received RF data. + + Args: + rf_data (Bytearray): the new received RF data. + """ + if rf_data is None: + self.__rf_data = None + else: + self.__rf_data = copy.copy(rf_data) + + # Modify the ioSample accordingly + if rf_data is not None and len(rf_data) >= 5: + self.__io_sample = IOSample(self.__rf_data) + else: + self.__io_sample = None + + def __get_io_sample(self): + """ + Returns the IO sample corresponding to the data contained in the packet. + + Returns: + :class:`.IOSample`: the IO sample of the packet, ``None`` if the packet has not any data or if the + sample could not be generated correctly. + + .. seealso:: + | :class:`.IOSample` + """ + return self.__io_sample + + def __set_io_sample(self, io_sample): + """ + Sets the IO sample of the packet. + + Args: + io_sample (:class:`.IOSample`): the new IO sample to set. + + .. seealso:: + | :class:`.IOSample` + """ + self.__io_sample = io_sample + + x64bit_source_addr = property(__get_64bit_addr, __set_64bit_addr) + """:class:`.XBee64BitAddress`. 64-bit source address.""" + + rssi = property(__get_rssi, __set_rssi) + """Integer. Received Signal Strength Indicator (RSSI) value.""" + + receive_options = property(__get_options, __set_options) + """Integer. Receive options bitfield.""" + + rf_data = property(__get_rf_data, __set_rf_data) + """Bytearray. Received RF data.""" + + io_sample = property(__get_io_sample, __set_io_sample) + """:class:`.IOSample`: IO sample corresponding to the data contained in the packet.""" + + +class RX16IOPacket(XBeeAPIPacket): + """ + This class represents an RX16 address IO packet. Packet is built using the + parameters of the constructor or providing a valid byte array. + + I/O data is sent out the UART using an API frame. + + .. seealso:: + | :class:`.XBeeAPIPacket` + """ + + __MIN_PACKET_LENGTH = 14 + + def __init__(self, x16bit_addr, rssi, receive_options, rf_data): + """ + Class constructor. Instantiates an :class:`.RX16IOPacket` object with the provided parameters. + + Args: + x16bit_addr (:class:`.XBee16BitAddress`): the 16-bit source address. + rssi (Integer): received signal strength indicator. + receive_options (Integer): bitfield indicating the receive options. + rf_data (Bytearray): received RF data. + + .. seealso:: + | :class:`.ReceiveOptions` + | :class:`.XBee16BitAddress` + | :class:`.XBeeAPIPacket` + """ + super(RX16IOPacket, self).__init__(ApiFrameType.RX_IO_16) + self.__x16bit_addr = x16bit_addr + self.__rssi = rssi + self.__options = receive_options + self.__rf_data = rf_data + self.__io_sample = IOSample(rf_data) if rf_data is not None and len(rf_data) >= 5 else None + + @staticmethod + def create_packet(raw, operating_mode): + """ + Override method. + + Returns: + :class:`.RX16IOPacket`. + + Raises: + InvalidPacketException: if the bytearray length is less than 14. (start delim. + length (2 bytes) + frame + type + 16bit addr. + rssi + receive options + rf data (5 bytes) + 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 different than :attr:`.ApiFrameType.RX_IO_16`. + InvalidOperatingModeException: if ``operating_mode`` is not supported. + + .. seealso:: + | :meth:`.XBeePacket.create_packet` + | :meth:`.XBeeAPIPacket._check_api_packet` + """ + if operating_mode != OperatingMode.ESCAPED_API_MODE and operating_mode != OperatingMode.API_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("This packet is not an RX 16 IO packet.") + + return RX16IOPacket(XBee16BitAddress(raw[4:6]), raw[6], raw[7], raw[8:-1]) + + 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 = self.__x16bit_addr.address + ret.append(self.__rssi) + ret.append(self.__options) + if self.__rf_data is not None: + ret += self.__rf_data + return ret + + def _get_api_packet_spec_data_dict(self): + """ + Override method. + + .. seealso:: + | :meth:`.XBeeAPIPacket._get_api_packet_spec_data_dict` + """ + base = {DictKeys.X16BIT_ADDR: self.__x16bit_addr.address, + DictKeys.RSSI: self.__rssi, + DictKeys.RECEIVE_OPTIONS: self.__options} + + if self.__io_sample is not None: + base[DictKeys.NUM_SAMPLES] = 1 + base[DictKeys.DIGITAL_MASK] = self.__io_sample.digital_mask + base[DictKeys.ANALOG_MASK] = self.__io_sample.analog_mask + + # Digital values + for i in range(16): + if self.__io_sample.has_digital_value(IOLine.get(i)): + base[IOLine.get(i).description + "digital value"] = \ + utils.hex_to_string(self.__io_sample.get_digital_value(IOLine.get(i))) + + # Analog values + for i in range(6): + if self.__io_sample.has_analog_value(IOLine.get(i)): + base[IOLine.get(i).description + "analog value"] = \ + utils.hex_to_string(self.__io_sample.get_analog_value(IOLine.get(i))) + + # Power supply + if self.__io_sample.has_power_supply_value(): + base["Power supply value "] = "%02X" % self.__io_sample.power_supply_value + + elif self.__rf_data is not None: + base[DictKeys.RF_DATA] = utils.hex_to_string(self.__rf_data) + + return base + + def __get_16bit_addr(self): + """ + Returns the 16-bit source address. + + Returns: + :class:`.XBee16BitAddress`: the 16-bit source address. + + .. seealso:: + | :class:`.XBee16BitAddress` + """ + return self.__x16bit_addr + + def __set_16bit_addr(self, x16bit_addr): + """ + Sets the 16-bit source address. + + Args: + x16bit_addr (:class:`.XBee16BitAddress`): the new 16-bit source address. + + .. seealso:: + | :class:`.XBee16BitAddress` + """ + self.__x16bit_addr = x16bit_addr + + def __get_rssi(self): + """ + Returns the received Signal Strength Indicator (RSSI). + + Returns: + Integer: the received Signal Strength Indicator (RSSI). + """ + return self.__rssi + + def __set_rssi(self, rssi): + """ + Sets the received Signal Strength Indicator (RSSI). + + Args: + rssi (Integer): the new received Signal Strength Indicator (RSSI). + + """ + self.__rssi = rssi + + def __get_options(self): + """ + Returns the receive options bitfield. + + Returns: + Integer: the receive options bitfield. + + .. seealso:: + | :class:`.ReceiveOptions` + """ + return self.__receive_options + + def __set_options(self, receive_options): + """ + Sets the receive options bitfield. + + Args: + receive_options (Integer): the new receive options bitfield. + + .. seealso:: + | :class:`.ReceiveOptions` + """ + self.__receive_options = receive_options + + def __get_rf_data(self): + """ + Returns the received RF data. + + Returns: + Bytearray: the received RF data. + """ + if self.__rf_data is None: + return None + return copy.copy(self.__rf_data) + + def __set_rf_data(self, rf_data): + """ + Sets the received RF data. + + Args: + rf_data (Bytearray): the new received RF data. + """ + if rf_data is None: + self.__rf_data = None + else: + self.__rf_data = copy.copy(rf_data) + + # Modify the ioSample accordingly + if rf_data is not None and len(rf_data) >= 5: + self.__io_sample = IOSample(self.__rf_data) + else: + self.__io_sample = None + + def __get_io_sample(self): + """ + Returns the IO sample corresponding to the data contained in the packet. + + Returns: + :class:`.IOSample`: the IO sample of the packet, ``None`` if the packet has not any data or if the + sample could not be generated correctly. + + .. seealso:: + | :class:`.IOSample` + """ + return self.__io_sample + + def __set_io_sample(self, io_sample): + """ + Sets the IO sample of the packet. + + Args: + io_sample (:class:`.IOSample`): the new IO sample to set. + + .. seealso:: + | :class:`.IOSample` + """ + self.__io_sample = io_sample + + x16bit_source_addr = property(__get_16bit_addr, __set_16bit_addr) + """:class:`.XBee16BitAddress`. 16-bit source address.""" + + rssi = property(__get_rssi, __set_rssi) + """Integer. Received Signal Strength Indicator (RSSI) value.""" + + receive_options = property(__get_options, __set_options) + """Integer. Receive options bitfield.""" + + rf_data = property(__get_rf_data, __set_rf_data) + """Bytearray. Received RF data.""" + + io_sample = property(__get_io_sample, __set_io_sample) + """:class:`.IOSample`: IO sample corresponding to the data contained in the packet.""" diff --git a/digi/xbee/packets/relay.py b/digi/xbee/packets/relay.py new file mode 100644 index 0000000..f681c33 --- /dev/null +++ b/digi/xbee/packets/relay.py @@ -0,0 +1,343 @@ +# 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.mode import OperatingMode +from digi.xbee.models.options import XBeeLocalInterface +from digi.xbee.packets.aft import ApiFrameType +from digi.xbee.packets.base import XBeeAPIPacket, DictKeys +from digi.xbee.exception import InvalidOperatingModeException, InvalidPacketException + + +class UserDataRelayPacket(XBeeAPIPacket): + """ + This class represents a User Data Relay packet. Packet is built using the + parameters of the constructor. + + The User Data Relay packet allows for data to come in on an interface with + a designation of the target interface for the data to be output on. + + The destination interface must be one of the interfaces found in the + corresponding enumerator (see :class:`.XBeeLocalInterface`). + + .. seealso:: + | :class:`.UserDataRelayOutputPacket` + | :class:`.XBeeAPIPacket` + | :class:`.XBeeLocalInterface` + """ + + __MIN_PACKET_LENGTH = 7 + + def __init__(self, frame_id, local_interface, data=None): + """ + Class constructor. Instantiates a new :class:`.UserDataRelayPacket` object with the provided parameters. + + Args: + frame_id (integer): the frame ID of the packet. + local_interface (:class:`.XBeeLocalInterface`): the destination interface. + data (Bytearray, optional): Data to send to the destination interface. + + .. seealso:: + | :class:`.XBeeAPIPacket` + | :class:`.XBeeLocalInterface` + + Raises: + ValueError: if ``local_interface`` is ``None``. + ValueError: if ``frame_id`` is less than 0 or greater than 255. + """ + if local_interface is None: + raise ValueError("Destination interface cannot be None") + if frame_id > 255 or frame_id < 0: + raise ValueError("frame_id must be between 0 and 255.") + + super().__init__(ApiFrameType.USER_DATA_RELAY_REQUEST) + self._frame_id = frame_id + self.__local_interface = local_interface + self.__data = data + + @staticmethod + def create_packet(raw, operating_mode): + """ + Override method. + + Returns: + :class:`.UserDataRelayPacket`. + + Raises: + InvalidPacketException: if the bytearray length is less than 7. (start delim. + length (2 bytes) + frame + type + frame id + relay interface + 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.USER_DATA_RELAY_REQUEST`. + InvalidOperatingModeException: if ``operating_mode`` is not supported. + + .. seealso:: + | :meth:`.XBeePacket.create_packet` + | :meth:`.XBeeAPIPacket._check_api_packet` + """ + if operating_mode != OperatingMode.ESCAPED_API_MODE and operating_mode != OperatingMode.API_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("This packet is not a user data relay packet.") + + return UserDataRelayPacket(raw[4], XBeeLocalInterface.get([5]), raw[6:-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.__local_interface.code) + if self.__data is not None: + return ret + self.__data + return ret + + def _get_api_packet_spec_data_dict(self): + """ + Override method. + + .. seealso:: + | :meth:`.XBeeAPIPacket._get_api_packet_spec_data_dict` + """ + return {DictKeys.DEST_INTERFACE: self.__local_interface.description, + DictKeys.DATA: list(self.__data) if self.__data is not None else None} + + def __get_data(self): + """ + Returns the data to send. + + Returns: + Bytearray: the data to send. + """ + if self.__data is None: + return None + return self.__data.copy() + + def __set_data(self, data): + """ + Sets the data to send. + + Args: + data (Bytearray): the new data to send. + """ + if data is None: + self.__data = None + else: + self.__data = data.copy() + + def __get_dest_interface(self): + """ + Returns the the destination interface. + + Returns: + :class:`.XBeeLocalInterface`: the destination interface. + + .. seealso:: + | :class:`.XBeeLocalInterface` + """ + return self.__local_interface + + def __set_dest_interface(self, local_interface): + """ + Sets the destination interface. + + Args: + local_interface (:class:`.XBeeLocalInterface`): the new destination interface. + + .. seealso:: + | :class:`.XBeeLocalInterface` + """ + self.__local_interface = local_interface + + dest_interface = property(__get_dest_interface, __set_dest_interface) + """:class:`.XBeeLocalInterface`. Destination local interface.""" + + data = property(__get_data, __set_data) + """Bytearray. Data to send.""" + + +class UserDataRelayOutputPacket(XBeeAPIPacket): + """ + This class represents a User Data Relay Output packet. Packet is built + using the parameters of the constructor. + + The User Data Relay Output packet can be received from any relay interface. + + The source interface must be one of the interfaces found in the + corresponding enumerator (see :class:`.XBeeLocalInterface`). + + .. seealso:: + | :class:`.UserDataRelayPacket` + | :class:`.XBeeAPIPacket` + | :class:`.XBeeLocalInterface` + """ + + __MIN_PACKET_LENGTH = 6 + + def __init__(self, local_interface, data=None): + """ + Class constructor. Instantiates a new + :class:`.UserDataRelayOutputPacket` object with the provided + parameters. + + Args: + local_interface (:class:`.XBeeLocalInterface`): the source interface. + data (Bytearray, optional): Data received from the source interface. + + Raises: + ValueError: if ``local_interface`` is ``None``. + + .. seealso:: + | :class:`.XBeeAPIPacket` + | :class:`.XBeeLocalInterface` + """ + if local_interface is None: + raise ValueError("Source interface cannot be None") + + super().__init__(ApiFrameType.USER_DATA_RELAY_OUTPUT) + self.__local_interface = local_interface + self.__data = data + + @staticmethod + def create_packet(raw, operating_mode): + """ + Override method. + + Returns: + :class:`.UserDataRelayOutputPacket`. + + Raises: + InvalidPacketException: if the bytearray length is less than 6. (start delim. + length (2 bytes) + frame + type + relay interface + checksum = 6 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.USER_DATA_RELAY_OUTPUT`. + InvalidOperatingModeException: if ``operating_mode`` is not supported. + + .. seealso:: + | :meth:`.XBeePacket.create_packet` + | :meth:`.XBeeAPIPacket._check_api_packet` + """ + if operating_mode != OperatingMode.ESCAPED_API_MODE and operating_mode != OperatingMode.API_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("This packet is not a user data relay output packet.") + + return UserDataRelayOutputPacket(XBeeLocalInterface.get(raw[4]), raw[5:-1]) + + 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.__local_interface.code) + if self.__data is not None: + return ret + self.__data + return ret + + def _get_api_packet_spec_data_dict(self): + """ + Override method. + + .. seealso:: + | :meth:`.XBeeAPIPacket._get_api_packet_spec_data_dict` + """ + return {DictKeys.SOURCE_INTERFACE: self.__local_interface.description, + DictKeys.DATA: list(self.__data) if self.__data is not None else None} + + def __get_data(self): + """ + Returns the received data. + + Returns: + Bytearray: the received data. + """ + if self.__data is None: + return None + return self.__data.copy() + + def __set_data(self, data): + """ + Sets the received data. + + Args: + data (Bytearray): the new received data. + """ + if data is None: + self.__data = None + else: + self.__data = data.copy() + + def __get_src_interface(self): + """ + Returns the the source interface. + + Returns: + :class:`.XBeeLocalInterface`: the source interface. + + .. seealso:: + | :class:`.XBeeLocalInterface` + """ + return self.__local_interface + + def __set_src_interface(self, local_interface): + """ + Sets the source interface. + + Args: + local_interface (:class:`.XBeeLocalInterface`): the new source interface. + + .. seealso:: + | :class:`.XBeeLocalInterface` + """ + self.__local_interface = local_interface + + src_interface = property(__get_src_interface, __set_src_interface) + """:class:`.XBeeLocalInterface`. Source local interface.""" + + data = property(__get_data, __set_data) + """Bytearray. Received data.""" diff --git a/digi/xbee/packets/wifi.py b/digi/xbee/packets/wifi.py new file mode 100644 index 0000000..a77d388 --- /dev/null +++ b/digi/xbee/packets/wifi.py @@ -0,0 +1,746 @@ +# 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 +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +from digi.xbee.packets.base import XBeeAPIPacket, DictKeys +from digi.xbee.util import utils +from digi.xbee.exception import InvalidOperatingModeException, InvalidPacketException +from digi.xbee.packets.aft import ApiFrameType +from digi.xbee.models.mode import OperatingMode +from digi.xbee.io import IOSample, IOLine +from ipaddress import IPv4Address +from digi.xbee.models.status import ATCommandStatus + + +class IODataSampleRxIndicatorWifiPacket(XBeeAPIPacket): + """ + This class represents a IO data sample RX indicator (Wi-Fi) packet. Packet is + built using the parameters of the constructor or providing a valid API + payload. + + When the module receives an IO sample frame from a remote device, it sends + the sample out the UART or SPI using this frame type. Only modules running + API mode will be able to receive IO samples. + + Among received data, some options can also be received indicating + transmission parameters. + + .. seealso:: + | :class:`.XBeeAPIPacket` + """ + + __MIN_PACKET_LENGTH = 16 + + def __init__(self, source_address, rssi, receive_options, rf_data=None): + """ + Class constructor. Instantiates a new :class:`.IODataSampleRxIndicatorWifiPacket` object with the + provided parameters. + + Args: + source_address (:class:`ipaddress.IPv4Address`): the 64-bit source address. + rssi (Integer): received signal strength indicator. + receive_options (Integer): bitfield indicating the receive options. + rf_data (Bytearray, optional): received RF data. Optional. + + Raises: + ValueError: if ``rf_data`` is not ``None`` and it's not valid for create an :class:`.IOSample`. + + .. seealso:: + | :class:`.IOSample` + | :class:`ipaddress.IPv4Address` + | :class:`.ReceiveOptions` + | :class:`.XBeeAPIPacket` + """ + super().__init__(ApiFrameType.IO_DATA_SAMPLE_RX_INDICATOR_WIFI) + self.__source_address = source_address + self.__rssi = rssi + self.__receive_options = receive_options + self.__rf_data = rf_data + self.__io_sample = IOSample(rf_data) if rf_data is not None and len(rf_data) >= 5 else None + + @staticmethod + def create_packet(raw, operating_mode): + """ + Override method. + + Returns: + :class:`.IODataSampleRxIndicatorWifiPacket`. + + Raises: + InvalidPacketException: if the bytearray length is less than 16. (start delim. + length (2 bytes) + frame + type + source addr. (4 bytes) + rssi + receive options + rf data (5 bytes) + checksum = 16 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.IO_DATA_SAMPLE_RX_INDICATOR_WIFI`. + InvalidOperatingModeException: if ``operating_mode`` is not supported. + + .. seealso:: + | :meth:`.XBeePacket.create_packet` + | :meth:`.XBeeAPIPacket._check_api_packet` + """ + if operating_mode != OperatingMode.ESCAPED_API_MODE and operating_mode != OperatingMode.API_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("This packet is not an IO data sample RX indicator Wi-Fi packet.") + + return IODataSampleRxIndicatorWifiPacket(IPv4Address(bytes(raw[4:8])), raw[7], raw[8], raw[9:-1]) + + 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(self.__source_address.packed) + ret += utils.int_to_bytes(self.__rssi, num_bytes=1) + ret += utils.int_to_bytes(self.__receive_options, num_bytes=1) + if self.__rf_data is not None: + ret += self.__rf_data + return ret + + def _get_api_packet_spec_data_dict(self): + """ + Override method. + + .. seealso:: + | :meth:`.XBeeAPIPacket._get_api_packet_spec_data_dict` + """ + base = {DictKeys.SRC_IPV4_ADDR: "%s (%s)" % (self.__source_address.packed, self.__source_address.exploded), + DictKeys.RSSI: self.__rssi, + DictKeys.RECEIVE_OPTIONS: self.__receive_options} + + if self.__io_sample is not None: + base[DictKeys.NUM_SAMPLES] = 1 + base[DictKeys.DIGITAL_MASK] = self.__io_sample.digital_mask + base[DictKeys.ANALOG_MASK] = self.__io_sample.analog_mask + + # Digital values + for i in range(16): + if self.__io_sample.has_digital_value(IOLine.get(i)): + base[IOLine.get(i).description + " digital value"] = \ + self.__io_sample.get_digital_value(IOLine.get(i)).name + + # Analog values + for i in range(6): + if self.__io_sample.has_analog_value(IOLine.get(i)): + base[IOLine.get(i).description + " analog value"] = \ + self.__io_sample.get_analog_value(IOLine.get(i)) + + # Power supply + if self.__io_sample.has_power_supply_value(): + base["Power supply value "] = "%02X" % self.__io_sample.power_supply_value + + elif self.__rf_data is not None: + base[DictKeys.RF_DATA] = utils.hex_to_string(self.__rf_data) + + return base + + def __get_source_address(self): + """ + Returns the IPv4 address of the source device. + + Returns: + :class:`ipaddress.IPv4Address`: the IPv4 address of the source device. + + .. seealso:: + | :class:`ipaddress.IPv4Address` + """ + 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. + + .. seealso:: + | :class:`ipaddress.IPv4Address` + """ + if source_address is not None: + self.__source_address = source_address + + def __get_rssi(self): + """ + Returns the received Signal Strength Indicator (RSSI). + + Returns: + Integer: the received Signal Strength Indicator (RSSI). + """ + return self.__rssi + + def __set_rssi(self, rssi): + """ + Sets the received Signal Strength Indicator (RSSI). + + Args: + rssi (Integer): the new received Signal Strength Indicator (RSSI). + """ + self.__rssi = rssi + + def __get_options(self): + """ + Returns the receive options bitfield. + + Returns: + Integer: the receive options bitfield. + + .. seealso:: + | :class:`.ReceiveOptions` + """ + return self.__receive_options + + def __set_options(self, receive_options): + """ + Sets the receive options bitfield. + + Args: + receive_options (Integer): the new receive options bitfield. + + .. seealso:: + | :class:`.ReceiveOptions` + """ + self.__receive_options = receive_options + + def __get_rf_data(self): + """ + Returns the received RF data. + + Returns: + Bytearray: the received RF data. + """ + if self.__rf_data is None: + return None + return self.__rf_data.copy() + + def __set_rf_data(self, rf_data): + """ + Sets the received RF data. + + Args: + rf_data (Bytearray): the new received RF data. + """ + if rf_data is None: + self.__rf_data = None + else: + self.__rf_data = rf_data.copy() + + # Modify the IO sample accordingly + if rf_data is not None and len(rf_data) >= 5: + self.__io_sample = IOSample(self.__rf_data) + else: + self.__io_sample = None + + def __get_io_sample(self): + """ + Returns the IO sample corresponding to the data contained in the packet. + + Returns: + :class:`.IOSample`: the IO sample of the packet, ``None`` if the packet has not any data or if the + sample could not be generated correctly. + + .. seealso:: + | :class:`.IOSample` + """ + return self.__io_sample + + def __set_io_sample(self, io_sample): + """ + Sets the IO sample of the packet. + + Args: + io_sample (:class:`.IOSample`): the new IO sample to set. + + .. seealso:: + | :class:`.IOSample` + """ + self.__io_sample = io_sample + + source_address = property(__get_source_address, __set_source_address) + """:class:`ipaddress.IPv4Address`. IPv4 source address.""" + + rssi = property(__get_rssi, __set_rssi) + """Integer. Received Signal Strength Indicator (RSSI) value.""" + + receive_options = property(__get_options, __set_options) + """Integer. Receive options bitfield.""" + + rf_data = property(__get_rf_data, __set_rf_data) + """Bytearray. Received RF data.""" + + io_sample = property(__get_io_sample, __set_io_sample) + """:class:`.IOSample`: IO sample corresponding to the data contained in the packet.""" + + +class RemoteATCommandWifiPacket(XBeeAPIPacket): + """ + This class represents a remote AT command request (Wi-Fi) packet. Packet is + built using the parameters of the constructor or providing a valid API + payload. + + Used to query or set module parameters on a remote device. For parameter + changes on the remote device to take effect, changes must be applied, either + by setting the apply changes options bit, or by sending an ``AC`` command + to the remote node. + + Remote command options are set as a bitfield. + + If configured, command response is received as a :class:`.RemoteATCommandResponseWifiPacket`. + + .. seealso:: + | :class:`.RemoteATCommandResponseWifiPacket` + | :class:`.XBeeAPIPacket` + """ + + __MIN_PACKET_LENGTH = 17 + + def __init__(self, frame_id, dest_address, transmit_options, command, parameter=None): + """ + Class constructor. Instantiates a new :class:`.RemoteATCommandWifiPacket` object with the provided parameters. + + Args: + frame_id (integer): the frame ID of the packet. + dest_address (:class:`ipaddress.IPv4Address`): the IPv4 address of the destination device. + transmit_options (Integer): bitfield of supported transmission options. + command (String): AT command to send. + parameter (Bytearray, optional): AT command parameter. Optional. + + Raises: + ValueError: if ``frame_id`` is less than 0 or greater than 255. + ValueError: if length of ``command`` is different than 2. + + .. seealso:: + | :class:`ipaddress.IPv4Address` + | :class:`.RemoteATCmdOptions` + | :class:`.XBeeAPIPacket` + """ + if frame_id < 0 or frame_id > 255: + raise ValueError("Frame id must be between 0 and 255.") + if len(command) != 2: + raise ValueError("Invalid command " + command) + + super().__init__(ApiFrameType.REMOTE_AT_COMMAND_REQUEST_WIFI) + self._frame_id = frame_id + self.__dest_address = dest_address + self.__transmit_options = transmit_options + self.__command = command + self.__parameter = parameter + + @staticmethod + def create_packet(raw, operating_mode): + """ + Override method. + + Returns: + :class:`.RemoteATCommandWifiPacket` + + Raises: + InvalidPacketException: if the Bytearray length is less than 17. (start delim. + length (2 bytes) + frame + type + frame id + dest. addr. (8 bytes) + transmit options + command (2 bytes) + 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.REMOTE_AT_COMMAND_REQUEST_WIFI`. + InvalidOperatingModeException: if ``operating_mode`` is not supported. + + .. seealso:: + | :meth:`.XBeePacket.create_packet` + | :meth:`.XBeeAPIPacket._check_api_packet` + """ + if operating_mode != OperatingMode.ESCAPED_API_MODE and operating_mode != OperatingMode.API_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("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"), + 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 = bytearray(self.__dest_address.packed) + ret += utils.int_to_bytes(self.__transmit_options, num_bytes=1) + ret += bytearray(self.__command, "utf8") + if self.__parameter is not None: + ret += self.__parameter + return ret + + def _get_api_packet_spec_data_dict(self): + """ + Override method. + + See: + :meth:`.XBeeAPIPacket._get_api_packet_spec_data_dict` + """ + return {DictKeys.DEST_IPV4_ADDR: "%s (%s)" % (self.__dest_address.packed, self.__dest_address.exploded), + DictKeys.TRANSMIT_OPTIONS: self.__transmit_options, + DictKeys.COMMAND: self.__command, + DictKeys.PARAMETER: list(self.__parameter) if self.__parameter is not None else None} + + def __get_dest_address(self): + """ + Returns the IPv4 address of the destination device. + + Returns: + :class:`ipaddress.IPv4Address`: the IPv4 address of the destination device. + + .. seealso:: + | :class:`ipaddress.IPv4Address` + """ + 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. + + .. seealso:: + | :class:`ipaddress.IPv4Address` + """ + if dest_address is not None: + self.__dest_address = dest_address + + def __get_transmit_options(self): + """ + Returns the transmit options bitfield. + + Returns: + Integer: the transmit options bitfield. + + .. seealso:: + | :class:`.RemoteATCmdOptions` + """ + return self.__transmit_options + + def __set_transmit_options(self, transmit_options): + """ + Sets the transmit options bitfield. + + Args: + transmit_options (Integer): the new transmit options bitfield. + + .. seealso:: + | :class:`.RemoteATCmdOptions` + """ + self.__transmit_options = transmit_options + + def __get_command(self): + """ + Returns the AT command. + + Returns: + String: the AT command. + """ + return self.__command + + def __set_command(self, command): + """ + Sets the AT command. + + Args: + command (String): the new AT command. + """ + self.__command = command + + def __get_parameter(self): + """ + Returns the AT command parameter. + + Returns: + Bytearray: the AT command parameter. + """ + return self.__parameter + + def __set_parameter(self, parameter): + """ + Sets the AT command parameter. + + Args: + parameter (Bytearray): the new AT command parameter. + """ + self.__parameter = parameter + + dest_address = property(__get_dest_address, __set_dest_address) + """:class:`ipaddress.IPv4Address`. IPv4 destination address.""" + + transmit_options = property(__get_transmit_options, __set_transmit_options) + """Integer. Transmit options bitfield.""" + + command = property(__get_command, __set_command) + """String. AT command.""" + + parameter = property(__get_parameter, __set_parameter) + """Bytearray. AT command parameter.""" + + +class RemoteATCommandResponseWifiPacket(XBeeAPIPacket): + """ + This class represents a remote AT command response (Wi-Fi) packet. Packet is + built using the parameters of the constructor or providing a valid API + payload. + + If a module receives a remote command response RF data frame in response + to a Remote AT Command Request, the module will send a Remote AT Command + Response message out the UART. Some commands may send back multiple frames + for example, Node Discover (``ND``) command. + + This packet is received in response of a :class:`.RemoteATCommandPacket`. + + Response also includes an :class:`.ATCommandStatus` object with the status + of the AT command. + + .. seealso:: + | :class:`.RemoteATCommandWifiPacket` + | :class:`.ATCommandStatus` + | :class:`.XBeeAPIPacket` + """ + + __MIN_PACKET_LENGTH = 17 + + def __init__(self, frame_id, source_address, command, response_status, comm_value=None): + """ + Class constructor. Instantiates a new :class:`.RemoteATCommandResponseWifiPacket` object with the + provided parameters. + + Args: + frame_id (Integer): the frame ID of the packet. + source_address (:class:`ipaddress.IPv4Address`): the IPv4 address of the source device. + command (String): the AT command of the packet. Must be a string. + response_status (:class:`.ATCommandStatus`): the status of the AT command. + comm_value (Bytearray, optional): the AT command response value. + + Raises: + ValueError: if ``frame_id`` is less than 0 or greater than 255. + ValueError: if length of ``command`` is different than 2. + + .. seealso:: + | :class:`.ATCommandStatus` + | :class:`ipaddress.IPv4Address` + """ + if frame_id > 255 or frame_id < 0: + raise ValueError("frame_id must be between 0 and 255.") + if len(command) != 2: + raise ValueError("Invalid command " + command) + + super().__init__(ApiFrameType.REMOTE_AT_COMMAND_RESPONSE_WIFI) + self._frame_id = frame_id + self.__source_address = source_address + self.__command = command + self.__response_status = response_status + self.__comm_value = comm_value + + @staticmethod + def create_packet(raw, operating_mode): + """ + Override method. + + Returns: + :class:`.RemoteATCommandResponseWifiPacket`. + + Raises: + InvalidPacketException: if the bytearray length is less than 17. (start delim. + length (2 bytes) + frame + type + frame id + source addr. (8 bytes) + command (2 bytes) + receive 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.REMOTE_AT_COMMAND_RESPONSE_WIFI`. + InvalidOperatingModeException: if ``operating_mode`` is not supported. + + .. seealso:: + | :meth:`.XBeePacket.create_packet` + | :meth:`.XBeeAPIPacket._check_api_packet` + """ + if operating_mode != OperatingMode.ESCAPED_API_MODE and operating_mode != OperatingMode.API_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("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]), + 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 = bytearray(self.__source_address.packed) + ret += bytearray(self.__command, "utf8") + ret += utils.int_to_bytes(self.__response_status.code, num_bytes=1) + if self.__comm_value is not None: + ret += self.__comm_value + return ret + + def _get_api_packet_spec_data_dict(self): + return {DictKeys.SRC_IPV4_ADDR: "%s (%s)" % (self.__source_address.packed, self.__source_address.exploded), + DictKeys.COMMAND: self.__command, + DictKeys.AT_CMD_STATUS: self.__response_status, + DictKeys.RF_DATA: list(self.__comm_value) if self.__comm_value is not None else None} + + def __get_source_address(self): + """ + Returns the IPv4 address of the source device. + + Returns: + :class:`ipaddress.IPv4Address`: the IPv4 address of the source device. + + .. seealso:: + | :class:`ipaddress.IPv4Address` + """ + 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. + + .. seealso:: + | :class:`ipaddress.IPv4Address` + """ + if source_address is not None: + self.__source_address = source_address + + def __get_command(self): + """ + Returns the AT command of the packet. + + Returns: + String: the AT command of the packet. + """ + return self.__command + + def __set_command(self, command): + """ + Sets the AT command of the packet. + + Args: + command (String): the new AT command of the packet. Must have length = 2. + + Raises: + ValueError: if length of ``command`` is different than 2. + """ + if len(command) != 2: + raise ValueError("Invalid command " + command) + self.__command = command + + 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 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. + + .. seealso:: + | :class:`.ATCommandStatus` + """ + self.__response_status = response_status + + def __get_value(self): + """ + Returns the AT command response value. + + Returns: + Bytearray: the AT command response value. + """ + return self.__comm_value + + def __set_value(self, comm_value): + """ + Sets the AT command response value. + + Args: + comm_value (Bytearray): the new AT command response value. + """ + self.__comm_value = comm_value + + source_address = property(__get_source_address, __set_source_address) + """:class:`ipaddress.IPv4Address`. IPv4 source address.""" + + command = property(__get_command, __set_command) + """String. AT command.""" + + status = property(__get_response_status, __set_response_status) + """:class:`.ATCommandStatus`. AT command response status.""" + + command_value = property(__get_value, __set_value) + """Bytearray. AT command value.""" diff --git a/digi/xbee/reader.py b/digi/xbee/reader.py new file mode 100644 index 0000000..b1f5dbd --- /dev/null +++ b/digi/xbee/reader.py @@ -0,0 +1,1245 @@ +# 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. + +from concurrent.futures import ThreadPoolExecutor +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.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.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 +from digi.xbee.io import IOSample + + +# Maximum number of parallel callbacks. +MAX_PARALLEL_CALLBACKS = 50 + +executor = ThreadPoolExecutor(max_workers=MAX_PARALLEL_CALLBACKS) + + +class XBeeEvent(list): + """ + This class represents a generic XBee event. + + New event callbacks can be added here following this prototype: + + :: + + def callback_prototype(*args, **kwargs): + #do something... + + All of them will be executed when the event is fired. + + .. seealso:: + | list (Python standard class) + """ + def __call__(self, *args, **kwargs): + for f in self: + future = executor.submit(f, *args, **kwargs) + future.add_done_callback(self.__execution_finished) + + def __repr__(self): + return "Event(%s)" % list.__repr__(self) + + def __iadd__(self, other): + self.append(other) + return self + + def __isub__(self, other): + self.remove(other) + return self + + def __execution_finished(self, future): + """ + Called when the execution of the callable has finished. + + Args: + future (:class:`.Future`): Future associated to the execution of the callable. + + Raises: + Exception: if the execution of the callable raised any exception. + """ + if future.exception(): + raise future.exception() + + +class PacketReceived(XBeeEvent): + """ + This event is fired when an XBee receives any packet, independent of + its frame type. + + The callbacks for handle this events will receive the following arguments: + 1. received_packet (:class:`.XBeeAPIPacket`): the received packet. + + .. seealso:: + | :class:`.XBeeAPIPacket` + | :class:`.XBeeEvent` + """ + pass + + +class DataReceived(XBeeEvent): + """ + This event is fired when an XBee receives data. + + The callbacks for handle this events will receive the following arguments: + 1. message (:class:`.XBeeMessage`): message containing the data received, the sender and the time. + + .. seealso:: + | :class:`.XBeeEvent` + | :class:`.XBeeMessage` + """ + pass + + +class ModemStatusReceived(XBeeEvent): + """ + This event is fired when a XBee receives a modem status packet. + + The callbacks for handle this events will receive the following arguments: + 1. modem_status (:class:`.ModemStatus`): the modem status received. + + .. seealso:: + | :class:`.XBeeEvent` + | :class:`.ModemStatus` + """ + pass + + +class IOSampleReceived(XBeeEvent): + """ + This event is fired when a XBee receives an IO packet. + + This includes: + + 1. IO data sample RX indicator packet. + 2. RX IO 16 packet. + 3. RX IO 64 packet. + + The callbacks that handle this event will receive the following arguments: + 1. io_sample (:class:`.IOSample`): the received IO sample. + 2. sender (:class:`.RemoteXBeeDevice`): the remote XBee device who has sent the packet. + 3. time (Integer): the time in which the packet was received. + + .. seealso:: + | :class:`.IOSample` + | :class:`.RemoteXBeeDevice` + | :class:`.XBeeEvent` + """ + pass + + +class DeviceDiscovered(XBeeEvent): + """ + This event is fired when an XBee discovers another remote XBee + during a discovering operation. + + The callbacks that handle this event will receive the following arguments: + 1. discovered_device (:class:`.RemoteXBeeDevice`): the discovered remote XBee device. + + .. seealso:: + | :class:`.RemoteXBeeDevice` + | :class:`.XBeeEvent` + """ + pass + + +class DiscoveryProcessFinished(XBeeEvent): + """ + This event is fired when the discovery process finishes, either + successfully or due to an error. + + The callbacks that handle this event will receive the following arguments: + 1. status (:class:`.NetworkDiscoveryStatus`): the network discovery status. + + .. seealso:: + | :class:`.NetworkDiscoveryStatus` + | :class:`.XBeeEvent` + """ + pass + + +class ExplicitDataReceived(XBeeEvent): + """ + This event is fired when an XBee receives an explicit data packet. + + The callbacks for handle this events will receive the following arguments: + 1. message (:class:`.ExplicitXBeeMessage`): message containing the data received, the sender, the time + and explicit data message parameters. + + .. seealso:: + | :class:`.XBeeEvent` + | :class:`.XBeeMessage` + """ + pass + + +class IPDataReceived(XBeeEvent): + """ + This event is fired when an XBee receives IP data. + + The callbacks for handle this events will receive the following arguments: + 1. message (:class:`.IPMessage`): message containing containing the IP address the message + belongs to, the source and destination ports, the IP protocol, and the content (data) of the message. + + .. seealso:: + | :class:`.XBeeEvent` + | :class:`.IPMessage` + """ + pass + + +class SMSReceived(XBeeEvent): + """ + This event is fired when an XBee receives an SMS. + + The callbacks for handle this events will receive the following arguments: + 1. message (:class:`.SMSMessage`): message containing the phone number that sent + the message and the content (data) of the message. + + .. seealso:: + | :class:`.XBeeEvent` + | :class:`.SMSMessage` + """ + pass + + +class RelayDataReceived(XBeeEvent): + """ + This event is fired when an XBee receives a user data relay output packet. + + The callbacks to handle these events will receive the following arguments: + 1. message (:class:`.UserDataRelayMessage`): message containing the source interface + and the content (data) of the message. + + .. seealso:: + | :class:`.XBeeEvent` + | :class:`.UserDataRelayMessage` + """ + pass + + +class BluetoothDataReceived(XBeeEvent): + """ + This event is fired when an XBee receives data from the Bluetooth interface. + + The callbacks to handle these events will receive the following arguments: + 1. data (Bytearray): received Bluetooth data. + + .. seealso:: + | :class:`.XBeeEvent` + """ + pass + + +class MicroPythonDataReceived(XBeeEvent): + """ + This event is fired when an XBee receives data from the MicroPython interface. + + The callbacks to handle these events will receive the following arguments: + 1. data (Bytearray): received MicroPython data. + + .. seealso:: + | :class:`.XBeeEvent` + """ + pass + + +class PacketListener(threading.Thread): + """ + This class represents a packet listener, which is a thread that's always + listening for incoming packets to the XBee. + + When it receives a packet, this class throws an event depending on which + packet it is. You can add your own callbacks for this events via certain + class methods. This callbacks must have a certain header, see each event + documentation. + + This class has fields that are events. Its recommended to use only the + append() and remove() method on them, or -= and += operators. + If you do something more with them, it's for your own risk. + + Here are the parameters which will be received by the event callbacks, + depending on which event it is in each case: + + The following parameters are passed via \*\*kwargs to event callbacks of: + + 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 + 3.1 modem_status (:class:`.ModemStatus`): the modem status received. + """ + + __DEFAULT_QUEUE_MAX_SIZE = 40 + """ + Default max. size that the queue has. + """ + + _LOG_PATTERN = "{port:<6s}{event:<12s}{fr_type:<10s}{sender:<18s}{more_data:<50s}" + """ + Generic pattern for display received messages (high-level) with logger. + """ + + _log = logging.getLogger(__name__) + """ + Logger. + """ + + def __init__(self, serial_port, xbee_device, queue_max_size=None): + """ + Class constructor. Instantiates a new :class:`.PacketListener` object with the provided parameters. + + Args: + 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) + + # User callbacks: + self.__packet_received = PacketReceived() + self.__data_received = DataReceived() + self.__modem_status_received = ModemStatusReceived() + self.__io_sample_received = IOSampleReceived() + self.__explicit_packet_received = ExplicitDataReceived() + self.__ip_data_received = IPDataReceived() + self.__sms_received = SMSReceived() + self.__relay_data_received = RelayDataReceived() + self.__bluetooth_data_received = BluetoothDataReceived() + self.__micropython_data_received = MicroPythonDataReceived() + + # API internal callbacks: + self.__packet_received_API = xbee_device.get_xbee_device_callbacks() + + self.__xbee_device = xbee_device + self.__serial_port = serial_port + self.__stop = True + + 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) + self.__data_xbee_queue = XBeeQueue(self.__queue_max_size) + self.__explicit_xbee_queue = XBeeQueue(self.__queue_max_size) + self.__ip_xbee_queue = XBeeQueue(self.__queue_max_size) + + self._log_handler = logging.StreamHandler() + self._log.addHandler(self._log_handler) + + def __del__(self): + self._log.removeHandler(self._log_handler) + + def run(self): + """ + This is the method that will be executing for listening packets. + + For each packet, it will execute the proper callbacks. + """ + try: + self.__stop = False + while not self.__stop: + # Try to read a packet. Read packet is unescaped. + 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. + if (self.__xbee_device.get_protocol() == XBeeProtocol.RAW_802_15_4 and + not self.__check_packet_802_15_4(raw_packet)): + continue + + # Build the packet. + try: + read_packet = factory.build_frame(raw_packet, self.__xbee_device.operating_mode) + except InvalidPacketException as e: + 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(port=self.__xbee_device.serial_port.port, + event="RECEIVED", + opmode=self.__xbee_device.operating_mode, + content=utils.hex_to_string(raw_packet))) + + # Add the packet to the queue. + self.__add_packet_queue(read_packet) + + # If the packet has information about a remote device, extract it + # and add/update this remote device to/in this XBee's network. + remote = self.__try_add_remote_device(read_packet) + + # Execute API internal callbacks. + self.__packet_received_API(read_packet) + + # Execute all user callbacks. + self.__execute_user_callbacks(read_packet, remote) + except Exception as e: + if not self.__stop: + self._log.exception(e) + finally: + if not self.__stop: + self.__stop = True + if self.__serial_port.isOpen(): + self.__serial_port.close() + + def stop(self): + """ + Stops listening. + """ + self.__stop = True + + def is_running(self): + """ + Returns whether this instance is running or not. + + Returns: + Boolean: ``True`` if this instance is running, ``False`` otherwise. + """ + return not self.__stop + + def get_queue(self): + """ + Returns the packets queue. + + Returns: + :class:`.XBeeQueue`: the packets queue. + """ + return self.__xbee_queue + + def get_data_queue(self): + """ + Returns the data packets queue. + + Returns: + :class:`.XBeeQueue`: the data packets queue. + """ + return self.__data_xbee_queue + + def get_explicit_queue(self): + """ + Returns the explicit packets queue. + + Returns: + :class:`.XBeeQueue`: the explicit packets queue. + """ + return self.__explicit_xbee_queue + + def get_ip_queue(self): + """ + Returns the IP packets queue. + + Returns: + :class:`.XBeeQueue`: the IP packets queue. + """ + return self.__ip_xbee_queue + + def add_packet_received_callback(self, callback): + """ + Adds a callback for the event :class:`.PacketReceived`. + + Args: + callback (Function): the callback. Receives two arguments. + + * The received packet as a :class:`.XBeeAPIPacket` + * The sender as a :class:`.RemoteXBeeDevice` + """ + self.__packet_received += 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.__data_received += 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.__modem_status_received += 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.__io_sample_received += callback + + def add_explicit_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.__explicit_packet_received += callback + + def add_ip_data_received_callback(self, callback): + """ + Adds a callback for the event :class:`.IPDataReceived`. + + Args: + callback (Function): the callback. Receives one argument. + + * The data received as an :class:`.IPMessage` + """ + self.__ip_data_received += callback + + def add_sms_received_callback(self, callback): + """ + Adds a callback for the event :class:`.SMSReceived`. + + Args: + callback (Function): the callback. Receives one argument. + + * The data received as an :class:`.SMSMessage` + """ + self.__sms_received += 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 data received as a :class:`.UserDataRelayMessage` + """ + self.__relay_data_received += 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 data received as a Bytearray + """ + self.__bluetooth_data_received += 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 data received as a Bytearray + """ + self.__micropython_data_received += callback + + def del_packet_received_callback(self, callback): + """ + 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:`.PacketReceived` event. + """ + self.__packet_received -= callback + + 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.__data_received -= 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.__modem_status_received -= 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.__io_sample_received -= callback + + def del_explicit_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.__explicit_packet_received -= callback + + def del_ip_data_received_callback(self, callback): + """ + 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:`.IPDataReceived` event. + """ + self.__ip_data_received -= callback + + def del_sms_received_callback(self, callback): + """ + 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:`.SMSReceived` event. + """ + self.__sms_received -= 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.__relay_data_received -= 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.__bluetooth_data_received -= 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.__micropython_data_received -= callback + + def __execute_user_callbacks(self, xbee_packet, remote=None): + """ + Executes callbacks corresponding to the received packet. + + Args: + xbee_packet (:class:`.XBeeAPIPacket`): the received packet. + remote (:class:`.RemoteXBeeDevice`): the XBee device that sent the packet. + """ + # All packets callback. + self.__packet_received(xbee_packet) + + # Data reception callbacks + 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.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(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(port=self.__xbee_device.serial_port.port, + event="RECEIVED", + fr_type="MODEM STATUS", + sender=str(remote.get_64bit_addr()) if remote is not None + else "None", + more_data=xbee_packet.modem_status)) + + # IO_sample callbacks + elif (xbee_packet.get_frame_type() == ApiFrameType.RX_IO_16 or + 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(port=self.__xbee_device.serial_port.port, + event="RECEIVED", + fr_type="IOSAMPLE", + sender=str(remote.get_64bit_addr()) if remote is not None + else "None", + more_data=str(xbee_packet.io_sample))) + + # Explicit packet callbacks + elif xbee_packet.get_frame_type() == ApiFrameType.EXPLICIT_RX_INDICATOR: + is_broadcast = False + # If it's 'special' packet, notify the data_received callbacks too: + 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(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(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(port=self.__xbee_device.serial_port.port, + event="RECEIVED", + fr_type="IP DATA", + sender=str(xbee_packet.source_address), + more_data=utils.hex_to_string(xbee_packet.data))) + + # 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(port=self.__xbee_device.serial_port.port, + event="RECEIVED", + fr_type="SMS", + sender=str(xbee_packet.phone_number), + more_data=xbee_packet.data)) + + # Relay + elif xbee_packet.get_frame_type() == ApiFrameType.USER_DATA_RELAY_OUTPUT: + # Notify generic callbacks. + self.__relay_data_received(UserDataRelayMessage(xbee_packet.src_interface, xbee_packet.data)) + # Notify specific callbacks. + if xbee_packet.src_interface == XBeeLocalInterface.BLUETOOTH: + 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(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))) + + 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): + """ + Creates a :class:`.RemoteXBeeDevice` that represents the device that + has sent the ``xbee_packet``. + + Returns: + :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, x16bit_addr) + + @staticmethod + def __get_remote_device_data_from_packet(xbee_packet): + """ + Extracts the 64 bit-address and the 16 bit-address from ``xbee_packet`` if is + possible. + """ + x64bit_addr = None + x16bit_addr = None + if hasattr(xbee_packet, "x64bit_source_addr"): + x64bit_addr = xbee_packet.x64bit_source_addr + if hasattr(xbee_packet, "x16bit_source_addr"): + x16bit_addr = xbee_packet.x16bit_source_addr + return x64bit_addr, x16bit_addr + + @staticmethod + def __check_packet_802_15_4(raw_data): + """ + If the current XBee's protocol is 802.15.4 and + the user sends many 'ND' commands, the device could return + an RX 64 IO packet with an invalid payload (length < 5). + + In this case the packet must be discarded, or an exception + must be raised. + + This method checks a received raw_data and returns False if + the packet mustn't be processed. + + Args: + raw_data (Bytearray): received data. + + Returns: + Boolean: ``True`` if the packet must be processed, ``False`` otherwise. + """ + if raw_data[3] == ApiFrameType.RX_IO_64 and len(raw_data[14:-1]) < IOSample.min_io_sample_payload(): + return False + return True + + def __try_add_remote_device(self, xbee_packet): + """ + If the packet has information about a remote device, this method + extracts that information from the packet, creates a remote device, and + adds it (if not exist yet) to the network. + + Returns: + :class:`.RemoteXBeeDevice`: the remote device extracted from the packet, `None`` if the packet has + not information about a remote device. + """ + 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().add_if_not_exist(x64, x16) + return remote + + @staticmethod + def __is_special_explicit_packet(xbee_packet): + """ + Checks if an explicit data packet is 'special'. + + '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 + 2. Destination endpoint = 0xE8 + 3. Cluster ID = 0x0011 + 4. 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): + """ + Creates a non-explicit data 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). + """ + 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_packet = RX64Packet(x64addr, 0, xbee_packet.receive_options, xbee_packet.rf_data) + elif x16addr != XBee16BitAddress.UNKNOWN_ADDRESS: + new_packet = RX16Packet(x16addr, 0, xbee_packet.receive_options, xbee_packet.rf_data) + else: # both address UNKNOWN + new_packet = RX64Packet(x64addr, 0, xbee_packet.receive_options, xbee_packet.rf_data) + else: + 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): + """ + Adds a packet to the queue. If the queue is full, + the first packet of the queue is removed and the given + packet is added. + + Args: + xbee_packet (:class:`.XBeeAPIPacket`): the packet to be added. + """ + # Data packets. + if xbee_packet.get_frame_type() in [ApiFrameType.RECEIVE_PACKET, ApiFrameType.RX_64, ApiFrameType.RX_16]: + if self.__data_xbee_queue.full(): + self.__data_xbee_queue.get() + self.__data_xbee_queue.put_nowait(xbee_packet) + # Explicit packets. + if xbee_packet.get_frame_type() == ApiFrameType.EXPLICIT_RX_INDICATOR: + if self.__explicit_xbee_queue.full(): + self.__explicit_xbee_queue.get() + self.__explicit_xbee_queue.put_nowait(xbee_packet) + # Check if the explicit packet is 'special'. + 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)) + # IP packets. + elif xbee_packet.get_frame_type() == ApiFrameType.RX_IPV4: + if self.__ip_xbee_queue.full(): + self.__ip_xbee_queue.get() + self.__ip_xbee_queue.put_nowait(xbee_packet) + # Rest of packets. + else: + if self.__xbee_queue.full(): + self.__xbee_queue.get() + self.__xbee_queue.put_nowait(xbee_packet) + + @staticmethod + def __expl_to_message(remote, broadcast, xbee_packet): + """ + Converts an explicit packet in an explicit message. + + Args: + remote (:class:`.RemoteXBeeDevice`): the remote XBee device that sent the packet. + broadcast (Boolean, optional, default=``False``): flag indicating whether the message is + broadcast (``True``) or not (``False``). Optional. + xbee_packet (:class:`.XBeeAPIPacket`): the packet to be converted. + + Returns: + :class:`.ExplicitXBeeMessage`: the explicit message generated from the provided parameters. + """ + 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) + + +class XBeeQueue(Queue): + """ + This class represents an XBee queue. + """ + + def __init__(self, maxsize=10): + """ + Class constructor. Instantiates a new :class:`.XBeeQueue` with the provided parameters. + + Args: + maxsize (Integer, default: 10) the maximum size of the queue. + """ + Queue.__init__(self, maxsize) + + def get(self, block=True, timeout=None): + """ + Returns the first element of the queue if there is some + element ready before timeout expires, in case of the timeout is not + ``None``. + + If timeout is ``None``, this method is non-blocking. In this case, if there + isn't any element available, it returns ``None``, otherwise it returns + an :class:`.XBeeAPIPacket`. + + Args: + block (Boolean): ``True`` to block during ``timeout`` waiting for a packet, ``False`` to not block. + timeout (Integer, optional): timeout in seconds. + + Returns: + :class:`.XBeeAPIPacket`: a packet if there is any packet available before ``timeout`` expires. + If ``timeout`` is ``None``, the returned value may be ``None``. + + Raises: + TimeoutException: if ``timeout`` is not ``None`` and there isn't any packet available + before the timeout expires. + """ + if timeout is None: + try: + xbee_packet = Queue.get(self, block=False) + except (Empty, ValueError): + xbee_packet = None + return xbee_packet + else: + try: + return Queue.get(self, True, timeout) + except Empty: + raise TimeoutException() + + def get_by_remote(self, remote_xbee_device, timeout=None): + """ + Returns the first element of the queue that had been sent + by ``remote_xbee_device``, if there is some in the specified timeout. + + If timeout is ``None``, this method is non-blocking. In this case, if there isn't + any packet sent by ``remote_xbee_device`` in the queue, it returns ``None``, + otherwise it returns an :class:`.XBeeAPIPacket`. + + Args: + remote_xbee_device (:class:`.RemoteXBeeDevice`): the remote XBee device to get its firs element from queue. + timeout (Integer, optional): timeout in seconds. + + Returns: + :class:`.XBeeAPIPacket`: if there is any packet available before the timeout expires. If timeout is + ``None``, the returned value may be ``None``. + + Raises: + TimeoutException: if timeout is not ``None`` and there isn't any packet available that has + been sent by ``remote_xbee_device`` before the timeout expires. + """ + if timeout is None: + with self.mutex: + for xbee_packet in self.queue: + if self.__remote_device_match(xbee_packet, remote_xbee_device): + self.queue.remove(xbee_packet) + return xbee_packet + return None + else: + 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, None) + if xbee_packet is None: + raise TimeoutException() + return xbee_packet + + def get_by_ip(self, ip_addr, timeout=None): + """ + Returns the first IP data packet from the queue whose IP address + matches the provided address. + + If timeout is ``None``, this method is non-blocking. In this case, if there isn't + any packet sent by ``remote_xbee_device`` in the queue, it returns ``None``, + otherwise it returns an :class:`.XBeeAPIPacket`. + + Args: + ip_addr (:class:`ipaddress.IPv4Address`): The IP address to look for in the list of packets. + timeout (Integer, optional): Timeout in seconds. + + Returns: + :class:`.XBeeAPIPacket`: if there is any packet available before the timeout expires. If timeout is + ``None``, the returned value may be ``None``. + + Raises: + TimeoutException: if timeout is not ``None`` and there isn't any packet available that has + been sent by ``remote_xbee_device`` before the timeout expires. + """ + if timeout is None: + with self.mutex: + for xbee_packet in self.queue: + if self.__ip_addr_match(xbee_packet, ip_addr): + self.queue.remove(xbee_packet) + return xbee_packet + return None + else: + 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, None) + if xbee_packet is None: + raise TimeoutException() + return xbee_packet + + def get_by_id(self, frame_id, timeout=None): + """ + Returns the first packet from the queue whose frame ID + matches the provided one. + + If timeout is ``None``, this method is non-blocking. In this case, if there isn't + any received packet with the provided frame ID in the queue, it returns ``None``, + otherwise it returns an :class:`.XBeeAPIPacket`. + + Args: + frame_id (Integer): The frame ID to look for in the list of packets. + timeout (Integer, optional): Timeout in seconds. + + Returns: + :class:`.XBeeAPIPacket`: if there is any packet available before the timeout expires. If timeout is + ``None``, the returned value may be ``None``. + + Raises: + TimeoutException: if timeout is not ``None`` and there isn't any packet available that matches + the provided frame ID before the timeout expires. + """ + if timeout is None: + with self.mutex: + for xbee_packet in self.queue: + if xbee_packet.needs_id() and xbee_packet.frame_id == frame_id: + self.queue.remove(xbee_packet) + return xbee_packet + return None + else: + 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, None) + if xbee_packet is None: + raise TimeoutException() + return xbee_packet + + def flush(self): + """ + Clears the queue. + """ + with self.mutex: + self.queue.clear() + + @staticmethod + def __remote_device_match(xbee_packet, remote_xbee_device): + """ + Returns whether or not the source address of the provided XBee packet + matches the address of the given remote XBee device. + + Args: + xbee_packet (:class:`.XBeePacket`): The XBee packet to get the address to compare. + remote_xbee_device (:class:`.RemoteXBeeDevice`): The remote XBee device to get the address to compare. + + Returns: + Boolean: ``True`` if the remote device matches, ``False`` otherwise. + """ + if xbee_packet.get_frame_type() == ApiFrameType.RECEIVE_PACKET: + if xbee_packet.x64bit_source_addr == remote_xbee_device.get_64bit_addr(): + return True + return xbee_packet.x16bit_source_addr == remote_xbee_device.get_16bit_addr() + + elif xbee_packet.get_frame_type() == ApiFrameType.REMOTE_AT_COMMAND_RESPONSE: + if xbee_packet.x64bit_source_addr == remote_xbee_device.get_64bit_addr(): + return True + return xbee_packet.x16bit_source_addr == remote_xbee_device.get_16bit_addr() + + elif xbee_packet.get_frame_type() == ApiFrameType.RX_16: + return xbee_packet.x16bit_source_addr == remote_xbee_device.get_16bit_addr() + + elif xbee_packet.get_frame_type() == ApiFrameType.RX_64: + return xbee_packet.x64bit_source_addr == remote_xbee_device.get_64bit_addr() + + elif xbee_packet.get_frame_type() == ApiFrameType.RX_IO_16: + return xbee_packet.x16bit_source_addr == remote_xbee_device.get_16bit_addr() + + elif xbee_packet.get_frame_type() == ApiFrameType.RX_IO_64: + return xbee_packet.x64bit_source_addr == remote_xbee_device.get_64bit_addr() + + elif xbee_packet.get_frame_type() == ApiFrameType.EXPLICIT_RX_INDICATOR: + return xbee_packet.x64bit_source_addr == remote_xbee_device.get_64bit_addr() + + else: + return False + + @staticmethod + def __ip_addr_match(xbee_packet, ip_addr): + """ + Returns whether or not the IP address of the XBee packet matches the + provided one. + + Args: + xbee_packet (:class:`.XBeePacket`): The XBee packet to get the address to compare. + ip_addr (:class:`ipaddress.IPv4Address`): The IP address to be compared with the XBee packet's one. + + Returns: + Boolean: ``True`` if the IP address matches, ``False`` otherwise. + """ + return xbee_packet.get_frame_type() == ApiFrameType.RX_IPV4 and xbee_packet.source_address == ip_addr diff --git a/digi/xbee/util/__init__.py b/digi/xbee/util/__init__.py new file mode 100644 index 0000000..8ea10d3 --- /dev/null +++ b/digi/xbee/util/__init__.py @@ -0,0 +1,13 @@ +# 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. diff --git a/digi/xbee/util/utils.py b/digi/xbee/util/utils.py new file mode 100644 index 0000000..3983855 --- /dev/null +++ b/digi/xbee/util/utils.py @@ -0,0 +1,307 @@ +# 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. + +import logging + + +# Number of bits to extract with the mask (__MASK) +__MASK_NUM_BITS = 8 + +# Bit mask to extract the less important __MAS_NUM_BITS bits of a number. +__MASK = 0xFF + + +def is_bit_enabled(number, position): + """ + Returns whether the bit located at ``position`` within ``number`` is enabled or not. + + Args: + number (Integer): the number to check if a bit is enabled. + position (Integer): the position of the bit to check if is enabled in ``number``. + + Returns: + Boolean: ``True`` if the bit located at ``position`` within ``number`` is enabled, ``False`` otherwise. + """ + return ((number & 0xFFFFFFFF) >> position) & 0x01 == 0x01 + + +def hex_string_to_bytes(hex_string): + """ + Converts a String (composed by hex. digits) into a bytearray with same digits. + + Args: + hex_string (String): String (made by hex. digits) with "0x" header or not. + + Returns: + Bytearray: bytearray containing the numeric value of the hexadecimal digits. + + Raises: + ValueError: if invalid literal for int() with base 16 is provided. + + Example: + >>> a = "0xFFFE" + >>> for i in hex_string_to_bytes(a): print(i) + 255 + 254 + >>> print(type(hex_string_to_bytes(a))) + + + >>> b = "FFFE" + >>> for i in hex_string_to_bytes(b): print(i) + 255 + 254 + >>> print(type(hex_string_to_bytes(b))) + + + """ + aux = int(hex_string, 16) + return int_to_bytes(aux) + + +def int_to_bytes(number, num_bytes=None): + """ + Converts the provided integer into a bytearray. + + If ``number`` has less bytes than ``num_bytes``, the resultant bytearray + is filled with zeros (0x00) starting at the beginning. + + If ``number`` has more bytes than ``num_bytes``, the resultant bytearray + is returned without changes. + + Args: + number (Integer): the number to convert to a bytearray. + num_bytes (Integer): the number of bytes that the resultant bytearray will have. + + Returns: + Bytearray: the bytearray corresponding to the provided number. + + Example: + >>> a=0xFFFE + >>> print([i for i in int_to_bytes(a)]) + [255,254] + >>> print(type(int_to_bytes(a))) + + + """ + byte_array = bytearray() + byte_array.insert(0, number & __MASK) + number >>= __MASK_NUM_BITS + while number != 0: + byte_array.insert(0, number & __MASK) + number >>= __MASK_NUM_BITS + + if num_bytes is not None: + while len(byte_array) < num_bytes: + byte_array.insert(0, 0x00) + + return byte_array + + +def length_to_int(byte_array): + """ + Calculates the length value for the given length field of a packet. + Length field are bytes 1 and 2 of any packet. + + Args: + byte_array (Bytearray): length field of a packet. + + Returns: + Integer: the length value. + + Raises: + ValueError: if ``byte_array`` is not a valid length field (it has length distinct than 0). + + Example: + >>> b = bytearray([13,14]) + >>> c = length_to_int(b) + >>> print("0x%02X" % c) + 0x1314 + >>> print(c) + 4884 + """ + if len(byte_array) != 2: + raise ValueError("bArray must have length 2") + return (byte_array[0] << 8) + byte_array[1] + + +def bytes_to_int(byte_array): + """ + Converts the provided bytearray in an Integer. + This integer is result of concatenate all components of ``byte_array`` + and convert that hex number to a decimal number. + + Args: + byte_array (Bytearray): bytearray to convert in integer. + + Returns: + Integer: the integer corresponding to the provided bytearray. + + Example: + >>> x = bytearray([0xA,0x0A,0x0A]) #this is 0xA0A0A + >>> print(bytes_to_int(x)) + 657930 + >>> b = bytearray([0x0A,0xAA]) #this is 0xAAA + >>> print(bytes_to_int(b)) + 2730 + """ + if len(byte_array) == 0: + return 0 + return int("".join(["%02X" % i for i in byte_array]), 16) + + +def ascii_to_int(ni): + """ + Converts a bytearray containing the ASCII code of each number digit in an Integer. + This integer is result of the number formed by all ASCII codes of the bytearray. + + Example: + >>> x = bytearray( [0x31,0x30,0x30] ) #0x31 => ASCII code for number 1. + #0x31,0x30,0x30 <==> 1,0,0 + >>> print(ascii_to_int(x)) + 100 + """ + return int("".join([str(i - 0x30) for i in ni])) + + +def int_to_ascii(number): + """ + Converts an integer number to a bytearray. Each element of the bytearray is the ASCII + code that corresponds to the digit of its position. + + Args: + number (Integer): the number to convert to an ASCII bytearray. + + Returns: + Bytearray: the bytearray containing the ASCII value of each digit of the number. + + Example: + >>> x = int_to_ascii(100) + >>> print(x) + 100 + >>> print([i for i in x]) + [49, 48, 48] + """ + return bytearray([ord(i) for i in str(number)]) + + +def int_to_length(number): + """ + Converts am integer into a bytearray of 2 bytes corresponding to the length field of a + packet. If this bytearray has length 1, a byte with value 0 is added at the beginning. + + Args: + number (Integer): the number to convert to a length field. + + Returns: + + + Raises: + ValueError: if ``number`` is less than 0 or greater than 0xFFFF. + + Example: + >>> a = 0 + >>> print(hex_to_string(int_to_length(a))) + 00 00 + + >>> a = 8 + >>> print(hex_to_string(int_to_length(a))) + 00 08 + + >>> a = 200 + >>> print(hex_to_string(int_to_length(a))) + 00 C8 + + >>> a = 0xFF00 + >>> print(hex_to_string(int_to_length(a))) + FF 00 + + >>> a = 0xFF + >>> print(hex_to_string(int_to_length(a))) + 00 FF + """ + if number < 0 or number > 0xFFFF: + raise ValueError("The number must be between 0 and 0xFFFF.") + length = int_to_bytes(number) + if len(length) < 2: + length.insert(0, 0) + return length + + +def hex_to_string(byte_array, pretty=True): + """ + Returns the provided bytearray in a pretty string format. All bytes are separated by blank spaces and + printed in hex format. + + Args: + byte_array (Bytearray): the bytearray to print in pretty string. + pretty (Boolean, optional): ``True`` for pretty string format, ``False`` for plain string format. + Default to ``True``. + + Returns: + String: the bytearray formatted in a string format. + """ + separator = " " if pretty else "" + return separator.join(["%02X" % i for i in byte_array]) + + +def doc_enum(enum_class, descriptions=None): + """ + Returns a string with the description of each value of an enumeration. + + Args: + enum_class (Enumeration): the Enumeration to get its values documentation. + descriptions (dictionary): each enumeration's item description. The key is the enumeration element name + and the value is the description. + + Returns: + String: the string listing all the enumeration values and their descriptions. + """ + tab = " "*4 + data = "\n| Values:\n" + for x in enum_class: + data += """| {:s}**{:s}**{:s} {:s}\n""".format(tab, x, + ":" if descriptions is not None else " =", + str(x.value) if descriptions is None else descriptions[x]) + return data + "| \n" + + +def enable_logger(name, level=logging.DEBUG): + """ + Enables a logger with the given name and level. + + Args: + name (String): name of the logger to enable. + level (Integer): logging level value. + + Assigns a default formatter and a default handler (for console). + """ + log = logging.getLogger(name) + log.disabled = False + ch = logging.StreamHandler() + ch.setLevel(level) + formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)-7s - %(message)s') + ch.setFormatter(formatter) + log.addHandler(ch) + log.setLevel(level) + + +def disable_logger(name): + """ + Disables the logger with the give name. + + Args: + name (String): the name of the logger to disable. + """ + log = logging.getLogger(name) + log.disabled = True 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 From 771378f0af1c4127e088b25eeabeab483c058cf3 Mon Sep 17 00:00:00 2001 From: alexglzg Date: Sat, 21 Dec 2019 17:27:20 -0600 Subject: [PATCH 4/7] python 2 library --- README.rst | 23 ++++------------------- 1 file changed, 4 insertions(+), 19 deletions(-) diff --git a/README.rst b/README.rst index 7dfc624..e052554 100644 --- a/README.rst +++ b/README.rst @@ -1,22 +1,12 @@ -Digi XBee Python library |pypiversion| |pythonversion| +Digi XBee Python library python 2 ====================================================== 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 From d563abc5c7931122462c453a4cfd7264c30799d5 Mon Sep 17 00:00:00 2001 From: alexglzg Date: Sat, 21 Dec 2019 17:27:56 -0600 Subject: [PATCH 5/7] python 2 library --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index e052554..3e11af0 100644 --- a/README.rst +++ b/README.rst @@ -1,4 +1,4 @@ -Digi XBee Python library python 2 +Digi XBee Python 2 library ====================================================== This project contains the source code of the XBee Python library, an From 0c4795b9ed3bea6f7cdcf33f3af97c7435e2b18d Mon Sep 17 00:00:00 2001 From: alexglzg Date: Sat, 21 Dec 2019 17:29:15 -0600 Subject: [PATCH 6/7] python 2 library --- requirements.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 From 3c906cee115865c29e27f5fdca9f6f88db5d7fe8 Mon Sep 17 00:00:00 2001 From: alexglzg Date: Sun, 22 Dec 2019 03:23:26 -0600 Subject: [PATCH 7/7] python 2 library --- setup.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) 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',