I use AltBeacon library for simple ble scanning. Scanning for about 7 or 8 seconds then I stop it. tapping button for rescanning. the problem that I have had from the previous version of this library , when I scan then stop it, and disconnect beacon power and I rescan , rangenotifier or observer(another method to watch beacons) could see disconnected beacon! for first time after disconnected it is happened. after that works correctly and if I do the whole process again it is happens.
in the previous library version I had to bind and unbind each time.(not good approach but I had to do) .but in newer version there are no unbind or bind methods. most of methods and functions are deprecated.
I use scanner in fragment. even it is not matter if switch to another fragment. when I come back to scanning fragment again it finds disconnected beacon for fist time after beacon power disconnected. I'm not sure if this library is suitable for a simple bacon scanning. But it is very powerful and simplified some complex thing.
class ScanningFragment() : androidx.fragment.app.Fragment(){
lateinit var beaconManager:BeaconManager
lateinit var region:Region
val rangeNotifier =object:RangeNotifier{
override fun didRangeBeaconsInRegion(beacons: MutableCollection<Beacon>?, region: Region?) {
Log.d(TAG,"in didRangeBeacon")
if (beacons!!.size > 0) {
Log.d(TAG, "didRangeBeaconsInRegion called count: " + beacons.size + beacons.iterator().next().id1)
val firstBeacon = beacons.iterator().next()
}
override fun onCreate(savedInstanceState: Bundle?)
{
Log.d("lifecycl","it is oncreate ")
super.onCreate(savedInstanceState)
BeaconManager.setDebug(true)
beaconManager=BeaconManager.getInstanceForApplication(requireContext()).apply {
foregroundScanPeriod=7000L
foregroundBetweenScanPeriod=5000L
updateScanPeriods()
beaconParsers.clear()
beaconParsers.add(BeaconParser().setBeaconLayout("m:2-3=0215,i:4-8,i:4-19,i:20-21,i:22-23,p:24-24"))
region = Region("prefixRegion", Identifier.parse("0x0000000000"), null, null)
}
setupPermissions()
}
fun rangingButtonTapped() {
if (beaconManager.rangedRegions.size == 0) {
beaconManager.addRangeNotifier(rangeNotifier)
beaconManager.startRangingBeacons(region)
binding.insideviewmodel?.isScanning?.value = true
}
else {
beaconManager.stopRangingBeacons(region)
binding.BTNScan.run {
Handler(Looper.getMainLooper()).postDelayed({ stopAnimation() }, 1000)
Handler(Looper.getMainLooper()).postDelayed({ revertAnimation() }, 2000)
}
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
binding.insideviewmodel?.isScanning?.observe(viewLifecycleOwner,Observer{
currentStatusScan->
if(currentStatusScan)
{
object : CountDownTimer(8500, 1000) {
override fun onTick(p0: Long) {
}
override fun onFinish() {
beaconManager.stopRangingBeacons(region)
beaconManager.removeRangeNotifier(rangeNotifier)
binding.insideviewmodel?.isScanning?.value = false
}
}.start()
}
})
}
}
}
it is a debug log for first time scanning.
https://jpst.it/2LVY4
it is a debug log for rescanning after disconnecting beacon power.
https://jpst.it/2LVZs
Profiler:
The second log line "after disconnecting beacon power" shows that the Android OS BLE scanner does indeed deliver an iBeacon detection at 12:53:31:
2022-02-24 12:53:31.117 23528-23528/ D/CycledLeScannerForLollipop: got record ... Processing pdu type FF: 0201041aff4c000215....
The library source code shows that this log line is issued immediately upon a callback from the operating system about a BLE advertisement detection. See here.
Clearly it is not possible for a Bluetooth scanner to detect an advertisement from a powered-off BLE device so there must be an alternate explanation. A few possibilities:
The BLE transmitter is not really powered off at (or slightly before) 12:53:31.117
The detected advertisement comes from a different transmitter
The callback from the Android OS is delayed, perhaps because the main thread on which is delivered was blocked by lots of CPU usage in the app.
Some flaw in the bluetooth stack or UI thread handling for the phone in question is delaying delivery of detections.
In order to figure out the cause I would suggest the following:
To eliminate a bluetooth stack flaw, test the same code on a different Android phone, preferably by a different manufacturer.
To eliminate the possibility of the UI thread being blocked, run this in the Android Studio profiler, or simply cut out as much code as possible that executes before the delay is seen.
To verify the transmitter is really off and that there are no other transmitters around, use a second phone with an off the shelf beacon scanner to monitor what devices are actually transmitting. Only perform your test when you confirm with a second device there are no other visible transmitters.
Related
I'm trying to write a fitness companion watch app that would collect heart rate, and calories via HealthServices API, and send them to the device, where we display a workout. I've been following suggested examples:
https://github.com/android/wear-os-samples/tree/main/AlwaysOnKotlin, https://github.com/android/health-samples/tree/2220ea6611770b56350d26502faefc28791f3cbd/health-services/ExerciseSample, and https://github.com/googlecodelabs/ongoing-activity .
I'm trying to achieve the following workflow:
Launch app on Wear device when X happens on the phone
Start exercise client on Wear
Send heart rate/calories update on a regular basis back to phone
Show summary screen, and stop exercise client when Y happens on the phone.
All of these work somewhat well until the watch goes into ambient mode. Then I run into the following problems:
When watch is in ambient mode, the capabilities client on the phone cannot locate watch, and tell it to start exercise. Nor can it tell it to stop exercise. What is a suggested workaround for this?
I use message client on phone to send message to the wearable. But nothing happens here, since the current node is empty.
currentNode?.also { nodeId ->
val sendTask: Task<*>? =
messageClient
?.sendMessage(nodeId, WORKOUT_STATUS_MESSAGE_PATH, "START.toByteArray())
When trying to simulate ambient mode by pressing 'hand' on the watch simulator, the ambient mode listener does not actually trigger to tell me the right thing. The screen gets "stuck" instead of updating to what I want it to.
Code for the ambient mode in MainActivity (I'm still learning Compose, so right now Main activity is where it's at, to eliminate other Compose specific errors):
In Manifest:
<uses-permission android:name="android.permission.WAKE_LOCK" />
In Main Activity:
class MainActivity : FragmentActivity(), AmbientModeSupport.AmbientCallbackProvider {
...
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
ambientController = AmbientModeSupport.attach(this)
setContent {
val ambientEvent by mainViewModel.ambientEventFlow.collectAsState()
StatsScreen(ambientEvent)
}
...
}
override fun getAmbientCallback(): AmbientModeSupport.AmbientCallback = AmbientModeCallback()
inner class AmbientModeCallback : AmbientModeSupport.AmbientCallback() {
override fun onEnterAmbient(ambientDetails: Bundle) {
Timber.e("ambient event: enter: $ambientDetails")
mainViewModel.sendAmbientEvent(AmbientEvent.Enter(ambientDetails))
}
override fun onExitAmbient() {
Timber.e("ambient event: exit")
mainViewModel.sendAmbientEvent(AmbientEvent.Exit)
}
override fun onUpdateAmbient() {
Timber.e("ambient event: update")
mainViewModel.sendAmbientEvent(AmbientEvent.Update)
}
}
I don't see anything printed in this callback, and then consequently, by StateScreen doesn't really do anything when the device enters in the ambient mode.
I'm using Android Kotlin with the SDK 30 and Coroutine 1.4.1.
I have a function that handles incoming messages to display them on my app in a form of temperature measurement. I use CoroutineScope to process the data and save it in the database. These messages are received from a socket.io connection. The problem is that the messages are not displayed in the correct order when a bulk of data is flowing in.
Now I've looked at my nodejs logs and these messages are sent in the correct order so it can't be that.
I'm using a standard Coroutine function.
See below.
fun receiveTmps(data){
CoroutineScope(IO).launch {
val usersJob = launch {
usersBg(data)
}
}
}
Now I know that with Coroutine I can add a join to wait for the job to finish before starting the next one. But because the messages do not come in at once, but flow continuously over a period of 5 to 20 seconds, it is possible that one message is completed faster than the older one. This causes incorrect order.
My question is, is there any way to handle these tasks 1 by 1 while adding multiple jobs to the list?
Any suggestion or idea is appreciated.
Thank you in advance.
UPDATED:
From what I read from the documentation you should also cancel the channel after its done. So that's going to be tricky to close the channel when messages are flowing in and since I don't have a clear number of what's flowing in I'm having a hard time defining that to the channel. I have tested several ways but most of the examples doesnt work or are outdated.
This is the most basic working example but it always has a defined repeat.
val channel = Channel<String>(UNLIMITED)
fun receiveTmps(data:String){
CoroutineScope(Dispatchers.Default).launch {
channel.send(data)
}
}
#ExperimentalCoroutinesApi
fun main() = runBlocking<Unit> {
launch {
// while(!channel.isClosedForReceive){
// val x = channel.receive()
// Log.d("deb", "Temperature.. "+ x)
// }
repeat(3) {
val x = channel.receive()
Log.d("deb", "Temperature.. "+ x)
}
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
receiveTmps("10")
receiveTmps("30")
// Many more...
main()
}
If we need to process some events sequentially then typical solution is to create a queue of events and start a single consumer to process them. In the case of coroutines we can use Channel as a queue and launch a coroutine running in a loop that will be our consumer.
I'm not very familiar with Android, so I may miss something, but it should be something along lines:
fun receiveTmps(data:String){
channel.trySend(data).getOrThrow()
}
fun main() {
lifecycleScope.launch(Dispatchers.Default) {
for (tmp in channel) {
...
}
}
}
My assumption is that you want to stop processing events when the activity/service will be destroyed, ignoring all temps that are still waiting in the queue.
I'm developing a frame exchange sequence between an nRF52840 and an Android smartphone using the BLE protocol.
The first time I connect, everything works fine.
I activate the listening of BLE notifications by the Android smartphone with this method:
fun enableBleNotificationsOnCentral(currentBluetoothGatt: BluetoothGatt, serviceUUID: UUID, characteristicUUID: UUID) {
getMainDeviceService(currentBluetoothGatt, serviceUUID)?.let { service ->
val notificationConfiguration = service.getCharacteristic(characteristicUUID)
val result = currentBluetoothGatt.setCharacteristicNotification(notificationConfiguration, true)
println(result)
}
}
And I enable sending BLE notifications on the nRF52840 with this method:
fun enableBleNotificationsOnPeripheral(currentBluetoothGatt: BluetoothGatt, serviceUUID: UUID, characteristicUUID: UUID, descriptorUUID: UUID) {
getMainDeviceService(currentBluetoothGatt, serviceUUID)?.let { service ->
val descriptorConfiguration = service.getCharacteristic(characteristicUUID).getDescriptor(
descriptorUUID).apply {
value = BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE
}
val result = currentBluetoothGatt.writeDescriptor(descriptorConfiguration)
println(result)
}
}
These methods are called each time my smartphone is connected to the nRF52840.
But if I disconnect and connect a second time, I receive each of the notifications in duplicate.
In addition, if I disconnect and connect a 3rd time, I receive each notification 3 times, and one more each time I reconnect.
I checked my code on the nRF52840 and it does not duplicate notifications.
Here is the method I call when I request a disconnection:
private fun disconnectFromCurrentDevice() {
currentBluetoothGatt?.disconnect()
BLECallbackManager.currentDevice = null
setUiMode(false)
}
I guess my problem is related to the fact that I don't disable the receipt of BLE notifications by my Android application when I disconnect but I'm not sure. And if that's where the problem comes from, when should I do it in the disconnect method? Can you help me?
I guess you're creating a new BluetoothGatt object for every new connection attempt, but you not destroy the previous one.
Try change disconnect() to close().
I have got Nearby Messages apparently working fine exchanging messages happily between IOS and Android, but my Android app has multiple Activities. Once I switched to the second Activity the messages stop.
I have stripped out all the code in the Android app except for the line Nearby.getMessagesClient(this).subscribe(listener).
I then have a button to switch to a new instance of the same Activity. This works (messages are received from an IOS App that is just sending messages every 15 seconds) as a first Activity, but then fails (no messages received) once I click on the button and it starts itself.
Note that the onSuccessListner callback is triggered (the onFailureListener isn't). It thinks it is registered, but just doesn't get any messages.
I also did this with 2 copies of Activity with the same code, just to check that it wasn't because it was the same Activity class that it was failing. Still failed.
I took the Google sample App. This still uses the deprecated GooglaApiClient. It still gives the same result though.
I did spot that if the user has to take an action to enable the messages it works (as in the sample where the user has to switch messages on). I therefore tried adding a delay. A delay of 400 milliseconds means on my device that it works. 300 milliseconds and it still fails.
So I seem to have 2 choices. Add a spurious 1 second delay before enabling messages, or convert my App to use Fragments and a single Activity (I tried using the Application context and contrary to some documentation I found it tells me it must be an Activity Context). Neither are satisfactory workarounds, so am hoping that I have done something stupid.
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
fab.setOnClickListener { view -> // Switch to self.
val intent = Intent(this, MainActivity2::class.java)
startActivity(intent)
}
mMessageListener = object : MessageListener() {
override public fun onFound(message: Message) {
Log.d(TAG, "Found message: " + String(message.content)) //Triggered ok until Activity switch
}
}
}
override public fun onResume() {
super.onResume();
uiScope.launch {// Using coroutines as a simple means of adding a delay. It also fails with no coroutine (and no delay)
delay(300) // This fails - but a value of 400 means that messages are received
Nearby.getMessagesClient(this#MainActivity2).subscribe(mMessageListener)
.addOnSuccessListener {
Log.e(TAG, "Success") // Always triggered
}.addOnFailureListener {
Log.e(TAG, "Failed " + it) // Never triggered
}
}
}
override fun onStop() {
Nearby.getMessagesClient(this).unsubscribe(mMessageListener)
super.onStop();
}
This is the code I am currently using, but as commented above it also fails with the old API, with Java, with no coroutines, etc. The Logcat is identical in the 300 or 400 ms cases.
I used the android beacon library to do the following action:
I switched on and off with a fast pace. On and off, on and off, and so on for 8-9 times.
However, the beacon then lost the signal for about 10 seconds and then the signal started to be received again.
Also, I tried an Android API function, "lescan", which resulted in the same situation.
Does anyone know why this happens?
MY testing device is:
HUAWEI P20 Pro 8.1
Samsung S6 7.0
override fun onResume() {
beaconManager = BeaconManager.getInstanceForApplication(this)
beaconManager.getBeaconParsers().add(BeaconParser().
setBeaconLayout(IBEACON_LAYOUT))
beaconManager.getBeaconParsers().add(BeaconParser().
setBeaconLayout(EDDYSTONE_UID_LAYOUT))
beaconManager.getBeaconParsers().add(BeaconParser().
setBeaconLayout(EDDYSTONE_URL_LAYOUT))
beaconManager.getBeaconParsers().add(BeaconParser().
setBeaconLayout(EDDYSTONE_TLM_LAYOUT))
beaconManager.bind(this)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
val permissions = ArrayList<String>()
if (PackageManager.PERMISSION_GRANTED != ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION)) permissions.add(Manifest.permission.ACCESS_COARSE_LOCATION)
if (permissions.size != 0) {
ActivityCompat.requestPermissions(this, permissions.toTypedArray(), 100)
}
}
}
override fun onBeaconServiceConnect() {
beaconManager.addRangeNotifier{ beacons,region ->
Log.d("addRangeNotifier",beacons.size.toString())
}
try {
beaconManager.startRangingBeaconsInRegion(Region("com.gigabyte.testkotlin", null, null, null))
} catch (e: RemoteException) {
e.printStackTrace()
}
}
override fun onPause() {
super.onPause()
beaconManager.unbind(this)
}
It's hard to say exactly what you are witnessing without seeing exact code to reproduce, but turning scanning on and off quickly is not necessarily a problem on all devices.
By default, the Android Beacon Library uses a foreground scan period of 1100 ms and a between scan period of 0ms, so it effectively turns scanning on and off 9 times in just over 10 seconds -- similar to what you describe.
I have never noticed these symptoms in normal use of the library on Samsung devices or the Huawei P9, so something else must be triggering this behaviour in your test case.
EDIT: The posted code indicates that the activity itself is what is started and stopped rapidly, and because it binds and unbinds to the beaconManager as it starts and stops, it also starts and stops the Android service that scans for beacons. These are heavy weight data structures that are not designed to be started and stopped rapidly. Short answer: don't do this. If you really need to start and stop your activity rapidly, bind to the beaconManager outside the activity lifcycle, perhaps only one at app startup in the onCreate method of a custom Android Application class.