Skip to content

Commit ad84cc4

Browse files
committed
Add UARTClient; refactor common code
1 parent 121a070 commit ad84cc4

File tree

4 files changed

+224
-60
lines changed

4 files changed

+224
-60
lines changed

adafruit_ble/scanner.py

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,21 @@ class Scanner:
5454
def __init__(self):
5555
self._scanner = bleio.Scanner()
5656

57+
def scan_unique(self, timeout, *, interval=0.1, window=0.1):
58+
"""Scan for advertisements from BLE devices. Suppress duplicates
59+
in returned `ScanEntry` objects.
60+
61+
:param int timeout: how long to scan for (in seconds)
62+
:param float interval: the interval (in seconds) between the start
63+
of two consecutive scan windows.
64+
Must be in the range 0.0025 - 40.959375 seconds.
65+
:param float window: the duration (in seconds) to scan a single BLE channel.
66+
`window` must be <= `interval`.
67+
:returns a list of `adafruit_ble.ScanEntry` objects.
68+
69+
"""
70+
return ScanEntry.unique(self.scan(timeout, interval=interval, window=window))
71+
5772
def scan(self, timeout, *, interval=0.1, window=0.1):
5873
"""Scan for advertisements from BLE devices.
5974
@@ -122,23 +137,25 @@ def name(self):
122137
@property
123138
def service_uuids(self):
124139
"""List of all the service UUIDs in the advertisement."""
140+
uuid_values = []
141+
125142
concat_uuids = self.item(AdvertisingPacket.ALL_16_BIT_SERVICE_UUIDS)
126143
concat_uuids = concat_uuids if concat_uuids else self.item(
127144
AdvertisingPacket.SOME_16_BIT_SERVICE_UUIDS)
128145

129-
uuid_values = []
130146
if concat_uuids:
131-
for i in range(0, len(uuid_values), 2):
147+
for i in range(0, len(concat_uuids), 2):
132148
uuid_values.append(struct.unpack("<H", concat_uuids[i:i+2]))
133149

134150
concat_uuids = self.item(AdvertisingPacket.ALL_128_BIT_SERVICE_UUIDS)
135151
concat_uuids = concat_uuids if concat_uuids else self.item(
136152
AdvertisingPacket.SOME_128_BIT_SERVICE_UUIDS)
137153

138154
if concat_uuids:
139-
for i in range(0, len(uuid_values), 16):
155+
for i in range(0, len(concat_uuids), 16):
140156
uuid_values.append(concat_uuids[i:i+16])
141157

158+
print(uuid_values)
142159
return [bleio.UUID(value) for value in uuid_values]
143160

144161
@property
@@ -150,10 +167,16 @@ def matches(self, other):
150167
"""True if two scan entries appear to be from the same device. Their
151168
addresses and advertisement_bytes must match.
152169
"""
153-
return self.address == other.address and self.advertisement_bytes == other.advertisement_bytes
170+
return (self.address == other.address and
171+
self.advertisement_bytes == other.advertisement_bytes)
172+
173+
@staticmethod
174+
def with_service_uuid(scan_entries, service_uuid):
175+
"""Return all scan entries advertising the given service_uuid."""
176+
return [se for se in scan_entries if service_uuid in se.service_uuids]
154177

155-
@classmethod
156-
def unique(self, scan_entries):
178+
@staticmethod
179+
def unique(scan_entries):
157180
"""Discard duplicate scan entries that appear to be from the same device.
158181
159182
:param sequence scan_entries: ScanEntry objects
@@ -162,5 +185,5 @@ def unique(self, scan_entries):
162185
unique = []
163186
for entry in scan_entries:
164187
if not any(entry.matches(unique_entry) for unique_entry in unique):
165-
unique.append(entry);
188+
unique.append(entry)
166189
return unique

adafruit_ble/uart.py

