no.nordicsemi.android.ble.data.Data Maven / Gradle / Ivy
Show all versions of ble Show documentation
/*
* Copyright (c) 2018, Nordic Semiconductor
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
* USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package no.nordicsemi.android.ble.data;
import android.annotation.SuppressLint;
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattDescriptor;
import android.os.Parcel;
import android.os.Parcelable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import androidx.annotation.IntDef;
import androidx.annotation.IntRange;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@SuppressWarnings({"WeakerAccess", "unused", "UnusedReturnValue"})
public class Data implements Parcelable {
@SuppressWarnings("deprecation")
@SuppressLint("UniqueConstants")
@Retention(RetentionPolicy.SOURCE)
@IntDef(value = {
FORMAT_UINT8,
FORMAT_UINT16,
FORMAT_UINT16_LE,
FORMAT_UINT16_BE,
FORMAT_UINT24,
FORMAT_UINT24_LE,
FORMAT_UINT24_BE,
FORMAT_UINT32,
FORMAT_UINT32_LE,
FORMAT_UINT32_BE,
FORMAT_SINT8,
FORMAT_SINT16,
FORMAT_SINT16_LE,
FORMAT_SINT16_BE,
FORMAT_SINT24,
FORMAT_SINT24_LE,
FORMAT_SINT24_BE,
FORMAT_SINT32,
FORMAT_SINT32_LE,
FORMAT_SINT32_BE,
FORMAT_FLOAT,
FORMAT_SFLOAT
})
public @interface ValueFormat {}
@SuppressWarnings("deprecation")
@SuppressLint("UniqueConstants")
@Retention(RetentionPolicy.SOURCE)
@IntDef(value = {
FORMAT_UINT8,
FORMAT_UINT16,
FORMAT_UINT16_LE,
FORMAT_UINT16_BE,
FORMAT_UINT24,
FORMAT_UINT24_LE,
FORMAT_UINT24_BE,
FORMAT_UINT32,
FORMAT_UINT32_LE,
FORMAT_UINT32_BE,
FORMAT_SINT8,
FORMAT_SINT16,
FORMAT_SINT16_LE,
FORMAT_SINT16_BE,
FORMAT_SINT24,
FORMAT_SINT24_LE,
FORMAT_SINT24_BE,
FORMAT_SINT32,
FORMAT_SINT32_LE,
FORMAT_SINT32_BE,
})
public @interface IntFormat {}
@SuppressWarnings("deprecation")
@SuppressLint("UniqueConstants")
@Retention(RetentionPolicy.SOURCE)
@IntDef(value = {
FORMAT_UINT32,
FORMAT_UINT32_LE,
FORMAT_UINT32_BE,
FORMAT_SINT32,
FORMAT_SINT32_LE,
FORMAT_SINT32_BE,
})
public @interface LongFormat {}
@Retention(RetentionPolicy.SOURCE)
@IntDef(value = {
FORMAT_FLOAT,
FORMAT_SFLOAT
})
public @interface FloatFormat {}
private final static char[] HEX_ARRAY = "0123456789ABCDEF".toCharArray();
/**
* Data value format type uint8
*/
public final static int FORMAT_UINT8 = 0x11;
/**
* Data value format type uint16
*/
@SuppressWarnings("DeprecatedIsStillUsed")
@Deprecated
public final static int FORMAT_UINT16 = 0x12;
public final static int FORMAT_UINT16_LE = 0x12;
public final static int FORMAT_UINT16_BE = 0x112;
/**
* Data value format type uint24
*/
@SuppressWarnings("DeprecatedIsStillUsed")
@Deprecated
public final static int FORMAT_UINT24 = 0x13;
public final static int FORMAT_UINT24_LE = 0x13;
public final static int FORMAT_UINT24_BE = 0x113;
/**
* Data value format type uint32
*/
@SuppressWarnings("DeprecatedIsStillUsed")
@Deprecated
public final static int FORMAT_UINT32 = 0x14;
public final static int FORMAT_UINT32_LE = 0x14;
public final static int FORMAT_UINT32_BE = 0x114;
/**
* Data value format type sint8
*/
public final static int FORMAT_SINT8 = 0x21;
/**
* Data value format type sint16
*/
@SuppressWarnings("DeprecatedIsStillUsed")
@Deprecated
public final static int FORMAT_SINT16 = 0x22;
public final static int FORMAT_SINT16_LE = 0x22;
public final static int FORMAT_SINT16_BE = 0x122;
/**
* Data value format type sint24
*/
@SuppressWarnings("DeprecatedIsStillUsed")
@Deprecated
public final static int FORMAT_SINT24 = 0x23;
public final static int FORMAT_SINT24_LE = 0x23;
public final static int FORMAT_SINT24_BE = 0x123;
/**
* Data value format type sint32
*/
@SuppressWarnings("DeprecatedIsStillUsed")
@Deprecated
public final static int FORMAT_SINT32 = 0x24;
public final static int FORMAT_SINT32_LE = 0x24;
public final static int FORMAT_SINT32_BE = 0x124;
/**
* Data value format type sfloat (16-bit float, IEEE-11073)
*/
public final static int FORMAT_SFLOAT = 0x32;
/**
* Data value format type float (32-bit float, IEEE-11073)
*/
public final static int FORMAT_FLOAT = 0x34;
protected byte[] mValue;
public Data() {
this.mValue = null;
}
public Data(@Nullable final byte[] value) {
this.mValue = value;
}
public static Data from(@NonNull final String value) {
return new Data(value.getBytes()); // UTF-8
}
public static Data from(@NonNull final BluetoothGattCharacteristic characteristic) {
return new Data(characteristic.getValue());
}
public static Data from(@NonNull final BluetoothGattDescriptor descriptor) {
return new Data(descriptor.getValue());
}
public static Data opCode(final byte opCode) {
return new Data(new byte[] { opCode });
}
public static Data opCode(final byte opCode, final byte parameter) {
return new Data(new byte[] { opCode, parameter });
}
/**
* Returns the underlying byte array.
*
* @return Data received.
*/
@Nullable
public byte[] getValue() {
return mValue;
}
/**
* Return the stored value of this characteristic.
* See {@link #getValue} for details.
*
* @param offset Offset at which the string value can be found.
* @return Cached value of the characteristic
*/
@Nullable
public String getStringValue(@IntRange(from = 0) final int offset) {
if (mValue == null || offset > mValue.length)
return null;
final byte[] strBytes = new byte[mValue.length - offset];
if (mValue.length - offset >= 0)
System.arraycopy(mValue, offset, strBytes, 0, mValue.length - offset);
return new String(strBytes);
}
/**
* Returns the size of underlying byte array.
*
* @return Length of the data.
*/
public int size() {
return mValue != null ? mValue.length : 0;
}
@NonNull
@Override
public String toString() {
if (size() == 0)
return "";
final char[] out = new char[mValue.length * 3 - 1];
for (int j = 0; j < mValue.length; j++) {
int v = mValue[j] & 0xFF;
out[j * 3] = HEX_ARRAY[v >>> 4];
out[j * 3 + 1] = HEX_ARRAY[v & 0x0F];
if (j != mValue.length - 1)
out[j * 3 + 2] = '-';
}
return "(0x) " + new String(out);
}
/**
* Returns a byte at the given offset from the byte array.
*
* @param offset Offset at which the byte value can be found.
* @return Cached value or null of offset exceeds value size.
*/
@Nullable
public Byte getByte(@IntRange(from = 0) final int offset) {
if (offset + 1 > size()) return null;
return mValue[offset];
}
/**
* Returns an integer value from the byte array.
*
*
The formatType parameter determines how the value
* is to be interpreted. For example, setting formatType to
* {@link #FORMAT_UINT16_LE} specifies that the first two bytes of the
* value at the given offset are interpreted to generate the
* return value.
*
* @param formatType The format type used to interpret the value.
* @param offset Offset at which the integer value can be found.
* @return Cached value or null of offset exceeds value size.
*/
@Nullable
public Integer getIntValue(@IntFormat final int formatType,
@IntRange(from = 0) final int offset) {
if ((offset + getTypeLen(formatType)) > size()) return null;
return switch (formatType) {
case FORMAT_UINT8 -> unsignedByteToInt(mValue[offset]);
case FORMAT_UINT16_LE -> unsignedBytesToInt(mValue[offset], mValue[offset + 1]);
case FORMAT_UINT16_BE -> unsignedBytesToInt(mValue[offset + 1], mValue[offset]);
case FORMAT_UINT24_LE -> unsignedBytesToInt(
mValue[offset],
mValue[offset + 1],
mValue[offset + 2],
(byte) 0
);
case FORMAT_UINT24_BE -> unsignedBytesToInt(
mValue[offset + 2],
mValue[offset + 1],
mValue[offset],
(byte) 0
);
case FORMAT_UINT32_LE -> unsignedBytesToInt(
mValue[offset],
mValue[offset + 1],
mValue[offset + 2],
mValue[offset + 3]
);
case FORMAT_UINT32_BE -> unsignedBytesToInt(
mValue[offset + 3],
mValue[offset + 2],
mValue[offset + 1],
mValue[offset]
);
case FORMAT_SINT8 -> unsignedToSigned(unsignedByteToInt(mValue[offset]), 8);
case FORMAT_SINT16_LE -> unsignedToSigned(unsignedBytesToInt(mValue[offset],
mValue[offset + 1]), 16);
case FORMAT_SINT16_BE -> unsignedToSigned(unsignedBytesToInt(mValue[offset + 1],
mValue[offset]), 16);
case FORMAT_SINT24_LE -> unsignedToSigned(unsignedBytesToInt(
mValue[offset],
mValue[offset + 1],
mValue[offset + 2],
(byte) 0
), 24);
case FORMAT_SINT24_BE -> unsignedToSigned(unsignedBytesToInt(
(byte) 0,
mValue[offset + 2],
mValue[offset + 1],
mValue[offset]
), 24);
case FORMAT_SINT32_LE -> unsignedToSigned(unsignedBytesToInt(
mValue[offset],
mValue[offset + 1],
mValue[offset + 2],
mValue[offset + 3]
), 32);
case FORMAT_SINT32_BE -> unsignedToSigned(unsignedBytesToInt(
mValue[offset + 3],
mValue[offset + 2],
mValue[offset + 1],
mValue[offset]
), 32);
default -> null;
};
}
/**
* Returns a long value from the byte array.
*
Only {@link #FORMAT_UINT32_LE} and {@link #FORMAT_SINT32_LE} are supported.
*
The formatType parameter determines how the value
* is to be interpreted. For example, setting formatType to
* {@link #FORMAT_UINT32_LE} specifies that the first four bytes of the
* value at the given offset are interpreted to generate the
* return value.
*
* @param formatType The format type used to interpret the value.
* @param offset Offset at which the integer value can be found.
* @return Cached value or null of offset exceeds value size.
*/
@Nullable
public Long getLongValue(@LongFormat final int formatType,
@IntRange(from = 0) final int offset) {
if ((offset + getTypeLen(formatType)) > size()) return null;
return switch (formatType) {
case FORMAT_UINT32_LE -> unsignedBytesToLong(
mValue[offset],
mValue[offset + 1],
mValue[offset + 2],
mValue[offset + 3]
);
case FORMAT_UINT32_BE -> unsignedBytesToLong(
mValue[offset + 3],
mValue[offset + 2],
mValue[offset + 1],
mValue[offset]
);
case FORMAT_SINT32_LE -> unsignedToSigned(unsignedBytesToLong(
mValue[offset],
mValue[offset + 1],
mValue[offset + 2],
mValue[offset + 3]
), 32);
case FORMAT_SINT32_BE -> unsignedToSigned(unsignedBytesToLong(
mValue[offset + 3],
mValue[offset + 2],
mValue[offset + 1],
mValue[offset]
), 32);
default -> null;
};
}
/**
* Returns an float value from the given byte array.
*
* @param formatType The format type used to interpret the value.
* @param offset Offset at which the float value can be found.
* @return Cached value at a given offset or null if the requested offset exceeds the value size.
*/
@Nullable
public Float getFloatValue(@FloatFormat final int formatType,
@IntRange(from = 0) final int offset) {
if ((offset + getTypeLen(formatType)) > size()) return null;
switch (formatType) {
case FORMAT_SFLOAT -> {
if (mValue[offset + 1] == 0x07 && mValue[offset] == (byte) 0xFE)
return Float.POSITIVE_INFINITY;
if ((mValue[offset + 1] == 0x07 && mValue[offset] == (byte) 0xFF) ||
(mValue[offset + 1] == 0x08 && mValue[offset] == 0x00) ||
(mValue[offset + 1] == 0x08 && mValue[offset] == 0x01))
return Float.NaN;
if (mValue[offset + 1] == 0x08 && mValue[offset] == 0x02)
return Float.NEGATIVE_INFINITY;
return bytesToFloat(mValue[offset], mValue[offset + 1]);
}
case FORMAT_FLOAT -> {
if (mValue[offset + 3] == 0x00) {
if (mValue[offset + 2] == 0x7F && mValue[offset + 1] == (byte) 0xFF) {
if (mValue[offset] == (byte) 0xFE)
return Float.POSITIVE_INFINITY;
if (mValue[offset] == (byte) 0xFF)
return Float.NaN;
} else if (mValue[offset + 2] == (byte) 0x80 && mValue[offset + 1] == 0x00) {
if (mValue[offset] == 0x00 || mValue[offset] == 0x01)
return Float.NaN;
if (mValue[offset] == 0x02)
return Float.NEGATIVE_INFINITY;
}
}
return bytesToFloat(mValue[offset], mValue[offset + 1],
mValue[offset + 2], mValue[offset + 3]);
}
}
return null;
}
/**
* Returns the size of a give value type.
*/
public static int getTypeLen(@ValueFormat final int formatType) {
return formatType & 0xF;
}
/**
* Convert a signed byte to an unsigned int.
*/
private static int unsignedByteToInt(final byte b) {
return b & 0xFF;
}
/**
* Convert a signed byte to an unsigned int.
*/
private static long unsignedByteToLong(final byte b) {
return b & 0xFFL;
}
/**
* Convert signed bytes to a 16-bit unsigned int.
*/
private static int unsignedBytesToInt(final byte b0, final byte b1) {
return (unsignedByteToInt(b0) + (unsignedByteToInt(b1) << 8));
}
/**
* Convert signed bytes to a 32-bit unsigned int.
*/
private static int unsignedBytesToInt(final byte b0, final byte b1, final byte b2, final byte b3) {
return (unsignedByteToInt(b0) + (unsignedByteToInt(b1) << 8))
+ (unsignedByteToInt(b2) << 16) + (unsignedByteToInt(b3) << 24);
}
/**
* Convert signed bytes to a 32-bit unsigned long.
*/
private static long unsignedBytesToLong(final byte b0, final byte b1, final byte b2, final byte b3) {
return (unsignedByteToLong(b0) + (unsignedByteToLong(b1) << 8))
+ (unsignedByteToLong(b2) << 16) + (unsignedByteToLong(b3) << 24);
}
/**
* Convert signed bytes to a 16-bit short float value.
*/
private static float bytesToFloat(final byte b0, final byte b1) {
int mantissa = unsignedToSigned(unsignedByteToInt(b0)
+ ((unsignedByteToInt(b1) & 0x0F) << 8), 12);
int exponent = unsignedToSigned(unsignedByteToInt(b1) >> 4, 4);
return (float) (mantissa * Math.pow(10, exponent));
}
/**
* Convert signed bytes to a 32-bit short float value.
*/
private static float bytesToFloat(final byte b0, final byte b1, final byte b2, final byte b3) {
int mantissa = unsignedToSigned(unsignedByteToInt(b0)
+ (unsignedByteToInt(b1) << 8)
+ (unsignedByteToInt(b2) << 16), 24);
return (float) (mantissa * Math.pow(10, b3));
}
/**
* Convert an unsigned integer value to a two's-complement encoded
* signed value.
*/
private static int unsignedToSigned(int unsigned, final int size) {
if ((unsigned & (1 << size - 1)) != 0) {
unsigned = -1 * ((1 << size - 1) - (unsigned & ((1 << size - 1) - 1)));
}
return unsigned;
}
/**
* Convert an unsigned long value to a two's-complement encoded
* signed value.
*/
@SuppressWarnings("SameParameterValue")
private static long unsignedToSigned(long unsigned, final int size) {
if ((unsigned & (1L << size - 1)) != 0) {
unsigned = -1 * ((1L << size - 1) - (unsigned & ((1L << size - 1) - 1)));
}
return unsigned;
}
// Parcelable
protected Data(final Parcel in) {
mValue = in.createByteArray();
}
@Override
public void writeToParcel(final Parcel dest, final int flags) {
dest.writeByteArray(mValue);
}
@Override
public int describeContents() {
return 0;
}
public static final Creator CREATOR = new Creator<>() {
@Override
public Data createFromParcel(final Parcel in) {
return new Data(in);
}
@Override
public Data[] newArray(final int size) {
return new Data[size];
}
};
}