Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Library detecting AltBeacons but not iBeacons #1223

Open
vinceprofeta opened this issue Feb 18, 2025 · 11 comments
Open

Library detecting AltBeacons but not iBeacons #1223

vinceprofeta opened this issue Feb 18, 2025 · 11 comments

Comments

@vinceprofeta
Copy link

vinceprofeta commented Feb 18, 2025

I'm developing a React Native module that uses the AltBeacon library to scan for iBeacons. While the implementation successfully detects AltBeacons, it's not detecting any iBeacons.

I am emitting beacons from BeaconScope on both Android and iOS. I am certainly doing something simple wrong, but would love some help.

https://github.com/vinceprofeta/react-native-beacon-radar/blob/main/android/src/main/java/com/beaconradar/BeaconRadarModule.java

Current Implementation:

beaconManager.getBeaconParsers().clear();
beaconManager.getBeaconParsers().add(new BeaconParser()
.setBeaconLayout("m:2-3=0215,i:4-19,i:20-21,i:22-23,p:24-24"));

What's Working:

  • AltBeacon detection works perfectly
  • Bluetooth permissions are properly handled
  • Scanning starts and runs without errors
  • We receive callbacks in the didRangeBeaconsInRegion method

What's Not Working:

  • iBeacons are not being detected at all
  • We've verified the iBeacons are broadcasting using other tools
  • No errors in the logs

Things I've Tried:

  • Different parser layouts
  • Verifying Bluetooth is enabled and permissions are granted
  • Confirming the iBeacons are within range and broadcasting
  • Logging beacon detection events

Questions:

  1. What is the correct parser layout for iBeacon detection?
  2. Do I need additional configuration specifically for iBeacon support?
  3. Are there common pitfalls when implementing iBeacon detection with the AltBeacon library?

Environment:

  • Android Beacon Library version: [version]
  • React Native version: [version]
  • Android target SDK: [version]
  • Testing device: [device model and Android version]

Any help would be greatly appreciated!

@VolodaUa
Copy link
Contributor

Hi @vinceprofeta

I haven't worked with AltBeacon, but I worked with iBeacons. The library follows the approuch how it is designed on iOS where we gave Region that we need to monitor.

I didn't see that you called BeaconManager.startMonitoring(region). It's important for iBeacon

I support it's enought to call startRaning to find AltBeacon.

Check this one first:

  1. Create BeaconManager, set layout, configure scan periods
  2. where you have startScanning
  • add range and monitor notifer
  • call BeaconManager.startMonitoring(region)
  1. Once you enter in region, MonitorNotifier.didEnterRegion() is called
  2. Then call BeaconManager.startRanging(region) and get ranged beacons callback.

Then:
Please also check the refence app. It actually has the code for iBeacon
https://github.com/davidgyoung/android-beacon-library-reference-kotlin

And finally:
Not sure that you implemented foreground service scanning correctly.
You need to call enableForegroundServiceScanning to enable ForegroundService scanning and call setEnableScheduledScanJobs(false)
More here:
https://altbeacon.github.io/android-beacon-library/foreground-service.html

@vinceprofeta
Copy link
Author

vinceprofeta commented Feb 20, 2025

@VolodaUa Thank you for getting back to me!

I took a look and tried to play around a bit but I can still only detect an AltBeacon. The below log shows what I get for the AltBeacon.

02-19 19:09:07.679 29908 29908 D BeaconRadarModule: The first beacon id1: 2f234454-cf6d-4a0f-adf2-f4911ba9ffa6 id2: 1 id3: 2 type altbeacon is about -1.0 meters away.
02-19 19:09:08.784 29908 29908 D BeaconRadarModule: didRangeBeaconsInRegion called with beacon count: 1

Through the BeaconScope app I grabbed the raw packet of the beacon I am emitting which is below

