I am trying to interface with a USB OTG device on Android. Since code is a bit lengthy, I will just outline the basics:
There’s an activity with an Intent filter for USB_DEVICE_ATTACHED (filtered for the device IDs I am interested in) declared in the manifest.
When the activity is launched with that particular intent, it starts a service and passes the USB device as an argument.
When the servie starts, it registers a broadcast receiver for USB_DEVICE_DETACHED, then connects to the device and starts interacting with it (which currently generates some log output).
When the USB disconnect broadcast is received and the device is the one currently in use, close() is called on the USB device connection and the service stops.
The device is a Si4701 USB FM tuner, which presents itself as a USB HID device.
When I first plug in the device, I see my service connects to it, can change channels and receives RDS data.
However, when I unplug it and plug it back in, I see the service connecting to it again (and again apparently successfully reading the device registers), but it fails to change frequencies and I never see any RDS data.
If I force the app to stop between unplugging and replugging the device, the device works normally after geing plugged back in.
This suggests some cleanup fails to take place, or some resource isn’t being released properly. However:
I don’t keep any data structures related to the device between connects. Everything related to the USB connection is in class instances which get created when a device is detected, and all static members of these classes are final.
Some things work after replugging the device, such as reading registers for device types and firmware version (which works by getting a USB HID feature report). Initialization also sets a few registers (through sending a USB feature report), which does not return an error—but changing frequency (which also involves setting registers) does not work.
A ~15-minute interval between unplugging and replugging has no effect (device doesn’t work). On the other hand, if I unplug the device, kill the service and plug it back in, it works instantly—so the issue is definitely on the Android side, not on the device side.
What do I need to do to make the USB device work after an unplug/replug cycle?
I still don’t fully understand what is happening here, but it seems to work now.
I should mention that the app is multi-threaded and the Si470x wrapper class is getting called from multiple threads.
Since this has resulted in race conditions (one thread accessing the device when another thread had just closed it), I properly declared all methods as synchronized so that no two threads can access methods of the tuner class at the same time. Plugged the device in, unplugged it, plugged it back in—and lo and behold, it changes frequencies and spits out RDS data the way it is supposed to.
Suspicion:
UsbDeviceConnection#close() might not be thread-safe.
Conclusions:
Always call UsbDeviceConnection#close() when you’re done with the device (including when the device is detached).
If your app is multithreaded, make sure that everything which accesses your USB connection (directly or indirectly) is properly synchronized.
Related
I'm working with app development for a HW-driven device company. Our current (BLE) HW-devices communicates with the phone, and when the HW is considered "active" (triggered by a physical activation on the HW), the phone retrieves continuous data from said HW.
In order for the application to be ready for the HW to become active, it is holding a permanent "binding" to the HW. On Android, the application holds a permanent ("sticky") notification, that prevents the app from being killed, and lets the background process stay alive at all times and listens for the HW to announce that it is active. On iOS, I believe that the HW is spamming "ping-requests" to the app in order to keep it alive.
All in all, the setup works, but is not ideal. I've tried to wrap my head around the different "peripheral modes" and master/slave-setups, but have yet to understand how to setup a relationship between phone and HW that behaves as we would like:
We would like to have the HW to behave like (for instance) BT-headphones work, i.e. when I open the case for my headphones, the application starts up (in the case of my earplugs, a battery status notification is shown) and communication starts. When I put the plugs back in the case, the application closes (at least as far as the user can tell). Once the HW is in range again and is activated (i.e. equivalent to opening the case) the app/communication is ready/resumed.
Is the described functionality reserved for headphones only, or is this a setup that can be achieved by any BLE-device?
tl;dr I don't want to have a persistent notification on Android to make sure that the app is always ready when the device is "active", but without it, the app is easily killed in the background.
It is tricky when you ask a question that covers both Android and iOS as both platforms will have different approaches.
I will answer for iOS as that is what I know, but I presume there is something similar on Android.
First, a little on how the BLE GATT service works in simple terms;
In BLE there are two roles; central and peripheral. 99% of the time, the peripheral advertises services and the central discovers peripherals advertising the services it is interested in. The central can then connect to the peripheral.
Once connected, the central can initiate reads from and writes to the peripheral. The central can also register for notifications from the peripheral. This allows the peripheral to send new data when it has it, rather than waiting until the central reads it.
Now, for how you can use all of this in iOS Core Bluetooth.
Your initial task is to discover the peripheral you are interested in. You do this by scanning for a peripheral advertising your specific service. You typically need to provide some sort of UI to show what has been discovered and allow the user to select the device to which they want to connect, but this depends on your specific use case.
Once you have discovered (and the user has selected) a peripheral, you can store the identifier that Core Bluetooth provides. This is a unique value for this peripheral on this iOS device; It is not the peripheral MAC address and it cannot be used on a different iOS device to identify the same peripheral.
In future you can use this identifier to try and retrieve a CBPeripheral object from Core Bluetooth without needing to scan for it.
Once you have a CBPeripheral you can issue a connect. Once the connect completes you can read/write data or register for notifications.
If you have enabled Core Bluetooth background mode in your app then the connect and notification events will be delivered to your app even when it is in the background. You can also issue read and write commands in the background in response to the connect and notification events, although you only have a few seconds execution time.
At some point it is likely that the peripheral will go out of range/be turned off and the connection will disconnect. Again, this event may be delivered in the background or foreground.
In response your app should immediately issue a new connect. If the device is not currently available then this connect will be pending and will complete when/if the peripheral is seen again. iOS will deliver this to your app in the foreground or background.
The final point to consider is the case where your app is not currently in the suspended state; Perhaps the iOS device has been rebooted or your app has been offloaded from memory by iOS because it hasn't been active for some time.
Your app can opt in to Core Bluetooth State Restoration by providing a n identifier parameter when it creates its CBCentralManager. This will relaunch your app.
On relaunch you need to re-establish your CBCentralManager with the same identifier and then you will receive the connect callback that triggered your apps relaunch. From this point you can proceed normally.
This is an article from Apple that, while old, explains Core Bluetooth background and state restoration pretty well.
Note that all of this can happen without pairing/bonding. If your peripheral requires encryption for any of its attributes then iOS will automatically present a pairing dialog the first time you connect. Once this pairing is in place it is not required again. Pairing/bonding only exchanges keys for encryption. It is not explicitly required to allow future connections.
We want Android to automatically connect to our custom made BLE peripheral.
Our peripheral should regularly (but infrequently) advertise and attempt to Indicate some time-sensitive sensor data to the phone. Thus we want the phone to be ready to connect at any time.
Generally, you can pair a smart watch with an Android, and Android will then automatically connect to the smart watch whenever it is in range. So we believe our use case should be feasible.
I read a lot of answers that advise to set the "autoconnect" parameter to true when connecting. I have tried that and the reconnections don't persist through a reboot or even after disabling and re-enabling Bluetooth on Android. This answer by Brian says I should scan in the background, but Android made this unrealistic. If I use a foreground service, my users will hate the app. If I use a background service, I may miss the peripheral's attempts to connect during Android's Doze and the code becomes error prone.
Ideally, I want to do something like what Emil said in his answer here. Please read the follow up question and response.
However, we can't see our app through Android's Bluetooth settings. We can only connect to the peripheral and pair with it using our app (or nrf Connect). In desperation, I tried modifying the peripheral's advertising flags. Then I could see it in Android's Bluetooth settings. But when I try to pair using Android's settings, the attempt fails because the peripheral is not in "pairing mode".
We are building both the app and the peripheral, so we can change both. I want to know if our use case is possible and what we need to do to get it working. We are using the STM32WB for our peripheral.
Use a combination of these techniques:
Bond the device. This might be needed due to the crappy Android Bluetooth LE API design that doesn't take the "address type" as an extra parameter when connecting to a device. When you connect using the Bluetooth device address, it looks up a device with this address in the bonding info, and uses the corresponding address type (random or public).
Use connectGatt with autoConnect set to true. This means no timeout, as well as auto-reconnect if the connection drops. Even if it takes days or weeks until the peripheral starts advertising, it will still work.
Listen to https://developer.android.com/reference/android/bluetooth/BluetoothAdapter#ACTION_STATE_CHANGED and restart your connections when Bluetooth is re-started.
Use a Foreground Service in your application's process to prevent the OS from killing the process. Users can nowadays hide the annoying notification in Android settings if they want to.
Listen to https://developer.android.com/reference/android/content/Intent#ACTION_BOOT_COMPLETED to start your app after boot, including your Foreground Service.
Listen to https://developer.android.com/reference/android/content/Intent#ACTION_PACKAGE_REPLACED to automatically restart your app after an app update. See https://stackoverflow.com/a/2134443/556495 for some instructions.
The best approach is to make sure your peripheral can be bonded. Once you have bonded with it you can ALWAYS use autoconnect because Android stores info about bonded devices and you don't have to scan for it anymore. Hence you avoid the issues with scanning in the background.
Although that resolves the need for scanning, you still need to deal with your app being killed once it is in the background. Using a Foreground Service is still the best solution to my knowledge. I don't think you users will hate your app for it...
My goal is to get the Android device to reconnect to a BLE device that it has previously connected to without user intervention in the same way it does for a classic BT paired device does (even works through power cycles).
One of the ideas of BTLE devices is that one saves service, bonding, and enabling states such that a reconnect is VERY fast and consumes very little power on the peripheral.
What I have done seems to work but it works poorly.
The first step is to connect or pair and connect to a new device setting the 'autoconnect' parameter to 'true'.
When the device disconnects, do not call gatt.close(). Everywhere I look I see that one should call gatt.close(). But if I do call gatt.close() the Android central app never reconnects. I have tested this many times.
If I have not called gatt.close() and have not power cycled the Android, the auto-connection usually happens. Sometimes it can take a long time, especially after version 5.0. It is, however, unreliable and it may be unreliable due to a very low-duty scan cycle and the device quitting advertising before a scan cycle actually detects the advertisement. I am not sure because there is no way to detect the scanning operation like there is advertisements! It is also possible the scanning stops after a certain amount of time but there is no documentation on that.
So what I think I need to do is to somehow set the background scan rate used by the Android to a higher duty cycle (only possible in 5.0 and up) when auto-connect has been set but I do not know how to do this. I do not want to start my own scan but somehow set the background scanning rate used by Android for the reconnect. Does anyone know how to do this? Does anyone really know how autoconnect and gatt.close() are to work?
Maybe the auto-connect was NOT meant to re-connect as I indicated above?
Well after many trials and tribulations this is how I best get the Android to auto connect with the only user action being to first select the device (if using the settings menu then first pairing).
You have to trap the paired event in a BroadcastReceiver and do a BluetoothDevice.connectGatt() setting the autoconnect to true. Then when the device disconnects, invoke a gatt.connect().
Update: While the above approach works in general, it is sometimes agonizingly slow probably because the pending connection uses extremely conservative scan rates. The other downside is that for each device you want to auto-reconnect to you have to keep a BluetoothGatt object performing a pending connection. In the embedded world this is insane. Instead what one does is continuously scan and connect to a desired device by inspecting its advertisement. One saves only the minimal amount of data about the device (the services, its paired state and keys, etc.). When an advertisement is captured you see if it is one of your known devices and connect to if it is.
I tried the equivalent on Android. Scan all the time (low power rate) and connect to advertisements of interest, and maintain a class representing a known device. There are some annoying details in this approach (like turning off scanning while connecting and restarting after connected) but it basically works without the overhead of maintaining connections. BUT there is one exception I do not understand. One pre-paired device's advertisements are never seen by the scanner. However, if I invoke a pending connection to this device, I re-connect. I do not understand this at all. On my embedded platforms it works as it should.
If anyone else has tried this approach for auto-reconnecting, please share your experiences!
I have discovered the reason the pre-paired device is not seen by Android. Android only reports scan results IF the device responds to a scan request. Once paired, this device only emits advertisements and ignores scan requests, so the Android system does not pass up its advertisements in the ScanCallback. Thus in order to work using the scan approach, I have to use the pending connect approach for those specific devices. It just seems like you can't win!
============= UPDATE 2020
Many years have passed and I have a lot more experience with the background scan approach. If one keeps the supported platforms 5 and up, one can use only the newest scanner APIs and use filters, eliminating the need to decode the raw advertisements yourself. I have also found that connection and re-connection is snappier if you DONT turn off scanning while connecting. I know it goes against all documentation, but it works and on some platforms allowed connections to happen that otherwise did not. Also, to date, I have found only one (health) device that needs pending connects. Disclaimer: All I have ever worked with is health devices.
This is how I was able to do it for my application.
I first stored the address of the device in a SharedPreference then in gattClientCallback funtion of my BluetoothLeService
else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
intentAction = ACTION_DISCONNECTED;
DeviceActivity.runOnUI(() -> {
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
String name_dev_1 = sharedPreferences.getString("Dev_1", null) ;
connectToDevice(name_dev_1);
disconnectGattServer();
});
}
}
What this does is if your device is disconnected it will keep on trying to connect to it until a connection is established.
I am working on a project where I need to put a USB device in to a reprogramming state and then send a software update to the device. I have been able to make this work by having the user disconnect and reconnect the USB cable between the android device and the USB device I am reprogramming. I have attempted many combinations of releasing interface and then trying to loop through the USBManager getDeviceList and then requestPermission on that device again. I have tried modifying the PendingIntent to be different than the original PendingIntent. I thought I had it once but it seems to have been a faulty OTG cable. In a last ditch effort I tried to craft my own android.hardware.usb.action.USB_DEVICE_ATTACH/DETACH message (which I got a Permission Denial on) Any other ideas out there or is it just not possible?
I am no Bluetooth specialist and wondering what possibilities are available to find already paired Bluetooth devices automatically when they are range of each other.
Background:
In our case an Android application needs to connect to a dedicated accessory via Bluetooth (Rfcomm). Both devices are known to each other (they are paired). The Android application registers a broadcast receiver. During the startup of the application, the app initiates a discovery to find the dedicated accessory. If the accessory is in range everything works great.
Problem:
The user starts the application outside the range of the dedicated accessory. The Android application tries to discover the accessory without success. Then the user goes into the range of the Bluetooth accessory. The broadcast receiver won’t get notified about the accessory that is in range now.
Similar Thread / Possible Solutions
Similar questions were already asked on stackoverflow (e.g. autoconnect to bluetooth device when in range).
But continuously trying to discover Bluetooth devices in range isn't what I am looking for because this would cause too much battery drain of the Android device.
Another solution would be to try to connect to the paired device in the onResume method of the Activity. This would work but has the disadvantage that the application can’t run in the background. So the user had to bring the application at least once to the foreground to initiate the connection.
A third idea I thought about is to implement a server socket into the Android application too. When the android application is started and the discovery finished without success, the Android application could create server socket and to listen to incoming notifications of the accessory. This would help in some scenarios (e.g. the user starts his application, approaches the accessory, activates the accessory and the accessory notifies the application on startup that it is in range now). But this is still no 100% solution because both devices can start outside the range of each other. Also it would be mandatory to implement additional functionality (Bluetooth server socket in the Android device…).
So I am wondering if better solutions exist. I am looking for a solution where no additional ServerSockets are required and I always get the notification that the two already paired devices are in range of each other :-)
Thanks for any help!
After connecting the device for the first time, keep the mac address in a local list.
On disconnect, use connectGatt with autoconnect set to true to automatically re-connect when you are in range.
Not a full solution, but maybe it's sufficient for your app to poll the accesory's presence whenever the screen is turned on? In that case, this may be helpful: Start Activity on wake up/sleep in Android