Skip to content

Commit 187afbf

Browse files
Scott Kilautatianaleon
authored andcommitted
Add support for the new XBee BLU device/product.
This product does not support the XBee protocol, but instead, only supports Bluetooth/BLE. The XBee BLU currently adds extra support over the existing Bluetooth support, by adding a few new API packet/frames. These frames are: Bluetooth GAP Scan Request - 0x34 Bluetooth GAP Scan Legacy Advertisement Response - 0xB4 Bluetooth GAP Scan Extended Advertisement Response - 0xB7 Bluetooth GAP Scan Status - 0xB5 In addition to creating a new device type called BluDevice that supports these new calls, there have also been a couple new functional tests to verify the support, along with a new Demo/Example application that will initiate a BLE GAP scan, and display all devices that are broadcasting BLE advertisements.
1 parent 6c0ed9f commit 187afbf

29 files changed

+3670
-29
lines changed

CHANGELOG.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ v1.4.2 - XX/XX/202X
1414
* XBee 3 Cellular North America Cat 4
1515
* XBee XR 900 TH
1616
* XBee XR 868 TH
17+
* XBee BLU
1718
* Support to retrieve XBee statistics.
1819
* Send/receive explicit data in 802.15.4.
1920
(XBee 3 modules support this feature)
@@ -23,6 +24,8 @@ v1.4.2 - XX/XX/202X
2324
* The library includes support to generate salt and verifier required to
2425
configure '$S', '$V', '$W', '$X', and '$Y' XBee parameters to establish
2526
Bluetooth password.
27+
* Support for sending BLE Generic Access Profile (GAP) scans.
28+
(Only XBee BLU modules support this feature)
2629
* Bug fixing:
2730

