Ì am trying to connect with beacons in my android app. But I can't seem to find my beacons. I am using Ibeacons. I am using the AltBeacon Library. The onBeaconServiceConnect starts and the didDetermineStateForRegion follows. But didEnterReion never gets called. This is my code:
public class ListScenarios extends AppCompatActivity implements BeaconConsumer, MonitorNotifier {
private static final String TAG = "ListScenarios";
private ListView listView;
public String persoonID;
public String adresID;
public Array beaconArray;
//private ArrayList<IBeacon> arrBeacons = new ArrayList<>();
private BeaconManager mBeaconManager;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.list_scenarios);
// Setup beaconmanager
mBeaconManager = BeaconManager.getInstanceForApplication(this.getApplicationContext());
// Detect iBeacon
mBeaconManager.getBeaconParsers().add(new BeaconParser()
.setBeaconLayout("m:2-3=beac,i:4-19,i:20-21,i:22-23,p:24-24,d:25-25"));
mBeaconManager.bind(this);
//listview
listView = (ListView) findViewById(R.id.list_scenario);
//send request to load list
getScenarios();
getBeacons();
}
// MARK: - Bluetooth
#Override
public void onBeaconServiceConnect() {
System.out.println("We are in onBeaconServiceConnect");
// Set the two identifiers below to null to detect any beacon regardless of identifiers
Identifier myBeaconNamespaceId = null;
Identifier myBeaconInstanceId = null;
Region region = new Region("my-beacon-region", myBeaconNamespaceId, myBeaconInstanceId, null);
mBeaconManager.addMonitorNotifier(this);
try {
mBeaconManager.startMonitoringBeaconsInRegion(region);
} catch (RemoteException e) {
e.printStackTrace();
}
}
public void didEnterRegion(Region region) {
System.out.println("We are in didEnterRegion");
Log.d(TAG, "I detected a beacon in the region with namespace id " + region.getId1() +
" and instance id: " + region.getId2());
}
public void didExitRegion(Region region) {
System.out.println("We are in didExitRegion");
}
public void didDetermineStateForRegion(int state, Region region) {
System.out.println("We are in didDetermineStateForRegion");
}
#Override
public void onPause() {
super.onPause();
mBeaconManager.unbind(this);
}
Two things to check:
Make sure you are using the correct BeaconParser expression. The expression shown in the question is for AltBeacon: "m:2-3=beac,i:4-19,i:20-21,i:22-23,p:24-24,d:25-25. If you are trying to detect iBeacon, you'll need to use a different expression. This site has a handy reference: https://beaconlayout.wordpress.com/
If you are running your app on Android 6+ and are targeting SDK 23 or higher, you need to dynamically request location permission for your app. If you don't do this, you will get no detections, and you will see this in the logs: 04-22 22:35:20.152 5158 5254 E BluetoothUtils: Permission denial: Need ACCESS_COARSE_LOCATION or ACCESS_FINE_LOCATION permission to get scan results You can read how to do this here: http://altbeacon.github.io/android-beacon-library/requesting_permission.html
Related
I am developing application for monitoring and configuring beacon devices. I need to monitor both iBeacon and Eddystone beacon devices. I have go through the nRF Master control application. Its working perfectly. But I need a source code for that. Is any other option available. Kindly get me the best solution for analysing beacon devices. Thanks in advance.
I would suggest you to use AltBeacon library, which I have used in one of my projects and it's pretty good (I am not associated with it in any way). It provides APIs to interact with beacons.
Here is a sample Activity to get you started:
public class MyActivity extends AppCompatActivity implements
BeaconConsumer,
BootstrapNotifier,
RangeNotifier
{
private RegionBootstrap mRegionBootstrap;
private org.altbeacon.beacon.BeaconManager mAltBeaconManager;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_my_application);
mAltBeaconManager = org.altbeacon.beacon.BeaconManager.getInstanceForApplication(MyActivity.this);
boolean isBleAvailableAndEnabled;
try {
isBleAvailableAndEnabled = mAltBeaconManager.checkAvailability();
} catch (BleNotAvailableException ex) {
isBleAvailableAndEnabled = false;
}
if (!isBleAvailableAndEnabled) {
// Handle case ...
finish();
}
// Disable Android L scanning on devices with Android 5.0 and above
if (Build.VERSION.SDK_INT >= 21) mAltBeaconManager.setAndroidLScanningDisabled(true);
// Add iBeacon Layout
mAltBeaconManager.getBeaconParsers().add(new BeaconParser().setBeaconLayout("m:2-3=0215,i:4-19,i:20-21,i:22-23,p:24-24"));
// Add Eddystone Layout
mAltBeaconManager.getBeaconParsers().add(new BeaconParser().setBeaconLayout("Eddystone_layout"));
mAltBeaconManager.setBackgroundBetweenScanPeriod(3000); // 3 sec
mAltBeaconManager.setBackgroundScanPeriod(5000); // 5 sec
mAltBeaconManager.bind(MyActivity.this);
// Enable Beacon scanning
mRegionBootstrap = new RegionBootstrap(MyActivity.this, getScanningRegion());
}
#Override
public Context getApplicationContext() {
return (!isFinishing()) ? MyActivity.this : null);
}
#Override
public void onBeaconServiceConnect() {
try {
// Attach beacon range listener
mAltBeaconManager.setRangeNotifier(this);
mAltBeaconManager.startRangingBeaconsInRegion(new Region("Region", null, null, null));
} catch (RemoteException ex) {
// Handle exception
}
}
#Override
public void unbindService(ServiceConnection serviceConnection) {
// Not needed
}
#Override
public boolean bindService(Intent intent, ServiceConnection serviceConnection, int i) {
return false;
}
#Override
public void didEnterRegion(Region region) {
// Handle event
}
#Override
public void didExitRegion(Region region) {
// Handle event
}
#Override
public void didDetermineStateForRegion(int i, Region region) {
// Handle event
}
#Override
public void didRangeBeaconsInRegion(Collection<org.altbeacon.beacon.Beacon> rangingBeacons, Region region) {
// Here you will receive the beacons which are currently in range
}
}
Add this to your Manifest:
<uses-sdk android:minSdkVersion="18" android:targetSdkVersion="18" />
<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
Note: Also, pay attention to this library's limitations, which essentially are Android's software and hardware limitations regarding beacons support.
I am working on Android beacon app with PiBeacon as a beacon device.
I have setup Respberry Pi As Beacon Device as given on adfruit website.
I can see that PiBeacon is transmitting by using other BLE Scanner application available on Play Store.
I have download the Altbeacon and configure my Android Studio Project according to given guide on Altbeacon Sample but the app is not showing the ble device. Can anyone help me what i am doing wrong ?
Following is the code that i am using for scanning PiBeacon.
public class MonitoringActivity extends Activity implements BeaconConsumer {
protected static final String TAG = "MonitoringActivity";
private BeaconManager beaconManager;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_ranging);
beaconManager = BeaconManager.getInstanceForApplication(this);
// To detect proprietary beacons, you must add a line like below corresponding to your beacon
// type. Do a web search for "setBeaconLayout" to get the proper expression.
// beaconManager.getBeaconParsers().add(new BeaconParser().
// setBeaconLayout("m:2-3=beac,i:4-19,i:20-21,i:22-23,p:24-24,d:25-25"));
beaconManager.bind(this);
}
#Override
protected void onDestroy() {
super.onDestroy();
beaconManager.unbind(this);
}
#Override
public void onBeaconServiceConnect() {
beaconManager.setMonitorNotifier(new MonitorNotifier() {
#Override
public void didEnterRegion(Region region) {
Log.i(TAG, "I just saw an beacon for the first time!");
}
#Override
public void didExitRegion(Region region) {
Log.i(TAG, "I no longer see an beacon");
}
#Override
public void didDetermineStateForRegion(int state, Region region) {
Log.i(TAG, "I have just switched from seeing/not seeing beacons: "+state);
}
});
try {
beaconManager.startMonitoringBeaconsInRegion(new Region("myMonitoringUniqueId", null, null, null));
} catch (RemoteException e) { }
}
}
The PiBeacon you reference sends and iBeacon transmission, but by default, the Android Beacon Library only detects open-source AltBeacon transmissions.
It's easy to detect other beacon types, but you need to add a new BeaconParser for the beacon type you are interested in. The instructions are right there in the comments of the code you posted:
To detect proprietary beacons, you must add a line like below corresponding to your beacon type. Do a web search for "setBeaconLayout" to get the proper expression.
Apologies, for the need to do this -- unfortunately intellectual property restrictions mean that we cannot include the expression in the library or post it on public forums like this. Fortunately, it is easy to find with a Google search.
I'm working with the AltBeacon library (2.5.1) to detect beacons.
I setup ranging with an "universal" Region to be able to detect any beacon in range, then do my stuff with it.
The issue is that when I have several beacons in range, the didRangeBeaconsInRegion callback always provides me a Collection of only 1 beacon at a time and this beacon is a random one among all the present beacons... Why can't I get all the beacons in range in my Collection ?
All of this is made from within a Service, I did clean all the other stuff to keep only the relevant parts of the code below -> Hopefully I am doing something wrong here ?
public class MonitorService extends Service implements BeaconConsumer
{
private BeaconManager beaconManager;
#Override
public void onCreate()
{
super.onCreate();
beaconManager = BeaconManager.getInstanceForApplication(this);
beaconManager.getBeaconParsers().add(new BeaconParser().
setBeaconLayout("m:2-3=0215,i:4-19,i:20-21,i:22-23,p:24-24"));
beaconManager.setForegroundScanPeriod(5000l);
beaconManager.setBackgroundScanPeriod(5000l);
beaconManager.setForegroundBetweenScanPeriod(1100l);
beaconManager.setBackgroundBetweenScanPeriod(1100l);
setupBeaconManager();
}
private void setupBeaconManager()
{
if (!beaconManager.isBound(this))
beaconManager.bind(this);
}
private void unsetBeaconManager()
{
if (beaconManager.isBound(this))
{
beaconManager.unbind(this);
try
{
beaconManager.stopRangingBeaconsInRegion(new Region("apr", null, null, null));
}
catch (RemoteException e)
{
Log.i(TAG, "RemoteException = "+e.toString());
}
}
}
#Override
public void onBeaconServiceConnect()
{
beaconManager.setRangeNotifier(new RangeNotifier() {
#Override
public void didRangeBeaconsInRegion(Collection<Beacon> beacons, Region region)
{
Log.i(TAG,"didRangeBeaconsInRegion, number of beacons detected = "+beacons.size());
// HERE IT IS : the size is Always 1, but the beacon (UUID etc. can be different)
}
});
try
{
beaconManager.startRangingBeaconsInRegion(new Region("apr", null, null, null));
}
catch (RemoteException e)
{
Log.i(TAG, "RemoteException = "+e.toString());
}
}
#Override
public void onDestroy()
{
unsetBeaconManager();
super.onDestroy();
}
}
I'm working on Android 5.1.1 with a Nexus 6 (but a Wiko cheap phone gives the same results). The beacons are setup to advertise every 600ms... But even with 100ms it also gives the exact same results...
The code looks OK. A couple of thoughts:
Try using an off the shelf beacon scanner app based on the same library like Locate. Does it detect all of your beacons simultaneously? If not, something may be wrong with the beacons or their configuration.
Do each of your beacons have unique identifiers? The library by default only detects multiple beacons if they have unique identifiers.
I'm following the same steps described here (the Google Fit client connection part is working fine).
final DataType dataType=TYPE_STEP_COUNT_DELTA;
DataSourcesRequest requestData = new DataSourcesRequest.Builder()
.setDataTypes(dataType) // At least one datatype must be specified.
.build();
Fitness.SensorsApi.findDataSources(mClient, requestData)
.setResultCallback(new ResultCallback<DataSourcesResult>() {
#Override
public void onResult(DataSourcesResult dataSourcesResult) {
Log.i(TAG, "Result: " + dataSourcesResult.getDataSources().size() + " sources "
+ dataSourcesResult.getStatus().toString());
for (DataSource dataSource : dataSourcesResult.getDataSources()) {
Log.i(TAG, "Data source found: " + dataSource.toString());
Log.i(TAG, "Data Source type: " + dataSource.getDataType().getName());
}
}
});
When I ask for data sources I get only one result which is the smartphone. If I add a listener then I really get data so it's working.
However it is also connected to an Android Wear smartwatch Gear Live with Android Wear app on the phone. Google Fit is installed in both of them but I'd like to get data from the smartwatch.
In the official guide I read
The Sensors API provides access to raw sensor data streams from
sensors available on the Android device and from sensors available in
companion devices, such as wearables.
This code is running on the smartphone so I think it would be right to expect data sources from companion smartwatch too. But it's like invisible to my phone application. Am I doing something wrong?
EDIT:
public class MainActivity extends AppCompatActivity {
private final static String TAG = "main_mobile";
private static final int REQUEST_OAUTH = 1;
private final static String DATE_FORMAT = "yyyy.MM.dd HH:mm:ss";
private static final String AUTH_PENDING = "auth_state_pending";
private boolean authInProgress = false;
private GoogleApiClient mClient = null;
private final static DataType dataType = TYPE_STEP_COUNT_DELTA;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (savedInstanceState != null) {
authInProgress = savedInstanceState.getBoolean(AUTH_PENDING);
}
mClient = new GoogleApiClient.Builder(this)
.addApi(Fitness.SENSORS_API)
.addApi(Fitness.RECORDING_API)
.addApi(Fitness.HISTORY_API)
.addScope(new Scope(Scopes.FITNESS_ACTIVITY_READ_WRITE))
.addConnectionCallbacks(connectionCallbacks)
.addOnConnectionFailedListener(connectionFailCallbacks)
.build();
}
private void initFitness() {
DataSourcesRequest requestData = new DataSourcesRequest.Builder()
.setDataTypes(dataType)
.build();
Fitness.SensorsApi.findDataSources(mClient, requestData)
.setResultCallback(new ResultCallback<DataSourcesResult>() {
#Override
public void onResult(DataSourcesResult dataSourcesResult) {
Log.i(TAG, "Result: " + dataSourcesResult.getDataSources().size() + " sources " + dataSourcesResult.getStatus().toString());
for (DataSource dataSource : dataSourcesResult.getDataSources()) {
Log.i(TAG, "\nData source found: \n\t" + dataSource.toString() + "\n\tType: " + dataSource.getDataType().getName());
}
}
});
}
#Override
protected void onStart() {
super.onStart();
Log.i(TAG, "Connecting...");
mClient.connect();
}
#Override
protected void onStop() {
super.onStop();
if (mClient.isConnected()) {
mClient.disconnect();
}
}
#Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putBoolean(AUTH_PENDING, authInProgress);
}
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == REQUEST_OAUTH) {
authInProgress = false;
if (resultCode == RESULT_OK) {
// Make sure the app is not already connected or attempting to connect
if (!mClient.isConnecting() && !mClient.isConnected()) {
mClient.connect();
}
}
}
}
GoogleApiClient.ConnectionCallbacks connectionCallbacks = new GoogleApiClient.ConnectionCallbacks() {
#Override
public void onConnected(Bundle bundle) {
Log.i(TAG, "Connected!!!");
// Now you can make calls to the Fitness APIs.
// Put application specific code here.
initFitness();
}
#Override
public void onConnectionSuspended(int i) {
// If your connection to the sensor gets lost at some point,
// you'll be able to determine the reason and react to it here.
if (i == GoogleApiClient.ConnectionCallbacks.CAUSE_NETWORK_LOST) {
Log.i(TAG, "Connection lost. Cause: Network Lost.");
} else if (i == GoogleApiClient.ConnectionCallbacks.CAUSE_SERVICE_DISCONNECTED) {
Log.i(TAG, "Connection lost. Reason: Service Disconnected");
}
}
};
GoogleApiClient.OnConnectionFailedListener connectionFailCallbacks = new GoogleApiClient.OnConnectionFailedListener() {
// Called whenever the API client fails to connect.
#Override
public void onConnectionFailed(ConnectionResult result) {
Log.i(TAG, "Connection failed. Cause: " + result.toString());
if (!result.hasResolution()) {
// Show the localized error dialog
GooglePlayServicesUtil.getErrorDialog(result.getErrorCode(), MainActivity.this, 0).show();
return;
}
// The failure has a resolution. Resolve it.
// Called typically when the app is not yet authorized, and an
// authorization dialog is displayed to the user.
if (!authInProgress) {
try {
Log.i(TAG, "Attempting to resolve failed connection");
authInProgress = true;
result.startResolutionForResult(MainActivity.this, REQUEST_OAUTH);
} catch (IntentSender.SendIntentException e) {
Log.e(TAG, "Exception while starting resolution activity", e);
}
}
}
};
}
I have not tried any of this.
It seems as though the Samsung Gear Live Sensors are not supported out of the box, but you might be able to make it work via software sensors:
Your Gear Live
As said in this SO answer,
The Samsung Gear Live watch does not advertise itself as a BLE heart
rate monitor and therefore does not make the heart rate data
available via the normal Bluetooth Low Energy API or the Google
Fit API which is built upon it.
Supported Sensors
As said in the official docs,
Google Fit includes support for sensors on the mobile device and
Bluetooth Low Energy sensors paired with the device. Google Fit lets
developers implement support for other sensors and expose them as
software sensors in Android apps. Sensors supported by Google Fit are
available to Android apps as data source objects.
Possible Solution
It seems possible to implement additional software sensors.
(Copied template for this is at the bottom of the post, because it is lengthy).
You would get the data on the wearable following get-heart-rate-from-sensor-samsung-gear-live.
Template (from https://developers.google.com/fit/android/new-sensors)
Add this to your manifest file:
<service android:name="com.example.MySensorService"
android:process=":sensor">
<intent-filter>
<action android:name="com.google.android.gms.fitness.service.FitnessSensorService"/>
<!-- include at least one mimeType filter for the supported data types -->
<data android:mimeType="vnd.google.fitness.data_type/com.google.heart_rate.bpm"/>
</intent-filter>
</service>
and flesh this Service out:
import com.google.android.gms.common.*;
import com.google.android.gms.common.api.*;
import com.google.android.gms.fitness.*;
import com.google.android.gms.fitness.data.*;
import com.google.android.gms.fitness.service.*;
...
public class MySensorService extends FitnessSensorService {
#Override
public void onCreate() {
super.onCreate();
// 1. Initialize your software sensor(s).
// 2. Create DataSource representations of your software sensor(s).
// 3. Initialize some data structure to keep track of a registration for each sensor.
}
#Override
protected List<DataSource> onFindDataSources(List<DataType> dataTypes) {
// 1. Find which of your software sensors provide the data types requested.
// 2. Return those as a list of DataSource objects.
}
#Override
protected boolean onRegister(FitnessSensorServiceRequest request) {
// 1. Determine which sensor to register with request.getDataSource().
// 2. If a registration for this sensor already exists, replace it with this one.
// 3. Keep (or update) a reference to the request object.
// 4. Configure your sensor according to the request parameters.
// 5. When the sensor has new data, deliver it to the platform by calling
// request.getDispatcher().publish(List<DataPoint> dataPoints)
}
#Override
protected boolean onUnregister(DataSource dataSource) {
// 1. Configure this sensor to stop delivering data to the platform
// 2. Discard the reference to the registration request object
}
}
I'm trying to detect a Kontakt Beacon with the following BeaconLayout:
setBeaconLayout("m:8-9=0215,i:10-13,i:14-15,i:16-17,i:18-25"));
but I don't seem to be doing it correctly. The advertising packet structure is like this:
Thanks in advance.
Thanks to #davidgyoung comments, I finally could detect my Kontakt beacon with the following code:
public class MainActivity extends Activity implements BeaconConsumer {
protected static final String TAG = "RangingActivity";
BeaconManager beaconManager = BeaconManager.getInstanceForApplication(this);
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
beaconManager.getBeaconParsers().add(new BeaconParser().setBeaconLayout("m:2-3=0215,i:4-19,i:20-21,i:22-23,p:24-24,d:25-25"));
beaconManager.bind(this);
}
#Override
public void onBeaconServiceConnect() {
beaconManager.setRangeNotifier(new RangeNotifier() {
#Override
public void didRangeBeaconsInRegion(Collection<Beacon> beacons, Region region) {
if (beacons.size() > 0) {
Log.d(TAG, "The first beacon I see is about "+beacons.iterator().next().getDistance()+" meters away.");
}
}
});
try {
beaconManager.startRangingBeaconsInRegion(new Region("myRangingUniqueId", null, null, null));
} catch (RemoteException e) { }
}
Please note that I'm using a 2.2 version Kontakt beacon, which is a different version from the layout posted above.
A few issues with your beaconLayout:
The byte offsets in the beaconLayout string start with the manufacturer data (byte 6 in the table you show) so you need to subtract 6 from all of your offsets.
The table shows there are only three identifiers in the beacon, but your beaconLayout string has four. Note the first identifier is 16 bytes long.
If you get it working, please post the correct beaconLayout you used.