Android 解析蓝牙广播数据

static String toString(Map<T, byte\[\]> map) {

if (map == null) {

return "null";

}

if (map.isEmpty()) {

return "{}";

}

StringBuilder buffer = new StringBuilder();

buffer.append('{');

Iterator<Map.Entry<T, byte\[\]>> it = map.entrySet().iterator();

while (it.hasNext()) {

Map.Entry<T, byte\[\]> entry = it.next();

Object key = entry.getKey();

buffer.append(key).append("=").append(Arrays.toString(map.get(key)));

if (it.hasNext()) {

buffer.append(", ");

}

}

buffer.append('}');

return buffer.toString();

}

private static final String TAG = "ScanRecordUtil";

// The following data type values are assigned by Bluetooth SIG.

// For more details refer to Bluetooth 4.1 specification, Volume 3, Part C, Section 18.

private static final int DATA_TYPE_FLAGS = 0x01;

private static final int DATA_TYPE_SERVICE_UUIDS_16_BIT_PARTIAL = 0x02;

private static final int DATA_TYPE_SERVICE_UUIDS_16_BIT_COMPLETE = 0x03;

private static final int DATA_TYPE_SERVICE_UUIDS_32_BIT_PARTIAL = 0x04;

private static final int DATA_TYPE_SERVICE_UUIDS_32_BIT_COMPLETE = 0x05;

private static final int DATA_TYPE_SERVICE_UUIDS_128_BIT_PARTIAL = 0x06;

private static final int DATA_TYPE_SERVICE_UUIDS_128_BIT_COMPLETE = 0x07;

private static final int DATA_TYPE_LOCAL_NAME_SHORT = 0x08;

private static final int DATA_TYPE_LOCAL_NAME_COMPLETE = 0x09;

private static final int DATA_TYPE_TX_POWER_LEVEL = 0x0A;

private static final int DATA_TYPE_SERVICE_DATA = 0x16;

private static final int DATA_TYPE_MANUFACTURER_SPECIFIC_DATA = 0xFF;

// Flags of the advertising data.

private final int mAdvertiseFlags;

@Nullable

private final List mServiceUuids;

private final SparseArray<byte\[\]> mManufacturerSpecificData;

private final Map<ParcelUuid, byte\[\]> mServiceData;

// Transmission power level(in dB).

private final int mTxPowerLevel;

// Local name of the Bluetooth LE device.

private final String mDeviceName;

// Raw bytes of scan record.

private final byte\[\] mBytes;

/**

  • Returns the advertising flags indicating the discoverable mode and capability of the device.

  • Returns -1 if the flag field is not set.

*/

public int getAdvertiseFlags() {

return mAdvertiseFlags;

}

/**

  • Returns a list of service UUIDs within the advertisement that are used to identify the

  • bluetooth GATT services.

*/

public List getServiceUuids() {

return mServiceUuids;

}

/**

  • Returns a sparse array of manufacturer identifier and its corresponding manufacturer specific

  • data.

*/

public SparseArray<byte\[\]> getManufacturerSpecificData() {

return mManufacturerSpecificData;

}

/**

  • Returns the manufacturer specific data associated with the manufacturer id. Returns

  • {@code null} if the {@code manufacturerId} is not found.

*/

@Nullable

public byte\[\] getManufacturerSpecificData(int manufacturerId) {

return mManufacturerSpecificData.get(manufacturerId);

}

/**

  • Returns a map of service UUID and its corresponding service data.

*/

public Map<ParcelUuid, byte\[\]> getServiceData() {

return mServiceData;

}

/**

  • Returns the service data byte array associated with the {@code serviceUuid}. Returns

  • {@code null} if the {@code serviceDataUuid} is not found.

*/

@Nullable

public byte\[\] getServiceData(ParcelUuid serviceDataUuid) {

if (serviceDataUuid == null) {

return null;

}

return mServiceData.get(serviceDataUuid);

}

/**

  • Returns the transmission power level of the packet in dBm. Returns {@link Integer#MIN_VALUE}

  • if the field is not set. This value can be used to calculate the path loss of a received

  • packet using the following equation:

  • pathloss = txPowerLevel - rssi

*/

public int getTxPowerLevel() {

return mTxPowerLevel;

}

/**

  • Returns the local name of the BLE device. The is a UTF-8 encoded string.

*/

