Я пишу приложение для Android с Xamarin.Android, но ответ на родном Android также был бы признателен. В моем приложении для Android есть характеристика записи BLE, на которую устройства могут записывать. Это работает, но я не могу отправить больше 20 байтов, остальное обрезается. Мой код для создания и добавления услуги/характеристики:
BluetoothGattService service = new BluetoothGattService(Java.Util.UUID.FromString(MyServiceUuid), GattServiceType.Primary);
// write characteristic (write-only, supports subscriptions)
BluetoothGattCharacteristic writeCharacteristic = new BluetoothGattCharacteristic(Java.Util.UUID.FromString(MyCharacteristicUuid), GattProperty.WriteNoResponse | GattProperty.Notify, GattPermission.Write);
service.AddCharacteristic(writeCharacteristic);
_bluetoothGattServer.AddService(service);
Мой код на стороне, которая пишет в характеристику:
public override void OnServicesDiscovered(BluetoothGatt gatt, [GeneratedEnum] GattStatus status)
{
base.OnServicesDiscovered(gatt, status);
characteristic = gatt.GetService(Java.Util.UUID.FromString(MyServiceUuid))
.GetCharacteristic(Java.Util.UUID.FromString(MyCharacteristicUuid));
if(characteristic.Properties.HasFlag(GattProperty.WriteNoResponse))
{
Log?.Invoke("writing characteristic...");
characteristic.SetValue(MyVeryLongString);
characteristic.WriteType = GattWriteType.NoResponse;
gatt.WriteCharacteristic(characteristic);
}
}
И на стороне, которая принимает запрос на запись:
public override void OnCharacteristicWriteRequest(BluetoothDevice device, int requestId, BluetoothGattCharacteristic characteristic, bool preparedWrite, bool responseNeeded, int offset, byte[] value)
{
base.OnCharacteristicWriteRequest(device, requestId, characteristic, preparedWrite, responseNeeded, offset, value);
Log?.Invoke("OnCharacteristicWriteRequest");
string data = System.Text.Encoding.UTF8.GetString(value);
Log?.Invoke(data);
if(responseNeeded)
{
BluetoothGattServer.SendResponse(device, requestId, GattStatus.Success, 0, Encoding.ASCII.GetBytes("ok"));
}
}
Я вижу, что есть offset
, но эта функция вызывается только один раз. Я должен что-то упустить с одной стороны?
Самое смешное, что, когда я тестирую это приложение для Android с версией приложения для iOS, у меня нет этой проблемы. У меня эта проблема возникает только тогда, когда оба устройства Android.
РЕДАКТИРОВАТЬ
Моя новая реализация OnCharacteristicWriteRequest
:
public override void OnCharacteristicWriteRequest(BluetoothDevice device, int requestId, BluetoothGattCharacteristic characteristic, bool preparedWrite, bool responseNeeded, int offset, byte[] value)
{
base.OnCharacteristicWriteRequest(device, requestId, characteristic, preparedWrite, responseNeeded, offset, value);
Log?.Invoke("OnCharacteristicWriteRequest");
string data = System.Text.Encoding.UTF8.GetString(value);
Log?.Invoke(data);
Guid characteristicId = new Guid(characteristic.Uuid.ToString());
var record = _writeCharacteristicsReceived.FirstOrDefault(c => c.DeviceAddress == device.Address && c.CharacteristicId == characteristicId);
if(record != null)
{
record.Data += data;
}
else
{
record = new CharacteristicWriteReceived()
{
CharacteristicId = characteristicId,
DeviceAddress = device.Address,
Data = data
};
_writeCharacteristicsReceived.Add(record);
}
if (record?.Data.EndsWith(Constants.WriteCharacteristicEndDelimiter) == true)
{
_writeCharacteristicsReceived.Remove(record);
record.Data = record.Data.Substring(0, record.Data.Length - Constants.WriteCharacteristicEndDelimiter.Length); // remove the end delimeter
Log?.Invoke(record.Data);
OnCharacteristicWriteReceived?.Invoke(record);
}
if (responseNeeded)
{
BluetoothGattServer.SendResponse(device, requestId, GattStatus.Success, offset, value);
}
}
Причина в том, что вы используете GattProperty.WriteNoResponse вместо GattProperty.Write. В варианте свойства без ответа клиент может использовать только команду ATT "Запись без ответа", которая ограничена MTU. В обычном варианте свойства записи клиент может использовать как запрос ATT "Запись с ответом", так и последовательность множественных подготовительных записей, за которыми следует запись выполнения, также известная как "Длинная запись". При длинных записях клиент (автоматически) разделяет запись на разные куски со смещениями. Обратите внимание, что длинная запись занимает значительно больше времени, чем если бы вы просто увеличивали MTU из-за необходимого количества многократных циклов.
Вот два момента здесь.
Базовая спецификация определяет MTU по умолчанию для ATT равным 23 байта. После удаления одного байта кода операции ATT и байта ручки ATT2 оставшиеся 20 байтов зарезервированы для GATT. Учитывая, что некоторые интеллектуальные устройства Bluetooth являются слабыми и не осмеливаются использовать слишком много места в памяти, спецификация ядра требует, чтобы каждое устройство поддерживало MTU 23. В начале соединения между двумя устройствами все как новые друг, я не знаю, другая сторона в порядке, поэтому строго следуйте порядку, то есть отправляйте до 20 байтов за раз, что является самой большой страховкой.
Поскольку максимальная длина ATT составляет 512 байтов, достаточно изменить MTU передаваемого ATT. В Android (API 21) интерфейс для изменения MTU ATT:
public boolean requestMtu (int mtu)
#Added in API level 21
#Request an MTU size used for a given connection.
#When performing a write request operation (write without response), the data sent is truncated to the MTU size. This function may be used to request a larger MTU size to be able to send more data at once.
#A onMtuChanged(BluetoothGatt, int, int) callback will indicate whether this operation was successful.
#Requires BLUETOOTH permission.
#Returns true, if the new MTU value has been requested successfully
Если ваше периферийное приложение меняет MTU и успешно, то этот обратный вызов также будет вызван.
@Override
public void onMtuChanged(BluetoothGatt gatt, int mtu, int status) {
super.onMtuChanged(gatt, mtu, status);
if (status == BluetoothGatt.GATT_SUCCESS) {
this.supportedMTU = mtu;//local var to record MTU size
}
}
После этого вы можете счастливо отправить длину поддерживаемых данных MTU. Вверху - пример кода Java, вот документ Xamarin для Android для справки. https://developer.xamarin.com/api/member/Android.Bluetooth.BluetoothGatt.RequestMtu/p/System.Int32/
public Boolean RequestMtu (Int32 mtu)
При чтении ответа у меня было нечто похожее - устройство BLE ограничивало каждое чтение до 20 байтов. Тем не менее, я мог бы преодолеть это, читая его в 20-байтовых чанках и затем ожидая завершающего символа перед обработкой данных.
Вы используете завершающий символ при написании? Когда вы пишете с вашей частичной строкой, вы получаете в результате событие OnCharacteristicChanged?
OnCharacteristicWriteRequest
только один раз.
OnCharacteristicWriteRequest
тестирования мой OnCharacteristicWriteRequest
вызов OnCharacteristicWriteRequest
был вызван дважды. Но я смог воспроизвести это только один раз. Теперь он вернулся к тому, чтобы быть вызванным только один раз. Кажется, он ведет себя непредсказуемо ... Я попробовал его с третьим устройством Android на случай, если оно неисправно, но все равно вызывается только один раз.
GattProperty.Write
но у меня все еще есть та же проблема. Я предпочел бы иметь «длинную запись», чем увеличивать MTU на данный момент.