2831
* Fix order of nodes when creating a Zigbee source route (#278)

digi/xbee/ble.py

Lines changed: 231 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,231 @@
1+
# Copyright 2024, Digi International Inc.
2+
#
3+
# This Source Code Form is subject to the terms of the Mozilla Public
4+
# License, v. 2.0. If a copy of the MPL was not distributed with this
5+
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
6+
#
7+
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
8+
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
9+
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
10+
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
11+
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
12+
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
13+
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
14+
15+
from digi.xbee.packets.bluetooth import BluetoothGAPScanRequestPacket
16+
from digi.xbee.models.status import BLEGAPScanStatus
17+
18+
19+
class BLEManager:
20+
"""
21+
Helper class used to manage the BLE Interface on the XBee.
22+
23+
NOTE: For more information about Shortened and Local Names
24+
when running a GAP scan, see:
25+
<https://www.bluetooth.com/wp-content/uploads/Files/Specification/HTML/Assigned_Numbers/out/en/Assigned_Numbers.pdf?v=1707939725200
26+
"""
27+
28+
GAP_SCAN_DURATION_INDEFINITELY = BluetoothGAPScanRequestPacket.INDEFINITE_SCAN_DURATION
29+
"""
30+
Value to have the GAP scan run indefinitely
31+
"""
32+
33+
def __init__(self, xbee):
34+
"""
35+
Class constructor. Instantiates a new :class:`.BLEManager` with
36+
the given parameters.
37+
38+
Args:
39+
xbee (:class:`.AbstractXBeeDevice`): XBee to manage its
40+
BLE interface.
41+
"""
42+
from digi.xbee.devices import XBeeDevice
43+
if not isinstance(xbee, XBeeDevice):
44+
raise ValueError("XBee must be an XBee class")
45+
46+
self.__xbee = xbee
47+
self.__listening_for_status_events = False
48+
self.__gap_scan_status = None
49+
50+
def __str__(self):
51+
return "BLE (%s)" % self.__xbee
52+
53+
def __status_callback(self, data):
54+
"""
55+
Internal BLE GAP scan status Callback
56+
57+
This function will get called whenever the GAP scanning status
58+
has changed.
59+
"""
60+
self.__gap_scan_status = data.status
61+
62+
def open(self):
63+
"""
64+
Opens the communication with the BLE Manager.
65+
66+
This method guarantees that all callbacks are started.
67+
"""
68+
if not self.__listening_for_status_events:
69+
self.__xbee._packet_listener.add_ble_gap_scan_status_received_callback(
70+
self.__status_callback)
71+
self.__listening_for_status_events = True
72+
73+
def close(self):
74+
"""
75+
Closes the communication with the BLE Manager.
76+
77+
This method guarantees that all callbacks are stopped/deleted.
78+
"""
79+
if self.__status_callback in self.__xbee._packet_listener.get_ble_gap_scan_status_received_callbacks():
80+
self.__xbee._packet_listener.del_ble_gap_scan_status_received_callback(self.__status_callback)
81+
self.__listening_for_status_events = False
82+
83+
@property
84+
def xbee(self):
85+
"""
86+
Returns the XBee of this BLE manager.
87+
88+
Returns:
89+
:class:`.AbstractXBeeDevice`: XBee to manage its BLE interface.
90+
"""
91+
return self.__xbee
92+
93+
def start_ble_gap_scan(self, duration, window, interval, enable_filter,
94+
custom_filter):
95+
"""
96+
Starts a Bluetooth BLE GAP Scan request
97+
98+
Args:
99+
duration (Integer): Scan Duration
100+
The Scan Duration parameter defines how long
101+
the scan should run.
102+
Scan duration should be between
103+
0 - 65535 (x 1s) (18.20 hr.)
104+
If the scan is set to 0 the scan will run
105+
indefinitely.
106+
107+
window (Integer): Scan Window
108+
The Scan Window parameter defines how long to
109+
scan at each interval.
110+
The range is from 2500 – 40959375 (x 1 us)
111+
(41 seconds)
112+
The window cannot be bigger than the
113+
scan interval.
114+
115+
interval (Integer): Scan Interval
116+
The Scan Interval parameter is the duration of
117+
time between two consecutive times that the
118+
scanner wakes up to receive the advertising messages.
119+
The range is from 2500 – 40959375 (x 1 us)
120+
(41 seconds)
121+
The Interval cannot be smaller than the
122+
scan window
123+
124+
enable_filter (Bool): Filter Type
125+
Supported Filter Types:
126+
False = Filter Disabled
127+
True = Filter advertisements containing the
128+
Shortened or Complete Local Name.
129+
130+
custom_filter (bytes): Filter (optional)
131+
When the 'enable_filter' option is enabled,
132+
the scan will filter the results returned
133+
by matching the filter against the Shortened
134+
or Complete Local Name.
135+
Only items that match will be returned.
136+
The range for the Filter is from 0-22 bytes
137+
In other words, it can only accept up to
138+
22 characters.
139+
140+
Returns:
141+
:class:`.XBeePacket`: Received response packet.
142+
143+
Raises:
144+
InvalidOperatingModeException: If the XBee's operating mode is not
145+
API or ESCAPED API. This method only checks the cached value of
146+
the operating mode.
147+
TimeoutException: If response is not received in the configured
148+
timeout.
149+
XBeeException: If the XBee's communication interface is closed.
150+
151+
.. seealso::
152+
| :meth:`.stop_ble_gap_scan`
153+
"""
154+
# Ensure we are always listening for status events
155+
if not self.__listening_for_status_events:
156+
self.open()
157+
158+
packet = BluetoothGAPScanRequestPacket(BluetoothGAPScanRequestPacket.START_SCAN,
159+
duration, window, interval,
160+
enable_filter, custom_filter)
161+
return self.__xbee.send_packet_sync_and_get_response(packet)
162+
163+
def stop_ble_gap_scan(self):
164+
"""
165+
Stops a Bluetooth BLE GAP Scan request
166+
167+
Returns:
168+
:class:`.XBeePacket`: Received response packet.
169+
170+
Raises:
171+
InvalidOperatingModeException: If the XBee's operating mode is not
172+
API or ESCAPED API. This method only checks the cached value of
173+
the operating mode.
174+
TimeoutException: If response is not received in the configured
175+
timeout.
176+
XBeeException: If the XBee's communication interface is closed.
177+
178+
.. seealso::
179+
| :meth:`.start_ble_gap_scan`
180+
| :meth:`.is_scan_running`
181+
"""
182+
packet = BluetoothGAPScanRequestPacket(BluetoothGAPScanRequestPacket.STOP_SCAN,
183+
BluetoothGAPScanRequestPacket.INDEFINITE_SCAN_DURATION,
184+
0x9C4, 0x9C4,
185+
0x00, "")
186+
return self.__xbee.send_packet_sync_and_get_response(packet)
187+
188+
def add_ble_gap_advertisement_received_callback(self, callback):
189+
"""
190+
Adds a callback for the event :class:`.BLEGAPScanReceived`.
191+
192+
Args:
193+
callback (Function): The callback. Receives one argument.
194+
195+
* The data received as a
196+
:class:`.BLEGAPScanLegacyAdvertisementMessage` or
197+
:class:`.BLEGAPScanExtendedAdvertisementMessage`
198+
199+
.. seealso::
200+
| :meth:`.del_ble_gap_advertisement_received_callback`
201+
| :meth:`.is_scan_running`
202+
"""
203+
self.__xbee._packet_listener.add_ble_gap_advertisement_received_callback(callback)
204+
205+
def del_ble_gap_advertisement_received_callback(self, callback):
206+
"""
207+
Deletes a callback for the callback list of
208+
:class:`.BLEGAPScanReceived` event.
209+
210+
Args:
211+
callback (Function): The callback to delete.
212+
213+
.. seealso::
214+
| :meth:`.add_ble_gap_advertisement_received_callback`
215+
"""
216+
if callback in self.__xbee._packet_listener.get_ble_gap_scan_received_callbacks():
217+
self.__xbee._packet_listener.del_ble_gap_advertisement_received_callback(callback)
218+
219+
def is_scan_running(self):
220+
"""
221+
Returns whether a Bluetooth BLE GAP scan is currently running
222+
223+
Returns:
224+
Boolean: `True` if scan is running, `False` otherwise.
225+
226+
.. seealso::
227+
| :meth:`.start_ble_gap_scan`
228+
| :meth:`.stop_ble_gap_scan`
229+
"""
230+
return self.__gap_scan_status in (BLEGAPScanStatus.STARTED,
231+
BLEGAPScanStatus.RUNNING)

0 commit comments

Comments
 (0)