AltBeacon / android-beacon-library

Allows Android apps to interact with BLE beacons

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Minimal reproducible example stops detecting HolyIoT beacons after screen is locked

egeres opened this issue · comments

commented

I bought a small beacon from Aliexpress that uses the NRF52810 chip and a different beacon based on the NRF51822 from Amazon, both are supposed to be ibeacons. I attempted to create a minimal working example (I'm new to Kotlin, so I'm probably making a lot of mistakes) that just ranges the beacons.

This works with no problem when I launch the code, and even if I switch the context to a different app. However, when I lock my screen, the Aliexpress beacon stops being detected, but the Amazon one keeps being detected. I quite can't tell if this is a library bug, if it's caused by my bad code, or if it comes from hardware... In any case, I tried building the reference app but didn't succeed, I'm not sure if copying the code into my code little by little is a good idea.

(Weirdly enough, the Aliexpress beacons are not detected by apps like beacon scanner)

Expected behavior

All beacons should be detected with the screen locked

Actual behavior

HolyIoT (Aliexpress) beacons stop being detected when I lock my screen

Steps to reproduce this behavior

Run the code with the same hardware...?

Mobile device model and OS version

  • Galaxy Note 10+
  • Android 12

Android Beacon Library version

2.19.5

Code:

import android.app.*
import android.content.*
import android.os.*
import android.util.Log
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.NotificationCompat
import androidx.lifecycle.Observer
import org.altbeacon.beacon.*

class BeaconService : Service(), BeaconConsumer {

    private lateinit var wakeLock: PowerManager.WakeLock
    private lateinit var beaconManager: BeaconManager
    private val FOREGROUND_NOTIFICATION_ID = 1
    private val CHANNEL_ID = "BeaconServiceChannel"
    private val TAG = "MainActivity"

    private val centralRangingObserver = Observer<Collection<Beacon>> { beacons ->
        Log.d(TAG, "Ranged: ${beacons.count()} beacons")
    }

    private fun createNotification(): Notification {
        val builder = NotificationCompat.Builder(this, CHANNEL_ID)
            .setContentTitle("My App")
            .setContentText("Scanning for beacons...")
            .setSmallIcon(R.drawable.ic_notification_0)
        return builder.build()
    }

    override fun onCreate() {
        super.onCreate()

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            val channel = NotificationChannel(CHANNEL_ID, "Beacon Service Channel", NotificationManager.IMPORTANCE_DEFAULT)
            getSystemService(NotificationManager::class.java).createNotificationChannel(channel)
        }

        beaconManager = BeaconManager.getInstanceForApplication(this)
        beaconManager.getBeaconParsers().clear()
        beaconManager.getBeaconParsers().add(BeaconParser().setBeaconLayout("m:2-3=0215,i:4-19,i:20-21,i:22-23,p:24-24"))
        beaconManager.backgroundBetweenScanPeriod = 0
        beaconManager.backgroundScanPeriod        = 3000
        beaconManager.foregroundBetweenScanPeriod = 0
        beaconManager.foregroundScanPeriod        = 3000
        beaconManager.enableForegroundServiceScanning(createNotification(), FOREGROUND_NOTIFICATION_ID)
        beaconManager.setEnableScheduledScanJobs(false)

        val region = Region("all-beacons", null, null, null)
        val regionViewModel = BeaconManager.getInstanceForApplication(this).getRegionViewModel(region)
        regionViewModel.rangedBeacons.observeForever(centralRangingObserver)
        beaconManager.startRangingBeacons(region)
    }

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        super.onStartCommand(intent, flags, startId)

        val powerManager = getSystemService(Context.POWER_SERVICE) as PowerManager
        wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "MyApp::MyWakelockTag")
        wakeLock.acquire()

        beaconManager.bind(this)

        val notification = NotificationCompat.Builder(this, CHANNEL_ID)
            .setContentTitle("My App")
            .setContentText("Scanning for beacons...")
            .setSmallIcon(R.drawable.ic_notification_0)
            .build()
        startForeground(1, notification)

        return START_STICKY
    }

    override fun onDestroy() {
        super.onDestroy()
        wakeLock.release()
        beaconManager.unbind(this) // Stop scanning for beacons
    }

    override fun onBeaconServiceConnect() {}
    override fun onBind(intent: Intent?): IBinder? { TODO("Not implemented yet") }
}

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val serviceIntent = Intent(this, BeaconService::class.java)
        startService(serviceIntent)
    }
}

