Мониторинг маяков, которые рекламируются из моего приложения через библиотеку AltBeacon - PullRequest
0 голосов
/ 05 августа 2020

Я работаю над решением, которое рекламирует и сканирует в формате iBeacon с использованием библиотеки AltBeacon . Меня беспокоит то, что библиотека сканирует все устройства, и это нормально, но после анализа отсканированных устройств она также отслеживает рекламные устройства, которые не рекламируются из моего приложения. Есть ли способ решить эту проблему с помощью библиотеки? Если нет, то что может быть альтернативным решением этой проблемы. Для меня очень важно отслеживать рекламные маяки, которые являются рекламой только из моего приложения.

Это код, который используется при рекламе в формате iBeacon через библиотеку AltBeacon:

BluetoothManager bluetoothManager =
        (BluetoothManager) applicationContext.getSystemService(Context.BLUETOOTH_SERVICE);
if (bluetoothManager != null) {
    BluetoothAdapter mBluetoothAdapter = bluetoothManager.getAdapter();
    BluetoothLeAdvertiser mBluetoothLeAdvertiser = mBluetoothAdapter.getBluetoothLeAdvertiser();
    if (mBluetoothLeAdvertiser != null) {
        beacon = new Beacon.Builder()
                .setId1(userId)
                .setId2("1")
                .setId3("1")
                .setManufacturer(0x004C)
                .setTxPower(-75)
                .setDataFields(Arrays.asList(new Long[]{0l}))
                .build();
        beaconParser = new BeaconParser()
                .setBeaconLayout("m:2-3=0215,i:4-19,i:20-21,i:22-23,p:24-24");
        beaconTransmitter = new BeaconTransmitter(InventaSdk.getContext(), beaconParser);
        beaconTransmitter.setBeacon(beacon);
    }
}

Изменить:

Разбор кода маяка:

/**
 * Construct a Beacon from a Bluetooth LE packet collected by Android's Bluetooth APIs,
 * including the raw Bluetooth device info
 *
 * @param scanData The actual packet bytes
 * @param rssi The measured signal strength of the packet
 * @param device The Bluetooth device that was detected
 * @return An instance of a <code>Beacon</code>
 */
public Beacon fromScanData(byte[] scanData, int rssi, BluetoothDevice device) {
    return fromScanData(scanData, rssi, device, new Beacon());
}

