Skip to content

Commit f152527

Browse files
committed
port ble file transfer
1 parent bbef3eb commit f152527

File tree

2 files changed

+362
-0
lines changed

2 files changed

+362
-0
lines changed
Lines changed: 331 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,331 @@
1+
/* Copyright 2021 The TensorFlow Authors. All Rights Reserved.
2+
Licensed under the Apache License, Version 2.0 (the "License");
3+
you may not use this file except in compliance with the License.
4+
You may obtain a copy of the License at
5+
http://www.apache.org/licenses/LICENSE-2.0
6+
Unless required by applicable law or agreed to in writing, software
7+
distributed under the License is distributed on an "AS IS" BASIS,
8+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9+
See the License for the specific language governing permissions and
10+
limitations under the License.
11+
==============================================================================*/
12+
13+
/*
14+
* Original here: https://github.com/petewarden/ble_file_transfer
15+
* Modified from original:
16+
* - added name to namespace
17+
* - moved to separate file with a header file
18+
* - moved BLEService outside of namespace
19+
*/
20+
21+
#include "ble_file_transfer.h"
22+
23+
// This is part of a simple demonstration of how to transfer small (tens of
24+
// kilobytes) files over BLE onto an Arduino Nano BLE Sense board. Most of this
25+
// sketch is internal implementation details of the protocol, but if you just
26+
// want to use it you can look at the bottom of this file.
27+
// The API is that you call setupBLEFileTransfer() in your setup() function to
28+
// open up communication with any clients that want to send you files, and then
29+
// onBLEFileReceived() is called when a file has been downloaded.
30+
31+
32+
// Comment this macro back in to log received data to the serial UART.
33+
//#define ENABLE_LOGGING
34+
35+
namespace ble_file_transfer {
36+
37+
// Controls how large a file the board can receive. We double-buffer the files
38+
// as they come in, so you'll need twice this amount of RAM. The default is set
39+
// to 50KB.
40+
constexpr int32_t file_maximum_byte_count = (50 * 1024);
41+
42+
// Macro based on a master UUID that can be modified for each characteristic.
43+
#define FILE_TRANSFER_UUID(val) ("bf88b656-" val "-4a61-86e0-769c741026c0")
44+
45+
// How big each transfer block can be. In theory this could be up to 512 bytes, but
46+
// in practice I've found that going over 128 affects reliability of the connection.
47+
constexpr int32_t file_block_byte_count = 128;
48+
49+
// Where each data block is written to during the transfer.
50+
BLECharacteristic file_block_characteristic(FILE_TRANSFER_UUID("3000"), BLEWrite, file_block_byte_count);
51+
52+
// Write the expected total length of the file in bytes to this characteristic
53+
// before sending the command to transfer a file.
54+
BLECharacteristic file_length_characteristic(FILE_TRANSFER_UUID("3001"), BLERead | BLEWrite, sizeof(uint32_t));
55+
56+
// Read-only attribute that defines how large a file the sketch can handle.
57+
BLECharacteristic file_maximum_length_characteristic(FILE_TRANSFER_UUID("3002"), BLERead, sizeof(uint32_t));
58+
59+
// Write the checksum that you expect for the file here before you trigger the transfer.
60+
BLECharacteristic file_checksum_characteristic(FILE_TRANSFER_UUID("3003"), BLERead | BLEWrite, sizeof(uint32_t));
61+
62+
// Writing a command of 1 starts a file transfer (the length and checksum characteristics should already have been set).
63+
// A command of 2 tries to cancel any pending file transfers. All other commands are undefined.
64+
BLECharacteristic command_characteristic(FILE_TRANSFER_UUID("3004"), BLEWrite, sizeof(uint32_t));
65+
66+
// A status set to 0 means a file transfer succeeded, 1 means there was an error, and 2 means a file transfer is
67+
// in progress.
68+
BLECharacteristic transfer_status_characteristic(FILE_TRANSFER_UUID("3005"), BLERead | BLENotify, sizeof(uint32_t));
69+
70+
// Informative text describing the most recent error, for user interface purposes.
71+
constexpr int32_t error_message_byte_count = 128;
72+
BLECharacteristic error_message_characteristic(FILE_TRANSFER_UUID("3006"), BLERead | BLENotify, error_message_byte_count);
73+
74+
// Internal globals used for transferring the file.
75+
uint8_t file_buffers[2][file_maximum_byte_count];
76+
int finished_file_buffer_index = -1;
77+
uint8_t* finished_file_buffer = nullptr;
78+
int32_t finished_file_buffer_byte_count = 0;
79+
80+
uint8_t* in_progress_file_buffer = nullptr;
81+
int32_t in_progress_bytes_received = 0;
82+
int32_t in_progress_bytes_expected = 0;
83+
uint32_t in_progress_checksum = 0;
84+
85+
void notifyError(const String& error_message) {
86+
Serial.println(error_message);
87+
constexpr int32_t error_status_code = 1;
88+
transfer_status_characteristic.notify32((int) error_status_code);
89+
90+
const char* error_message_bytes = error_message.c_str();
91+
uint8_t error_message_buffer[error_message_byte_count];
92+
bool at_string_end = false;
93+
for (int i = 0; i < error_message_byte_count; ++i) {
94+
const bool at_last_byte = (i == (error_message_byte_count - 1));
95+
if (!at_string_end && !at_last_byte) {
96+
const char current_char = error_message_bytes[i];
97+
if (current_char == 0) {
98+
at_string_end = true;
99+
} else {
100+
error_message_buffer[i] = current_char;
101+
}
102+
}
103+
104+
if (at_string_end || at_last_byte) {
105+
error_message_buffer[i] = 0;
106+
}
107+
}
108+
error_message_characteristic.notify(error_message_buffer, error_message_byte_count);
109+
}
110+
111+
void notifySuccess() {
112+
constexpr int32_t success_status_code = 0;
113+
transfer_status_characteristic.notify32( (int) success_status_code);
114+
}
115+
116+
void notifyInProgress() {
117+
constexpr int32_t in_progress_status_code = 2;
118+
transfer_status_characteristic.notify32( (int) in_progress_status_code);
119+
}
120+
121+
// See http://home.thep.lu.se/~bjorn/crc/ for more information on simple CRC32 calculations.
122+
uint32_t crc32_for_byte(uint32_t r) {
123+
for (int j = 0; j < 8; ++j) {
124+
r = (r & 1? 0: (uint32_t)0xedb88320L) ^ r >> 1;
125+
}
126+
return r ^ (uint32_t)0xff000000L;
127+
}
128+
129+
uint32_t crc32(const uint8_t* data, size_t data_length) {
130+
constexpr int table_size = 256;
131+
static uint32_t table[table_size];
132+
static bool is_table_initialized = false;
133+
if (!is_table_initialized) {
134+
for(size_t i = 0; i < table_size; ++i) {
135+
table[i] = crc32_for_byte(i);
136+
}
137+
is_table_initialized = true;
138+
}
139+
uint32_t crc = 0;
140+
for (size_t i = 0; i < data_length; ++i) {
141+
const uint8_t crc_low_byte = static_cast<uint8_t>(crc);
142+
const uint8_t data_byte = data[i];
143+
const uint8_t table_index = crc_low_byte ^ data_byte;
144+
crc = table[table_index] ^ (crc >> 8);
145+
}
146+
return crc;
147+
}
148+
149+
// This is a small test function for the CRC32 implementation, not normally called but left in
150+
// for debugging purposes. We know the expected CRC32 of [97, 98, 99, 100, 101] is 2240272485,
151+
// or 0x8587d865, so if anything else is output we know there's an error in the implementation.
152+
void testCrc32() {
153+
constexpr int test_array_length = 5;
154+
const uint8_t test_array[test_array_length] = {97, 98, 99, 100, 101};
155+
const uint32_t test_array_crc32 = crc32(test_array, test_array_length);
156+
Serial.println(String("CRC32 for [97, 98, 99, 100, 101] is 0x") + String(test_array_crc32, 16) +
157+
String(" (") + String(test_array_crc32) + String(")"));
158+
}
159+
160+
void onFileTransferComplete() {
161+
uint32_t computed_checksum = crc32(in_progress_file_buffer, in_progress_bytes_expected);;
162+
if (in_progress_checksum != computed_checksum) {
163+
notifyError(String("File transfer failed: Expected checksum 0x") + String(in_progress_checksum, 16) +
164+
String(" but received 0x") + String(computed_checksum, 16));
165+
in_progress_file_buffer = nullptr;
166+
return;
167+
}
168+
169+
if (finished_file_buffer_index == 0) {
170+
finished_file_buffer_index = 1;
171+
} else {
172+
finished_file_buffer_index = 0;
173+
}
174+
finished_file_buffer = &file_buffers[finished_file_buffer_index][0];;
175+
finished_file_buffer_byte_count = in_progress_bytes_expected;
176+
177+
in_progress_file_buffer = nullptr;
178+
in_progress_bytes_received = 0;
179+
in_progress_bytes_expected = 0;
180+
181+
notifySuccess();
182+
183+
onBLEFileReceived(finished_file_buffer, finished_file_buffer_byte_count);
184+
}
185+
186+
void onFileBlockWritten(uint16_t conn_hdl, BLECharacteristic* characteristic, uint8_t* data, uint16_t len) {
187+
if (in_progress_file_buffer == nullptr) {
188+
notifyError("File block sent while no valid command is active");
189+
return;
190+
}
191+
192+
const int32_t file_block_length = len;
193+
if (file_block_length > file_block_byte_count) {
194+
notifyError(String("Too many bytes in block: Expected ") + String(file_block_byte_count) +
195+
String(" but received ") + String(file_block_length));
196+
in_progress_file_buffer = nullptr;
197+
return;
198+
}
199+
200+
const int32_t bytes_received_after_block = in_progress_bytes_received + file_block_length;
201+
if ((bytes_received_after_block > in_progress_bytes_expected) ||
202+
(bytes_received_after_block > file_maximum_byte_count)) {
203+
notifyError(String("Too many bytes: Expected ") + String(in_progress_bytes_expected) +
204+
String(" but received ") + String(bytes_received_after_block));
205+
in_progress_file_buffer = nullptr;
206+
return;
207+
}
208+
209+
uint8_t* file_block_buffer = in_progress_file_buffer + in_progress_bytes_received;
210+
memcpy(file_block_buffer, data, file_block_length);
211+
212+
// Enable this macro to show the data in the serial log.
213+
#ifdef ENABLE_LOGGING
214+
Serial.print("Data received: length = ");
215+
Serial.println(file_block_length);
216+
217+
char string_buffer[file_block_byte_count + 1];
218+
for (int i = 0; i < file_block_byte_count; ++i) {
219+
unsigned char value = file_block_buffer[i];
220+
if (i < file_block_length) {
221+
string_buffer[i] = value;
222+
} else {
223+
string_buffer[i] = 0;
224+
}
225+
}
226+
string_buffer[file_block_byte_count] = 0;
227+
Serial.println(String(string_buffer));
228+
#endif // ENABLE_LOGGING
229+
230+
if (bytes_received_after_block == in_progress_bytes_expected) {
231+
onFileTransferComplete();
232+
} else {
233+
in_progress_bytes_received = bytes_received_after_block;
234+
}
235+
}
236+
237+
void startFileTransfer() {
238+
if (in_progress_file_buffer != nullptr) {
239+
notifyError("File transfer command received while previous transfer is still in progress");
240+
return;
241+
}
242+
243+
int32_t file_length_value = (int32_t) file_length_characteristic.read32();
244+
if (file_length_value > file_maximum_byte_count) {
245+
notifyError(
246+
String("File too large: Maximum is ") + String(file_maximum_byte_count) +
247+
String(" bytes but request is ") + String(file_length_value) + String(" bytes"));
248+
return;
249+
}
250+
251+
in_progress_checksum = file_checksum_characteristic.read32();
252+
253+
int in_progress_file_buffer_index;
254+
if (finished_file_buffer_index == 0) {
255+
in_progress_file_buffer_index = 1;
256+
} else {
257+
in_progress_file_buffer_index = 0;
258+
}
259+
260+
in_progress_file_buffer = &file_buffers[in_progress_file_buffer_index][0];
261+
in_progress_bytes_received = 0;
262+
in_progress_bytes_expected = file_length_value;
263+
264+
notifyInProgress();
265+
}
266+
267+
void cancelFileTransfer() {
268+
if (in_progress_file_buffer != nullptr) {
269+
notifyError("File transfer cancelled");
270+
in_progress_file_buffer = nullptr;
271+
}
272+
}
273+
274+
void onCommandWritten(uint16_t conn_hdl, BLECharacteristic* characteristic, uint8_t* data, uint16_t len) {
275+
int32_t command_value = (int32_t) characteristic->read32();
276+
277+
if ((command_value != 1) && (command_value != 2)) {
278+
notifyError(String("Bad command value: Expected 1 or 2 but received ") + String(command_value));
279+
return;
280+
}
281+
282+
if (command_value == 1) {
283+
startFileTransfer();
284+
} else if (command_value == 2) {
285+
cancelFileTransfer();
286+
}
287+
288+
}
289+
290+
// Starts the BLE handling you need to support the file transfer.
291+
void setupBLEFileTransfer(BLEService service) {
292+
293+
294+
// Add in the characteristics we'll be making available.
295+
file_block_characteristic.setWriteCallback(onFileBlockWritten);
296+
file_block_characteristic.begin();
297+
298+
file_length_characteristic.begin();
299+
300+
file_maximum_length_characteristic.write32((int) file_maximum_byte_count);
301+
file_maximum_length_characteristic.begin();
302+
303+
file_checksum_characteristic.begin();
304+
305+
command_characteristic.setWriteCallback(onCommandWritten);
306+
command_characteristic.begin();
307+
308+
transfer_status_characteristic.begin();
309+
error_message_characteristic.begin();
310+
311+
Serial.println("BLE setup and advertising");
312+
}
313+
314+
315+
// Called in your loop function to handle BLE housekeeping.
316+
void updateBLEFileTransfer() {
317+
// BLEDevice central = BLE.central();
318+
// static bool was_connected_last = false;
319+
// if (central && !was_connected_last) {
320+
// Serial.print("Connected to central: ");
321+
// Serial.println(central.address());
322+
// }
323+
// was_connected_last = central;
324+
}
325+
326+
327+
bool isTransfering(){
328+
return in_progress_file_buffer != nullptr;
329+
}
330+
331+
} // namespace
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/* Copyright 2021 Google LLC
2+
3+
Licensed under the Apache License, Version 2.0 (the "License");
4+
you may not use this file except in compliance with the License.
5+
You may obtain a copy of the License at
6+
7+
https://www.apache.org/licenses/LICENSE-2.0
8+
9+
Unless required by applicable law or agreed to in writing, software
10+
distributed under the License is distributed on an "AS IS" BASIS,
11+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
See the License for the specific language governing permissions and
13+
limitations under the License.
14+
==============================================================================*/
15+
16+
#ifndef BLE_FILE_TRANSFER_CPP
17+
#define BLE_FILE_TRANSFER_CPP
18+
19+
//#include <ArduinoBLE.h>
20+
#include <bluefruit.h>
21+
22+
// Forward declare the function that will be called when data has been delivered to us.
23+
void onBLEFileReceived(uint8_t* file_data, int file_length);
24+
25+
namespace ble_file_transfer{
26+
void updateBLEFileTransfer();
27+
bool isTransfering();
28+
void setupBLEFileTransfer(BLEService service);
29+
}
30+
31+
#endif // BLE_FILE_TRANSFER_CPP

0 commit comments

Comments
 (0)