可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
I am having trouble using startLeScan( new UUID[]{ MY_DESIRED_128_BIT_SERVICE_UUID }, callback ) on the new introduced BLE API of Android 4.3 on my Nexus 4.
The callback just doesn't get called. I still can see incoming packages in the log:
08-02 15:48:57.985: I/bt-hci(1051): btu_ble_process_adv_pkt 08-02 15:48:58.636: I/bt-hci(1051): BLE HCI(id=62) event = 0x02)
If I don't use the parameter to filter for UUIDs it works. We are using a manufacturer specific 128bit UUID for device of our company.
Now, our device offers more services than I am providing in the array. But that shouldn't be the problem.
Is anyone facing the same problem? Any solutions?
Edit
There are several problems related to scanning, this question only discusses one: If you also have some issue with scanning, read this comment first. Also keep in mind, that my device imposes a 16bit and a 128bit UUID. Most of you guys use 16bit UUIDs provided by the BLE standard like Heart rate or Speed and Cadence.
回答1:
@Navin's code is good, but it includes an overflow bug from the original 16-bit Android code. (If either byte is larger than 127 then it becomes a negative integer.)
Here's an implementation which fixes that bug and adds 128-bit support:
private List parseUuids(byte[] advertisedData) { List uuids = new ArrayList(); ByteBuffer buffer = ByteBuffer.wrap(advertisedData).order(ByteOrder.LITTLE_ENDIAN); while (buffer.remaining() > 2) { byte length = buffer.get(); if (length == 0) break; byte type = buffer.get(); switch (type) { case 0x02: // Partial list of 16-bit UUIDs case 0x03: // Complete list of 16-bit UUIDs while (length >= 2) { uuids.add(UUID.fromString(String.format( "%08x-0000-1000-8000-00805f9b34fb", buffer.getShort()))); length -= 2; } break; case 0x06: // Partial list of 128-bit UUIDs case 0x07: // Complete list of 128-bit UUIDs while (length >= 16) { long lsb = buffer.getLong(); long msb = buffer.getLong(); uuids.add(new UUID(msb, lsb)); length -= 16; } break; default: buffer.position(buffer.position() + length - 1); break; } } return uuids; }
回答2:
Try this to retrieve/filter the device from the advertised 128-bit UUIDs:
private List parseUUIDs(final byte[] advertisedData) { List uuids = new ArrayList(); int offset = 0; while (offset 1) { int uuid16 = advertisedData[offset++]; uuid16 += (advertisedData[offset++] = 16) { try { // Wrap the advertised bits and order them. ByteBuffer buffer = ByteBuffer.wrap(advertisedData, offset++, 16).order(ByteOrder.LITTLE_ENDIAN); long mostSignificantBit = buffer.getLong(); long leastSignificantBit = buffer.getLong(); uuids.add(new UUID(leastSignificantBit, mostSignificantBit)); } catch (IndexOutOfBoundsException e) { // Defensive programming. Log.e(LOG_TAG, e.toString()); continue; } finally { // Move the offset to read the next uuid. offset += 15; len -= 16; } } break; default: offset += (len - 1); break; } } return uuids; }
回答3:
This is a reported bug at least in Android 4.3 JWR66Y:
- Filtering works, if I provide my 16bit UUID
- Filtering doesn't return any scan results, if I provide my 128bit UUID or if I provide both UUIDs
My setting: My device offers 2 UUIDs on advertising (1 16bit and 1 128bit) and 4 UUIDs on service discovery (1 128bit and 3 16bit).
Even if it gets fixed, I warn everybody against using the filter option provided by Android. For backward compatibility and since it's broken on Samsung Galaxy S3 with Android 4.3
回答4:
Although 4.3 doesn't seem to support filtering by 128-bit UUIDs, these UUIDs are likely present in the byte[] scanRecord returned by the LeScanCallback.
There's probably a correct way to parse this data, but if you're getting the same data each time you can filter the results manually by finding the offsets of the UUIDs you're looking for. You might do this by printing the scan data to a log (as a hex string) and looking for the UUIDs you're interested in (they will probably follow a 0x06 or 0x07 and will be reversed). Once you find the offset, it shouldn't be too hard to set up a basic filter.
Here is a simple example that filters by a single UUID (uses Apache Commons Lang for ArrayUtils and the bytes-to-hex method found here, but you can substitute your own code where necessary)
public static boolean hasMyService(byte[] scanRecord) { // UUID we want to filter by (without hyphens) final String myServiceID = "0000000000001000800000805F9B34FB"; // The offset in the scan record. In my case the offset was 13; it will probably be different for you final int serviceOffset = 13; try{ // Get a 16-byte array of what may or may not be the service we're filtering for byte[] service = ArrayUtils.subarray(scanRecord, serviceOffset, serviceOffset + 16); // The bytes are probably in reverse order, so we need to fix that ArrayUtils.reverse(service); // Get the hex string String discoveredServiceID = bytesToHex(service); // Compare against our service return myServiceID.equals(discoveredServiceID); } catch (Exception e){ return false; } }
回答5:
Are you certain that the peripheral is listing the specified service UUID in the advertisement data or scan response data?
回答6:
The best method to list out the Service UUIDs from the scan result is to copy exactly the parseFromBytes method from ScanRecord.java which is located inside the android.bluetooth.le package (make sure you have the latest Android SDK), change the return to a List of ParcelUuid since that's the only thing we care about
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; public static List parseFromBytes(byte[] scanRecord) { if (scanRecord == null) { return null; } int currentPos = 0; int advertiseFlag = -1; List serviceUuids = new ArrayList(); String localName = null; int txPowerLevel = Integer.MIN_VALUE; SparseArray manufacturerData = new SparseArray(); Map serviceData = new HashMap(); try { while (currentPos
You would need to import BluetoothUuid.java from the same package as well:
import java.util.UUID; import android.os.ParcelUuid; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.util.Arrays; import java.util.HashSet; /** * Static helper methods and constants to decode the ParcelUuid of remote devices. * @hide */ public final class BluetoothUuid { /* See Bluetooth Assigned Numbers document - SDP section, to get the values of UUIDs * for the various services. * * The following 128 bit values are calculated as: * uuid * 2^96 + BASE_UUID */ public static final ParcelUuid AudioSink = ParcelUuid.fromString("0000110B-0000-1000-8000-00805F9B34FB"); public static final ParcelUuid AudioSource = ParcelUuid.fromString("0000110A-0000-1000-8000-00805F9B34FB"); public static final ParcelUuid AdvAudioDist = ParcelUuid.fromString("0000110D-0000-1000-8000-00805F9B34FB"); public static final ParcelUuid HSP = ParcelUuid.fromString("00001108-0000-1000-8000-00805F9B34FB"); public static final ParcelUuid HSP_AG = ParcelUuid.fromString("00001112-0000-1000-8000-00805F9B34FB"); public static final ParcelUuid Handsfree = ParcelUuid.fromString("0000111E-0000-1000-8000-00805F9B34FB"); public static final ParcelUuid Handsfree_AG = ParcelUuid.fromString("0000111F-0000-1000-8000-00805F9B34FB"); public static final ParcelUuid AvrcpController = ParcelUuid.fromString("0000110E-0000-1000-8000-00805F9B34FB"); public static final ParcelUuid AvrcpTarget = ParcelUuid.fromString("0000110C-0000-1000-8000-00805F9B34FB"); public static final ParcelUuid ObexObjectPush = ParcelUuid.fromString("00001105-0000-1000-8000-00805f9b34fb"); public static final ParcelUuid Hid = ParcelUuid.fromString("00001124-0000-1000-8000-00805f9b34fb"); public static final ParcelUuid Hogp = ParcelUuid.fromString("00001812-0000-1000-8000-00805f9b34fb"); public static final ParcelUuid PANU = ParcelUuid.fromString("00001115-0000-1000-8000-00805F9B34FB"); public static final ParcelUuid NAP = ParcelUuid.fromString("00001116-0000-1000-8000-00805F9B34FB"); public static final ParcelUuid BNEP = ParcelUuid.fromString("0000000f-0000-1000-8000-00805F9B34FB"); public static final ParcelUuid PBAP_PCE = ParcelUuid.fromString("0000112e-0000-1000-8000-00805F9B34FB"); public static final ParcelUuid PBAP_PSE = ParcelUuid.fromString("0000112f-0000-1000-8000-00805F9B34FB"); public static final ParcelUuid MAP = ParcelUuid.fromString("00001134-0000-1000-8000-00805F9B34FB"); public static final ParcelUuid MNS = ParcelUuid.fromString("00001133-0000-1000-8000-00805F9B34FB"); public static final ParcelUuid MAS = ParcelUuid.fromString("00001132-0000-1000-8000-00805F9B34FB"); public static final ParcelUuid SAP = ParcelUuid.fromString("0000112D-0000-1000-8000-00805F9B34FB"); public static final ParcelUuid BASE_UUID = ParcelUuid.fromString("00000000-0000-1000-8000-00805F9B34FB"); /** Length of bytes for 16 bit UUID */ public static final int UUID_BYTES_16_BIT = 2; /** Length of bytes for 32 bit UUID */ public static final int UUID_BYTES_32_BIT = 4; /** Length of bytes for 128 bit UUID */ public static final int UUID_BYTES_128_BIT = 16; public static final ParcelUuid[] RESERVED_UUIDS = { AudioSink, AudioSource, AdvAudioDist, HSP, Handsfree, AvrcpController, AvrcpTarget, ObexObjectPush, PANU, NAP, MAP, MNS, MAS, SAP}; public static boolean isAudioSource(ParcelUuid uuid) { return uuid.equals(AudioSource); } public static boolean isAudioSink(ParcelUuid uuid) { return uuid.equals(AudioSink); } public static boolean isAdvAudioDist(ParcelUuid uuid) { return uuid.equals(AdvAudioDist); } public static boolean isHandsfree(ParcelUuid uuid) { return uuid.equals(Handsfree); } public static boolean isHeadset(ParcelUuid uuid) { return uuid.equals(HSP); } public static boolean isAvrcpController(ParcelUuid uuid) { return uuid.equals(AvrcpController); } public static boolean isAvrcpTarget(ParcelUuid uuid) { return uuid.equals(AvrcpTarget); } public static boolean isInputDevice(ParcelUuid uuid) { return uuid.equals(Hid); } public static boolean isPanu(ParcelUuid uuid) { return uuid.equals(PANU); } public static boolean isNap(ParcelUuid uuid) { return uuid.equals(NAP); } public static boolean isBnep(ParcelUuid uuid) { return uuid.equals(BNEP); } public static boolean isMap(ParcelUuid uuid) { return uuid.equals(MAP); } public static boolean isMns(ParcelUuid uuid) { return uuid.equals(MNS); } public static boolean isMas(ParcelUuid uuid) { return uuid.equals(MAS); } public static boolean isSap(ParcelUuid uuid) { return uuid.equals(SAP); } /** * Returns true if ParcelUuid is present in uuidArray * * @param uuidArray - Array of ParcelUuids * @param uuid */ public static boolean isUuidPresent(ParcelUuid[] uuidArray, ParcelUuid uuid) { if ((uuidArray == null || uuidArray.length == 0) && uuid == null) return true; if (uuidArray == null) return false; for (ParcelUuid element: uuidArray) { if (element.equals(uuid)) return true; } return false; } /** * Returns true if there any common ParcelUuids in uuidA and uuidB. * * @param uuidA - List of ParcelUuids * @param uuidB - List of ParcelUuids * */ public static boolean containsAnyUuid(ParcelUuid[] uuidA, ParcelUuid[] uuidB) { if (uuidA == null && uuidB == null) return true; if (uuidA == null) { return uuidB.length == 0 ? true : false; } if (uuidB == null) { return uuidA.length == 0 ?