don/cordova-plugin-ble-central

[Android] Local name is not always updated during Scan

murilommp opened this issue · 3 comments

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 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!

Thanks for the tip and code samples @murilommp

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

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.

@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