I am working on an application that makes multiple BLE connections in a minimum amount of time. I developed my application in Java Android. I set up a statistics-based system so that, among a group of phones, each phone makes connections on half the group and receives connections from the other half. This gives a pretty even distribution of BLE servers and clients. If a connection was made and failed or could not be launched, the other device that should have been a server will become a client and make the connection for the other in order to waste a minimum of time.
My problem arises when the number of devices present to make connections is greater than 5. When there are only two or three connections left to make, it takes a lot of tries to succeed. A lot of 133 errors or timeouts are thrown before getting a connection. The only time it is possible to speed up this is by clearing the Bluetooth cache. This is only possible by reflection and on the current connection. This operation doesn't work all the time and I'm trying to find out how to clear my own Bluetooth cache without it, faster and more reliably. Does anyone have any advice or idea to help me?
Here is the reflexive method we use to clear another's cache through a Gatt connection:
/**
* Method to refresh cache device
*/
private boolean refreshDeviceCache(BluetoothGatt gatt){
try {
Method localMethod = gatt.getClass().getMethod("refresh");
return (boolean) (Boolean) localMethod.invoke(gatt, new Object[0]);
}
catch (Exception localException) {
Log.e(TAG, "An exception occurred while refreshing device");
}
return false;
}
This method not being recommended, we had no choice since there is no other solution, and for the moment we haven't found one yet.
Related
I am having an Android app which needs continuous network monitoring and I require to be notified when I have internet and when not. I have tried Android connectivity manager, which only tells if the internet wifi is connected or not, but doesn't tell if there is reachability. So I used the following ping method to check the reachability.
private fun isOnline(): Boolean {
return try {
val timeoutMs = 1500
val sock = Socket()
val socketAddress = InetSocketAddress("8.8.8.8", 53)
sock.connect(socketAddress, timeoutMs)
sock.close()
true
} catch (e: IOException) {
Logger.e(TAG, e.toString())
false
}
}
Now to keep checking this every 1 minute I am using a fixedRateTimer which will call this method every 1 minute and notify accordingly.
Now the problem I am facing is, this works fine for few hours and I get proper connection status. But after few hours the ping starts timing out. I get a timeout exception every alternate ping.
I want to understand few things,
First thing, is it okay to ping for every 1 minute to check the network?
Can the client be blocked by Google for frequent pings?
Or is it anything related to the ISP?
Is there a better approach in android to achieve what I want?
Any advice would be appreciated. Thanks in advance!
PS : I have also tried onCapabilitiesChanged and the callbacks are not immediate for every connection and disconnection, though the google documentation says callbacks will be immediate.
but doesn't tell if there is reachability. So I used the following ping method to check the reachability
First, that is not a ping. That is opening and closing a socket.
Second, it can only tell you if you can open a socket connection to that IP address. It does not tell you if you can access anything else. So, this is subject to false positives (you can reach 8.8.8.8 but not your real server) and false negatives (8.8.8.8 is blocked by network management, but your real server is not).
Now to keep checking this every 1 minute I am using a fixedRateTimer which will call this method every 1 minute and notify accordingly.
That will only work so long as your process is running, and only until you lose Internet access due to Doze mode/app standby/manufacturer-specific power management solutions.
First thing, is it okay to ping for every 1 minute to check the network?
It has flaws. "Okay" is a subjective measure; only you can decide whether it is "okay" for you.
Can the client be blocked by Google for frequent pings?
This is not a ping. It is certainly within Google's power to take action for buggy clients like this.
Or is it anything related to the ISP?
There are lots of pieces involved in an Android device reaching 8.8.8.8:
The network management for whatever WiFi network the phone is using for connectivity (where relevant)
The mobile carrier or ISP
The various other ISPs between you and Google
Google's own network management
Any of them could take steps, if they so chose.
Is there a better approach in android to achieve what I want?
I would aim to eliminate the "need" entirely, as Android and device manufacturers will be fighting you every step of the way.
At minimum:
Do a valid expected operation, such as an actual ping; and
Do it against a relevant server
IIRC, 8.8.8.8 is a DNS server. If your app is a DNS client, you are welcome perform a valid, useful DNS operation against 8.8.8.8. If your app is not a DNS client, quit messing with somebody else's server. Run your own server and test reaching it. For example, you could run a Web server and test whether you can retrieve your robots.txt file.
Most of the question has been answered, but I want to pick up on the most important one.
First thing, is it okay to ping for every 1 minute to check the network?
No. It is not OK.
It is wasteful
You are consuming Google's resources. Resources that you are not paying for. If everyone did what you are doing it would cost Google a lot of money ... to run a much larger fleet of DNS servers, etc to cope with bazillions of vacuous connections.
You are also consuming resources in the along the route from the user's app to Google with the (unnecessary) network traffic.
This would also apply if you were doing real (ICMP) pings, though not to the same extent.
And bear in mind, this is also consuming electricity. And that means more fossil fuel is burned.
It may be incurring costs for the user
Depending on the what their mobile phone plan is, this may be costing the user of your app network charges. Each of those connections your App is making probably being metered. If they are not aware of this ... or they can't turn this (mis-)feature off, they could get rather annoyed about this. (I would be!)
It doesn't actually work
What you are doing doesn't actually test if the internet is available. What you are actually doing is seeing if your App can connect to the Google DNS services. But the fact that the DNS server is accepting connections doesn't mean that the real services that the user wants to use will be accessible and working. (And vice versa!)
As you noted, connections will occasionally fail for reasons that are probably due to transient problems that resolve themselves. There is nothing you can do about that. That could be a false negative for the internet being "up".
Even if there was a reliable way to find out if the internet is "working", your "pinging" is only giving you a single sample. The internet could go "down" (or come back "up") any time in the up to 60 seconds between your pings. More false negatives and positives.
And as noted, Google DNS is not the same as "the internet", and "the internet" is not the same as the service that the user of you App is really interested in.
Your app doesn't REALLY need this information
The user does not need to know minute by minute that the internet is available.
Most of the time a typical user is doing something else.
They only actually need to know if they are actively using some service. And even then, knowing that the internet was up 60 seconds ago is probably no help to them.
Unfortunately, the only way that the user can tell if the service they are talking to is available right now ... is to actually try to use it.
So what is the real solution?
IMO, there are two approaches:
Forget it. In most cases, the user really doesn't need to know. It is not actually going to materially effect the user if your App does not distinguish "service down" from "internet down".
If you can't forget it.
Implement an end-point on your service that you can ping ... and pay the bills!
If you are trying to implement this in an App where you are talking to someone else's services, stop free-loading on Google. If you want an "internet is up/down" feature in your App, implement your own service for doing this ... and pay the bills.
Note that you will still have false positives and false negatives to deal with. There is no solution to that. It is a fundamental property of the internet.
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 a ble device that I need to regularly extract data from securely and constantly. So on startup I want to make sure to bond the device if it is not already. I have the Mac address of the device in this case.
public void startApp(){
remoteDevice = bluetoothManager.getAdapter().getRemoteDevice(MAC_AD);
if(remoteDevice.getBondState()!=12){
remoteDevice.createBond();
}
}
What ends up happening is that the bond state will alternate between unbonded and currently_bonding but not fully bond.
The strange thing is that sometimes it will work, but usually not, particularly on my google pixel. Bonding through the settings is also very inconsistent.
Is there any way to retry this or some kind of Bluetooth reset that should be done so that I can bond consistently?
I've tried calling createBond() in intervals;
I've often found that calling createBond() directly can have hit-and-miss results depending on the platform (both ends). Logically it should use the same mechanism internally, but I've tended to get better results by calling read on a simple characteristic which has bonded requirements.
It either successes - meaning your connection is bonded - and you can continue. Or it fails, which internally triggers the bonding, and then you can try again after a short delay, at which point it should now be bonded.
I'm using Connectivity library to see the internet(Wifi or network data) is active and saving the data in Storage if there is no connectivity(Offline) and synchronize with server when connected to internet. I'm having issues in public places where the internet is consistently unstable(esp. in basements, offices, stores, Coffee shops etc., where there is internet connects in and out). When I check the Connectivity is active but by the time I started synchronizing internet goes offline (Something like this). this leads inconsistent /partial updates to the server. Also, in coffee shops and Airports where wifi gets connected but there will be "Agree Terms and Conditions" page to connect. Not all the browsers will take you to that page directly after joining the Wifi. In that case I see the wifi is active in Mobile but actually it is not activated until I accept the terms and Conditions in IE or some specific browser. Any one else having difficulty in handling these kind of issue from Mobile App?
My App - Hangs on Login screen if I'm trying to login when there is in-stable/in consistent internet.It thinks wifi is there but not.
IF I'm on a screen where I will display list, screen will show blank for infinite time. Adding timeout for server request/response or something will help such scenario.
I know I'm not handling this case in code to show some kind of error message but I need some guidance to detect these conditions suing CN1 API to handle through my app.Please advise.
Code:
public boolean isOffline() {
if (Connectivity.isConnected() && forceOffline) {
relogin();
}
return forceOffline || !Connectivity.isConnected();
}
The problem is that it's impossible to detect online/offline properly as you might be connected to a local wifi/network but it might be a bad connection that won't let you reach the server. As far as the library is concerned you are connected... But in reality you don't have a connection.
First set the timeout values in NetworkManager to lower values to improve the experience although this won't solve a situation where data starts downloading and stops in the middle.
Next you need to handle these cases one by one and provide the user with a way to toggle the offline mode. Unfortunately there is no silver bullet for network reliability. You just need to go through every path and try to detect these things.
I have a simple app that periodically sends HTTP_GET requests to a server. When sending requests over 3G, I noticed that the requests sometimes time out (and the server-side logging shows that it NEVER receives the request either).
After trying out different combinations I found one consistant pattern when this problem occures (it times out after every 5-15 successful requests).
- TelephonyRegistry: notifyDataConnection() state=2isDataConnectivityPossible()true, reason=null
- TelephonyRegistry: broadcastDataConnectionStateChanged() state=CONNECTEDtypes=default supl, interfaceName=rmnet0
- NetworkLocationProvider: onDataConnectionStateChanged 3
According to Google, NetworkLocationProvider is changed to 'DATA_SUSPENDED', which implies "connection is up, but IP traffic is temporarily unavailable". (see TelephonyManager). On the situations where HTTP_GET requests succeeds, the state is changed to '8'. My app doesn't use the location manage and I've shut down every other non-critical app from running!
I want to know:
What is the cause of this issue? Why does the connection status go to DATA_SUSPENDED?
Is it possible to avoid/overcome this problem?
Any help/insight into this is much appreciated! Thanks in advance!
I have the same problem with my app running on an Huawei IDEOS X3 with Android 2.3.5. The app sends data each minute to a server using HttpClient.
Using logcat I can see that the data connection is lost and then reestablished after a short while. Previously my app stopped working since it tried to send data without a connection causing an exception which was not properly handed.
I don't know the reason for the intermittently dropped data connection but I now handle the situation by checking if there is a data connection prior to sending the data. In my case it does not matter if some data is never sent. If it was important to avoid data loss, I could buffer the data and send it once the connection was back.
public Boolean isDataConnection() {
TelephonyManager tm = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
return tm.getDataState() == TelephonyManager.DATA_CONNECTED;
}