protected Beacon fromScanData(byte[] bytesToProcess, int rssi, BluetoothDevice device, Beacon beacon) {
    BleAdvertisement advert = new BleAdvertisement(bytesToProcess);
    boolean parseFailed = false;
    Pdu pduToParse = null;
    int startByte = 0;
    ArrayList<Identifier> identifiers = new ArrayList<Identifier>();
    ArrayList<Long> dataFields = new ArrayList<Long>();

    for (Pdu pdu: advert.getPdus()) {
        if (pdu.getType() == Pdu.GATT_SERVICE_UUID_PDU_TYPE ||
                pdu.getType() == Pdu.MANUFACTURER_DATA_PDU_TYPE) {
            pduToParse = pdu;
            LogHelper.d(TAG, "Processing pdu type: "+pdu.getType()+bytesToHex(bytesToProcess)+" with startIndex: "+pdu.getStartIndex()+" endIndex: "+pdu.getEndIndex());
            break;
        }
        else {
            LogHelper.d(TAG, "Ignoring pdu type %02X "+ pdu.getType());
        }
    }
    if (pduToParse == null) {
        LogHelper.d(TAG, "No PDUs to process in this packet.");
        parseFailed = true;
    }
    else {
        byte[] serviceUuidBytes = null;
        byte[] typeCodeBytes = longToByteArray(getMatchingBeaconTypeCode(), mMatchingBeaconTypeCodeEndOffset - mMatchingBeaconTypeCodeStartOffset + 1);
        if (getServiceUuid() != null) {
            serviceUuidBytes = longToByteArray(getServiceUuid(), mServiceUuidEndOffset - mServiceUuidStartOffset + 1, false);
        }
        startByte = pduToParse.getStartIndex();
        boolean patternFound = false;

        if (getServiceUuid() == null) {
            if (byteArraysMatch(bytesToProcess, startByte + mMatchingBeaconTypeCodeStartOffset, typeCodeBytes)) {
                patternFound = true;
            }
        } else {
            if (byteArraysMatch(bytesToProcess, startByte + mServiceUuidStartOffset, serviceUuidBytes) &&
                    byteArraysMatch(bytesToProcess, startByte + mMatchingBeaconTypeCodeStartOffset, typeCodeBytes)) {
                patternFound = true;
            }
        }



        if (patternFound == false) {
            // This is not a beacon
            if (getServiceUuid() == null) {
                LogHelper.d(TAG, "This is not a matching Beacon advertisement. (Was expecting   "+byteArrayToString(typeCodeBytes)
                                    + ".The bytes I see are: "+
                            bytesToHex(bytesToProcess));

            } else {
                LogHelper.d(TAG, "This is not a matching Beacon advertisement. Was expecting "+
                        byteArrayToString(serviceUuidBytes)+
                        " at offset "+startByte + mServiceUuidStartOffset+"and "+byteArrayToString(typeCodeBytes)+
                        " at offset "+ startByte + mMatchingBeaconTypeCodeStartOffset + "The bytes I see are: "
                        + bytesToHex(bytesToProcess));
            }
            parseFailed = true;
            beacon =  null;
        } else {
            LogHelper.d(TAG, "This is a recognized beacon advertisement -- "+
                        byteArrayToString(typeCodeBytes)+"seen");
            LogHelper.d(TAG, "Bytes are: "+ bytesToHex(bytesToProcess));
        }

        if (patternFound) {
            if (bytesToProcess.length <= startByte+mLayoutSize && mAllowPduOverflow) {
                // If the layout size is bigger than this PDU, and we allow overflow.  Make sure
                // the byte buffer is big enough by zero padding the end so we don't try to read
                // outside the byte array of the advertisement
                LogHelper.d(TAG, "Expanding buffer because it is too short to parse: "+bytesToProcess.length+", needed: "+(startByte+mLayoutSize));
                bytesToProcess = ensureMaxSize(bytesToProcess, startByte+mLayoutSize);
            }
            for (int i = 0; i < mIdentifierEndOffsets.size(); i++) {
                int endIndex = mIdentifierEndOffsets.get(i) + startByte;

                if (endIndex > pduToParse.getEndIndex() && mIdentifierVariableLengthFlags.get(i)) {
                    LogHelper.d(TAG, "Need to truncate identifier by "+(endIndex-pduToParse.getEndIndex()));
                    // If this is a variable length identifier, we truncate it to the size that
                    // is available in the packet
                    int start = mIdentifierStartOffsets.get(i) + startByte;
                    int end = pduToParse.getEndIndex()+1;
                    if (end <= start) {
                        LogHelper.d(TAG, "PDU is too short for identifer.  Packet is malformed");
                        return null;
                    }
                    Identifier identifier = Identifier.fromBytes(bytesToProcess, start, end, mIdentifierLittleEndianFlags.get(i));
                    identifiers.add(identifier);
                }
                else if (endIndex > pduToParse.getEndIndex() && !mAllowPduOverflow) {
                    parseFailed = true;
                    LogHelper.d(TAG, "Cannot parse identifier "+i+" because PDU is too short.  endIndex: " + endIndex + " PDU endIndex: " + pduToParse.getEndIndex());
                }
                else {
                    Identifier identifier = Identifier.fromBytes(bytesToProcess, mIdentifierStartOffsets.get(i) + startByte, endIndex+1, mIdentifierLittleEndianFlags.get(i));
                    identifiers.add(identifier);
                }
            }
            for (int i = 0; i < mDataEndOffsets.size(); i++) {
                int endIndex = mDataEndOffsets.get(i) + startByte;
                if (endIndex > pduToParse.getEndIndex() && !mAllowPduOverflow) {
                    LogHelper.d(TAG, "Cannot parse data field "+i+" because PDU is too short.  endIndex: " + endIndex + " PDU endIndex: " + pduToParse.getEndIndex()+".  Setting value to 0");
                    dataFields.add(new Long(0l));
                }
                else {
                    String dataString = byteArrayToFormattedString(bytesToProcess, mDataStartOffsets.get(i) + startByte, endIndex, mDataLittleEndianFlags.get(i));
                    dataFields.add(Long.decode(dataString));
                }
            }

            if (mPowerStartOffset != null) {
                int endIndex = mPowerEndOffset + startByte;
                int txPower = 0;
                try {
                    if (endIndex > pduToParse.getEndIndex() && !mAllowPduOverflow) {
                        parseFailed = true;
                        LogHelper.d(TAG, "Cannot parse power field because PDU is too short.  endIndex: " + endIndex + " PDU endIndex: " + pduToParse.getEndIndex());
                    }
                    else {
                        String powerString = byteArrayToFormattedString(bytesToProcess, mPowerStartOffset + startByte, mPowerEndOffset + startByte, false);
                        txPower = Integer.parseInt(powerString)+mDBmCorrection;
                        // make sure it is a signed integer
                        if (txPower > 127) {
                            txPower -= 256;
                        }
                        beacon.mTxPower = txPower;
                    }
                }
                catch (NumberFormatException e1) {
                    // keep default value
                }
                catch (NullPointerException e2) {
                    // keep default value
                }
            }
        }
    }

    if (parseFailed) {
        beacon = null;
    }
    else {
        int beaconTypeCode = 0;
        String beaconTypeString = byteArrayToFormattedString(bytesToProcess, mMatchingBeaconTypeCodeStartOffset+startByte, mMatchingBeaconTypeCodeEndOffset+startByte, false);
        beaconTypeCode = Integer.parseInt(beaconTypeString);
        // TODO: error handling needed on the parse

        int manufacturer = 0;
        String manufacturerString = byteArrayToFormattedString(bytesToProcess, startByte, startByte+1, true);
        manufacturer = Integer.parseInt(manufacturerString);

        String macAddress = null;
        String name = null;
        if (device != null) {
            macAddress = device.getAddress();
            name = device.getName();
        }

        beacon.mIdentifiers = identifiers;
        beacon.mDataFields = dataFields;
        beacon.mRssi = rssi;
        beacon.mBeaconTypeCode = beaconTypeCode;
        if (mServiceUuid != null) {
            beacon.mServiceUuid = (int) mServiceUuid.longValue();
        }
        else {
            beacon.mServiceUuid = -1;
        }

        beacon.mBluetoothAddress = macAddress;
        beacon.mBluetoothName= name;
        beacon.mManufacturer = manufacturer;
        beacon.mParserIdentifier = mIdentifier;
        beacon.mMultiFrameBeacon = extraParsers.size() > 0 || mExtraFrame;
    }
    return beacon;
}

