From 35328c4ad50f09cafa1478a608e20502de7f7987 Mon Sep 17 00:00:00 2001 From: "David G. Young" Date: Thu, 28 Aug 2014 17:02:35 -0400 Subject: [PATCH] make distance calculations specific to Android model --- .../beacon/distance/AndroidModel.java | 68 +++++++++++ .../CurveFittedDistanceCalculator.java | 48 ++++++++ .../beacon/distance/DistanceCalculator.java | 8 ++ .../ModelSpecificDistanceCalculator.java | 111 ++++++++++++++++++ .../model-distance-calculations.json | 24 ++++ src/main/resources/test.properties | 0 .../ModelSpecificDistanceCalculatorTest.java | 40 +++++++ 7 files changed, 299 insertions(+) create mode 100644 src/main/java/org/altbeacon/beacon/distance/AndroidModel.java create mode 100644 src/main/java/org/altbeacon/beacon/distance/CurveFittedDistanceCalculator.java create mode 100644 src/main/java/org/altbeacon/beacon/distance/DistanceCalculator.java create mode 100644 src/main/java/org/altbeacon/beacon/distance/ModelSpecificDistanceCalculator.java create mode 100644 src/main/resources/model-distance-calculations.json create mode 100644 src/main/resources/test.properties create mode 100644 src/test/java/org/altbeacon/beacon/distance/ModelSpecificDistanceCalculatorTest.java diff --git a/src/main/java/org/altbeacon/beacon/distance/AndroidModel.java b/src/main/java/org/altbeacon/beacon/distance/AndroidModel.java new file mode 100644 index 000000000..c12d5d6cd --- /dev/null +++ b/src/main/java/org/altbeacon/beacon/distance/AndroidModel.java @@ -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; + } +} diff --git a/src/main/java/org/altbeacon/beacon/distance/CurveFittedDistanceCalculator.java b/src/main/java/org/altbeacon/beacon/distance/CurveFittedDistanceCalculator.java new file mode 100644 index 000000000..6e14db9fe --- /dev/null +++ b/src/main/java/org/altbeacon/beacon/distance/CurveFittedDistanceCalculator.java @@ -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; + } +} diff --git a/src/main/java/org/altbeacon/beacon/distance/DistanceCalculator.java b/src/main/java/org/altbeacon/beacon/distance/DistanceCalculator.java new file mode 100644 index 000000000..ada386e21 --- /dev/null +++ b/src/main/java/org/altbeacon/beacon/distance/DistanceCalculator.java @@ -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); +} diff --git a/src/main/java/org/altbeacon/beacon/distance/ModelSpecificDistanceCalculator.java b/src/main/java/org/altbeacon/beacon/distance/ModelSpecificDistanceCalculator.java new file mode 100644 index 000000000..87c874a33 --- /dev/null +++ b/src/main/java/org/altbeacon/beacon/distance/ModelSpecificDistanceCalculator.java @@ -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 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(); + 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); + } +} diff --git a/src/main/resources/model-distance-calculations.json b/src/main/resources/model-distance-calculations.json new file mode 100644 index 000000000..bfe42648c --- /dev/null +++ b/src/main/resources/model-distance-calculations.json @@ -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", + } + ] +} diff --git a/src/main/resources/test.properties b/src/main/resources/test.properties new file mode 100644 index 000000000..e69de29bb diff --git a/src/test/java/org/altbeacon/beacon/distance/ModelSpecificDistanceCalculatorTest.java b/src/test/java/org/altbeacon/beacon/distance/ModelSpecificDistanceCalculatorTest.java new file mode 100644 index 000000000..e127acb59 --- /dev/null +++ b/src/test/java/org/altbeacon/beacon/distance/ModelSpecificDistanceCalculatorTest.java @@ -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); + } +}