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);

相关推荐
ps酷教程42 分钟前
Jackson 解决没有无参构造函数的反序列化问题
java
NiceCloud喜云1 小时前
Opus 4.8 的 Effort Control 怎么选:Low 到 Max 五档策略
android·java·大数据·前端·c++·python·spring
wordbaby1 小时前
React Native + RNOH:跨页面数据回传的最佳实践与避坑指南
前端·react native
丷丩1 小时前
MapLibre GL JS第22课:查看本地GeoJSON
前端·javascript·map·mapbox·maplibre gl js
_日拱一卒2 小时前
LeetCode:994腐烂的橘子
java·数据结构·算法·leetcode·深度优先
隔窗听雨眠2 小时前
Nginx网关响应慢排查手记
java·服务器·nginx
智慧物业老杨2 小时前
智慧物业合同周期管理系统:从风险预警到智能交接的全流程数智化落地方案
java·人工智能·python
Front思2 小时前
AI前端工程师需要具备能力+
前端·人工智能·ai
源码宝3 小时前
MES系统源码:Java8 + SpringBoot2.7 + MySQL8 + Redis,后端源码清爽易扩展
java·后端·源码·springboot·mes系统·源码二开·mes源码
JAVA社区3 小时前
Java高级全套教程(十)—— SpringCloudAlibaba超详细实战详解
java·开发语言·spring cloud·面试·职场和发展