Обратные вызовы сканирования:

private ScanCallback getNewLeScanCallback() {
    if (leScanCallback == null) {
        leScanCallback = new ScanCallback() {
            @MainThread
            @Override
            public void onScanResult(int callbackType, ScanResult scanResult) {
                    LogHelper.d(TAG, "got record");
                    List<ParcelUuid> uuids = scanResult.getScanRecord().getServiceUuids();
                    if (uuids != null) {
                        for (ParcelUuid uuid : uuids) {
                            LogHelper.d(TAG, "with service uuid: "+uuid);
                        }
                    }

                    try {
                        LogHelper.d("ScanRecord", "Raw Data: " + scanResult.toString());
                        LogHelper.d("ScanRecord", "Device Data Name: " + scanResult.getDevice().getName() + "Rssi: " + scanResult.getRssi() + "Address: " + scanResult.getDevice().getAddress() + "Service uuid: " + scanResult.getScanRecord().getServiceUuids());
                    }catch (Exception e){
                        LogHelper.d("ScanRecord",e.getMessage());
                        e.printStackTrace();
                    }
                mCycledLeScanCallback.onLeScan(scanResult.getDevice(),
                        scanResult.getRssi(), scanResult.getScanRecord().getBytes());
                if (mBackgroundLScanStartTime > 0) {
                    LogHelper.d(TAG, "got a filtered scan result in the background.");
                }
            }

            @MainThread
            @Override
            public void onBatchScanResults(List<ScanResult> results) {
                LogHelper.d(TAG, "got batch records");
                for (ScanResult scanResult : results) {
                    mCycledLeScanCallback.onLeScan(scanResult.getDevice(),
                            scanResult.getRssi(), scanResult.getScanRecord().getBytes());
                }
                if (mBackgroundLScanStartTime > 0) {
                    LogHelper.d(TAG, "got a filtered batch scan result in the background.");
                }
            }

            @MainThread
            @Override
            public void onScanFailed(int errorCode) {
                Intent intent = new Intent("onScanFailed");
                intent.putExtra("errorCode", errorCode);
                LocalBroadcastManager.getInstance(CycledLeScannerForLollipop.this.mContext).sendBroadcast(intent);
                switch (errorCode) {
                    case SCAN_FAILED_ALREADY_STARTED:
                        LogHelper.e(TAG, "Scan failed: a BLE scan with the same settings is already started by the app");
                        break;
                    case SCAN_FAILED_APPLICATION_REGISTRATION_FAILED:
                        LogHelper.e(TAG, "Scan failed: app cannot be registered");
                        break;
                    case SCAN_FAILED_FEATURE_UNSUPPORTED:
                        LogHelper.e(TAG, "Scan failed: power optimized scan feature is not supported");
                        break;
                    case SCAN_FAILED_INTERNAL_ERROR:
                        LogHelper.e(TAG, "Scan failed: internal error");
                        break;
                    default:
                        LogHelper.e(TAG, "Scan failed with unknown error (errorCode=" + errorCode + ")");
                        break;
                }
            }
        };
    }
    return leScanCallback;
}