@Nullable

public String getDeviceName() {

return mDeviceName;

}

/**

  • Returns raw bytes of scan record.

*/

public byte\[\] getBytes() {

return mBytes;

}

private ScanRecordUtil(List serviceUuids,

SparseArray<byte\[\]> manufacturerData,

Map<ParcelUuid, byte\[\]> serviceData,

int advertiseFlags, int txPowerLevel,

String localName, byte\[\] bytes) {

mServiceUuids = serviceUuids;

mManufacturerSpecificData = manufacturerData;

mServiceData = serviceData;

mDeviceName = localName;

mAdvertiseFlags = advertiseFlags;

mTxPowerLevel = txPowerLevel;

mBytes = bytes;

}

/**

  • Parse scan record bytes to {@link ScanRecord}.

  • The format is defined in Bluetooth 4.1 specification, Volume 3, Part C, Section 11 and 18.

  • All numerical multi-byte entities and values shall use little-endian byte

  • order.

  • @param scanRecord The scan record of Bluetooth LE advertisement and/or scan response.

  • @hide

*/

public static ScanRecordUtil parseFromBytes(byte\[\] scanRecord) {

if (scanRecord == null) {

return null;

}

Log.e(TAG + "MYX23P", "进入parseFromBytes");

int currentPos = 0;

int advertiseFlag = -1;

List serviceUuids = new ArrayList();

String localName = null;

int txPowerLevel = Integer.MIN_VALUE;

SparseArray<byte\[\]> manufacturerData = new SparseArray<byte\[\]>();

Map<ParcelUuid, byte\[\]> serviceData = new ArrayMap<ParcelUuid, byte\[\]>();

try {

while (currentPos < scanRecord.length) {

// length is unsigned int.

int length = scanRecordcurrentPos++ & 0xFF;

if (length == 0) {

break;

}

// Note the length includes the length of the field type itself.

int dataLength = length - 1;

// fieldType is unsigned int.

int fieldType = scanRecordcurrentPos++ & 0xFF;

switch (fieldType) {

case DATA_TYPE_FLAGS:

advertiseFlag = scanRecordcurrentPos & 0xFF;

break;

case DATA_TYPE_SERVICE_UUIDS_16_BIT_PARTIAL:

case DATA_TYPE_SERVICE_UUIDS_16_BIT_COMPLETE:

parseServiceUuid(scanRecord, currentPos,

dataLength,16, serviceUuids);

break;

case DATA_TYPE_SERVICE_UUIDS_32_BIT_PARTIAL:

case DATA_TYPE_SERVICE_UUIDS_32_BIT_COMPLETE:

parseServiceUuid(scanRecord, currentPos, dataLength,

32, serviceUuids);

break;

case DATA_TYPE_SERVICE_UUIDS_128_BIT_PARTIAL:

case DATA_TYPE_SERVICE_UUIDS_128_BIT_COMPLETE:

parseServiceUuid(scanRecord, currentPos, dataLength,

128, serviceUuids);

break;

case DATA_TYPE_LOCAL_NAME_SHORT:

case DATA_TYPE_LOCAL_NAME_COMPLETE:

localName = new String(

extractBytes(scanRecord, currentPos, dataLength));

break;

case DATA_TYPE_TX_POWER_LEVEL:

txPowerLevel = scanRecordcurrentPos;

break;

case DATA_TYPE_SERVICE_DATA:

// The first two bytes of the service data are service data UUID in little

// endian. The rest bytes are service data.

int serviceUuidLength = 16;

byte\[\] serviceDataUuidBytes = extractBytes(scanRecord, currentPos,

serviceUuidLength);

ParcelUuid serviceDataUuid = parseUuidFrom(

serviceDataUuidBytes);

byte\[\] serviceDataArray = extractBytes(scanRecord,

currentPos + serviceUuidLength, dataLength - serviceUuidLength);

serviceData.put(serviceDataUuid, serviceDataArray);

break;

case DATA_TYPE_MANUFACTURER_SPECIFIC_DATA:

// The first two bytes of the manufacturer specific data are

// manufacturer ids in little endian.

int manufacturerId = ((scanRecordcurrentPos + 1 & 0xFF) << 8) +

(scanRecordcurrentPos & 0xFF);

byte\[\] manufacturerDataBytes = extractBytes(scanRecord, currentPos + 2,

dataLength - 2);

manufacturerData.put(manufacturerId, manufacturerDataBytes);

break;

default:

// Just ignore, we don't handle such data type.

break;

}

currentPos += dataLength;

}

if (serviceUuids.isEmpty()) {

serviceUuids = null;

}

return new ScanRecordUtil(serviceUuids, manufacturerData, serviceData,

advertiseFlag, txPowerLevel, localName, scanRecord);

} catch (Exception e) {

Log.e(TAG, "unable to parse scan record: " + Arrays.toString(scanRecord));

// As the record is invalid, ignore all the parsed results for this packet

// and return an empty record with raw scanRecord bytes in results

return new ScanRecordUtil(null, null, null, -1, Integer.MIN_VALUE, null, scanRecord);

}

}

