Beacon.java

/*
 * Copyright (C) 2018 Alberto Irurueta Carro (alberto@irurueta.com)
 *
 * Licensed 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 com.irurueta.navigation.indoor;

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.
 * </pre>
 * <p>
 * Based on:
 * <a href="https://github.com/AltBeacon/android-beacon-library/blob/master/src/main/java/org/altbeacon/beacon/Beacon.java">
 *   https://github.com/AltBeacon/android-beacon-library/blob/master/src/main/java/org/altbeacon/beacon/Beacon.java
 * </a>
 */
public class Beacon implements Serializable, RadioSource {

    /**
     * Default frequency used by a Beacon when none is specified (expressed in Hz).
     */
    public static final double DEFAULT_FREQUENCY = 2.4e9;

    /**
     * The list of the multipart identifiers of the beacon. Together, these identifiers signify
     * a unique beacon. The identifiers are ordered by significance for the purpose of grouping
     * beacons.
     */
    private ArrayList<BeaconIdentifier> identifiers;

    /**
     * The calibrated measured Tx power of the Beacon in RSSI (expressed in dBm's).
     * This value is baked into a Beacon when it is manufactured, and it is
     * transmitted with each packet to aid in the distance estimate.
     */
    private double transmittedPower;

    /**
     * The Bluetooth mac address.
     */
    private String bluetoothAddress;

    /**
     * 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.
     */
    private int beaconTypeCode;

    /**
     * A two byte code indicating the beacon manufacturer. A list of registered manufacturer codes
     * may be found here:
     * <a href="https://www.bluetooth.org/en-us/specification/assigned-numbers/company-identifiers">
     *   https://www.bluetooth.org/en-us/specification/assigned-numbers/company-identifiers
     * </a>
     * <p>
     * If the beacon is a GATT-based beacon, this field will be set to -1.
     */
    private int manufacturer;

    /**
     * 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
     */
    private int serviceUuid = -1;

    /**
     * The Bluetooth device name.  This is a field transmitted by the remote beacon device separate
     * from the advertisement data
     */
    private String bluetoothName;

    /**
     * Frequency used by this Beacon(expressed in Hz).
     */
    private double frequency = DEFAULT_FREQUENCY;

    /**
     * Constructor.
     *
     * @param identifiers      list of the multipart identifiers of the beacon.
     * @param transmittedPower calibrated measured Tx power of the Beacon in RSSI (expressed in dBm's).
     * @throws IllegalArgumentException if identifiers is null.
     */
    public Beacon(final List<BeaconIdentifier> identifiers, final double transmittedPower) {
        if (identifiers == null) {
            throw new IllegalArgumentException();
        }

        this.identifiers = new ArrayList<>(identifiers);
        this.transmittedPower = transmittedPower;
    }

    /**
     * Constructor.
     *
     * @param identifiers      list of the multipart identifiers of the beacon.
     * @param transmittedPower calibrated measured Tx power of the Beacon in RSSI (expressed in dBm's).
     * @param bluetoothAddress the bluetooth mac address.
     * @param beaconTypeCode   the two byte value indicating the type of beacon.
     * @param manufacturer     a two byte code indicating the beacon manufacturer.
     * @param serviceUuid      a 32 bit service uuid for the beacon.
     * @param bluetoothName    the bluetooth device name.
     * @throws IllegalArgumentException if identifiers is null.
     */
    public Beacon(final List<BeaconIdentifier> identifiers, final double transmittedPower,
                  final String bluetoothAddress, final int beaconTypeCode, final int manufacturer,
                  final int serviceUuid, final String bluetoothName) {
        this(identifiers, transmittedPower);

        this.bluetoothAddress = bluetoothAddress;
        this.beaconTypeCode = beaconTypeCode;
        this.manufacturer = manufacturer;
        this.serviceUuid = serviceUuid;
        this.bluetoothName = bluetoothName;
    }

    /**
     * Constructor.
     *
     * @param identifiers      list of the multipart identifiers of the beacon.
     * @param transmittedPower calibrated measured Tx power of the Beacon in RSSI (expressed in dBm's).
     * @param frequency        frequency used by this Beacon.
     * @throws IllegalArgumentException if identifiers is null or frequency is negative.
     */
    public Beacon(final List<BeaconIdentifier> identifiers, final double transmittedPower, final double frequency) {
        this(identifiers, transmittedPower);

        if (frequency < 0.0) {
            throw new IllegalArgumentException();
        }

        this.frequency = frequency;
    }

