Skip to content

Commit d4913a4

Browse files
committed
Fix reconnects and multiple packets
1 parent bcd8db8 commit d4913a4

File tree

2 files changed

+160
-69
lines changed

2 files changed

+160
-69
lines changed

adafruit_ble_file_transfer.py

Lines changed: 75 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@ def __init__(self, service):
118118
self._service = service
119119

120120
def _write(self, buffer):
121+
# print("write", binascii.hexlify(buffer))
121122
sent = 0
122123
while sent < len(buffer):
123124
remaining = len(buffer) - sent
@@ -127,21 +128,22 @@ def _write(self, buffer):
127128

128129
def _readinto(self, buffer):
129130
read = 0
131+
long_buffer = bytearray(512)
130132
# Read back how much we can write
131133
while read == 0:
132134
try:
133135
read = self._service.raw.readinto(buffer)
134-
except ValueError as error:
135-
print(error)
136-
long_buffer = bytearray(512)
136+
except ValueError:
137137
read = self._service.raw.readinto(long_buffer)
138-
print("long packet", long_buffer[:read])
138+
buffer[:read] = long_buffer[:read]
139139
return read
140140

141141
def read(self, path, *, offset=0):
142142
"""Returns the contents of the file at the given path starting at the given offset"""
143+
# pylint: disable=too-many-locals
143144
path = path.encode("utf-8")
144145
chunk_size = CHUNK_SIZE
146+
start_offset = offset
145147
encoded = (
146148
struct.pack(
147149
"<BxHII", FileTransferService.READ, len(path), offset, chunk_size
@@ -150,30 +152,45 @@ def read(self, path, *, offset=0):
150152
)
151153
self._write(encoded)
152154
b = bytearray(struct.calcsize("<BBxxIII") + chunk_size)
153-
current_offset = offset
155+
current_offset = start_offset
156+
chunk_done = True
154157
content_length = None
155-
buf = None
158+
chunk_end = 0
159+
# pylint: disable=unsupported-assignment-operation
160+
buf = []
156161
data_header_size = struct.calcsize("<BBxxIII")
157162
while content_length is None or current_offset < content_length:
158163
read = self._readinto(b)
159-
(
160-
cmd,
161-
status,
162-
current_offset,
163-
content_length,
164-
_,
165-
) = struct.unpack_from("<BBxxIII", b)
166-
if cmd != FileTransferService.READ_DATA:
167-
raise ProtocolError("Incorrect reply")
168-
if status != FileTransferService.OK:
169-
raise ValueError("Missing file")
170-
if buf is None:
171-
buf = bytearray(content_length - offset)
172-
out_offset = current_offset - offset
173-
buf[out_offset : out_offset + (read - data_header_size)] = b[
174-
data_header_size:read
175-
]
176-
current_offset += read - data_header_size
164+
if chunk_done:
165+
(
166+
cmd,
167+
status,
168+
content_offset,
169+
content_length,
170+
chunk_length,
171+
) = struct.unpack_from("<BBxxIII", b)
172+
chunk_end = content_offset + chunk_length
173+
if cmd != FileTransferService.READ_DATA:
174+
print("error:", b)
175+
raise ProtocolError("Incorrect reply")
176+
if status != FileTransferService.OK:
177+
raise ValueError("Missing file")
178+
if not buf:
179+
buf = bytearray(content_length - start_offset)
180+
out_offset = current_offset - start_offset
181+
buf[out_offset : out_offset + (read - data_header_size)] = b[
182+
data_header_size:read
183+
]
184+
current_offset += read - data_header_size
185+
else:
186+
out_offset = current_offset - start_offset
187+
buf[out_offset : out_offset + read] = b[:read]
188+
current_offset += read
189+
190+
chunk_done = current_offset == chunk_end
191+
if not chunk_done:
192+
continue
193+
177194
chunk_size = min(CHUNK_SIZE, content_length - current_offset)
178195
if chunk_size == 0:
179196
break
@@ -206,6 +223,7 @@ def write(self, path, contents, *, offset=0):
206223
self._readinto(b)
207224
cmd, status, current_offset, free_space = struct.unpack("<BBxxII", b)
208225
if status != FileTransferService.OK:
226+
print("write error", status)
209227
raise RuntimeError()
210228
if (
211229
cmd != FileTransferService.WRITE_PACING
@@ -256,6 +274,7 @@ def mkdir(self, path):
256274

257275
def listdir(self, path):
258276
"""Returns a list of tuples, one tuple for each file or directory in the given path"""
277+
# pylint: disable=too-many-locals
259278
paths = []
260279
path = path.encode("utf-8")
261280
encoded = struct.pack("<BxH", FileTransferService.LISTDIR, len(path)) + path
@@ -264,31 +283,42 @@ def listdir(self, path):
264283
i = 0
265284
total = 10 # starting value that will be replaced by the first response
266285
header_size = struct.calcsize("<BBHIIII")
286+
path_length = 0
287+
encoded_path = b""
267288
while i < total:
268289
read = self._readinto(b)
269290
offset = 0
291+
file_size = 0
292+
flags = 0
270293
while offset < read:
271-
(
272-
cmd,
273-
status,
274-
path_length,
275-
i,
276-
total,
277-
flags,
278-
file_size,
279-
) = struct.unpack_from("<BBHIIII", b, offset=offset)
280-
if cmd != FileTransferService.LISTDIR_ENTRY:
281-
raise ProtocolError()
282-
if i >= total:
283-
break
284-
path = str(
285-
b[offset + header_size : offset + header_size + path_length],
286-
"utf-8",
287-
)
288-
paths.append((path, file_size, flags))
289-
offset += header_size + path_length
290-
if status != FileTransferService.OK:
291-
break
294+
if len(encoded_path) == path_length:
295+
if path_length > 0:
296+
path = str(
297+
encoded_path,
298+
"utf-8",
299+
)
300+
paths.append((path, file_size, flags))
301+
(
302+
cmd,
303+
status,
304+
path_length,
305+
i,
306+
total,
307+
flags,
308+
file_size,
309+
) = struct.unpack_from("<BBHIIII", b, offset=offset)
310+
offset += header_size
311+
encoded_path = b""
312+
if cmd != FileTransferService.LISTDIR_ENTRY:
313+
raise ProtocolError()
314+
if status != FileTransferService.OK:
315+
break
316+
if i >= total:
317+
break
318+
319+
path_read = min(path_length - len(encoded_path), read - offset)
320+
encoded_path += b[offset : offset + path_read]
321+
offset += path_read
292322
return paths
293323

294324
def delete(self, path):

examples/ble_file_transfer_simpletest.py

Lines changed: 85 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -5,55 +5,95 @@
55
Used with ble_uart_echo_test.py. Transmits "echo" to the UARTService and receives it back.
66
"""
77

8+
import binascii
89
import random
910
import time
1011

1112
from adafruit_ble import BLERadio
12-
from adafruit_ble.advertising.standard import ProvideServicesAdvertisement
13+
from adafruit_ble.advertising.standard import (
14+
ProvideServicesAdvertisement,
15+
Advertisement,
16+
)
1317
import adafruit_ble_file_transfer
1418

1519

1620
def _write(client, filename, contents, *, offset=0):
21+
# pylint: disable=redefined-outer-name
1722
start = time.monotonic()
18-
client.write(filename, contents, offset=offset)
19-
duration = time.monotonic() - start
23+
try:
24+
client.write(filename, contents, offset=offset)
25+
duration = time.monotonic() - start
26+
client = wait_for_reconnect()
27+
except RuntimeError:
28+
print("write failed. is usb connected?")
29+
return client
2030
print("wrote", filename, "at rate", len(contents) / duration, "B/s")
31+
return client
2132

2233

2334
def _read(client, filename, *, offset=0):
35+
# pylint: disable=redefined-outer-name
2436
start = time.monotonic()
25-
contents = client.read(filename, offset=offset)
26-
duration = time.monotonic() - start
37+
try:
38+
contents = client.read(filename, offset=offset)
39+
duration = time.monotonic() - start
40+
except ValueError:
41+
print("missing file:", filename)
42+
return b""
2743
print("read", filename, "at rate", len(contents) / duration, "B/s")
2844
return contents
2945

3046

3147
ble = BLERadio()
48+
49+
peer_address = None
50+
51+
52+
def wait_for_reconnect():
53+
print("waiting for disconnect")
54+
while ble.connected:
55+
pass
56+
print("reconnecting to", peer_address)
57+
new_connection = ble.connect(peer_address)
58+
print("reconnected")
59+
if not new_connection.paired:
60+
print("pairing")
61+
new_connection.pair()
62+
new_service = new_connection[adafruit_ble_file_transfer.FileTransferService]
63+
new_client = adafruit_ble_file_transfer.FileTransferClient(new_service)
64+
print("sleeping")
65+
time.sleep(2)
66+
return new_client
67+
68+
3269
# ble._adapter.erase_bonding()
3370
# print("erased")
3471
while True:
3572
try:
3673
while ble.connected:
3774
for connection in ble.connections:
38-
print(
39-
"services", connection._bleio_connection.discover_remote_services()
40-
)
75+
# pylint: disable=redefined-outer-name
4176
if adafruit_ble_file_transfer.FileTransferService not in connection:
4277
continue
4378
if not connection.paired:
4479
print("pairing")
4580
connection.pair()
81+
print("paired")
4682
service = connection[adafruit_ble_file_transfer.FileTransferService]
4783
client = adafruit_ble_file_transfer.FileTransferClient(service)
48-
_write(client, "/hello.txt", "Hello world".encode("utf-8"))
84+
client = _write(client, "/hello.txt", "Hello world".encode("utf-8"))
85+
time.sleep(1)
4986
c = _read(client, "/hello.txt")
5087
print(len(c), c)
51-
client.mkdir("/world/")
88+
try:
89+
client.mkdir("/world/")
90+
except ValueError:
91+
print("path exists or isn't valid")
5292
print(client.listdir("/world/"))
53-
_write(client, "/world/hi.txt", "Hi world".encode("utf-8"))
93+
client = _write(client, "/world/hi.txt", "Hi world".encode("utf-8"))
5494

5595
hello_world = "Hello world".encode("utf-8")
56-
_write(client, "/world/hello.txt", hello_world)
96+
client = _write(client, "/world/hello.txt", hello_world)
5797
c = _read(client, "/world/hello.txt")
5898
print(c)
5999

@@ -62,39 +102,60 @@ def _read(client, filename, *, offset=0):
62102
c = _read(client, "/world/hello.txt", offset=hello)
63103
print(c)
64104

65-
_write(client, "/world/hello.txt", "offsets!", offset=hello)
105+
client = _write(
106+
client, "/world/hello.txt", "offsets!".encode("utf-8"), offset=hello
107+
)
66108
c = _read(client, "/world/hello.txt", offset=0)
67109
print(c)
68110

69111
# Test deleting
70112
print(client.listdir("/world/"))
71-
client.delete("/world/hello.txt")
113+
try:
114+
client.delete("/world/hello.txt")
115+
except ValueError:
116+
print("exception correctly raised")
117+
72118
try:
73119
client.delete("/world/") # should raise an exception
74120
except ValueError:
75121
print("exception correctly raised")
76122
print(client.listdir("/world/"))
77-
client.delete("/world/hi.txt")
78-
client.delete("/world/")
123+
try:
124+
client.delete("/world/hi.txt")
125+
except ValueError:
126+
print("missing /world/hi.txt")
127+
try:
128+
client.delete("/world/")
129+
except ValueError:
130+
print("cannot delete /world/")
79131
print(client.listdir("/"))
80132

81133
large_1k = bytearray(1024)
82-
for i in range(len(large_1k)):
134+
for i, _ in enumerate(large_1k):
83135
large_1k[i] = random.randint(0, 255)
84-
_write(client, "/random.txt", large_1k)
85-
time.sleep(0.1)
136+
client = _write(client, "/random.txt", large_1k)
86137
contents = _read(client, "/random.txt")
87138
if large_1k != contents:
88-
print("large contents don't match!")
139+
print(binascii.hexlify(large_1k))
140+
print(binascii.hexlify(contents))
141+
raise RuntimeError("large contents don't match!")
89142
time.sleep(20)
90-
except ConnectionError:
143+
except ConnectionError as e:
91144
pass
92145

93146
print("disconnected, scanning")
94-
for advertisement in ble.start_scan(ProvideServicesAdvertisement, timeout=1):
95-
if adafruit_ble_file_transfer.FileTransferService not in advertisement.services:
147+
for advertisement in ble.start_scan(
148+
ProvideServicesAdvertisement, Advertisement, timeout=1
149+
):
150+
# print(advertisement.address, advertisement.address.type)
151+
if (
152+
not hasattr(advertisement, "services")
153+
or adafruit_ble_file_transfer.FileTransferService
154+
not in advertisement.services
155+
):
96156
continue
97157
ble.connect(advertisement)
98-
print("connected")
158+
peer_address = advertisement.address
159+
print("connected to", advertisement.address)
99160
break
100161
ble.stop_scan()

0 commit comments

Comments
 (0)