1A FF 4C 00 02 15 2F 23 44 54 CF 6D 4A OF AD F2 F4 91 1B A9
FF A6 00 01 00 02 C5 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00

I tried this exact template "m:0-3=4c000215,i:4-19,i:20-21,i:22-23,p:24-24" but no luck with this either. Below is my latest attempts to get it working. Hacky, but trying to get the ibeacon detection.

Any ideas or issues with what you see below? If it were a enableForegroundServiceScanning issue would I still see the altBeacon?

package com.beaconradar;

import android.Manifest;
import android.bluetooth.BluetoothAdapter;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.os.Build;
import android.util.Log;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;

import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.WritableArray;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.module.annotations.ReactModule;
import com.facebook.react.modules.core.DeviceEventManagerModule;
import com.facebook.react.modules.core.PermissionAwareActivity;
import com.facebook.react.modules.core.PermissionListener;

import org.altbeacon.beacon.Beacon;
import org.altbeacon.beacon.BeaconManager;
import org.altbeacon.beacon.BeaconParser;
import org.altbeacon.beacon.Identifier;
import org.altbeacon.beacon.RangeNotifier;
import org.altbeacon.beacon.Region;
import org.altbeacon.beacon.MonitorNotifier;
import org.altbeacon.beacon.service.MonitoringStatus;
import org.altbeacon.beacon.BeaconConsumer;

import java.util.Collection;

@ReactModule(name = BeaconRadarModule.NAME)
public class BeaconRadarModule extends ReactContextBaseJavaModule implements PermissionListener, MonitorNotifier {
  public static final String NAME = "BeaconRadar";
  private BeaconManager beaconManager;
  private Region region;
  private static BeaconRadarModule instance;
  private final ReactContext reactContext;
  private final BluetoothAdapter bluetoothAdapter;

  public static BeaconRadarModule getInstance() {
    return instance;
  }

  @Override
  @NonNull
  public String getName() {
    return NAME;
  }

  public BeaconRadarModule(ReactApplicationContext reactContext) {
    super(reactContext);
    this.reactContext = reactContext;
    beaconManager = BeaconManager.getInstanceForApplication(reactContext);
    // beaconManager.getBeaconParsers().clear();
    beaconManager.getBeaconParsers().add(new BeaconParser().setBeaconLayout("m:2-3=0215,i:4-19,i:20-21,i:22-23,p:24-24"));

    instance = this;
    bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
  }

  public void runScan(String uuid, final Promise promise) {
    // Run on main thread using the Activity
    getCurrentActivity().runOnUiThread(new Runnable() {
        @Override
        public void run() {
            Log.d("BeaconRadarModule", "running scan");
            beaconManager.setDebug(true);
            beaconManager.addMonitorNotifier(BeaconRadarModule.this);
            region = new Region("my-region-2", null, null, null);
            for (Region region: beaconManager.getMonitoredRegions()) {
                Log.d("BeaconRadarModule", "stopping monitoring");
                beaconManager.stopMonitoring(region);
            }
            Log.d("BeaconRadarModule", "starting monitoring");
            beaconManager.startMonitoring(region);
            Log.d("BeaconRadarModule", "monitoring started success");

            RangeNotifier rangeNotifier = new RangeNotifier() {
                @Override
                public void didRangeBeaconsInRegion(Collection<Beacon> beacons, Region region) {
                    if (beacons.size() > 0) {
                        Log.d("BeaconRadarModule", "didRangeBeaconsInRegion called with beacon count: "+beacons.size());
                        Beacon firstBeacon = beacons.iterator().next();
                        Log.d("BeaconRadarModule", "The first beacon " + firstBeacon.toString() + " is about " + firstBeacon.getDistance() + " meters away.");
                    }
                }
            };
            beaconManager.addRangeNotifier(rangeNotifier);
            beaconManager.startRangingBeacons(region);
        }
    });
  }


  @Override
  public void didEnterRegion(Region arg0) {
      Log.d("BeaconRadarModule", "did enter region.");
  }