@Override

public String toString() {

return "ScanRecord [mAdvertiseFlags=" + mAdvertiseFlags + ", mServiceUuids=" + mServiceUuids

  • ", mManufacturerSpecificData=" + ScanRecordUtil.toString(mManufacturerSpecificData)

  • ", mServiceData=" + ScanRecordUtil.toString(mServiceData)

  • ", mTxPowerLevel=" + mTxPowerLevel + ", mDeviceName=" + mDeviceName + "]";

}

/**

  • byte数组转化为string

*/

static final char\[\] hexArray = "0123456789ABCDEF".toCharArray();

public static String bytesToHex(byte\[\] bytes) {

char\[\] hexChars = new charbytes.length \* 2;

for (int j = 0; j < bytes.length; j++) {

int v = bytesj & 0xFF;

hexCharsj \* 2 = hexArrayv \>\>\> 4;

hexCharsj \* 2 + 1 = hexArrayv \& 0x0F;

}

return new String(hexChars);

}

// Parse service UUIDs.

private static int parseServiceUuid(byte\[\] scanRecord, int currentPos, int dataLength,

int uuidLength, List serviceUuids) {

while (dataLength > 0) {

byte\[\] uuidBytes = extractBytes(scanRecord, currentPos,

uuidLength);

serviceUuids.add(parseUuidFrom(uuidBytes));

dataLength -= uuidLength;

currentPos += uuidLength;

}

return currentPos;

}

// Helper method to extract bytes from byte array.

private static byte\[\] extractBytes(byte\[\] scanRecord, int start, int length) {

byte\[\] bytes = new bytelength;

System.arraycopy(scanRecord, start, bytes, 0, length);

return bytes;

}

/**

  • 转化方法

  • @param uuidBytes

  • @return

*/

public static ParcelUuid parseUuidFrom(byte\[\] uuidBytes) {

if (uuidBytes == null) {

throw new IllegalArgumentException("uuidBytes cannot be null");

}

int length = uuidBytes.length;

if (length != 16 && length != 32 &&

length != 128) {

throw new IllegalArgumentException("uuidBytes length invalid - " + length);

相关推荐
敲代码的嘎仔18 分钟前
用 Redis 合并写 + DelayQueue 延迟检测,把视频播放进度写库频率降到 1/60
java·开发语言·数据库·redis·缓存·音视频·高并发
事圆则缓40 分钟前
从suspend字节码到 Retrofit 协程桥:挂起函数识别与恢复
android·retrofit
youyou-06061 小时前
AUTOSAR‑COM 常见问题 QA (1)
java·linux·服务器·单片机·汽车
谢亮_vipxieliang1 小时前
ValidX时间段验证详解:ISO 8601标准与简化格式
java·spring boot·后端·spring·spring cloud·hibernate
Lsetea1 小时前
Java 请求 HTTPS 报 PKIX path building failed:从证书链到 truststore 的完整排查
java·https·ssl证书·keytool·truststore
一拳不是超人1 小时前
一个没测暗色模式的 Bug,吃掉了我一半 谷歌扩展用户
前端·javascript·程序员
liangshanbo12151 小时前
React useState 函数式更新面试题整理
前端·react.js·前端框架
涛涛ing1 小时前
为什么你的页面在 Safari 上总出问题?Interop 2027 正在解决这个 20 年老毛病
前端
不一样的少年_1 小时前
WebP 压缩到底在干嘛?小白也能看懂的原理拆解
前端·后端·图片资源
问心无愧05132 小时前
ctf show web 177
前端·笔记