    /**
     * Constructor.
     *
     * @param identifiers      list of the multipart identifiers of the beacon.
     * @param transmittedPower calibrated measured Tx power of the Beacon in RSSI (expressed in dBm's).
     * @param frequency        frequency used by this Beacon.
     * @param bluetoothAddress the bluetooth mac address.
     * @param beaconTypeCode   the two byte value indicating the type of beacon.
     * @param manufacturer     a two byte code indicating the beacon manufacturer.
     * @param serviceUuid      a 32 bit service uuid for the beacon.
     * @param bluetoothName    the bluetooth device name.
     * @throws IllegalArgumentException if identifiers is null.
     */
    public Beacon(final List<BeaconIdentifier> identifiers, final double transmittedPower, final double frequency,
                  final String bluetoothAddress, final int beaconTypeCode, final int manufacturer,
                  final int serviceUuid, final String bluetoothName) {
        this(identifiers, transmittedPower, bluetoothAddress, beaconTypeCode, manufacturer, serviceUuid, bluetoothName);

        if (frequency < 0.0) {
            throw new IllegalArgumentException();
        }

        this.frequency = frequency;
    }

    /**
     * Empty constructor.
     */
    protected Beacon() {
    }

    /**
     * Returns the specified identifier - 0 indexed.
     * Note: to read id1, call getIdentifier(0);
     *
     * @param i index identifier.
     * @return identifier or null if not available.
     */
    public BeaconIdentifier getIdentifier(final int i) {
        if (i < 0 || identifiers == null || identifiers.size() <= i) {
            return null;
        }
        return identifiers.get(i);
    }

    /**
     * Convenience method to get the first identifier.
     *
     * @return first identifier or null if not available.
     */
    public BeaconIdentifier getId1() {
        if (identifiers == null || identifiers.isEmpty()) {
            return null;
        }
        return identifiers.get(0);
    }

    /**
     * Convenience method to get the second identifier.
     *
     * @return second identifier or null if not available.
     */
    public BeaconIdentifier getId2() {
        if (identifiers == null || identifiers.size() < 2) {
            return null;
        }
        return identifiers.get(1);
    }

    /**
     * Convenience method to get the third identifier.
     *
     * @return third identifier or null if not available.
     */
    public BeaconIdentifier getId3() {
        if (identifiers == null || identifiers.size() < 3) {
            return null;
        }
        return identifiers.get(2);
    }

    /**
     * Gets the list of the multipart identifiers of the beacon. Together, these identifiers signify
     * a unique beacon. The identifiers are ordered by significance for the purpose of grouping
     * beacons.
     *
     * @return list of identifiers of the beacon or null if not available.
     */
    public List<BeaconIdentifier> getIdentifiers() {
        return identifiers != null ? Collections.unmodifiableList(identifiers) : null;
    }

    /**
     * Returns the calibrated measured Tx power of the Beacon in RSSI (expressed in dBm's).
     * This value is baked into a Beacon when it is manufactured, and it is
     * transmitted with each packet to aid in the distance estimate.
     *
     * @return the calibrated measured Tx power.
     */
    public double getTransmittedPower() {
        return transmittedPower;
    }

    /**
     * Gets the bluetooth mac address.
     *
     * @return bluetooth mac address.
     */
    public String getBluetoothAddress() {
        return bluetoothAddress;
    }

    /**
     * Gets 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.
     *
     * @return two byte value indicating the type of beacon.
     */
    public int getBeaconTypeCode() {
        return beaconTypeCode;
    }


    /**
     * Gets the Bluetooth device name. This is a field transmitted by the remote beacon device separate
     * from the advertisement data.
     *
     * @return bluetooth device name.
     */
    public String getBluetoothName() {
        return bluetoothName;
    }

    /**
     * Gets a two byte code indicating the beacon manufacturer. A list of registered manufacturer codes
     * may be found here:
     * <a href="https://www.bluetooth.org/en-us/specification/assigned-numbers/company-identifiers">
     *   https://www.bluetooth.org/en-us/specification/assigned-numbers/company-identifiers
     * </a>
     * <p>
     * If the beacon is a GATT-based beacon, this field will be set to -1.
     *
     * @return a two byte code indicating the beacon manufacturer.
     */
    public int getManufacturer() {
        return manufacturer;
    }

    /**
     * Gets 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
     *
     * @return service uuid.
     */
    public int getServiceUuid() {
        return serviceUuid;
    }

    /**
     * Gets frequency used by this radio source (expressed in Hz).
     *
     * @return frequency used by this radio source (expressed in Hz).
     */
    @Override
    public double getFrequency() {
        return frequency;
    }

    /**
     * Checks whether two beacons are considered equal if they share the same identifiers.
     *
     * @param that beacon to be compared.
     * @return true if both beacons are considered equal, false otherwise.
     */
    @Override
    public boolean equals(final Object that) {
        if (!(that instanceof Beacon thatBeacon)) {
            return false;
        }

        return identifiers != null && identifiers.equals(thatBeacon.identifiers);
    }

    /**
     * Computes hash code for this instance.
     *
     * @return this instance hash code.
     */
    @Override
    public int hashCode() {
        return identifiers.hashCode();
    }

    /**
     * Gets radio source type, which can be either a Wi-Fi Access point or a bluetooth Beacon.
     *
     * @return radio source type.
     */
    @Override
    public RadioSourceType getType() {
        return RadioSourceType.BEACON;
    }
}