  @Override
  public void didExitRegion(Region region) {
      Log.d("BeaconRadarModule", "didExitRegion");
  }

  @Override
  public void didDetermineStateForRegion(int state, Region region) {
      Log.d("BeaconRadarModule", "didDetermineStateForRegion");
  }


  @ReactMethod
  public void startScanning(String uuid, ReadableMap config, final Promise promise) {
    runScan(uuid, promise);
  }

}

@VolodaUa
Copy link
Contributor

@vinceprofeta Have you checked the reference app? Is it works there?

@vinceprofeta
Copy link
Author

@VolodaUa I will have to try it out. My code mirrors the java code in the reference app, and the reference app is using a really old version of Gradle, and not building. ill have to dig in.

 BeaconParser: This is not a matching Beacon advertisement. Was expecting 84 7a 32 c1 2c 21 56 93 de 4d 97 cd 26 27 05 4c at offset 5 and 00 at offset 21.  The bytes I see are: 02011a1aff4c0002152f234454cf6d4a0fadf2f4911ba9ffa600010002c50000000000000000000000000000000000000000000000000000000000000000
02-19 20:15:32.296 27305  4701 D BeaconParser: Ignoring pdu type 01
02-19 20:15:32.296 27305  4701 D BeaconParser: Processing pdu type FF: 02011a1aff4c0002152f234454cf6d4a0fadf2f4911ba9ffa600010002c50000000000000000000000000000000000000000000000000000000000000000 with startIndex: 5, endIndex: 29

It may be worth noting that from the logs It appears to see the right packet with uuid ...9ffa but then gets ignored.

@vinceprofeta
Copy link
Author

@VolodaUa the Kotlin version of the reference applicaiton detect the beacon but the Java application does not.

@vinceprofeta
Copy link
Author

Okay update. I rewrote it in KT following the reference app exactly. I can still only detect AltBeacons

package com.beaconradar

import android.Manifest
import android.app.Notification
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.bluetooth.BluetoothAdapter
import android.bluetooth.BluetoothManager
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.content.pm.PackageManager
import android.os.Build
import android.util.Log
import androidx.core.app.NotificationCompat
import androidx.core.content.ContextCompat
import androidx.lifecycle.Observer
import com.facebook.react.bridge.*
import com.facebook.react.module.annotations.ReactModule
import com.facebook.react.modules.core.DeviceEventManagerModule
import com.facebook.react.modules.core.PermissionAwareActivity
import com.facebook.react.modules.core.PermissionListener
import org.altbeacon.beacon.*
import java.util.*

