/** * Radius Networks, Inc. * http://www.radiusnetworks.com * * @author David G. Young * * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ package org.altbeacon.beacon; import android.os.Parcel; import android.os.Parcelable; import org.altbeacon.beacon.client.BeaconDataFactory; import org.altbeacon.beacon.client.NullBeaconDataFactory; import org.altbeacon.beacon.distance.DistanceCalculator; import org.altbeacon.beacon.logging.LogManager; import java.io.Serializable; import java.util.ArrayList; import java.util.Collections; import java.util.List; /** * The <code>Beacon</code> class represents a single hardware Beacon detected by * an Android device. * * <pre>A Beacon is identified by a unique multi-part identifier, with the first of the ordered * identifiers being more significant for the purposes of grouping beacons. * * A Beacon sends a Bluetooth Low Energy (BLE) advertisement that contains these * three identifiers, along with the calibrated tx power (in RSSI) of the * Beacon's Bluetooth transmitter. * * This class may only be instantiated from a BLE packet, and an RSSI measurement for * the packet. The class parses out the identifier, along with the calibrated * tx power. It then uses the measured RSSI and calibrated tx power to do a rough * distance measurement (the mDistance field) * * @author David G. Young * @see Region#matchesBeacon(Beacon Beacon) */ public class Beacon implements Parcelable, Serializable { private static final String TAG = "Beacon"; private static final List<Long> UNMODIFIABLE_LIST_OF_LONG = Collections.unmodifiableList(new ArrayList<Long>()); private static final List<Identifier> UNMODIFIABLE_LIST_OF_IDENTIFIER = Collections.unmodifiableList(new ArrayList<Identifier>()); /** * Determines whether a the bluetoothAddress (mac address) must be the same for two Beacons * to be configured equal. */ protected static boolean sHardwareEqualityEnforced = false; protected static DistanceCalculator sDistanceCalculator = null; /** * The a list of the multi-part identifiers of the beacon. Together, these identifiers signify * a unique beacon. The identifiers are ordered by significance for the purpose of grouping * beacons */ protected List<Identifier> mIdentifiers; /** * A list of generic non-identifying data fields included in the beacon advertisement. Data * fields are limited to the size of a Java long, or six bytes. */ protected List<Long> mDataFields; /** * A list of generic non-identifying data fields included in a secondary beacon advertisement * and merged into this beacon. Data fields are limited to the size of a Java long, or six * bytes. */ protected List<Long> mExtraDataFields; /** * A double that is an estimate of how far the Beacon is away in meters. Note that this number * fluctuates quite a bit with RSSI, so despite the name, it is not super accurate. */ protected Double mDistance; /** * The measured signal strength of the Bluetooth packet that led do this Beacon detection. */ protected int mRssi; /** * The calibrated measured Tx power of the Beacon in RSSI * This value is baked into an Beacon when it is manufactured, and * it is transmitted with each packet to aid in the mDistance estimate */ protected int mTxPower; /** * The Bluetooth mac address */ protected String mBluetoothAddress; /** * If multiple RSSI samples were available, this is the running average */ private Double mRunningAverageRssi = null; /** * Used to attach data to individual Beacons, either locally or in the cloud */ protected static BeaconDataFactory beaconDataFactory = new NullBeaconDataFactory(); /** * The two byte value indicating the type of beacon that this is, which is used for figuring * out the byte layout of the beacon advertisement */ protected int mBeaconTypeCode; /** * A two byte code indicating the beacon manufacturer. A list of registered manufacturer codes * may be found here: * https://www.bluetooth.org/en-us/specification/assigned-numbers/company-identifiers * * If the beacon is a GATT-based beacon, this field will be set to -1 */ protected int mManufacturer; /** * A 32 bit service uuid for the beacon * * This is valid only for GATT-based beacons. If the beacon is a manufacturer data-based * beacon, this field will be -1 */ protected int mServiceUuid = -1; /** * The Bluetooth device name. This is a field transmitted by the remote beacon device separate * from the advertisement data */ protected String mBluetoothName; /** * The identifier of the beaconParser used to create this beacon. Useful for figuring out * beacon types. */ protected String mParserIdentifier; /** * An indicator marking this beacon as a potential multi frame beacon. * * This will be set to true if the beacon was parsed by a BeaconParser which has extra * data parsers defined. */ protected boolean mMultiFrameBeacon = false; /** * Required for making object Parcelable. If you override this class, you must provide an * equivalent version of this method. */ @Deprecated public static final Parcelable.Creator<Beacon> CREATOR = new Parcelable.Creator<Beacon>() { public Beacon createFromParcel(Parcel in) { return new Beacon(in); } public Beacon[] newArray(int size) { return new Beacon[size]; } }; /** * Sets the DistanceCalculator to use with this beacon * @param dc */ public static void setDistanceCalculator(DistanceCalculator dc) { sDistanceCalculator = dc; } /** * Gets the DistanceCalculator to use with this beacon */ public static DistanceCalculator getDistanceCalculator() { return sDistanceCalculator; } /** * Configures whether a the bluetoothAddress (mac address) must be the same for two Beacons * to be configured equal. This setting applies to all beacon instances in the same process. * Defaults to false for backward compatibility. * * @param e */ public static void setHardwareEqualityEnforced(boolean e) { sHardwareEqualityEnforced = e; } public static boolean getHardwareEqualityEnforced() { return sHardwareEqualityEnforced; } /** * Required for making Beacon parcelable * @param in parcel */ @Deprecated protected Beacon(Parcel in) { int size = in.readInt(); this.mIdentifiers = new ArrayList<Identifier>(size); for (int i = 0; i < size; i++) { mIdentifiers.add(Identifier.parse(in.readString())); } mDistance = in.readDouble(); mRssi = in.readInt(); mTxPower = in.readInt(); mBluetoothAddress = in.readString(); mBeaconTypeCode = in.readInt(); mServiceUuid = in.readInt(); int dataSize = in.readInt(); this.mDataFields = new ArrayList<Long>(dataSize); for (int i = 0; i < dataSize; i++) { mDataFields.add(in.readLong()); } int extraDataSize = in.readInt(); this.mExtraDataFields = new ArrayList<Long>(extraDataSize); for (int i = 0; i < extraDataSize; i++) { mExtraDataFields.add(in.readLong()); } mManufacturer = in.readInt(); mBluetoothName = in.readString(); mParserIdentifier = in.readString(); mMultiFrameBeacon = in.readByte() != 0; } /** * Copy constructor * @param otherBeacon */ protected Beacon(Beacon otherBeacon) { super(); mIdentifiers = new ArrayList<>(otherBeacon.mIdentifiers); mDataFields = new ArrayList<>(otherBeacon.mDataFields); mExtraDataFields = new ArrayList<>(otherBeacon.mExtraDataFields); this.mDistance = otherBeacon.mDistance; this.mRunningAverageRssi = otherBeacon.mRunningAverageRssi; this.mRssi = otherBeacon.mRssi; this.mTxPower = otherBeacon.mTxPower; this.mBluetoothAddress = otherBeacon.mBluetoothAddress; this.mBeaconTypeCode = otherBeacon.getBeaconTypeCode(); this.mServiceUuid = otherBeacon.getServiceUuid(); this.mBluetoothName = otherBeacon.mBluetoothName; this.mParserIdentifier = otherBeacon.mParserIdentifier; } /** * Basic constructor that simply allocates fields */ protected Beacon() { mIdentifiers = new ArrayList<Identifier>(1); mDataFields = new ArrayList<Long>(1); mExtraDataFields = new ArrayList<Long>(1); } /** * Sets the running average rssi for use in distance calculations * @param rssi the running average rssi */ public void setRunningAverageRssi(double rssi) { mRunningAverageRssi = rssi; mDistance = null; // force calculation of accuracy and proximity next time they are requested } /** * Returns the running average rssi * @param rssi * @return */ public double getRunningAverageRssi(double rssi) { return mRunningAverageRssi = rssi; } /** * Sets the most recently measured rssi for use in distance calculations if a running average is * not available * @param rssi */ public void setRssi(int rssi) { mRssi = rssi; } /** * @see #mManufacturer */ public int getManufacturer() { return mManufacturer; } /** * @see #mServiceUuid */ public int getServiceUuid() { return mServiceUuid; } /** * Returns the specified identifier - 0 indexed * Note: to read id1, call getIdentifier(0); * @param i - index identfier * @return identifier */ public Identifier getIdentifier(int i) { return mIdentifiers.get(i); } /** * Convenience method to get the first identifier * @return */ public Identifier getId1() { return mIdentifiers.get(0); } /** * Convenience method to get the second identifier * @return */ public Identifier getId2() { return mIdentifiers.get(1); } /** * Convenience method to get the third identifier * @return */ public Identifier getId3() { return mIdentifiers.get(2); } /** * Returns the list of data fields transmitted with the advertisement * @return dataFields */ public List<Long> getDataFields() { if (mDataFields.getClass().isInstance(UNMODIFIABLE_LIST_OF_LONG)) { return mDataFields; } else { return Collections.unmodifiableList(mDataFields); } } /** * Returns the list of data fields transmitted with the advertisement * @return dataFields */ public List<Long> getExtraDataFields() { if (mExtraDataFields.getClass().isInstance(UNMODIFIABLE_LIST_OF_LONG)) { return mExtraDataFields; } else { return Collections.unmodifiableList(mExtraDataFields); } } /** * Sets extra data fields * @param fields */ public void setExtraDataFields(List<Long> fields) { mExtraDataFields = fields; } /** * Returns the list of identifiers transmitted with the advertisement * @return identifier */ public List<Identifier> getIdentifiers() { if (mIdentifiers.getClass().isInstance(UNMODIFIABLE_LIST_OF_IDENTIFIER)) { return mIdentifiers; } else { return Collections.unmodifiableList(mIdentifiers); } } /** * Provides a calculated estimate of the distance to the beacon based on a running average of * the RSSI and the transmitted power calibration value included in the beacon advertisement. * This value is specific to the type of Android device receiving the transmission. * * @see #mDistance * @return distance */ public double getDistance() { if (mDistance == null) { double bestRssiAvailable = mRssi; if (mRunningAverageRssi != null) { bestRssiAvailable = mRunningAverageRssi; } else { LogManager.d(TAG, "Not using running average RSSI because it is null"); } mDistance = calculateDistance(mTxPower, bestRssiAvailable); } return mDistance; } /** * @see #mRssi * @return mRssi */ public int getRssi() { return mRssi; } /** * @see #mTxPower * @return txPowwer */ public int getTxPower() { return mTxPower; } /** * @see #mBeaconTypeCode * @return beaconTypeCode */ public int getBeaconTypeCode() { return mBeaconTypeCode; } /** * @see #mBluetoothAddress * @return mBluetoothAddress */ public String getBluetoothAddress() { return mBluetoothAddress; } /** * @see #mBluetoothName * @return mBluetoothName */ public String getBluetoothName() { return mBluetoothName; } /** * @see #mParserIdentifier * @return mParserIdentifier */ public String getParserIdentifier() { return mParserIdentifier; } /** * @see #mMultiFrameBeacon * @return mMultiFrameBeacon */ public boolean isMultiFrameBeacon() { return mMultiFrameBeacon; } /** * Calculate a hashCode for this beacon * @return */ @Override public int hashCode() { StringBuilder sb = toStringBuilder(); if (sHardwareEqualityEnforced) { sb.append(mBluetoothAddress); } return sb.toString().hashCode(); } /** * Two detected beacons are considered equal if they share the same three identifiers, regardless of their mDistance or RSSI. */ @Override public boolean equals(Object that) { if (!(that instanceof Beacon)) { return false; } Beacon thatBeacon = (Beacon) that; if (!this.mIdentifiers.equals(thatBeacon.mIdentifiers)) { return false; } return sHardwareEqualityEnforced ? this.getBluetoothAddress().equals(thatBeacon.getBluetoothAddress()) : true; } /** * Requests server-side data for this beacon. Requires that a BeaconDataFactory be set up with * a backend service. * @param notifier interface providing a callback when data are available */ public void requestData(BeaconDataNotifier notifier) { beaconDataFactory.requestBeaconData(this, notifier); } /** * Formats a beacon as a string showing only its unique identifiers * @return */ @Override public String toString() { return toStringBuilder().toString(); } private StringBuilder toStringBuilder() { final StringBuilder sb = new StringBuilder(); int i = 1; for (Identifier identifier: mIdentifiers) { if (i > 1) { sb.append(" "); } sb.append("id"); sb.append(i); sb.append(": "); sb.append(identifier == null ? "null" : identifier.toString()); i++; } if (mParserIdentifier != null) { sb.append(" type "+mParserIdentifier); } return sb; } /** * Required for making object Parcelable */ @Deprecated public int describeContents() { return 0; } /** * Required for making object Parcelable. If you override this class, you must override this * method if you add any additional fields. */ @Deprecated public void writeToParcel(Parcel out, int flags) { out.writeInt(mIdentifiers.size()); for (Identifier identifier: mIdentifiers) { out.writeString(identifier == null ? null : identifier.toString()); } out.writeDouble(getDistance()); out.writeInt(mRssi); out.writeInt(mTxPower); out.writeString(mBluetoothAddress); out.writeInt(mBeaconTypeCode); out.writeInt(mServiceUuid); out.writeInt(mDataFields.size()); for (Long dataField: mDataFields) { out.writeLong(dataField); } out.writeInt(mExtraDataFields.size()); for (Long dataField: mExtraDataFields) { out.writeLong(dataField); } out.writeInt(mManufacturer); out.writeString(mBluetoothName); out.writeString(mParserIdentifier); out.writeByte((byte) (mMultiFrameBeacon ? 1: 0)); } /** * Indicates whether this beacon is an "Extra data beacon," meaning one that has no identifiers * but has data fields. * @return */ public boolean isExtraBeaconData() { return mIdentifiers.size() == 0 && mDataFields.size() != 0; } /** * Estimate the distance to the beacon using the DistanceCalculator set on this class. If no * DistanceCalculator has been set, return -1 as the distance. * @see org.altbeacon.beacon.distance.DistanceCalculator * * @param txPower * @param bestRssiAvailable * @return */ protected static Double calculateDistance(int txPower, double bestRssiAvailable) { if (Beacon.getDistanceCalculator() != null) { return Beacon.getDistanceCalculator().calculateDistance(txPower, bestRssiAvailable); } else { LogManager.e(TAG, "Distance calculator not set. Distance will bet set to -1"); return -1.0; } } /** * Builder class for Beacon objects. Provides a convenient way to set the various fields of a * Beacon * * <p>Example: * * <pre> * Beacon beacon = new Beacon.Builder() * .setId1("2F234454-CF6D-4A0F-ADF2-F4911BA9FFA6") * .setId2("1") * .setId3("2") * .build(); * </pre> */ public static class Builder { protected final Beacon mBeacon; private Identifier mId1, mId2, mId3; /** * Creates a builder instance */ public Builder() { mBeacon = new Beacon(); } /** * Builds an instance of this beacon based on parameters set in the Builder * @return beacon */ public Beacon build() { if (mId1!= null) { mBeacon.mIdentifiers.add(mId1); if (mId2!= null) { mBeacon.mIdentifiers.add(mId2); if (mId3!= null) { mBeacon.mIdentifiers.add(mId3); } } } return mBeacon; } /** * @param beacon the beacon whose fields we should copy to this beacon builder * @return */ public Builder copyBeaconFields(Beacon beacon) { setIdentifiers(beacon.getIdentifiers()); setBeaconTypeCode(beacon.getBeaconTypeCode()); setDataFields(beacon.getDataFields()); setBluetoothAddress(beacon.getBluetoothAddress()); setBluetoothName(beacon.getBluetoothName()); setExtraDataFields(beacon.getExtraDataFields()); setManufacturer(beacon.getManufacturer()); setTxPower(beacon.getTxPower()); setRssi(beacon.getRssi()); setServiceUuid(beacon.getServiceUuid()); setMultiFrameBeacon(beacon.isMultiFrameBeacon()); return this; } /** * @see Beacon#mIdentifiers * @param identifiers identifiers to set * @return builder */ public Builder setIdentifiers(List<Identifier>identifiers) { mId1 = null; mId2 = null; mId3 = null; mBeacon.mIdentifiers = identifiers; return this; } /** * Convenience method allowing the first beacon identifier to be set as a String. It will * be parsed into an Identifier object * @param id1String string to parse into an identifier * @return builder */ public Builder setId1(String id1String) { mId1 = Identifier.parse(id1String); return this; } /** * Convenience method allowing the second beacon identifier to be set as a String. It will * be parsed into an Identifier object * @param id2String string to parse into an identifier * @return builder */ public Builder setId2(String id2String) { mId2 = Identifier.parse(id2String); return this; } /** * Convenience method allowing the third beacon identifier to be set as a String. It will * be parsed into an Identifier object * @param id3String string to parse into an identifier * @return builder */ public Builder setId3(String id3String) { mId3 = Identifier.parse(id3String); return this; } /** * @see Beacon#mRssi * @param rssi * @return builder */ public Builder setRssi(int rssi) { mBeacon.mRssi = rssi; return this; } /** * @see Beacon#mRssi * @param rssi * @return builder */ public Builder setRunningAverageRssi(double rssi) { mBeacon.mRunningAverageRssi = rssi; return this; } /** * @see Beacon#mTxPower * @param txPower * @return builder */ public Builder setTxPower(int txPower) { mBeacon.mTxPower = txPower; return this; } /** * @see Beacon#mBeaconTypeCode * @param beaconTypeCode * @return builder */ public Builder setBeaconTypeCode(int beaconTypeCode) { mBeacon.mBeaconTypeCode = beaconTypeCode; return this; } /** * @see Beacon#mServiceUuid * @param serviceUuid * @return builder */ public Builder setServiceUuid(int serviceUuid) { mBeacon.mServiceUuid = serviceUuid; return this; } /** * @see Beacon#mBluetoothAddress * @param bluetoothAddress * @return builder */ public Builder setBluetoothAddress(String bluetoothAddress) { mBeacon.mBluetoothAddress = bluetoothAddress; return this; } /** * @see Beacon#mDataFields * @param dataFields * @return builder */ public Builder setDataFields(List<Long> dataFields) { mBeacon.mDataFields = dataFields; return this; } /** * @see Beacon#mDataFields * @param extraDataFields * @return builder */ public Builder setExtraDataFields(List<Long> extraDataFields) { mBeacon.mExtraDataFields = extraDataFields; return this; } /** * @see Beacon#mManufacturer * @param manufacturer * @return builder */ public Builder setManufacturer(int manufacturer) { mBeacon.mManufacturer = manufacturer; return this; } /** * @see Beacon#mBluetoothName * @param name * @return builder */ public Builder setBluetoothName(String name) { mBeacon.mBluetoothName = name; return this; } /** * @see Beacon#mParserIdentifier * @param id * @return builder */ public Builder setParserIdentifier(String id) { mBeacon.mParserIdentifier = id; return this; } /** * @see Beacon#mMultiFrameBeacon * @return multiFrameBeacon */ public Builder setMultiFrameBeacon(boolean multiFrameBeacon) { mBeacon.mMultiFrameBeacon = multiFrameBeacon; return this; } } }