I am using Samsung Galaxy S4 running Android 4.3
I am trying to convert an app that ran on Samsung's LEB API with Android 4.2.2.
I can discover and connect (connectGatt) to the peripheral and run all "client" code successfully, provided I do not try to connect as a server.
Whenever I connected
mBluetoothGattServer = mBluetoothManager.openGattServer(this, mGattServerCallbacks);
the LogCat's indicated that the communication with BluetoothGatt cease.
I've tried to openGattServer after all the Gatt services are discovered. I attempt to addServices in the GattServerCallback onConnectionStateChange. The code runs but I do not get an onServiceAdded callback
Both the "gatt client" and "gatt server" code in my app fails to run.
I do get onConnectionStateChange callbacks for both.
How, when and where (MainActivity or BLEService) do I instantiate mBluetoothGattServer?
Is this documented somewhere? With some sample code?
The first part of my question is resolved temporarily. I started a new thread, added a one second delay, then called openGattServer. My "client" code now works fine. (I am not happy with this - it was a try generated out of frustration - I may try using a queue if I can get server code to work).
I do get a GattServerCallback for onConnectionStateChange.
However, I do not seem to have yet got any "server" code running.
I have found a temporary solution.
When I instantiated the mBluetoothGattServer object, the callback "onConnectionStateChange" was called while mBluetoothGattServer = null (still). This was a surprise as instantiating the object caused the callback to be registered. I put a delay into the code of onConnectionStateChange until mBluetoothGattServer != null (with suitable limit on number of tries) and was able to add a service successfully. At the time of writing it appears that I will have to add services one at a time and wait for the onServiceAdded callback before adding each service (this is not yet tested)
I must say the Samsung BLE SDK was easier to understand and manage.
You should initialize the BluetoothGattServer before calling connectGatt on any BluetoothDevice :
mBluetoothGattServer = mBluetoothManager.openGattServer(self, mGattServerCallbacks);
mBluetoothGatt = device.connectGatt(this, false, mGattCallbacks);
The fact is you only need one BluetoothGattServer instance, and the callbacks in mGattServerCallbacks will be called simultaneously with those in mGattCallbacks.
When you connect a device, the methods called onConnectionStateChange will be called in both callbacks wrappers.
Related
I've been working with flutter for a few weeks now and am attempting to build an app to communicate with a power bank over BLE. In its current state, it works correctly on iOS. On android, partly due to the BLE device's behavior, I'm having exceptions thrown during the connect/discover phases.
Details and Versioning
Flutter: 1.17.5
flutter_blue: 0.7.2
target Android device: Google Pixel 3A running Android 10
target iOS device: iPhone XS running 13.6, iPhone 6s running iOS 12
Code
void connect(String deviceId) async {
var dev = devices[deviceId];
if (connectedDevices[deviceId] != null) return;
await dev.device.connect(autoConnect: false);
dev.connection = dev.device.state.listen((state) async {
dev.deviceState = state;
notifyListeners();
if (state == BluetoothDeviceState.disconnected) {
connectedDevices[dev.id] = null;
await dev.dispose();
notifyListeners();
}
if (state == BluetoothDeviceState.connected) {
dev.services = await dev.device.discoverServices();
for (BluetoothService service in dev.services) {
// set services based on uuid
}
for (BluetoothCharacteristic characteristic
in dev.deviceInfoService.characteristics) {
// set characteristics from services
}
for (BluetoothCharacteristic characteristic
in dev.notificationService.characteristics) {
switch (characteristic.uuid.toString()) {
case notificationServiceCharacteristic:
dev.notificationServiceCharacteristic = characteristic;
if (!dev.notificationServiceCharacteristic.isNotifying) {
await dev.notificationServiceCharacteristic
.setNotifyValue(true);
dev.valueChangedSubscription = dev
.notificationServiceCharacteristic.value
.listen((value) {
_onValuesChanged(dev, value);
notifyListeners();
});
connectedDevices[dev.id] = dev;
}
break;
case writeCharacteristic:
dev.writeCharacteristic = characteristic;
break;
default:
break;
}
}
notifyListeners(); //using scopedModel for handling state
await readServiceCharacteristics(dev);
}
});
}
What should happen
The program connects to the device. Upon connecting (state == BluetoothDeviceState.connected), services and characteristics are discovered. When the notification service is found, turn notifications on with a callback to process data. After doing this, read from read-only characteristics.
What is happening
On iOS devices, there are no apparent errors. The program functions normally; albeit with the initial disconnect caused by the BLE device. Attempting to reconnect works successfully without fail.
On Android:
Exception thrown: /flutter ( 1557): [ERROR:flutter/lib/ui/ui_dart_state.cc(157)] Unhandled Exception: PlatformException(set_notification_error, error when writing the descriptor, null)
Line responsible:
await dev.notificationServiceCharacteristic
.setNotifyValue(true);
Why is happening / BLE device bug
In my research, I seem to have figured out one of the causes of the bug. The BLE device, for whatever reason, does not complete the connection process on the first connection attempt. Typical connections work as follows:
Connect called
Device Connected
Service Discovery / Notification section of code is called
Device is disconnected
Connect called again
Discovery / Notification section runs properly and device maintains connection
Program receives notifications
Debugging Steps
Firstly, I switch from a forEach to a for(BluetoothService service in dev.services) "for-in" style as this ensures the iteration waits for callback/return.
In debugging, I can use breakpoints to assure the program doesn't throw an exception. By placing a breakpoint at the await line and waiting for a second or two, it works without issue. Running the code without a breakpoint set throws the error every time, without fail.
I've built a bare-bones PoC with just RaisedButtons to call the init, scan, connect, and disconnect functions. The error is still present
It's clear that in the StreamListener for the device's state that it goes from
Connected
Disconnected (at this point I can logically stop by disconnecting and disposing the connection or letting it attempt again by commenting this out)
In the case I don't dispose and disconnect, it switches to connected again
Thoughts
While I know that this issue is partially caused by the BLE device itself, I'm wondering if there's any way to fix this or workaround it. One idea I'm thinking of is a means to terminate the asynchronous calls and set some sort of flag so that, on the subsequent call, the program knows to cleanup and/or delay to ensure it works without issue.
There's some github issues that I've looked at that seem to be running into similar problems - solutions are generally forced delays or (in a perfect world) not having a device that disconnects initially. I'll link some for context:
PlatformException(set_notification_error, error when writing the descriptor, null) on setNotifyValue #295
Characteristics are sending duplicate notifications when device is reconnected #525 (Not exactly the same, but duplicate firing makes me think it's not too far off)
Either way, thanks for reading and I hope to be past this thorn in my side soon!
I don't want to offend you, but your code is very hard to read. First I suggest that you should use some conventions. Like Future, curly braces after if, break that enormous method into smaller ones.
I have seen this error before. Here I suggest some options to solve.
In the switch, you are starting to listen to one stream. At the end
of your function, you are trying to read a characteristic. I'm not
sure if your POC supports more than one method at the same time. I
would remove readServiceCharacteristics while you listen to the
stream or start with reading and wait until it's finished and then
start the stream listener.
It's hard to tell what is happening when
you notify the listeners perhaps you try to read or write on the BLE
device. Remove it. See what happens.
So basically try to use the characteristics one by one and try to avoid parallel requests.
Hope it help!
I have an issue with the current 2019 Samsung Galaxy devices (S10). I can make a write to a Bluetooth Characteristic once. Then, if I try the call again, the onConnectionStateChange override method returns a status of "8". According to Google this means GATT_CONNECTION_CONGESTED. I can validate the first call works too. Also, soon after the congested errors returns, the phone disconnects from the app.
After pulling Samsung's bluetooth logs using adb shell dumpsys bluetooth_manager, I can see this error:
BluetoothRemoteDevices -- ACTION_ACL_DISCONNECTED, device is 30AEA4,
reason is 8, linktype is 2
Weirdly, I can use just about any other Android device, Pixel, Essential, older Samsungs, and not one of them returns this error message after a second call and work as expected. All of the current line of Samsung Galaxy devices all seems throw this error. Does anyone know what this error message means exactly and how to avoid it?
I am building an app to read data from a ble device. After connecting to a device I call mBluetoothGatt.discoverServices() and in onServicesDiscovered(..) I am reading the list of service by calling getServices().
The problem is sometimes the list is empty.Has anyone faced this issue.The issue is random and not 100% reproducible.
Yes,this is a Common problem.If you get empty services,you can retry discover .
If you get empty more than 3 or 5 times,you should restart you Bluetooth,because it is may be unstable
I just run into this, but even retrying the discovery, gatt services were empty. For me the solution was to restart both BLE and Android devices.
using the android 4.4 BLE APIs on my Nexus7, i'm able to successfully interact with a peripheral BLE device -- connect, disconnect, read, write....
if however an active connection breaks for whatever reason (in this case, the peripheral is reset), i observe the following behavior....
my peripheral (by design) begins advertising after any active connection is terminated (for whatever reason); i can see this via my bluetooth packet sniffer....
i receive the onConnectionStateChanged callback as expected in my android app, at which point i invoke close() on my active BluetoothGatt instance; this is the same procedure i follow during a "normal" disconnect initiated from the client...
shortly after this, the android BLE stack tries to re-connect to the same peripheral; through the packet sniffer i can see the BLE connection request going out over the air...
my app, however, did not initiate this re-connection; indeed, i see no information from any bluetooth log suggesting this even happened!!!!
is there some "mode" in the BLE stack where it attempts to re-establish busted connections automatically???
thanks....
This happens on various Android phones whether the autoConnect flag is set to false or true.
Couldn't yet find a complete solution, it seems as the android BLE stack is spontaneously re-initiating the connection once it is getting the advertising signal again, just ignoring that it was the app that disconnected on purpose...
A partial solution may involve not using the BluetoothGatt.connect() method as explained here:
https://stackoverflow.com/a/23749770/4144487
So, a sample connect method can look like:
void connect(Context context) {
if (mGatt != null) {
mGatt.close();
}
mGatt = mDevice.connectGatt(context, false, callback);
}
To explain the importance of this issue, when it happens the peripheral thinks it is connected and my "real" app can't find it any more. At some phones like Galaxy S3 and Redmi note 3 I found that closing the bluetooth switch from the notification bar is "releasing" the peripheral and allowing me to discover the device. At others like Nexus 5x only a phone reboot will do the trick.
I've observed this happening if you use autoConnect=true when calling BluetoothGatt#connectGatt(). Generally I've found that it is best to use autoConnect=false, but with some devices you simply cannot connect unless you use true, so I usually do both. I try false first and if that fails then use true and then the behavior you're describing is something you simply have to work around.
I'm trying to get the Android Bluetooth APIs working, and I'm running into a strange problem. I have one device (Samsung Nexus S) as the client, calling socket.connect(), and the other device (also Samsung Nexus S) calling serverSocket.accept() (after doing device discovery, creating the sockets using createInsecureRfcommSocketToServiceRecord and listenInsecure...).
I put debug statements after connect(), and I can see that connect() is returning successfully (because it would throw an exception if it weren't). However, the accept() call on the other end just blocks, and eventually times out.
Any ideas what could cause this? As far as I can tell, the socket is connecting, but my process isn't being informed.
UPDATE:
The manifestation of the bug was a bit mysterious, but I'm pretty sure that I was mishandling threading, and that was somehow preventing the thread running accept() from unblocking.