@ReactModule(name = BeaconRadarModule.NAME)
class BeaconRadarModule(private val reactContext: ReactApplicationContext) :
    ReactContextBaseJavaModule(reactContext),
    PermissionListener {

    companion object {
        const val TAG = "BeaconReference"
        const val NAME = "BeaconRadar"
        private const val BLUETOOTH_ERROR = "BLUETOOTH_ERROR"
        private const val PERMISSION_REQUEST_CODE = 1
        const val UPDATE = "updateBeacons"
        const val BEACONS = "beacons"
        private var instance: BeaconRadarModule? = null

        @JvmStatic
        fun getInstance(): BeaconRadarModule? {
            return instance
        }
    }

    private val beaconManager: BeaconManager = BeaconManager.getInstanceForApplication(reactContext)
    private var region: Region = Region("RNIbeaconScannerRegion222", null, null, null)
    private var permissionPromise: Promise? = null
    private val bluetoothAdapter: BluetoothAdapter? by lazy {
        (reactContext.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager).adapter
    }

    init {
        instance = this
        setupBeaconManager()
    }

    private fun setupBeaconManager() {
        BeaconManager.setDebug(true)

        // Clear existing parsers
        // beaconManager.beaconParsers.clear()

        // Add iBeacon parser with hardware assist
        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.beaconParsers.add(parser)

        // Optimize scanning settings
        beaconManager.foregroundScanPeriod = 1100L
        beaconManager.foregroundBetweenScanPeriod = 0L
        beaconManager.backgroundScanPeriod = 1100L
        beaconManager.backgroundBetweenScanPeriod = 0L
    }

    fun setupBeaconScanning() {
        try {
            setupForegroundService()
        }
        catch (e: SecurityException) {
            Log.d(TAG, "Not setting up foreground service scanning until location permission granted by user")
            return
        }

        beaconManager.startMonitoring(region)
        beaconManager.startRangingBeacons(region)

        // Run observers on main thread
        reactContext.runOnUiQueueThread {
            val regionViewModel = BeaconManager.getInstanceForApplication(reactContext).getRegionViewModel(region)
            regionViewModel.regionState.observeForever(centralMonitoringObserver)
            regionViewModel.rangedBeacons.observeForever(centralRangingObserver)
        }
    }

    fun setupForegroundService() {
        val builder = NotificationCompat.Builder(reactContext, "BeaconReferenceApp")
            .setSmallIcon(android.R.drawable.ic_dialog_info)
            .setContentTitle("Scanning for Beacons")
            .setForegroundServiceBehavior(NotificationCompat.FOREGROUND_SERVICE_IMMEDIATE)

        // Create a default intent that opens the app's package
        val intent = reactContext.packageManager.getLaunchIntentForPackage(reactContext.packageName)
            ?: Intent().apply {
                setPackage(reactContext.packageName)
            }

        val pendingIntent = PendingIntent.getActivity(
            reactContext,
            0,
            intent,
            PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
        )

        builder.setContentIntent(pendingIntent)

        val channel = NotificationChannel(
            "beacon-ref-notification-id",
            "My Notification Name",
            NotificationManager.IMPORTANCE_DEFAULT
        ).apply {
            description = "My Notification Channel Description"
        }

        val notificationManager = reactContext.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
        notificationManager.createNotificationChannel(channel)
        builder.setChannelId(channel.id)

        Log.d(TAG, "Calling enableForegroundServiceScanning")
        BeaconManager.getInstanceForApplication(reactContext).enableForegroundServiceScanning(
            builder.build(),
            456
        )
        Log.d(TAG, "Back from enableForegroundServiceScanning")
    }

    val centralMonitoringObserver = Observer<Int> { state ->
        if (state == MonitorNotifier.OUTSIDE) {
            Log.d(TAG, "outside beacon region: $region")
        } else {
            Log.d(TAG, "inside beacon region: $region")
            // sendNotification() // Implement this if needed
        }
    }

    val centralRangingObserver = Observer<Collection<Beacon>> { beacons ->
        reactContext.runOnUiQueueThread {
            Log.d(TAG, "Raw beacon count: ${beacons.count()}")

            beacons.forEach { beacon ->
                Log.d(TAG, """
                    Raw Beacon Data:
                    UUID: ${beacon.id1}
                    Major: ${beacon.id2}
                    Minor: ${beacon.id3}
                    Distance: ${beacon.distance} meters
                    RSSI: ${beacon.rssi}
                    Last Detection: ${beacon.lastCycleDetectionTimestamp}
                    Current Time: ${System.currentTimeMillis()}
                    Age: ${System.currentTimeMillis() - beacon.lastCycleDetectionTimestamp} ms
                    ----------------------------------------
                """.trimIndent())
            }

            // Filter out beacons older than 10 seconds
            val recentBeacons = beacons.filter { beacon ->
                val age = System.currentTimeMillis() - beacon.lastCycleDetectionTimestamp
                age < 10000
            }

            Log.d(TAG, "Recent beacons count: ${recentBeacons.size}")
            recentBeacons.forEach { beacon ->
                Log.d(TAG, """
                    Recent Beacon Details:
                    UUID: ${beacon.id1}
                    Major: ${beacon.id2}
                    Minor: ${beacon.id3}
                    Distance: ${beacon.distance} meters
                    RSSI: ${beacon.rssi}
                    Tx Power: ${beacon.txPower}
                    Bluetooth Name: ${beacon.bluetoothName}
                    Bluetooth Address: ${beacon.bluetoothAddress}
                    Manufacturer: ${beacon.manufacturer}
                    ----------------------------------------
                """.trimIndent())
            }
        }
    }

    override fun getName(): String = NAME

    @ReactMethod
    fun startScanning(uuid: String?, config: ReadableMap?, promise: Promise) {
        // Use null identifiers for wildcard matching if no UUID provided
        region = if (uuid != null) {
            Region("all-beacons", null, null, null)
            // Region("RNIbeaconScannerRegion", Identifier.parse(uuid), null, null)
        } else {
            Region("all-beacons", null, null, null)
        }

        setupBeaconScanning()
        promise.resolve(null)
    }

    private fun startForegroundService(config: ReadableMap, uuid: String) {
    }

    @ReactMethod
    fun requestAlwaysAuthorization(promise: Promise) {
        // Implementation here
    }

    @ReactMethod
    fun initializeBluetoothManager(promise: Promise) {
        // Implementation here
    }

    private fun getBluetoothState(): String {
        return "unknown"

    }

    @ReactMethod
    fun getAuthorizationStatus(promise: Promise) {
        // Implementation here
    }

    @ReactMethod
    fun runScan(uuid: String, promise: Promise) {
        // Implementation here
    }

    @ReactMethod
    fun stopScanning(promise: Promise) {
        // Implementation here
    }

    fun runScanForAllBeacons(promise: Promise) {
        // Implementation here
    }

    override fun onRequestPermissionsResult(
        requestCode: Int,
        permissions: Array<String>,
        grantResults: IntArray
    ): Boolean {

        return true
    }
}