It's not like the log output yields much new insight, but it looks like this:

2023-03-13 08:45:36.164 5494-5494/com.example.personal_beacon D/MainActivity: Ranged: 2 beacons
2023-03-13 08:45:39.172 5494-5494/com.example.personal_beacon D/MainActivity: Ranged: 2 beacons
2023-03-13 08:45:42.179 5494-5494/com.example.personal_beacon D/MainActivity: Ranged: 2 beacons (after this point the screen is locked)
2023-03-13 08:45:45.195 5494-5494/com.example.personal_beacon D/MainActivity: Ranged: 1 beacons
2023-03-13 08:45:48.204 5494-5494/com.example.personal_beacon D/MainActivity: Ranged: 1 beacons
2023-03-13 08:45:51.215 5494-5494/com.example.personal_beacon D/MainActivity: Ranged: 1 beacons

I suspect the Aliexpress beacon is using an incorrect manufacturer code for iBeacon. The manufacturer code should be 0x004c (Apple). This code is inside the advertisement and is used on both iOS and Android apps in the background to detect beacons using BLE hardware scan filters. If the manufacturer code is wrong in the advertisements, no beacons will be detected in the background. This is a common problem with beacons made by cheap manufacturers who don't fully understand what they are doing.

To confirm if this is the problem set beaconManager.setDebug(true) then look at LogCat while only the AliExpress beacon is advertising for a line like this:

Processing pdu type FF: 02011a1aff4c000215e20a39f473f54bc4a12f17d1ad07a96100010000c80000000000000000000000000000000000000000000000000000000000000000 with startIndex: 5, endIndex: 29

Please reply to this issue the the log line you see similar to the above.

An iBeacon advertisement should start with ff4c000215 as shown above. The manufacturer code is the two bytes that shows up between ff and 0215 (in this case 4c 00 which is a little endian representation of 0x004c). Check to see if your beacon is advertising a different code.

You can configure the library to work with beacons advertising incorrect or non-standard manufacturer codes by using code like this, where you replace 0x004c with the manufacturer code you find above (remember to reverse the bytes as they are in little endian order in the log):

        val parser = BeaconParser().
        setBeaconLayout("m:2-3=0215,i:4-19,i:20-21,i:22-23,p:24-24")
        parser.setHardwareAssistManufacturerCodes(arrayOf(0x004c).toIntArray())
        beaconManager.getBeaconParsers().add(
            parser)

Be careful with this workaround. There is no such workaround on iOS and it generally will be impossible to detect that beacon on iOS. If you confirm this is the cause of the problem and you need a cross-platform beacon, it is best to destroy those units from AliExpress so they do not cause anyone else this trouble again!

commented

Thank you so much for such a detailed answer with so much dedication in the explanation part (and all the wonderful work you've done with the library)!!

I really had a hard time googling a lot to fill in the knowledge gaps... but you're right, the Aliexpress beacon was advertising a different code... in this case 0xffff as seen in:
Processing pdu type FF: 0201061affffff0215fda50693a4e24fb1afcfc6eb07640000271b4cb9c90d09410000000000000000000000101642524160ed03fe693e89040306000000 with startIndex: 5, endIndex: 29

So for me I just have to add:

parser.setHardwareAssistManufacturerCodes(arrayOf(0xffff).toIntArray())

For now I won't cast the Aliexpress beacon into the fires of mount doom (where they are forged) because I just wanted them for a hobbyist project, but I'll keep my eyes open!