don / cordova-plugin-ble-central

Bluetooth Low Energy (BLE) Central plugin for Apache Cordova (aka PhoneGap)
Apache License 2.0
941 stars 601 forks source link

[Android] Local name is not always updated during Scan #995

Open murilommp opened 6 months ago

murilommp commented 6 months ago

Hello, everyone.

I'm building a cordova app for Android with the goal of write data to and read data from a proprietary BLE device. Everything works great, but I am facing an issue when scanning devices with "ble.startScan()" just after I have changed the device name. The new device name is not updated immediately.

My proprietary device is advertising it's name correctaly and I can receive the updated information when I use third party apps (like ST BLE TooBox) to perform an scan.

I realize that method asJSONObject() in Peripheral class (in file Peripheral.java) uses method getName() from class BluetoothDevice to fill attribute name in JSON object passed as parameter to javascript callback. I saw in [method documentation](https://developer.android.com/reference/android/bluetooth/BluetoothDevice#getName()) that the name returned by it may be old due to have been extracted from cache.

I also checked that class ScanRecord has the method getDeviceName and it returns the local name received in advertising packet. So I created an attribute called "advertsingName" in class Peripheral to hold that information:

private BluetoothDevice device;
private byte[] advertisingData;
private String advertsingName = null;
private int advertisingRSSI;
private boolean autoconnect = false;
private boolean connected = false;
private boolean connecting = false;
private ConcurrentLinkedQueue<BLECommand> commandQueue = new ConcurrentLinkedQueue<BLECommand>();
private final Map<Integer, L2CAPContext> l2capContexts = new HashMap<Integer, L2CAPContext>();
private final AtomicBoolean bleProcessing = new AtomicBoolean();

Then, I changed the constructor from:

public Peripheral(BluetoothDevice device, int advertisingRSSI, byte[] scanRecord) {
        this.device = device;
        this.advertisingRSSI = advertisingRSSI;
        this.advertisingData = scanRecord;
}

to

public Peripheral(BluetoothDevice device, int advertisingRSSI, byte[] scanRecord, String advertsingName) {
      this.device = device;
      this.advertisingRSSI = advertisingRSSI;
      this.advertisingData = scanRecord;
      this.advertsingName = advertsingName;
}

And finally, I changed method asJSONObject from:

public JSONObject asJSONObject()  {

    JSONObject json = new JSONObject();

    try {
        json.put("name", device.getName());
        json.put("id", device.getAddress()); // mac address
        if (advertisingData != null) {
            json.put("advertising", byteArrayToJSON(advertisingData));
        }
        // TODO real RSSI if we have it, else
        if (advertisingRSSI != FAKE_PERIPHERAL_RSSI) {
            json.put("rssi", advertisingRSSI);
        }
    } catch (JSONException e) { // this shouldn't happen
        e.printStackTrace();
    }

    return json;
}

to

public JSONObject asJSONObject()  {

    JSONObject json = new JSONObject();

    try {
        if(this.advertsingName == null)
            json.put("name", device.getName());
        else
            json.put("name", this.advertsingName);

        json.put("id", device.getAddress()); // mac address
        if (advertisingData != null) {
            json.put("advertising", byteArrayToJSON(advertisingData));
        }
        // TODO real RSSI if we have it, else
        if (advertisingRSSI != FAKE_PERIPHERAL_RSSI) {
            json.put("rssi", advertisingRSSI);
        }
    } catch (JSONException e) { // this shouldn't happen
        e.printStackTrace();
    }

    return json;
}

I also changed the onScanResult() in BLECentralPlugin.java file from:

public void onScanResult(int callbackType, ScanResult result) {
    LOG.w(TAG, "Scan Result");
    super.onScanResult(callbackType, result);
    BluetoothDevice device = result.getDevice();
    String address = device.getAddress();
    boolean alreadyReported = peripherals.containsKey(address) && !peripherals.get(address).isUnscanned();

    if (!alreadyReported) {

        Peripheral peripheral = new Peripheral(device, result.getRssi(), result.getScanRecord().getBytes());
        peripherals.put(device.getAddress(), peripheral);

        if (discoverCallback != null) {
            PluginResult pluginResult = new PluginResult(PluginResult.Status.OK, peripheral.asJSONObject());
            pluginResult.setKeepCallback(true);
            discoverCallback.sendPluginResult(pluginResult);
        }

    } else {
        Peripheral peripheral = peripherals.get(address);
        if (peripheral != null) {
            peripheral.update(result.getRssi(), result.getScanRecord().getBytes());
            if (reportDuplicates && discoverCallback != null) {
                PluginResult pluginResult = new PluginResult(PluginResult.Status.OK, peripheral.asJSONObject());
                pluginResult.setKeepCallback(true);
                discoverCallback.sendPluginResult(pluginResult);
            }
        }
    }
}

to

public void onScanResult(int callbackType, ScanResult result) {
    LOG.w(TAG, "Scan Result");
    super.onScanResult(callbackType, result);
    BluetoothDevice device = result.getDevice();
    String address = device.getAddress();
    boolean alreadyReported = peripherals.containsKey(address) && !peripherals.get(address).isUnscanned();

    if (!alreadyReported) {

        Peripheral peripheral = new Peripheral(device, result.getRssi(), result.getScanRecord().getBytes(), result.getScanRecord().getDeviceName());
        peripherals.put(device.getAddress(), peripheral);

        if (discoverCallback != null) {
            PluginResult pluginResult = new PluginResult(PluginResult.Status.OK, peripheral.asJSONObject());
            pluginResult.setKeepCallback(true);
            discoverCallback.sendPluginResult(pluginResult);
        }

    } else {
        Peripheral peripheral = peripherals.get(address);
        if (peripheral != null) {
            peripheral.update(result.getRssi(), result.getScanRecord().getBytes());
            if (reportDuplicates && discoverCallback != null) {
                PluginResult pluginResult = new PluginResult(PluginResult.Status.OK, peripheral.asJSONObject());
                pluginResult.setKeepCallback(true);
                discoverCallback.sendPluginResult(pluginResult);
            }
        }
    }
}

Now I always have the local name updated during scan, even just after I have writen a new value to it.

Is there any other way to have the local name updated during scan instead having a cached info?

Thanks in advance!

peitschie commented 6 months ago

Thanks for the tip and code samples @murilommp

I'll have a look... it seems like something we should easily support in the plugin.

GerardoPrototype commented 6 months ago

The same thing happened to me, I performed a scan and changed the name. On my Android the name was updated but my iPhone was not. I restarted the phone but it still showed the same name.

peitschie commented 6 months ago

@GerardoPrototype I think you're hitting an iOS caching issue. The specific code suggestion here is for Android only, which sounds like it's already working correctly for you.

iOS does a lot of caching. For some good info, check out: https://developer.apple.com/forums/thread/19381