Lines changed: 27 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# The MIT License (MIT)
22
#
3-
# Copyright (c) 2018 Dan Halbert for Adafruit Industries
3+
# Copyright (c) 2019 Dan Halbert for Adafruit Industries
44
#
55
# Permission is hereby granted, free of charge, to any person obtaining a copy
66
# of this software and associated documentation files (the "Software"), to deal
@@ -23,68 +23,42 @@
2323
`adafruit_ble.uart`
2424
====================================================
2525
26-
UART-style communication.
26+
BLE UART-style communication. Common definitions.
2727
2828
* Author(s): Dan Halbert for Adafruit Industries
2929
3030
"""
31-
from bleio import UUID, Characteristic, Service, Peripheral, CharacteristicBuffer
32-
from .advertising import ServerAdvertisement
31+
from bleio import UUID, CharacteristicBuffer
3332

34-
class UARTServer:
33+
34+
35+
36+
class UART:
3537
"""
36-
Provide UART-like functionality via the Nordic NUS service.
38+
Common superclass for Nordic UART Service (NUS) clients or servers.
39+
Not for general use: use `UARTServer` and `UARTClient` for Peripheral and Central,
40+
respectively.
3741
42+
:param read_characteristic Characteristic: Characteristic to read from
43+
:param write_characteristic Characteristic: Characteristic to write to
3844
:param int timeout: the timeout in seconds to wait
39-
for the first character and between subsequent characters.
45+
for the first character and between subsequent characters
4046
:param int buffer_size: buffer up to this many bytes.
4147
If more bytes are received, older bytes will be discarded.
42-
:param str name: Name to advertise for server. If None, use default Peripheral name.
43-
44-
Example::
45-
46-
from adafruit_ble.uart import UARTServer
47-
uart = UARTServer()
48-
uart.start_advertising()
49-
50-
# Wait for a connection.
51-
while not uart.connected:
52-
pass
53-
54-
uart.write('abc')
5548
"""
5649

5750
NUS_SERVICE_UUID = UUID("6E400001-B5A3-F393-E0A9-E50E24DCCA9E")
51+
"""Nordic UART Service UUID"""
5852
NUS_RX_CHAR_UUID = UUID("6E400002-B5A3-F393-E0A9-E50E24DCCA9E")
53+
"""Nordic UART Service RX Characteristic UUID"""
5954
NUS_TX_CHAR_UUID = UUID("6E400003-B5A3-F393-E0A9-E50E24DCCA9E")
55+
"""Nordic UART Service TX Characteristic UUID"""
6056

61-
def __init__(self, *, timeout=1.0, buffer_size=64, name=None):
62-
self._nus_tx_char = Characteristic(self.NUS_TX_CHAR_UUID, notify=True)
63-
self._nus_rx_char = Characteristic(self.NUS_RX_CHAR_UUID,
64-
write=True, write_no_response=True)
65-
66-
nus_uart_service = Service(self.NUS_SERVICE_UUID, (self._nus_tx_char, self._nus_rx_char))
67-
68-
self._periph = Peripheral((nus_uart_service,), name=name)
69-
self._rx_buffer = CharacteristicBuffer(self._nus_rx_char,
70-
timeout=timeout, buffer_size=buffer_size)
71-
self._advertisement = ServerAdvertisement(self._periph)
72-
73-
def start_advertising(self):
74-
"""Start advertising the service. When a client connects, advertising will stop.
75-
When the client disconnects, restart advertising by calling ``start_advertising()`` again.
76-
"""
77-
self._periph.start_advertising(data=self._advertisement.advertising_data_bytes,
78-
scan_response=self._advertisement.scan_response_bytes)
79-
80-
def stop_advertising(self):
81-
"""Stop advertising the service."""
82-
self._periph.stop_advertising()
83-
84-
@property
85-
def connected(self):
86-
"""True if someone connected to the server."""
87-
return self._periph.connected
57+
def __init__(self, *, read_characteristic, write_characteristic, timeout=5.0, buffer_size=64):
58+
self._read_char = read_characteristic
59+
self._write_char = write_characteristic
60+
self._read_buffer = CharacteristicBuffer(self._read_char,
61+
timeout=timeout, buffer_size=buffer_size)
8862

8963
def read(self, nbytes=None):
9064
"""
@@ -95,7 +69,7 @@ def read(self, nbytes=None):
9569
:return: Data read
9670
:rtype: bytes or None
9771
"""
98-
return self._rx_buffer.read(nbytes)
72+
return self._read_buffer.read(nbytes)
9973

10074
def readinto(self, buf, nbytes=None):
10175
"""
@@ -105,7 +79,7 @@ def readinto(self, buf, nbytes=None):
10579
:return: number of bytes read and stored into ``buf``
10680
:rtype: int or None (on a non-blocking error)
10781
"""
108-
return self._rx_buffer.readinto(buf, nbytes)
82+
return self._read_buffer.readinto(buf, nbytes)
10983

11084
def readline(self):
11185
"""
@@ -114,21 +88,21 @@ def readline(self):
11488
:return: the line read
11589
:rtype: int or None
11690
"""
117-
return self._rx_buffer.readline()
91+
return self._read_buffer.readline()
11892

11993
@property
12094
def in_waiting(self):
12195
"""The number of bytes in the input buffer, available to be read."""
122-
return self._rx_buffer.in_waiting
96+
return self._read_buffer.in_waiting
12397

12498
def reset_input_buffer(self):
12599
"""Discard any unread characters in the input buffer."""
126-
self._rx_buffer.reset_input_buffer()
100+
self._read_buffer.reset_input_buffer()
127101

128102
def write(self, buf):
129103
"""Write a buffer of bytes."""
130104
# We can only write 20 bytes at a time.
131105
offset = 0
132106
while offset < len(buf):
133-
self._nus_tx_char.value = buf[offset:offset+20]
107+
self._write_char.value = buf[offset:offset+20]
134108
offset += 20

adafruit_ble/uart_client.py

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
# The MIT License (MIT)
2+
#
3+
# Copyright (c) 2019 Dan Halbert for Adafruit Industries
4+
#
5+
# Permission is hereby granted, free of charge, to any person obtaining a copy
6+
# of this software and associated documentation files (the "Software"), to deal
7+
# in the Software without restriction, including without limitation the rights
8+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
# copies of the Software, and to permit persons to whom the Software is
10+
# furnished to do so, subject to the following conditions:
11+
#
12+
# The above copyright notice and this permission notice shall be included in
13+
# all copies or substantial portions of the Software.
14+
#
15+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21+
# THE SOFTWARE.
22+
"""
23+
`adafruit_ble.uart_client`
24+
====================================================
25+
26+
UART-style communication by a Central as a GATT Client
27+
28+
* Author(s): Dan Halbert for Adafruit Industries
29+
30+
"""
31+
from bleio import Characteristic, Central
32+
from .uart import UART
33+
34+
class UARTClient(UART):
35+
"""
36+
Provide UART-like functionality via the Nordic NUS service.
37+
38+
:param int timeout: the timeout in seconds to wait
39+
for the first character and between subsequent characters.
40+
:param int buffer_size: buffer up to this many bytes.
41+
If more bytes are received, older bytes will be discarded.
42+
:param str name: Name to advertise for server. If None, use default Peripheral name.
43+
44+
Example::
45+
46+
from adafruit_ble.uart_client import UARTClient
47+
from adafruit_ble.scanner import Scanner, ScanEntry
48+
49+
scanner = Scanner()
50+
uarts = ScanEntry.with_service_uuid(scanner.scan_unique(3), UART.NUS_SERVICE_UUID)
51+
if not uarts:
52+
raise ValueError("No UART for connection")
53+
54+
uart_client = UARTClient()
55+
uart_client.connect(uarts[0].address, 5, service_uuids=(UART.NUS_SERVICE_UUID,))
56+
57+
uart_client.write('abc')
58+
"""
59+
60+
def __init__(self, *, timeout=5.0, buffer_size=64):
61+
# Since we're remote we receive on tx and send on rx. The names
62+
# are from the point of view of the server.
63+
super().__init__(read_characteristic=Characteristic(UART.NUS_TX_CHAR_UUID),
64+
write_characteristic=Characteristic(UART.NUS_RX_CHAR_UUID),
65+
timeout=timeout, buffer_size=buffer_size)
66+
67+
self._central = Central()
68+
69+
@property
70+
def connected(self):
71+
"""True if we are connected to a peripheral."""
72+
return self._central.connected
73+
74+
def connect(self, address, timeout):
75+
"""Try to connect to the peripheral at the given address.
76+
77+
:param bleio.Address address: The address of the peripheral to connect to
78+
:param float/int timeout: Try to connect for timeout seconds.
79+
"""
80+
self._central.connect(address, timeout, service_uuids=(UART.NUS_SERVICE_UUID,))
81+
82+
def disconnect(self):
83+
"""Disconnect from the peripheral."""
84+
self._central.disconnect()

adafruit_ble/uart_server.py

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
# The MIT License (MIT)
2+
#
3+
# Copyright (c) 2018 Dan Halbert for Adafruit Industries
4+
#
5+
# Permission is hereby granted, free of charge, to any person obtaining a copy
6+
# of this software and associated documentation files (the "Software"), to deal
7+
# in the Software without restriction, including without limitation the rights
8+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
# copies of the Software, and to permit persons to whom the Software is
10+
# furnished to do so, subject to the following conditions:
11+
#
12+
# The above copyright notice and this permission notice shall be included in
13+
# all copies or substantial portions of the Software.
14+
#
15+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21+
# THE SOFTWARE.
22+
"""
23+
`adafruit_ble.uart_server`
24+
====================================================
25+
26+
UART-style communication: Peripheral acting as a GATT Server.
27+
28+
* Author(s): Dan Halbert for Adafruit Industries
29+
30+
"""
31+
from bleio import Characteristic, Service, Peripheral
32+
from .advertising import ServerAdvertisement
33+
from .uart import UART
34+
35+
class UARTServer(UART):
36+
"""
37+
Provide UART-like functionality via the Nordic NUS service.
38+
39+
:param int timeout: the timeout in seconds to wait
40+
for the first character and between subsequent characters.
41+
:param int buffer_size: buffer up to this many bytes.
42+
If more bytes are received, older bytes will be discarded.
43+
:param str name: Name to advertise for server. If None, use default Peripheral name.
44+
45+
Example::
46+
47+
from adafruit_ble.uart_server import UARTServer
48+
uart = UARTServer()
49+
uart.start_advertising()
50+
51+
# Wait for a connection.
52+
while not uart.connected:
53+
pass
54+
55+
uart.write('abc')
56+
"""
57+
58+
def __init__(self, *, timeout=1.0, buffer_size=64, name=None):
59+
read_char = Characteristic(UART.NUS_RX_CHAR_UUID, write=True, write_no_response=True)
60+
write_char = Characteristic(UART.NUS_TX_CHAR_UUID, notify=True)
61+
super().__init__(read_characteristic=read_char, write_characteristic=write_char,
62+
timeout=timeout, buffer_size=buffer_size)
63+
64+
nus_uart_service = Service(UART.NUS_SERVICE_UUID, (read_char, write_char))
65+
66+
self._periph = Peripheral((nus_uart_service,), name=name)
67+
self._advertisement = ServerAdvertisement(self._periph)
68+
69+
def start_advertising(self):
70+
"""Start advertising the service. When a client connects, advertising will stop.
71+
When the client disconnects, restart advertising by calling ``start_advertising()`` again.
72+
"""
73+
self._periph.start_advertising(self._advertisement.advertising_data_bytes,
74+
scan_response=self._advertisement.scan_response_bytes)
75+
76+
def stop_advertising(self):
77+
"""Stop advertising the service."""
78+
self._periph.stop_advertising()
79+
80+
@property
81+
def connected(self):
82+
"""True if someone connected to the server."""
83+
return self._periph.connected

0 commit comments

Comments
 (0)