1 Ответ

1 голос
/ 05 августа 2020

Общий подход к фильтрации «ваших» маяков состоит в том, чтобы увидеть префикс идентификатора, который является общим для всех ваших маяков. Затем вы определяете, является ли это ваш маяк, фильтруя по маякам, которые соответствуют этому префиксу идентификатора.

Два способа выполнить фильтрацию:

A) Программная фильтрация после получения результатов сканирования.

При таком подходе вы ждете, пока не проанализируете маяки, а затем используете оператор if, чтобы увидеть, соответствуют ли идентификаторы маяков вашему префиксу. Если нет, не обрабатывайте его. Библиотека маячков Android имеет это как встроенную функцию, используя объекты «Регион» для предоставления шаблонов соответствия для «ваших» маяков.

    // replace uuid with your own
    beaconManager.startRangingBeaconsInRegion(new Region("matchOnlyMyBeacons", Identifier.parse(“2F234454-CF6D-4A0F-ADF2-F4911BA9”)), null, null));

    beaconManager.addRangeNotifier(new RangeNotifier() {
        @Override
        public void didRangeBeaconsInRegion(Collection<Beacon> beacons, Region region) {
           // only beacons matching the identifiers in the Region are included here
        }
    });

Поскольку вы не используете библиотеку в целом, а копируете некоторые его кода, вам, возможно, придется самостоятельно построить аналогичные c logi, например:

  // replace the uuid with yours below
  if (beacon.getID1().equals(Identifier.parse(“2F234454-CF6D-4A0F-ADF2-F4911BA9”)){
    // only process matching beacons here
  }

Это простой подход и очень гибкий. Он хорошо работает в тех случаях, когда ваше приложение работает только на переднем плане или в фоновом режиме, когда обычно есть несколько устройств BLE, которые не представляют интереса.

Недостатком является то, что оно может сжечь процессор и батарею, если много Вокруг есть маяки, которые не представляют интереса.

B) Используйте аппаратные фильтры сканирования

Android 6+ API-интерфейсы позволяют вам помещать аналогичные функции сопоставления в сам чип Bluetooth, чтобы все обратные вызовы сканирования вызывали вас get уже соответствует префиксу идентификатора. Это снижает нагрузку на процессор и батарею, но имеет недостатки:

  1. Не все устройства поддерживают это, хотя большинство устройств, выпущенных с 2018 года, поддерживают.

  2. Аппаратные фильтры - ограниченный ресурс. Если другие приложения примут их все, вы не получите результатов сканирования.

  3. Фильтры негибкие. Если даже один байт рекламного префикса не соответствует (обычно из-за другого кода производителя), вы не получите результат сканирования.

     ScanFilter.Builder builder = new ScanFilter.Builder();
     builder.setServiceUuid(null);
     byte[] filterBytes = new byte[]{
             /* 0215 are the start of iBeacon.  Use beac for AltBeacon */
             (byte) 0x02, (byte) 0x15,
             // These bytes are your 16 byte proximityUUID (ID1)
             (byte) 0x2F, (byte) 0x23, (byte) 0x44, (byte) 0x54, (byte) 0xCF, (byte) 0x6D, (byte) 0x4A, (byte) 0x0F, (byte) 0xAD, (byte) 0xF2, (byte) 0xF4, (byte) 0x91, (byte) 0x1B, (byte) 0xA9, (byte) 0xFF, (byte) 0xA6
     };
    
     byte[] maskBytes = new byte[]{
             /* Make this the same length as your filter bytes, and set every value to 0xff to match all bytes */
             (byte) 0xff, (byte) 0xff,
             (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff
     };
     builder.setManufacturerData((int) 0x004c /* apple for iBeacon, use 0x0118 for AltBeacon */, filterBytes, maskBytes);
     ScanFilter[] scanFilters = new ScanFilter[] { builder.build() };
     scanner.startScan(scanFilters, scanSettings, scanCallback);
    
...