Skip to content

Commit

Permalink
make distance calculations specific to Android model
Browse files Browse the repository at this point in the history
  • Loading branch information
davidgyoung committed Aug 28, 2014
1 parent c05082b commit 35328c4
Show file tree
Hide file tree
Showing 7 changed files with 299 additions and 0 deletions.
68 changes: 68 additions & 0 deletions src/main/java/org/altbeacon/beacon/distance/AndroidModel.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package org.altbeacon.beacon.distance;

import android.os.Build;

/**
* Created by dyoung on 8/28/14.
*/
public class AndroidModel {
String mVersion;
String mBuildNumber;
String mModel;
String mManufacturer;

public AndroidModel(String version, String buildNumber,
String model,
String manufacturer) {
mVersion = version;
mBuildNumber = buildNumber;
mModel = model;
mManufacturer = manufacturer;

}
public static AndroidModel forThisDevice() {
return new AndroidModel(
Build.VERSION.RELEASE,
Build.ID,
Build.MODEL,
Build.MANUFACTURER);
}

public String getVersion() {
return mVersion;
}

public void setVersion(String mVersion) {
this.mVersion = mVersion;
}

public String getBuildNumber() {
return mBuildNumber;
}

public String getModel() {
return mModel;
}


public String getManufacturer() {
return mManufacturer;
}

public int matchScore(AndroidModel otherModel) {
int score = 0;
if (this.mManufacturer.equals(otherModel.mManufacturer)) {
score = 1;
}
if (this.mModel.equals(otherModel.mModel)) {
score = 2;
}
if (this.mBuildNumber.equals(otherModel.mBuildNumber)) {
score = 3;
}
if (this.mVersion.equals(otherModel.mVersion)) {
score = 4;
}
return score;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package org.altbeacon.beacon.distance;

import org.altbeacon.beacon.BeaconManager;

/**
* Created by dyoung on 8/28/14.
*/
public class CurveFittedDistanceCalculator implements DistanceCalculator {

public static final String TAG = "CurveFittedDistanceCalculator";
private double mCoefficient1;
private double mCoefficient2;
private double mCoefficient3;

public CurveFittedDistanceCalculator(double coefficient1, double coefficient2, double coefficient3) {
mCoefficient1 = coefficient1;
mCoefficient2 = coefficient2;
mCoefficient3 = coefficient3;
}

/**
* Calculated the estimated distance in meters to the beacon based on a reference rssi at 1m
* and the known actual rssi at the current location
* @param txPower
* @param rssi
* @return estimated distance
*/
@Override
public double calculateDistance(int txPower, double rssi) {
if (rssi == 0) {
return -1.0; // if we cannot determine accuracy, return -1.
}

BeaconManager.logDebug(TAG, "calculating distance based on mRssi of " + rssi + " and txPower of " + txPower);


double ratio = rssi*1.0/txPower;
double distance;
if (ratio < 1.0) {
distance = Math.pow(ratio,10);
}
else {
distance = (0.42093)*Math.pow(ratio,6.9476) + 0.54992;
}
BeaconManager.logDebug(TAG, " avg mRssi: "+rssi+" distance: "+distance);
return distance;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package org.altbeacon.beacon.distance;

/**
* Created by dyoung on 8/28/14.
*/
public interface DistanceCalculator {
public double calculateDistance(int txPower, double rssi);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
package org.altbeacon.beacon.distance;

import android.os.Build;
import android.util.Log;

import org.json.JSONArray;
import org.json.JSONObject;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;

/**
* Created by dyoung on 8/28/14.
*/
public class ModelSpecificDistanceCalculator implements DistanceCalculator {
Map<AndroidModel,DistanceCalculator> mModelMap;
//private static final String CONFIG_FILE = "/model-distance-calculations.json";
private static final String CONFIG_FILE = "test.properties";
private static final String TAG = "ModelSpecificDistanceCalculator";
private DistanceCalculator mDefaultDistanceCalculator;
private DistanceCalculator mDistanceCalculator;

public ModelSpecificDistanceCalculator() {
this(AndroidModel.forThisDevice());
}
public ModelSpecificDistanceCalculator(AndroidModel model) {
loadModelMap();
mDistanceCalculator = findCalculatorForModel(model);
}

private DistanceCalculator findCalculatorForModel(AndroidModel model) {
Log.d(TAG, "Finding best distance calculator for "+model.getVersion()+","+
model.getBuildNumber()+","+model.getModel()+"," +
""+model.getManufacturer());

int highestScore = 0;
AndroidModel bestMatchingModel = null;
for (AndroidModel candidateModel : mModelMap.keySet()) {
if (candidateModel.matchScore(model) > highestScore) {
highestScore = candidateModel.matchScore(model);
bestMatchingModel = candidateModel;
}
}
if (bestMatchingModel != null) {
Log.d(TAG, "found a match with score "+highestScore);
Log.d(TAG, "Finding best distance calculator for "+bestMatchingModel.getVersion()+","+
bestMatchingModel.getBuildNumber()+","+bestMatchingModel.getModel()+"," +
""+bestMatchingModel.getManufacturer());
return mModelMap.get(bestMatchingModel);
}

Log.d(TAG, "Cannot find match for this device. Using default");
return mDefaultDistanceCalculator;
}

private void loadModelMap() {
mModelMap = new HashMap<AndroidModel, DistanceCalculator>();
try {
JSONObject jsonObject = new JSONObject(stringFromFilePath(CONFIG_FILE));
JSONArray array = jsonObject.getJSONArray("models");
for (int i = 0; i < array.length(); i++) {
JSONObject modelObject = array.getJSONObject(i);
boolean defaultFlag = modelObject.getBoolean("default");
Double coefficient1 = modelObject.getDouble("coefficient1");
Double coefficient2 = modelObject.getDouble("coefficient2");
Double coefficient3 = modelObject.getDouble("coefficient3");
String version = modelObject.getString("version");
String buildNumber = modelObject.getString("build_number");
String model = modelObject.getString("model");
String manufacturer = modelObject.getString("manufacturer");

CurveFittedDistanceCalculator distanceCalculator =
new CurveFittedDistanceCalculator(coefficient1,coefficient2,coefficient3);

AndroidModel androidModel = new AndroidModel(version, buildNumber, model, manufacturer);
mModelMap.put(androidModel, distanceCalculator);
if (defaultFlag) {
mDefaultDistanceCalculator = distanceCalculator;
}
}
}
catch (Exception e) {
throw new RuntimeException("Cannot build model distance calculations", e);
}
}

private String stringFromFilePath(String path) throws IOException {
InputStream stream = ModelSpecificDistanceCalculator.class.getResourceAsStream(path);
if (stream == null) {
throw new RuntimeException("Cannot load resource at "+path);
}
StringBuilder inputStringBuilder = new StringBuilder();
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(stream, "UTF-8"));
String line = bufferedReader.readLine();
while(line != null){
inputStringBuilder.append(line);inputStringBuilder.append('\n');
line = bufferedReader.readLine();
}
return inputStringBuilder.toString();
}

@Override
public double calculateDistance(int txPower, double rssi) {
return mDistanceCalculator.calculateDistance(txPower, rssi);
}
}
24 changes: 24 additions & 0 deletions src/main/resources/model-distance-calculations.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"models":
[
{
"coefficient1": 0.42093,
"coefficient2": 6.9476,
"coefficient3": 0.54992,
"version":"4.4.2",
"build_number":"KOT49H",
"model":"Nexus 4",
"manufacturer":"LGE",
"default": true
},
{
"coefficient1": 0.42093,
"coefficient2": 6.9476,
"coefficient3": 0.54992,
"version":"4.4.2",
"build_number":"LPV79",
"model":"Nexus 5",
"manufacturer":"LGE",

This comment has been minimized.

Copy link
@samskiter

samskiter Sep 14, 2015

is it intentional that the coefficients are duplicated

}
]
}
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package org.altbeacon.beacon.distance;

import android.os.Parcel;

import static android.test.MoreAsserts.assertNotEqual;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;

import org.altbeacon.beacon.AltBeacon;
import org.altbeacon.beacon.AltBeaconParser;
import org.altbeacon.beacon.Beacon;
import org.robolectric.RobolectricTestRunner;

import org.junit.runner.RunWith;
import org.junit.Test;
import org.robolectric.annotation.Config;

@Config(emulateSdk = 18)
@RunWith(RobolectricTestRunner.class)
/*
HOW TO SEE DEBUG LINES FROM YOUR UNIT TESTS:
1. set a line like this at the start of your test:
org.robolectric.shadows.ShadowLog.stream = System.err;
2. run the tests from the command line
3. Look at the test report file in your web browser, e.g.
file:///Users/dyoung/workspace/AndroidProximityLibrary/build/reports/tests/index.html
4. Expand the System.err section
/**
* Created by dyoung on 8/28/14.
*/
public class ModelSpecificDistanceCalculatorTest {
@Test
public void testRecognizeBeacon() {
ModelSpecificDistanceCalculator distanceCalculator = new ModelSpecificDistanceCalculator();
Double distance = distanceCalculator.calculateDistance(-59, -59);
assertEquals("Distance should be 1.0 for same power and rssi", 1.0, (double) distance, 0.001);
}
}

1 comment on commit 35328c4

@davidgyoung
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, this is not intentional. While close, the coefficients should be different for the Nexus 4 and 5.

The planned fix is to shift to a path loss formula, which is easier to implement for multiple devices. It is a work in progress here:
#251

Please sign in to comment.