Skip to content

Commit 71603dc

Browse files
committed
bluetooth: update the write process to slice the data based on the MTU size
It has been observed that, if a data packet bigger than the negotiated MTU size is sent using the Tx characteristic, the connection with the BLE device is lost after a small timeout. This commit fixes this scenario as follows: - The data packet to send is sliced using the MTU size minus 3 bytes as established in the Bluetooth core specification. - If any of the slices is not written correctly, the whole write operation is considered as failed. While on it, make the write_data method async by awaiting the inner write task. This avoids spurious micro-hangs while sending data to the BLE device. Signed-off-by: Diego Escalona <[email protected]> Signed-off-by: David Escalona <[email protected]>
1 parent f156e1f commit 71603dc

File tree

1 file changed

+52
-12
lines changed

1 file changed

+52
-12
lines changed

XBeeLibrary.Xamarin/Connection/Bluetooth/BluetoothInterface.cs

Lines changed: 52 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
using Plugin.BLE.Abstractions.Contracts;
2020
using Plugin.BLE.Abstractions.EventArgs;
2121
using System;
22+
using System.Collections.Generic;
2223
using System.Diagnostics;
2324
using System.Text.RegularExpressions;
2425
using System.Threading;
@@ -44,7 +45,7 @@ public class BluetoothInterface : IConnectionInterface
4445

4546
private static readonly int LENGTH_COUNTER = 16;
4647

47-
private static readonly int REQUESTED_MTU = 255;
48+
private static readonly int REQUESTED_MTU = 512; // Maximum ATT layer MTU size.
4849

4950
private static readonly string ERROR_INVALID_MAC_GUID = "Invalid MAC address or GUID, it has to follow the format 00112233AABB or " +
5051
"00:11:22:33:AA:BB for the MAC address or 01234567-0123-0123-0123-0123456789AB for the GUID";
@@ -413,7 +414,7 @@ public void WriteData(byte[] data)
413414
/// <param name="length">The number of bytes to write.</param>
414415
/// <exception cref="XBeeException">If there is any error writing data.</exception>
415416
/// <seealso cref="WriteData(byte[])"/>
416-
public void WriteData(byte[] data, int offset, int length)
417+
public async void WriteData(byte[] data, int offset, int length)
417418
{
418419
// Wait for other device operations.
419420
deviceSemaphore.Wait(WRITE_TIMEOUT);
@@ -423,24 +424,27 @@ public void WriteData(byte[] data, int offset, int length)
423424
throw new XBeeException(ERROR_CONNECTION_CLOSED);
424425
}
425426

426-
// Prepare data to write.
427-
Debug.WriteLine("----- WriteData " + HexUtils.ByteArrayToHexString(data));
428-
byte[] buffer = new byte[length];
429-
Array.Copy(data, offset, buffer, 0, length);
430-
byte[] dataToWrite = encrypt ? encryptor.TransformFinalBlock(buffer, 0, buffer.Length) : buffer;
431-
432427
// Abort the write operation if the write timeout expires.
433428
CancellationTokenSource cancelToken = new CancellationTokenSource(WRITE_TIMEOUT);
434429
bool success = false;
435-
Task writeTask = Task.Run(async () =>
430+
await Task.Run(async () =>
436431
{
437432
// According to BLE.Plugin API documentation, every write operation
438433
// must be executed in the main thread.
439434
await MainThread.InvokeOnMainThreadAsync(async () =>
440435
{
441436
try
442437
{
443-
success = await txCharacteristic.WriteAsync(dataToWrite, cancellationToken: cancelToken.Token);
438+
// Split the data in slices with a max length of the current MTU minus 3.
439+
foreach (byte[] slice in SliceData(data))
440+
{
441+
// Write the slices in the TX characteristic. Stop if one of them fails.
442+
Debug.WriteLine("----- WriteData " + HexUtils.ByteArrayToHexString(slice));
443+
byte[] dataToWrite = encrypt ? encryptor.TransformFinalBlock(slice, 0, slice.Length) : slice;
444+
success = await txCharacteristic.WriteAsync(dataToWrite, cancellationToken: cancelToken.Token);
445+
if (!success)
446+
throw new XBeeException(ERROR_WRITE);
447+
}
444448
}
445449
catch (Exception)
446450
{
@@ -452,8 +456,7 @@ await MainThread.InvokeOnMainThreadAsync(async () =>
452456
}
453457
});
454458
});
455-
// Wait for write task to complete.
456-
Task.WaitAll(writeTask);
459+
457460
// Free semaphore.
458461
deviceSemaphore.Release();
459462
// Check for error.
@@ -463,6 +466,43 @@ await MainThread.InvokeOnMainThreadAsync(async () =>
463466
}
464467
}
465468

469+
470+
/// <summary>
471+
/// Returns a list with the data slices of the given byte array. The
472+
/// maximum length of each slice is the negotiated MTU minus 3 bytes.
473+
///
474+
/// According to the Bluetooth core specification, the attribute protocol
475+
/// needs 3 bytes for the opcode (write command) and the attribute handle
476+
/// (which attribute to write to). So, the actual data length is always
477+
/// ATT_MTU minus 3.
478+
/// </summary>
479+
/// <param name="data">Data to get the slices from.</param>
480+
/// <returns>A list with the data slices.</returns>
481+
private List<byte[]> SliceData(byte[] data)
482+
{
483+
List<byte[]> slices = new List<byte[]>();
484+
485+
if (data.Length <= (mtu - 3))
486+
{
487+
slices.Add(data);
488+
}
489+
else
490+
{
491+
int i = 0;
492+
while (i < data.Length)
493+
{
494+
int remainingLength = data.Length - i;
495+
int bufferLength = remainingLength < (mtu - 3) ? remainingLength : (mtu - 3);
496+
byte[] buffer = new byte[bufferLength];
497+
Array.Copy(data, i, buffer, 0, bufferLength);
498+
slices.Add(buffer);
499+
i += bufferLength;
500+
}
501+
}
502+
503+
return slices;
504+
}
505+
466506
/// <summary>
467507
/// Returns the connection type of this bluetooth XBee interface.
468508
/// </summary>

0 commit comments

Comments
 (0)