studio - Comunicación entre iOS y Android con Bluetooth LE
bluetooth low energy android studio tutorial (6)
Tengo una aplicación que funciona con CoreBluetooth para comunicarme entre un iPad (central) y un iPhone (periférico). Tengo un servicio que tiene dos características. Tengo un Nexus 7 con el último Android 4.3 con soporte BTLE. Android es un poco tarde para subirse al tren BTLE, pero parece que se están acercando a él de manera similar a como lo hizo iOS, donde inicialmente solo admiten actuar como central con el modo periférico disponible en una versión posterior. Puedo cargar la aplicación Android BTLE de muestra y buscar periféricos cercanos. Con mi publicidad de iPhone como un periférico puedo ver el valor de CBAdvertisementDataLocalNameKey en la lista de periféricos cercanos en el lado de Android. Puedo conectarme al iPhone y el símbolo de Bluetooth cambia de gris claro a negro cuando se establece la conexión. La conexión siempre dura exactamente 10 segundos y luego se desconecta. En el lado de Android, se supone que debo ver una lista de los servicios disponibles y las características que aparecen inmediatamente después de la conexión. Probé que el código de Android está configurado correctamente porque puedo conectarlo con el hardware TI CC2541DK-SENSOR que tengo y todos los servicios y características aparecen en la lista al conectarse.
Pasé los últimos días solucionando el problema sin éxito. El problema es que no puedo determinar qué dispositivo está experimentando un error y, por lo tanto, está causando la desconexión. No hay devoluciones de llamada desde CBPeripheralManagerDelegate durante la fase de conexión o la fase de descubrimiento del servicio, así que no tengo idea de en qué momento ocurre un error (si el error está en el lado de iOS). En el lado de Android, se llama a un método para iniciar el descubrimiento del servicio, pero nunca se llama a su devolución de llamada "onServicesDiscovered", lo cual es desconcertante. ¿Hay alguna manera de que pueda profundizar en las entrañas de la comunicación BTLE en el lado de iOS para ver qué está sucediendo y determinar qué error está ocurriendo?
Estoy haciendo algo similar con un Android central y un periférico iOS. Descubrí que se desconectarían si nada se suscribía a ninguno de los servicios del periférico.
No se olvide de actualizar el descriptor cuando se suscriba, en realidad no hace nada (es decir, llame al método de delegado en el lado de iOS).
public void setCharacteristicNotification(BluetoothGattCharacteristic characteristic, boolean enabled) {
if (mBluetoothAdapter == null || mBluetoothGatt == null) {
Log.v(TAG, "BluetoothAdapter not initialized");
return;
}
UUID uuid = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb"); // UUID for client config desc
BluetoothGattDescriptor descriptor = characteristic.getDescriptor(uuid);
descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
mBluetoothGatt.writeDescriptor(descriptor);
mBluetoothGatt.setCharacteristicNotification(characteristic, enabled);
}
También podría ser notable que ni siquiera podía ver el dispositivo iOS haciendo un escaneo BLE normal en el dispositivo Android (startLeScan), pero comenzar un escaneo BT Classic con un receptor de transmisión resolvió el problema (startDiscovery).
He escrito un ejemplo de trabajo simple, relativamente simple, y lo incluí en open-source en Github: https://github.com/GitGarage . Hasta ahora solo se ha probado con un Android Nexus 9 y un iPhone 5s, pero supongo que también funcionaría con un Nexus 6 y varios tipos de iPhone. Hasta ahora está configurado explícitamente para comunicarse entre un Android y un iPhone, pero supongo que es modificable para hacer mucho más.
Aquí están los métodos clave ...
DROID SIDE - Enviando a iOS:
private void sendMessage() {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
if (mBTAdapter == null) {
return;
}
if (mBTAdvertiser == null) {
mBTAdvertiser = mBTAdapter.getBluetoothLeAdvertiser();
}
// get the full message from the UI
String textMessage = mEditText.getText().toString();
if (textMessage.length() > 0)
{
// add ''Android'' as the user name
String message = "Android: " + textMessage;
while (message.length() > 0) {
String subMessage;
if(message.length() > 8)
{ // add dash to unfinished messages
subMessage = message.substring(0,8) + "-";
message = message.substring(8);
for (int i = 0; i < 20; i++) // twenty times (better safe than sorry) send this part of the message. duplicate parts will be ignored
{
AdvertiseData ad = BleUtil.makeAdvertiseData(subMessage);
mBTAdvertiser.startAdvertising(BleUtil.createAdvSettings(true, 100), ad, mAdvCallback);
mBTAdvertiser.stopAdvertising(mAdvCallback);
}
}
else
{ // otherwise, send the last part
subMessage = message;
message = "";
for (int i = 0; i < 5; i++)
{
AdvertiseData ad = BleUtil.makeAdvertiseData(subMessage);
mBTAdvertiser.startAdvertising(
BleUtil.createAdvSettings(true, 40), ad,
mAdvCallback);
mBTAdvertiser.stopAdvertising(mAdvCallback);
}
}
}
threadHandler.post(updateRunnable);
}
}
});
thread.start();
}
DROID SIDE - Recibiendo desde iOS:
@Override
public void onLeScan(final BluetoothDevice newDevice, final int newRssi,
final byte[] newScanRecord) {
int startByte = 0;
String hex = asHex(newScanRecord).substring(0,29);
// check five times, startByte was used for something else before
while (startByte <= 5) {
// check if this is a repeat message
if (!Arrays.asList(used).contains(hex)) {
used[ui] = hex;
String message = new String(newScanRecord);
String firstChar = message.substring(5, 6);
Pattern pattern = Pattern.compile("[ a-zA-Z0-9~!@#$%^&*()_+{}|:/"<>?`//-=;'',//.///[//]////]", Pattern.DOTALL);
// if the message is comprised of standard characters...
Matcher matcher = pattern.matcher(firstChar);
if (firstChar.equals("L"))
{
firstChar = message.substring(6, 7);
pattern = Pattern.compile("[ a-zA-Z0-9~!@#$%^&*()_+{}|:/"<>?`//-=;'',//.///[//]////]", Pattern.DOTALL);
matcher = pattern.matcher(firstChar);
}
if(matcher.matches())
{
TextView textViewToChange = (TextView) findViewById(R.id.textView);
String oldText = textViewToChange.getText().toString();
int len = 0;
String subMessage = "";
// add this portion to our final message
while (matcher.matches())
{
subMessage = message.substring(5, 6+len);
matcher = pattern.matcher(message.substring(5+len, 6+len));
len++;
}
subMessage = subMessage.substring(0,subMessage.length()-1);
Log.e("Address",newDevice.getAddress());
Log.e("Data",asHex(newScanRecord));
boolean enter = subMessage.length() == 16;
enter = enter && !subMessage.substring(15).equals("-");
enter = enter || subMessage.length() < 16;
textViewToChange.setText(oldText + subMessage.substring(0, subMessage.length() - 1) + (enter ? "/n" : ""));
ui = ui == 2 ? -1 : ui;
ui++;
Log.e("String", subMessage);
}
break;
}
startByte++;
}
}
LADO DE iOS - Enviando a Android:
func startAdvertisingToPeripheral() {
var allTime:UInt64 = 0;
if (dataToSend != nil)
{
datastring = NSString(data:dataToSend, encoding:NSUTF8StringEncoding) as String
datastring = "iPhone: " + datastring
if (datastring.length > 15)
{
for (var i:Double = 0; i < Double(datastring.length)/15.000; i++)
{
let delay = i/10.000 * Double(NSEC_PER_SEC)
let time = dispatch_time(DISPATCH_TIME_NOW, Int64(delay))
allTime = time
dispatch_after(time, dispatch_get_main_queue(), { () -> Void in self.sendPart() });
}
}
else
{
var messageUUID = StringToUUID(datastring)
if !peripheralManager.isAdvertising {
peripheralManager.startAdvertising([CBAdvertisementDataServiceUUIDsKey: [CBUUID(string: messageUUID)]])
}
}
}
}
iOS SIDE - Recibiendo desde Android:
func centralManager(central: CBCentralManager!, didDiscoverPeripheral peripheral: CBPeripheral!, advertisementData: [NSObject : AnyObject]!, RSSI: NSNumber!) {
delegate?.didDiscoverPeripheral(peripheral)
var splitUp = split("/(advertisementData)") {$0 == "/n"}
if (splitUp.count > 1)
{
var chop = splitUp[1]
chop = chop[0...chop.length-2]
var chopSplit = split("/(chop)") {$0 == "/""}
if !(chopSplit.count > 1 && chopSplit[1] == "Device Information")
{
var hexString = chop[4...7] + chop[12...19] + chop[21...26]
var datas = hexString.dataFromHexadecimalString()
var string = NSString(data: datas!, encoding: NSUTF8StringEncoding) as String
if (!contains(usedList,string))
{
usedList.append(string)
if (string.length == 9 && string[string.length-1...string.length-1] == "-")
{
finalString = finalString + string[0...string.length-2]
}
else
{
lastString = finalString + string + "/n"
println(lastString)
finalString = ""
usedList = newList
usedList.append(string)
}
}
}
}
}
Me gustaría agregar poca información a este hilo como parte de nuestro tema RnD sobre BLE entre plataforma cruzada.
El modo periférico funciona sin problemas con Xiomi Mi A1 (versión del sistema operativo Oreo, Android 8.0).
Aquí hay algunas observaciones sobre el rendimiento que encontramos durante nuestro RnD en el iPhone 8 y Xiomi Mi A1, pero todavía tiene que madurar con otro sistema operativo Android personalizado que se utiliza en el último Samsung S8. Los datos a continuación se basan en write_with_response.
iPhone 8 (BLE 5.0) como escritorio Central y Linux (Ubuntu 16.04 con BLE dongle 4.0): MTU = 2048: rendimiento - 2.5 Kilobytes por segundo.
iPhone 8 (BLE 5.0) como Sistema operativo Central y Android con BLE versión 4.2 como Periférico (Xiomi Mi A1): MTU = 180: Rendimiento - 2.5 KiloBytes por segundo.
iPhone 8 (BLE 5.0) como Central y iPhone 7 plus (BLE 4.2) como Periférico: MTU = 512: Rendimiento - 7.1 KiloBytes por segundo.
iPhone 8 (BLE 5.0) como Central y Samsung S8 (BLE 5.0) como periféricos: Samsung S8 no funcionó como periférico
iPhone 8 (BLE 5.0) como Central y iPhone 8 plus (BLE 5.0) como Periférico: MTU = 512: Rendimiento - 15.5 KiloBytes por segundo.
Solo quería compartir mi conocimiento sobre eso, ya que lo traté hace un tiempo y lo dejé porque no hay soporte por parte de google. El código antes mencionado, lo que agradezco mucho, no funciona. Puede codificar en un tiempo razonable un iOS a iOS o Android a una aplicación de Bluetooth para Android, pero el problema surge cuando intenta comunicarse entre iOS y Android. Existe un problema de Google bien documentado ( code.google.com/p/android/issues/… ) code.google.com/p/android/issues/… pero Google no se pronunció y parece que cerraron el problema y nada ha cambiado en Android M ya que he estado investigando el código y no puedo ver más diferencias. El problema surge cuando Android intenta conectarse y específicamente en una frase "if else else"; este código básicamente rechaza la transmisión y corta la comunicación para que no funcione. Por el momento, no hay solución para eso. Puede hacer una solución directa de WiFi, pero es una limitación y hay más problemas para hacerlo. El problema no existe si desea implementar BLE con hardware externo (frambuesa, sensores, etc.) pero no funciona entre iOS y Android. La tecnología es bastante similar en ambas plataformas, pero no está bien implementada en Android o el objetivo es que Google no abra una trampa para no abrir el espectro para comunicarse entre ambas plataformas.
Tal vez un poco retrasado, pero tal vez su dolor puede aliviarse ligeramente;)
Hemos estado experimentando mucho con conexiones BLE multiplataforma (iOS <-> Android) y aprendimos que todavía hay muchas incompatibilidades y problemas de conexión.
Si su caso de uso se basa en funciones y solo necesita un intercambio básico de datos, le sugiero que consulte Frameworks y Bibliotecas que pueden lograr la comunicación multiplataforma para usted, sin necesidad de crearlo desde cero.
Por ejemplo: http://p2pkit.io o google cerca
Descargo de responsabilidad: trabajo para Uepaa, desarrollando p2pkit.io para Android e iOS.
Ya he pasado por esto durante al menos una semana teniendo este mismo problema. Ya hice una pregunta aquí y ya he respondido por mi cuenta. El problema principal es un problema de ERROR de Android. Está enviando un comando no permitido en un canal fijo de L2CAP.
Pero cuando Android se comunica con dispositivos BLE periféricos normales, funciona bastante bien. De hecho, la muestra BLE funciona como un encanto. El problema es cuando se comunica con un dispositivo con iOS, por ejemplo: justo después de que se establece la conexión, comienzan a negociar sus parámetros de conexión (esta fase no ocurre con los periféricos BLE normales), y aquí es cuando aparece el problema. Android envía un mal comando a iOS, iOS desconecta la conexión. Eso es básicamente como funciona
Algunos problemas ya han sido informados a Google, y uno de ellos ya ha sido aceptado y espero que comiencen a trabajar en él pronto.
Desafortunadamente, lo que puedes hacer es esperar hasta el próximo lanzamiento de Android. De todos modos, le sugiero que eche un vistazo a mi informe de problemas con todos mis documentos de prueba si quiere aclarar este problema.
Aquí está el enlace: https://code.google.com/p/android/issues/detail?id=58725