Source code for digi.xbee.packets.bluetooth

# Copyright 2024, Digi International Inc.
#
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

from digi.xbee.packets.base import XBeeAPIPacket, DictKeys
from digi.xbee.exception import InvalidOperatingModeException, \
                                InvalidPacketException
from digi.xbee.packets.aft import ApiFrameType
from digi.xbee.models.mode import OperatingMode
from digi.xbee.util import utils
from digi.xbee.models.address import XBeeBLEAddress


[docs] class BluetoothGAPScanRequestPacket(XBeeAPIPacket): """ This class represents a Bluetooth GAP scan request packet. Packet is built using the parameters of the constructor or providing a valid byte array. .. seealso:: | :class:`.BluetoothGAPScanRequestPacket` | :class:`.XBeeAPIPacket` """ # Various defines, max/mins STOP_SCAN = 0x00 """ Constant value used to stop the scan """ START_SCAN = 0x01 """ Constant value used to start the scan """ INDEFINITE_SCAN_DURATION = 0x00 """ Constant value for an indefinite scan """ # Internal only __SCAN_DURATION_MINIMUM = 0x1 __SCAN_DURATION_MAXIMUM = 0xFFFF __SCAN_WINDOW_MINIMUM = 0x9C4 __SCAN_WINDOW_MAXIMUM = 0x270FD8F __SCAN_INTERVAL_MINIMUM = 0x9C4 __SCAN_INTERVAL_MAXIMUM = 0x270FD8F __SCAN_FILTER_DISABLED = 0x00 __SCAN_FILTER_ENABLED = 0x01 __SCAN_CUSTOM_FILTER_MAXIMUM_LENGTH = 22 __MIN_PACKET_LENGTH = 17 def __init__(self, start_command, scan_duration, scan_window, scan_interval, filter_type, filter_data, op_mode=OperatingMode.API_MODE): """ Class constructor. Instantiates a new :class:`.BluetoothGAPScanRequestPacket` object with the provided parameters. Args: start_command (Integer): To start scan set to 1. To stop scan set to 0. scan_duration (Integer): Scan duration in seconds. Value must be between 0 and 0xFFFF. A value of 0 indicates the scan will run indefinitely. scan_window (Integer): Scan window in microseconds. Value must be between 0x9C4 and 0x270FD8F and value can't be larger than the scan_interval. scan_interval (Integer): Scan interval in microseconds. Value must be between 0x9C4 and 0x270FD8F and value can't be smaller than the scan_window. filter_type (Integer): Value of 0 disables filter. Value of 1 enables filter looking for Complete Local Name (0x09) and Short Local Name (0x08) types. filter_data (String or bytearray): The size of the filter_data is from 0-22 bytes. op_mode (:class:`.OperatingMode`, optional, default=`OperatingMode.API_MODE`): The mode in which the frame was captured. Raises: ValueError: if `start_command` is not 0 nor 1. ValueError: if `scan_duration` is not between 0 and 0xFFFF. ValueError: if `scan_window` is not between 0x9C4 and 0x270FD8F or larger than the scan_interval. ValueError: if `scan_interval` is not between 0x9C4 and 0x270FD8F or smaller than the scan_window. ValueError: if `filter_type` is not 0 nor 1. .. seealso:: | :class:`.XBeeAPIPacket` """ if start_command not in (self.START_SCAN, self.STOP_SCAN): raise ValueError(f"Command must be {self.START_SCAN} or {self.STOP_SCAN}") if scan_duration is None: raise ValueError("The scan duration cannot be None") if not self.INDEFINITE_SCAN_DURATION <= scan_duration <= self.__SCAN_DURATION_MAXIMUM: raise ValueError(f"scan_duration must be between {self.INDEFINITE_SCAN_DURATION} and {self.__SCAN_DURATION_MAXIMUM}") if scan_window is None: raise ValueError("The scan window cannot be None") if not self.__SCAN_WINDOW_MINIMUM <= scan_window <= self.__SCAN_WINDOW_MAXIMUM: raise ValueError(f"scan_window must be between {self.__SCAN_WINDOW_MINIMUM} and {self.__SCAN_WINDOW_MAXIMUM}") if scan_interval is None: raise ValueError("The scan interval cannot be None") if not self.__SCAN_INTERVAL_MINIMUM <= scan_interval <= self.__SCAN_INTERVAL_MAXIMUM: raise ValueError(f"scan_interval must be between {self.__SCAN_INTERVAL_MINIMUM} and {self.__SCAN_INTERVAL_MAXIMUM}") if scan_interval < scan_window: raise ValueError("scan_interval can't be smaller than the scan_window") if filter_type not in (self.__SCAN_FILTER_DISABLED, self.__SCAN_FILTER_DISABLED): raise ValueError(f"filter_type must be {self.__SCAN_FILTER_DISABLED} or {self.__SCAN_FILTER_DISABLED}") if filter_data and len(filter_data) > self.__SCAN_CUSTOM_FILTER_MAXIMUM_LENGTH: raise ValueError(f"The length of the custom filter must be from 0-{self.__SCAN_CUSTOM_FILTER_MAXIMUM_LENGTH}") super().__init__(ApiFrameType.BLUETOOTH_GAP_SCAN_REQUEST, op_mode=op_mode) self.__start_command = start_command self.__scan_duration = scan_duration self.__scan_window = scan_window self.__scan_interval = scan_interval self.__filter_type = filter_type if isinstance(filter_data, str): self.__filter_data = filter_data.encode('utf8', errors='ignore') else: self.__filter_data = filter_data
[docs] @staticmethod def create_packet(raw, operating_mode): """ Override method. Returns: :class:`.BluetoothGAPScanRequestPacket` Raises: InvalidPacketException: if the bytearray length is less than 17. (start delim, length (2 bytes), frame type, start_command, scan_duration (2 bytes), scan_window (4 bytes), scan_interval (4 bytes), filter_type, checksum = 17 bytes) InvalidPacketException: if the length field of `raw` is different from its real length. (length field: bytes 2 and 3) InvalidPacketException: if the first byte of `raw` is not the header byte. See :class:`.SPECIAL_BYTE`. InvalidPacketException: if the calculated checksum is different from the checksum field value (last byte). InvalidPacketException: if the frame type is different from :py:attr:`.ApiFrameType.BLUETOOTH_GAP_SCAN_REQUEST`. InvalidOperatingModeException: if `operating_mode` is not supported. .. seealso:: | :meth:`.XBeePacket.create_packet` """ if operating_mode not in (OperatingMode.ESCAPED_API_MODE, OperatingMode.API_MODE): raise InvalidOperatingModeException(op_mode=operating_mode) XBeeAPIPacket._check_api_packet(raw, min_length=BluetoothGAPScanRequestPacket.__MIN_PACKET_LENGTH) if raw[3] != ApiFrameType.BLUETOOTH_GAP_SCAN_REQUEST.code: raise InvalidPacketException(message="This packet is not a BluetoothGAPScanRequestPacket packet") filter_data = None if len(raw) > BluetoothGAPScanRequestPacket.__MIN_PACKET_LENGTH: filter_data = raw[17:-1] return BluetoothGAPScanRequestPacket( raw[4], utils.bytes_to_int(raw[5:7]), utils.bytes_to_int(raw[7:11]), utils.bytes_to_int(raw[11:15]), raw[15], filter_data, op_mode=operating_mode)
[docs] def needs_id(self): """ Override method. .. seealso:: | :meth:`.XBeeAPIPacket.needs_id` """ return False
def _get_api_packet_spec_data(self): """ Override method. .. seealso:: | :meth:`.XBeeAPIPacket._get_api_packet_spec_data` """ ret = bytearray() ret.append(self.__start_command) ret += utils.int_to_bytes(self.__scan_duration, num_bytes=2) ret += utils.int_to_bytes(self.__scan_window, num_bytes=4) ret += utils.int_to_bytes(self.__scan_interval, num_bytes=4) ret.append(self.__filter_type) if self.__filter_data is not None: ret += self.__filter_data return ret def _get_api_packet_spec_data_dict(self): """ Override method. .. seealso:: | :meth:`.XBeeAPIPacket._get_api_packet_spec_data_dict` """ return {DictKeys.BLE_SCAN_START: self.__start_command, DictKeys.BLE_SCAN_DURATION.value: "%02X" % self.__scan_duration, DictKeys.BLE_SCAN_WINDOW.value: "%04X" % self.__scan_window, DictKeys.BLE_SCAN_INTERVAL.value: "%04X" % self.__scan_interval, DictKeys.BLE_SCAN_FILTER_TYPE: self.__scan_interval, DictKeys.PAYLOAD.value: utils.hex_to_string(self.__filter_data, True) if self.__filter_data is not None else None} @property def start_command(self): """ Returns the start_command. Returns: Integer: the start_command. """ return self.__start_command @start_command.setter def start_command(self, start_command): """ Sets the start_command. Args: start_command (Integer): To start scan set to 1. To stop scan set to 0. Raises: ValueError: if `start_command` is less than 0 or greater than 1. """ if start_command < 0 or start_command > 1: raise ValueError("start_command must be between 0 and 1.") self.__start_command = start_command @property def scan_duration(self): """ Returns the scan duration. Returns: Integer: the scan duration. """ return self.__scan_duration @scan_duration.setter def scan_duration(self, scan_duration): """ Sets the scan duration. Args: scan_duration (Integer): Scan duration in seconds. Value must be between 0 and 0xFFFF. Raises: ValueError: if `scan_duration` is less than 0 or greater than 0xFFFF. """ if scan_duration < 0 or scan_duration > 0xFFFF: raise ValueError("scan_duration must be between 0 and 0xFFFF.") self.__scan_duration = scan_duration @property def scan_window(self): """ Returns the scan window. Returns: Integer: the scan window. """ return self.__scan_window @scan_window.setter def scan_window(self, scan_window): """ Sets the scan window. Args: scan_window (Integer): Scan window in microseconds. Scan window in microseconds. Value must be between 0x9C4 and 0x270FD8F. Raises: ValueError: if `scan_window` is less than 0x9C4 or greater than 0x270FD8F. """ if scan_window < 0x9C4 or scan_window > 0x270FD8F: raise ValueError(f"scan_window must be between {self.__SCAN_WINDOW_MINIMUM} and {self.__SCAN_WINDOW_MAXIMUM}.") self.__scan_window = scan_window @property def scan_interval(self): """ Returns the scan interval. Returns: Integer: the scan interval. """ return self.__scan_interval @scan_interval.setter def scan_interval(self, scan_interval): """ Sets the scan interval. Args: scan_interval (Integer): Scan window in microseconds. Scan window in microseconds. Value must be between 0x9C4 and 0x270FD8F. Raises: ValueError: if `scan_interval` is less than 0x9C4 or greater than 0x270FD8F. """ if scan_interval < 0x9C4 or scan_interval > 0x270FD8F: raise ValueError(f"scan_interval must be between {self.__SCAN_INTERVAL_MINIMUM} and {self.__SCAN_INTERVAL_MAXIMUM}.") self.__scan_interval = scan_interval @property def filter_type(self): """ Returns the filter_type. Returns: Integer: the filter_type. """ return self.__filter_type @filter_type.setter def filter_type(self, filter_type): """ Sets the filter_type. Args: filter_type (Integer): Value of 0 disables filter. Value of 1 enables filter looking for Complete Local Name (0x09) and Short Local Name (0x08) types. Raises: ValueError: if `filter_type` is less than 0 or greater than 1. """ if filter_type < 0 or filter_type > 1: raise ValueError("filter_type must be between 0 and 1.") self.__filter_type = filter_type @property def filter_data(self): """ Returns the filter data. Returns: Bytearray: packet's filter data. """ return self.__filter_data @filter_data.setter def filter_data(self, filter_data): """ Sets the filter data. Args: filter_data (String or Bytearray): the new filter data. """ if isinstance(filter_data, str): self.__filter_data = filter_data.encode('utf8', errors='ignore') else: self.__filter_data = filter_data
[docs] class BluetoothGAPScanLegacyAdvertisementResponsePacket(XBeeAPIPacket): """ This class represents a Bluetooth GAP scan legacy advertisement response packet. Packet is built using the parameters of the constructor or providing a valid byte array. .. seealso:: | :class:`.BluetoothGAPScanLegacyAdvertisementResponsePacket` | :class:`.XBeeAPIPacket` """ __MIN_PACKET_LENGTH = 16 def __init__(self, address, address_type, advertisement_flags, rssi, payload, op_mode=OperatingMode.API_MODE): """ Class constructor. Instantiates a new :class:`.BluetoothGAPScanLegacyAdvertisementResponsePacket` object with the provided parameters. Args: address (:class:`.XBeeBLEAddress`): The Bluetooth MAC address of the received advertisement address_type (Integer): Indicates whether the Bluetooth address is a public address or a randomly generated address. advertisement_flags (Integer): bitfield structure where bit0 indicates whether the advertisement is connectable. Other bits are reserved. rssi (Integer): The received signal strength of the advertisement, in dBm. payload (bytearray): The actual data payload of the advertisement which can contain various information elements, manufacturer-specific data, or other relevant details about the advertising device. op_mode (:class:`.OperatingMode`, optional, default=`OperatingMode.API_MODE`): The mode in which the frame was captured. Raises: ValueError: if length of `payload` is less than 16 or greater than 255. .. seealso:: | :class:`.XBeeBLEAddress` """ if len(payload) > 255: raise ValueError("Response payload length must be less than 256 bytes") super().__init__(ApiFrameType.BLUETOOTH_GAP_SCAN_LEGACY_ADVERTISEMENT_RESPONSE, op_mode=op_mode) self.__address = address self.__address_type = address_type self.__advertisement_flags = advertisement_flags self.__rssi = rssi self.__payload = payload
[docs] @staticmethod def create_packet(raw, operating_mode): """ Override method. Returns: :class:`.BluetoothGAPScanLegacyAdvertisementResponsePacket` Raises: InvalidPacketException: if the bytearray length is less than 32. (start delim + length (2 bytes) + frame type + address (6 bytes) + address_type + advertisement_flags + rssi + reserved + payload_length + checksum = 16 bytes) InvalidPacketException: if the length field of `raw` is different from its real length. (length field: bytes 2 and 3) InvalidPacketException: if the first byte of `raw` is not the header byte. See :class:`.SPECIAL_BYTE`. InvalidPacketException: if the calculated checksum is different from the checksum field value (last byte). InvalidPacketException: if the frame type is different from :py:attr:`.ApiFrameType.BluetoothGAPScanLegacyAdvertisementResponsePacket`. InvalidPacketException: if the payload_length field does not match the actual length of the payload. InvalidOperatingModeException: if `operating_mode` is not supported. .. seealso:: | :meth:`.XBeePacket.create_packet` """ if operating_mode not in (OperatingMode.ESCAPED_API_MODE, OperatingMode.API_MODE): raise InvalidOperatingModeException(op_mode=operating_mode) XBeeAPIPacket._check_api_packet( raw, min_length=BluetoothGAPScanLegacyAdvertisementResponsePacket.__MIN_PACKET_LENGTH) if raw[3] != ApiFrameType.BLUETOOTH_GAP_SCAN_LEGACY_ADVERTISEMENT_RESPONSE.code: raise InvalidPacketException(message="This packet is not an BLUETOOTH_GAP_SCAN_LEGACY_ADVERTISEMENT_RESPONSE") payload = None if len(raw) > BluetoothGAPScanLegacyAdvertisementResponsePacket.__MIN_PACKET_LENGTH: payload = raw[15:-1] payload_length = raw[14] if len(payload) != payload_length: raise InvalidPacketException( message="BluetoothGAPScanLegacyAdvertisementResponsePacket payload length field is %u and does not match actual length of payload that is %u".format( payload_length, len(payload) )) return BluetoothGAPScanLegacyAdvertisementResponsePacket( XBeeBLEAddress(raw[4:10]), raw[10], raw[11], raw[12], payload, op_mode=operating_mode)
[docs] def needs_id(self): """ Override method. .. seealso:: | :meth:`.XBeeAPIPacket.needs_id` """ return False
def _get_api_packet_spec_data(self): """ Override method. .. seealso:: | :meth:`.XBeeAPIPacket._get_API_packet_spec_data` """ ret = bytearray() ret.append(len(self.__address.address)) ret.append(self.__address_type) ret.append(self.__advertisement_flags) ret.append(self.__rssi) ret.append(0) if self.__payload is not None: ret.append(len(self.__payload)) ret += self.__payload else: ret.append(0) return ret def _get_api_packet_spec_data_dict(self): """ Override method. .. seealso:: | :meth:`.XBeeAPIPacket._get_API_packet_spec_data_dict` """ return {DictKeys.BLE_ADDRESS: "%s (%s)" % (self.__address.packed, self.__address.exploded), DictKeys.BLE_ADDRESS_TYPE: self.__address_type, DictKeys.BLE_ADVERTISEMENT_FLAGS: self.__advertisement_flags, DictKeys.RSSI: self.__rssi, DictKeys.RESERVED: 0, DictKeys.PAYLOAD.value: utils.hex_to_string(self.__payload, True) if self.__payload is not None else None} @property def address(self): """ Returns the Bluetooth MAC address of the received advertisement. Returns: address (:class:`.XBeeBLEAddress`): returns the Bluetooth MAC address. """ return self.__address @address.setter def address(self, address): """ Sets the BLE MAC address of the received advertisement Args: address (:class:`.XBeeBLEAddress`): the 48 bit BLE MAC address. """ if address is not None: self.__address = address @property def address_type(self): """ Returns the address type of the received advertisement. Returns: Integer: the address type. """ return self.__address_type @address_type.setter def address_type(self, address_type): """ Sets the address type indicating whether the Bluetooth address is a public or randomly generated address. Args: address_type (Integer): address type """ self.__address_type = address_type @property def advertisement_flags(self): """ Returns the advertisement flags. Returns: Integer: the advertisement flags. """ return self.__advertisement_flags @advertisement_flags.setter def advertisement_flags(self, advertisement_flags): """ Sets the Advertisement flags type. Bit 0 indicates whether the advertisement is connectable. Args: advertisement_flags (Integer): advertisement flag """ self.__advertisement_flags = advertisement_flags @property def rssi(self): """ Returns the RSSI of the advertisement. Returns: Integer: the RSSI. """ return self.__rssi @rssi.setter def rssi(self, rssi): """ Sets the RSSI of the advertisement in dBm. Args: rssi (Integer): the RSSI """ self.__rssi = rssi @property def payload(self): """ Returns the payload that was received. Returns: Bytearray: the payload that was received. """ if self.__payload is None: return None return self.__payload.copy() @payload.setter def payload(self, payload): """ Sets the payload that was received. Args: payload (Bytearray): the new payload that was received. """ if payload is None: self.__payload = None else: self.__payload = payload.copy()
[docs] class BluetoothGAPScanExtendedAdvertisementResponsePacket(XBeeAPIPacket): """ This class represents a Bluetooth GAP scan extended advertisement response packet. Packet is built using the parameters of the constructor or providing a valid byte array. .. seealso:: | :class:`.BluetoothGAPScanExtendedAdvertisementResponsePacket` | :class:`.XBeeAPIPacket` """ __MIN_PACKET_LENGTH = 23 def __init__(self, address, address_type, advertisement_flags, rssi, advertisement_set_id, primary_phy, secondary_phy, tx_power, periodic_interval, data_completeness, payload, op_mode=OperatingMode.API_MODE): """ Class constructor. Instantiates a new :class:`.BluetoothGAPScanExtendedAdvertisementResponsePacket` object with the provided parameters. Args: address (:class:`.XBeeBLEAddress`): The Bluetooth MAC address of the received advertisement address_type (Integer): Indicates whether the Bluetooth address is a public address or a randomly generated address. advertisement_flags (Integer): bitfield structure where bit0 indicates whether the advertisement is connectable. Other bits are reserved. rssi (Integer) The received signal strength of the advertisement, in dBm. advertisement_set_id (Integer): A device can broadcast multiple advertisements at a time. The set identifier will help identify which advertisement you received. primary_phy (Integer): This is the preferred PHY for connecting with this device. Values are: 0x1 : 1M PHY 0x2 : 2M PHY 0x4 : LE Coded PHY 125k 0x8 : LE Coded PHY 500k 0xFF : Any PHY supported secondary_phy (Integer): This is the secondary PHY for connecting with this device. This has the same values as primary_phy. tx_power (Integer): Transmission power of received advertisement. This is a signed value. periodic_interval (Integer): Interval for periodic advertising. 0 indicates no periodic advertising. Interval value is in increments of 1.25 ms. data_completeness (Integer): Values are 0x0 indicates all data of the advertisement has been reported. 0x1 : Data is incomplete, but more data will follow. 0x2 : Data is incomplete, but no more data is following. Data has be truncated. payload (bytearray): The actual data payload of the advertisement which can contain various information elements, manufacturer-specific data, or other relevant details about the advertising device. op_mode (:class:`.OperatingMode`, optional, default=`OperatingMode.API_MODE`): The mode in which the frame was captured. Raises: ValueError: if length of `payload` is less than 22 or greater than 255. """ if len(payload) > 255: raise ValueError("Response payload length must be less than 256 bytes") super().__init__(ApiFrameType.BLUETOOTH_GAP_SCAN_EXTENDED_ADVERTISEMENT_RESPONSE, op_mode=op_mode) self.__address = address self.__address_type = address_type self.__advertisement_flags = advertisement_flags self.__rssi = rssi self.__advertisement_set_id = advertisement_set_id self.__primary_phy = primary_phy self.__secondary_phy = secondary_phy self.__tx_power = tx_power self.__periodic_interval = periodic_interval self.__data_completeness = data_completeness self.__payload = payload
[docs] @staticmethod def create_packet(raw, operating_mode): """ Override method. Returns: :class:`.BluetoothGAPScanExtendedAdvertisementResponsePacket` Raises: InvalidPacketException: if the bytearray length is less than 45. (start delim + length (2 bytes) + frame type + address (6 bytes) + address_type + advertisement_flags + rssi + reserved + advertisement_set_id + primary_phy + secondary_phy + tx_power + periodic_interval (2 bytes) + data_completeness + payload_length + checksum = 23 bytes) InvalidPacketException: if the length field of `raw` is different from its real length. (length field: bytes 2 and 3) InvalidPacketException: if the first byte of `raw` is not the header byte. See :class:`.SPECIAL_BYTE`. InvalidPacketException: if the calculated checksum is different from the checksum field value (last byte). InvalidPacketException: if the frame type is different from :py:attr:`.ApiFrameType.BluetoothGAPScanExtendedAdvertisementResponsePacket`. InvalidOperatingModeException: if `operating_mode` is not supported. .. seealso:: | :meth:`.XBeePacket.create_packet` """ if operating_mode not in (OperatingMode.ESCAPED_API_MODE, OperatingMode.API_MODE): raise InvalidOperatingModeException(op_mode=operating_mode) XBeeAPIPacket._check_api_packet( raw, min_length=BluetoothGAPScanExtendedAdvertisementResponsePacket.__MIN_PACKET_LENGTH) if raw[3] != ApiFrameType.BLUETOOTH_GAP_SCAN_EXTENDED_ADVERTISEMENT_RESPONSE.code: raise InvalidPacketException(message="This packet is not an BLUETOOTH_GAP_SCAN_EXTENDED_ADVERTISEMENT_RESPONSE") payload = None if len(raw) > BluetoothGAPScanExtendedAdvertisementResponsePacket.__MIN_PACKET_LENGTH: payload = raw[22:-1] payload_length = raw[21] if len(payload) != payload_length: raise InvalidPacketException( message="BluetoothGAPScanExtendedAdvertisementResponsePacket payload length field is %u and does not match actual length of payload that is %u".format( payload_length, len(payload) )) return BluetoothGAPScanExtendedAdvertisementResponsePacket( XBeeBLEAddress(raw[4:10]), raw[10], raw[11], raw[12], raw[14], raw[15], raw[16], raw[17], raw[18:19], raw[20], payload, op_mode=operating_mode)
[docs] def needs_id(self): """ Override method. .. seealso:: | :meth:`.XBeeAPIPacket.needs_id` """ return False
def _get_api_packet_spec_data(self): """ Override method. .. seealso:: | :meth:`.XBeeAPIPacket._get_API_packet_spec_data` """ ret = bytearray() ret.append(len(self.__address.address)) ret.append(self.__address_type) ret.append(self.__advertisement_flags) ret.append(self.__rssi) ret.append(self.__advertisement_set_id) ret.append(self.__primary_phy) ret.append(self.__secondary_phy) ret.append(self.__tx_power) ret += self.__periodic_interval ret.append(self.__data_completeness) ret.append(0) if self.__payload is not None: ret.append(len(self.__payload)) ret += self.__payload else: ret.append(0) return ret def _get_api_packet_spec_data_dict(self): """ Override method. .. seealso:: | :meth:`.XBeeAPIPacket._get_API_packet_spec_data_dict` """ return {DictKeys.BLE_ADDRESS: "%s (%s)" % (self.__address.packed, self.__address.exploded), DictKeys.BLE_ADDRESS_TYPE: self.__address_type, DictKeys.BLE_ADVERTISEMENT_FLAGS: self.__advertisement_flags, DictKeys.RSSI: self.__rssi, DictKeys.RESERVED: 0, DictKeys.ADVERTISEMENT_SET_ID: self.__advertisement_set_id, DictKeys.PRIMARY_PHY: self.__primary_phy, DictKeys.SECONDARY_PHY: self.__secondary_phy, DictKeys.TX_POWER: self.__tx_power, DictKeys.PERIODIC_INTERVAL: self.__periodic_interval, DictKeys.DATA_COMPLETENESS: self.__data_completeness, DictKeys.PAYLOAD.value: utils.hex_to_string(self.__payload, True) if self.__payload is not None else None} @property def address(self): """ Returns the Bluetooth MAC address of the received advertisement. Returns: address (:class:`.XBeeBLEAddress`): returns the Bluetooth MAC address. """ return self.__address @address.setter def address(self, address): """ Sets the BLE MAC address of the received advertisement Args: address (byt:class:`.XBeeBLEAddress`): the 48 bit BLE MAC address. """ self.__address = address @property def address_type(self): """ Returns the address type Returns: Integer: the address type. """ return self.__address_type @address_type.setter def address_type(self, address_type): """ Sets the address type indicating whether the Bluetooth address is a public or randomly generated address. Args: address_type (Integer): address type """ self.__address_type = address_type @property def advertisement_flags(self): """ Returns the Advertisement flags Returns: Integer: the advertisement flags. """ return self.__advertisement_flags @advertisement_flags.setter def advertisement_flags(self, advertisement_flags): """ Sets the Advertisement flags type. Bit 0 indicates whether the advertisement is connectable. Args: advertisement_flags (Integer): advertisement flag """ self.__advertisement_flags = advertisement_flags @property def rssi(self): """ Returns the RSSI of the advertisement. Returns: Integer: the RSSI. """ return self.__rssi @rssi.setter def rssi(self, rssi): """ Sets the RSSI of the advertisement in dBm. Args: rssi (Integer): the RSSI """ self.__rssi = rssi @property def advertisement_set_id(self): """ Returns the advertisement set identifier used to help identify which advertisement you received. Returns: Integer: the advertisement set identifier. """ return self.__advertisement_set_id @advertisement_set_id.setter def advertisement_set_id(self, advertisement_set_id): """ Sets the advertisement set identifier. Args: advertisement_set_id (Integer): """ self.__advertisement_set_id = advertisement_set_id @property def primary_phy(self): """ Returns the preferred PHY for connecting this device. Returns: Integer: the primary PHY """ return self.__primary_phy @primary_phy.setter def primary_phy(self, primary_phy): """ Sets the preferred PHY for connecting this device. Args: primary_phy (Integer): primary PHY """ self.__primary_phy = primary_phy @property def secondary_phy(self): """ Returns the secondary PHY for connecting this device. Returns: Integer: the secondary PHY. """ return self.__secondary_phy @secondary_phy.setter def secondary_phy(self, secondary_phy): """ Sets the secondary PHY for connecting this device. Args: secondary_phy (Integer): secondary PHY """ self.__secondary_phy = secondary_phy @property def tx_power(self): """ Returns the transmission power of received advertisement. This is a signed value. Returns: Integer: transmission power. """ return self.__tx_power @tx_power.setter def tx_power(self, tx_power): """ Sets the transmission power of received advertisement. Args: tx_power (Integer): transmission power. """ self.__tx_power = tx_power @property def periodic_interval(self): """ Returns the interval for periodic advertising. 0 indicates no periodic advertising. Returns: Integer: periodic interval. """ return int.from_bytes(self.__periodic_interval, "big") @periodic_interval.setter def periodic_interval(self, periodic_interval): """ Sets the Interval for periodic advertising. Args: periodic_interval (Integer): periodic interval. """ self.__periodic_interval = periodic_interval @property def data_completeness(self): """ Returns the data completeness field. Returns: Integer: data completeness field. """ return self.__data_completeness @data_completeness.setter def data_completeness(self, data_completeness): """ Sets the data completeness field. Args: data_completeness (Integer): data completeness. """ self.__data_completeness = data_completeness @property def payload(self): """ Returns the payload that was received. Returns: Bytearray: the payload that was received. """ if self.__payload is None: return None return self.__payload.copy() @payload.setter def payload(self, payload): """ Sets the payload that was received. Args: payload (Bytearray): the new payload that was received. """ if payload is None: self.__payload = None else: self.__payload = payload.copy()
[docs] class BluetoothGAPScanStatusPacket(XBeeAPIPacket): """ This class represents a Bluetooth GAP scan status packet. Packet is built using the parameters of the constructor or providing a valid byte array. .. seealso:: | :class:`.BluetoothGAPScanStatusPacket` | :class:`.XBeeAPIPacket` """ __MIN_PACKET_LENGTH = 6 def __init__(self, scan_status, op_mode=OperatingMode.API_MODE): """ Class constructor. Instantiates a new :class:`.BluetoothGAPScanStatusPacket` object with the provided parameters. Args: scan_status (Integer): Reflects the status of the Bluetooth scanner. Values are: 0x00: Scanner has successfully started. 0x01: Scanner is currently running. 0x02: Scanner has successfully stopped. 0x03: Scanner was unable to start or stop. 0x04: Invalid parameters. op_mode (:class:`.OperatingMode`, optional, default=`OperatingMode.API_MODE`): The mode in which the frame was captured. """ super().__init__(ApiFrameType.BLUETOOTH_GAP_SCAN_STATUS, op_mode=op_mode) self.__scan_status = scan_status
[docs] @staticmethod def create_packet(raw, operating_mode): """ Override method. Returns: :class:`.BluetoothGAPScanStatusPacket` Raises: InvalidPacketException: if the bytearray length is not 6. (start delim + length (2 bytes) + frame type + scan_status + checksum = 6 bytes) InvalidPacketException: if the length field of `raw` is different from its real length. (length field: bytes 2 and 3) InvalidPacketException: if the first byte of `raw` is not the header byte. See :class:`.SPECIAL_BYTE`. InvalidPacketException: if the calculated checksum is different from the checksum field value (last byte). InvalidPacketException: if the frame type is different from :py:attr:`.ApiFrameType.BluetoothGAPScanLegacyAdvertisementResponsePacket`. InvalidOperatingModeException: if `operating_mode` is not supported. .. seealso:: | :meth:`.XBeePacket.create_packet` """ if operating_mode not in (OperatingMode.ESCAPED_API_MODE, OperatingMode.API_MODE): raise InvalidOperatingModeException(op_mode=operating_mode) XBeeAPIPacket._check_api_packet( raw, min_length=BluetoothGAPScanStatusPacket.__MIN_PACKET_LENGTH) if raw[3] != ApiFrameType.BLUETOOTH_GAP_SCAN_STATUS.code: raise InvalidPacketException( message="This packet is not an BLUETOOTH_GAP_SCAN_STATUS" ) return BluetoothGAPScanStatusPacket( raw[4], op_mode=operating_mode)
[docs] def needs_id(self): """ Override method. .. seealso:: | :meth:`.XBeeAPIPacket.needs_id` """ return False
def _get_api_packet_spec_data(self): """ Override method. .. seealso:: | :meth:`.XBeeAPIPacket._get_API_packet_spec_data` """ ret = bytearray() ret.append(self.__scan_status) ret.append(0) return ret def _get_api_packet_spec_data_dict(self): """ Override method. .. seealso:: | :meth:`.XBeeAPIPacket._get_API_packet_spec_data_dict` """ return {DictKeys.SCAN_STATUS: self.__scan_status} @property def scan_status(self): """ Returns the scan status of the Bluetooth scanner. Returns: Integer: scan status """ return self.__scan_status @scan_status.setter def scan_status(self, scan_status): """ Sets the scan status. Args: scan_status: (Integer) scan status """ self.__scan_status = scan_status