Как написать большое количество текста, чтобы написать характеристику - PullRequest
2 голосов
/ 18 апреля 2019

Я пишу приложение для 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.

EDIT

Моя новая реализация 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);
    }
}

Ответы [ 3 ]

4 голосов
/ 22 апреля 2019

Причина в том, что вы используете GattProperty.WriteNoResponse вместо GattProperty.Write.В варианте свойства без ответа клиент может использовать только команду ATT «Запись без ответа», которая ограничена MTU.В обычном варианте свойства записи клиент может использовать как запрос ATT «Запись с ответом», так и последовательность множественных подготовительных записей, за которыми следует запись выполнения, также известная как «Длинная запись».При длинных записях клиент (автоматически) разделяет запись на разные куски со смещениями.Обратите внимание, что длинная запись занимает значительно больше времени, чем если бы вы просто увеличивали MTU из-за необходимого количества многократных циклов.

2 голосов
/ 22 апреля 2019

Здесь есть две точки.

  1. почему он ограничен 20 байтами?

Базовая спецификация определяет MTU по умолчанию для ATT как23 байта.После удаления одного байта кода операции ATT и байта ручки ATT2 оставшиеся 20 байтов зарезервированы для GATT.Учитывая, что некоторые интеллектуальные устройства Bluetooth являются слабыми и не осмеливаются использовать слишком много места в памяти, спецификация ядра требует, чтобы каждое устройство поддерживало MTU 23. В начале соединения между двумя устройствами все как новыедруг, я не знаю, в порядке ли собеседник, так что строго следуйте порядку, то есть отправляйте до 20 байт за раз, что является самой большой страховкой.

Как пробить 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)
0 голосов
/ 22 апреля 2019

У меня было нечто подобное при чтении ответа - устройство BLE ограничивало каждое чтение до 20 байтов. Тем не менее, я мог бы преодолеть это, прочитав его в 20-байтовых чанках, а затем дождавшись завершающего символа перед обработкой данных.

Используете ли вы завершающий символ при записи? Когда вы пишете с вашей частичной строкой, вы получаете в результате событие OnCharacteristicChanged?

...