/* * Copyright (C) 2015 Neo Visionaries Inc. * * 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.neovisionaries.bluetooth.ble.advertising; import java.util.UUID; import com.neovisionaries.bluetooth.ble.util.Bytes; import com.neovisionaries.bluetooth.ble.util.UUIDCreator; /** * iBeacon. */ public class IBeacon extends ADManufacturerSpecific { private static final long serialVersionUID = 2L; private static final int UUID_INDEX = 4; private static final int MAJOR_INDEX = 20; private static final int MINOR_INDEX = 22; private static final int POWER_INDEX = 24; private static final String STRING_FORMAT = "iBeacon(UUID=%s,Major=%d,Minor=%d,Power=%d)"; private UUID mUUID; private int mMajor; private int mMinor; private int mPower; /** * Constructor which creates an instance with UUID=all-zeros, * major=0, minor=0, and power=0. */ public IBeacon() { this(26, 0xFF, new byte[] { (byte)0x4C, (byte)0x00, // Apple, Inc. (byte)0x02, (byte)0x15, // iBeacon // Proximity UUID (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, // Major number (byte)0x00, (byte)0x00, // Minor number (byte)0x00 // Power }, 0x004C); } /** * Constructor. * * @param length * The length of the AD structure. * * @param type * The AD type. The value should always be 0xFF * (Manufacturer Specific Data). * * @param data * The AD data whose format is <i>iBeacon</i>. * * @param companyId * The company ID. The value should always be 0x004C * (Apple, Inc.). * * @throws IllegalArgumentException * {@code data} is {@code null} or the length of * {@code data} is less than 25. */ public IBeacon(int length, int type, byte[] data, int companyId) { super(length, type, data, companyId); parse(data); } /** * Get the proximity UUID. * * @return * The proximity UUID. */ public UUID getUUID() { return mUUID; } /** * Set the proximity UUID. * * * @param uuid * The proximity UUID. The value must not be {@code null}. * * @throws IllegalArgumentException * The given value is {@code null}. */ public void setUUID(UUID uuid) { if (uuid == null) { throw new IllegalArgumentException("'uuid' is null."); } mUUID = uuid; long msbits = uuid.getMostSignificantBits(); long lsbits = uuid.getLeastSignificantBits(); byte[] data = getData(); data[UUID_INDEX + 0] = (byte)((msbits >> 56) & 0xFF); data[UUID_INDEX + 1] = (byte)((msbits >> 48) & 0xFF); data[UUID_INDEX + 2] = (byte)((msbits >> 40) & 0xFF); data[UUID_INDEX + 3] = (byte)((msbits >> 32) & 0xFF); data[UUID_INDEX + 4] = (byte)((msbits >> 24) & 0xFF); data[UUID_INDEX + 5] = (byte)((msbits >> 16) & 0xFF); data[UUID_INDEX + 6] = (byte)((msbits >> 8) & 0xFF); data[UUID_INDEX + 7] = (byte)((msbits ) & 0xFF); data[UUID_INDEX + 8] = (byte)((lsbits >> 56) & 0xFF); data[UUID_INDEX + 9] = (byte)((lsbits >> 48) & 0xFF); data[UUID_INDEX + 10] = (byte)((lsbits >> 40) & 0xFF); data[UUID_INDEX + 11] = (byte)((lsbits >> 32) & 0xFF); data[UUID_INDEX + 12] = (byte)((lsbits >> 24) & 0xFF); data[UUID_INDEX + 13] = (byte)((lsbits >> 16) & 0xFF); data[UUID_INDEX + 14] = (byte)((lsbits >> 8) & 0xFF); data[UUID_INDEX + 15] = (byte)((lsbits >> 0) & 0xFF); } /** * Get the major number. * * @return * The major number. */ public int getMajor() { return mMajor; } /** * Set the major number. * * @param major * The major number. The value should be in the range from 0 to 65535. * * @throws IllegalArgumentException * The given value is out of the valid range. */ public void setMajor(int major) { if (major < 0 || 0xFFFF < major) { throw new IllegalArgumentException("'major' is out of the valid range: " + major); } mMajor = major; byte[] data = getData(); data[MAJOR_INDEX ] = (byte)((major >> 8) & 0xFF); data[MAJOR_INDEX + 1] = (byte)((major ) & 0xFF); } /** * Get the minor number. * * @return * The minor number. */ public int getMinor() { return mMinor; } /** * Set the minor number. * * @param minor * The minor number. The value should be in the range from 0 to 65535. * * @throws IllegalArgumentException * The given value is out of the valid range. */ public void setMinor(int minor) { if (minor < 0 || 0xFFFF < minor) { throw new IllegalArgumentException("'minor' is out of the valid range: " + minor); } mMinor = minor; byte[] data = getData(); data[MINOR_INDEX ] = (byte)((minor >> 8) & 0xFF); data[MINOR_INDEX + 1] = (byte)((minor ) & 0xFF); } /** * Get the power. * * @return * The power. */ public int getPower() { return mPower; } /** * Set the power. * * @param power * The power. The value should be in the range from -128 to 127. * * @throws IllegalArgumentException * The given value is out of the valid range. */ public void setPower(int power) { if (power < -128 || 127 < power) { throw new IllegalArgumentException("'power' is out of the valid range: " + power); } mPower = power; getData()[POWER_INDEX] = (byte)(power & 0xFF); } private void parse(byte[] data) { // 25 = 2 (company ID) + 2 (format ID) + 16 (UUID) + 2 (major) + 2 (minor) + 1 (power) if (data == null || data.length < 25) { throw new IllegalArgumentException("The byte sequence cannot be parsed as an iBeacon."); } mUUID = buildUUID(data); mMajor = buildMajor(data); mMinor = buildMinor(data); mPower = buildPower(data); } private UUID buildUUID(byte[] data) { return UUIDCreator.from128(data, 4, false); } private int buildMajor(byte[] data) { return Bytes.parseBE2BytesAsInt(data, MAJOR_INDEX); } private int buildMinor(byte[] data) { return Bytes.parseBE2BytesAsInt(data, MINOR_INDEX); } private int buildPower(byte[] data) { return data[POWER_INDEX]; } /** * Create an {@link IBeacon} instance. * * <p> * The format of {@code data} should be as described in the following table. * </p> * * <table border="1" style="border-collapse: collapse;" cellpadding="5"> * <thead> * <tr> * <th></th> * <th>Value</th> * <th>Description</th> * </tr> * </thead> * <tbody> * <tr> * <td>Company ID</td> * <td><code>0x4C 0x00</code></td> * <td>The company ID assigned to Apple, Inc. (Little Endian)</td> * </tr> * <tr> * <td>Format ID</td> * <td><code>0x02 0x15</code></td> * <td>The format ID which represents <i>iBeacon</i></td> * </tr> * <tr> * <td>Proximity UUID</td> * <td>16-byte data</td> * <td>Proximity UUID</td> * </tr> * <tr> * <td>Major number</td> * <td>2-byte data</td> * <td>Major number (Big Endian)</td> * </tr> * <tr> * <td>Minor number</td> * <td>2-byte data</td> * <td>Minor number (Big Endian)</td> * </tr> * <tr> * <td>Power</td> * <td>1-byte data</td> * <td>The 2's complement of the calibrated Tx Power</td> * </tr> * </tbody> * </table> * * @param length * The length of the AD structure. * * @param type * The AD type. The value should always be 0xFF which represents * <i>Manufacturer Specific Data</i>. * * @param data * The AD type. The value of the first two bytes is the company ID. * * @param companyId * The company ID. The value should always be 0x004C which represents * <i>Apple, Inc.</i> * * @return * An {@link IBeacon} instance. {@code null} is returned if the * length of {@code data} is less than 25. */ public static IBeacon create(int length, int type, byte[] data, int companyId) { if (data == null || data.length < 25) { return null; } return new IBeacon(length, type, data, companyId); } @Override public String toString() { return String.format(STRING_FORMAT, mUUID, mMajor, mMinor, mPower); } }