@VolodaUa
Copy link
Contributor

Hi @vinceprofeta

Please specify the UUID of the beacon in your region

Region(
regioidentifier,
Identifier.parse("$uuid"),
null,
null
)

@vinceprofeta
Copy link
Author

@VolodaUa I've tried that, confirmed in the logs, confirmed the right parser was being used.

Altbeacons work fine, and I see the packet in the logs but they are getting filtered out. I've feel like I have exhausted all potential solutions without digging into the core of this library...

Im not sure if there is an issue with the RN bridge or context where there is some override happening or a parsing issue.

@VolodaUa
Copy link
Contributor

@vinceprofeta focus on iBeacons first and specify region. Not sure that library will support two types of beacons at the same time.

When you start monitoring , please also add request StateForRegion to get didDetermibateState callback with region state and start ranging if you are inside.

@vinceprofeta
Copy link
Author

@VolodaUa thank you. Sorry I was out. Can you be a bit more clear what you mean by add request StateForRegion to get didDetermibateState callback with region state and start ranging if you are inside.

I have tried every combination of beacon parser. single, multiple, clearing, not clearing, etc.

@VolodaUa
Copy link
Contributor

VolodaUa commented Feb 26, 2025

Hi @vinceprofeta

I would suggest moving this issue to StackOverflow and closing it here.

BeaconManager.requestStateForRegion() function.

I would suggest call it when you request to start monitoring beacons.

beaconManager.startMonitoring(region)
beaconManager.requestStateForRegion(region)

MonitorNotifierImpl.kt
`override fun didEnterRegion(region: Region) {
logger.w("The user entered in the region: $region")

    beaconManager.startRangingBeacons(region)
 
}

override fun didDetermineStateForRegion(state: Int, region: Region) {
logger.i("Determining the region ${region.uniqueId}} state is ${state}}")

    if (state == 1) {
        this.didEnterRegion(region)
    }
}`

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants