hypfvieh / bluez-dbus

bluetooth library for linux OSes using DBus and bluez (http://www.bluez.org/).
MIT License
68 stars 20 forks source link

Scan filter "RSSI" causing DBusExecutionException #37

Closed srware closed 4 years ago

srware commented 4 years ago

I haven't tested this extensively but whenever I include an "RSSI" discovery filter in a Map and call DeviceManager.setScanFilter() or BluetoothAdapter.setDiscoveryFilter() I get a DBusExecutionException: org.freedesktop.dbus.exceptions.DBusExecutionException: Invalid arguments in method call

I have tried executing Adapter1.GetDiscoveryFilters() which return "RSSI" in the returned String[] so i'm not sure why this isn't working as expected...

If I exclude the "RSSI" filter everything works as expected with other filter types (UUID, Transport etc...).

srware commented 4 years ago

Interestingly BluetoothDevice.getRssi() is also returning 'null' for all devices so I am wondering if something has changed with regards to Bluez API and RSSI. I am using UBuntu 18.04 which has Bluez 5.48 on all devices. I see this library is aligned to a newer Bluez API which might be the issue?

hypfvieh commented 4 years ago

The value for RSSI is usually negative, as this value is represented in decibel-milliwatts (dBm). If you provide positive values >20 you will get the failure you've seen.

srware commented 4 years ago

@hypfvieh , thanks for the response. In this case I am getting it with any value. I have tried positive, 0 and negative values all which return the same exception. Trying to get the RSSI value from a physical device is also not working. The value is always null.

I tried upgrading Bluez to v5.50 on Ubuntu 18.04 but I still get the same issues. Is there any debug information I can gather for this? Bluetoothctl is showing RSSI values without issue.

hypfvieh commented 4 years ago

I just tried it and I don't get any RSSI as well.. but I don't even get it when using d-feet, so I guess that requesting RSSI is not always working or is not always supported...

I also tried to use bluetoothctl which also did not show any RSSI values. After disable scanning for devices using bluetoothctl, all RSSI values are displayed as 'nil'. Querying for more info (info [MAC]) does show some information but nothing about RSSI.

Setting the filter is working for me (at least it does not throw any exceptions):

public class RssiFilterTest {

    public static void main(String[] args){

        DeviceManager createInstance = null;
        try {
            createInstance = DeviceManager.createInstance(false);
            Map<DiscoveryFilter, Object> x = new HashMap<>();
            x.put(DiscoveryFilter.RSSI, (short) -32);

            createInstance.setScanFilter(x);
            List<BluetoothDevice> scanForBluetoothDevices = createInstance.scanForBluetoothDevices(5);

            if (scanForBluetoothDevices.isEmpty()) {
                System.err.println("No devices found");
            } else {

                for (BluetoothDevice bluetoothDevice : scanForBluetoothDevices) {
                    System.out.println(bluetoothDevice.getAddress() + " -> " + bluetoothDevice.getAlias() + ", RSSI: " + bluetoothDevice.getRssi());
                }
            }

        } catch (DBusException _ex) {
            _ex.printStackTrace();

        } finally {
            if (createInstance != null) {
                createInstance.closeConnection();
            }
        }
    }
}
hypfvieh commented 4 years ago

Quick update on this: If you want to get RSSI, the bluetooth adapter has to be in "discovery mode", otherwise no RSSI will be found. Also be sure that, if you set the scanFilter, the desired device will match the filter, otherwise it will not appear.

Here is a quick sample code:

public static void main(String[] args){

        DeviceManager createInstance = null;
        try {
            createInstance = DeviceManager.createInstance(false);
            Map<DiscoveryFilter, Object> x = new HashMap<>();
            x.put(DiscoveryFilter.RSSI, Short.valueOf((short) -32));
            //createInstance.setScanFilter(x);

            createInstance.getAdapter().startDiscovery();
            createInstance.findBtDevicesByIntrospection(createInstance.getAdapter());

            // wait for some devices to appear
            Thread.sleep(5000);

            List<BluetoothDevice> scanForBluetoothDevices = createInstance.getDevices();

            if (scanForBluetoothDevices.isEmpty()) {
                System.err.println("No devices found");
            } else {

                for (BluetoothDevice bluetoothDevice : scanForBluetoothDevices) {
                    System.out.println(bluetoothDevice.getAddress() + " -> " + bluetoothDevice.getAlias() + ", RSSI: " + bluetoothDevice.getRssi());
                }
            }

        } catch (DBusException | InterruptedException _ex) {
            _ex.printStackTrace();

        } finally {
            if (createInstance != null) {
                if (createInstance.getAdapter() != null) {
                    createInstance.getAdapter().stopDiscovery();
                }
                createInstance.closeConnection();
            }
        }
    }

The output in my test is something like: AA:BB:CC:DD:EE:FF -> HUAWEI Band 2, RSSI: -50 Please note I disabled the scanFilter for this test, so my test device will always be found

srware commented 4 years ago

It seems that if I try and set RSSI and Pathloss in the same filter request I get the exception. If I only include RSSI or Pathloss it succeeds... Is this expected?

srware commented 4 years ago

I have tested your code and can confirm that I get RSSI values for devices which are active. Can the scanForBluetoothDevices() be refactored in this way so that RSSI values are retrieved? Currently if I use scanForBluetoothDevices() or any of the getDevices() methods without explicitly handling the adapters discovery state RSSI is not returned.

hypfvieh commented 4 years ago

I think this is expected. As far as I know, you only can set RSSI OR Pathloss, not both at the same time. This is even not possible using the bluetoothctl commandline tool, which will always reset the other filter if you set one of them.

hypfvieh commented 4 years ago

I have tested your code and can confirm that I get RSSI values for devices which are active. Can the scanForBluetoothDevices() be refactored in this way so that RSSI values are retrieved? Currently if I use scanForBluetoothDevices() or any of the getDevices() methods without explicitly handling the adapters discovery state RSSI is not returned.

I don't think that this is a good idea. Providing old, outdated or stale data has no use for anyone.

The provided RSSI value would be outdated when you query for it and adapter is no longer in discovery mode. RSSI value will change depending on the distance between adapter and device, obstacles and some other parameters.

srware commented 4 years ago

@hypfvieh , I'm not sure I understand. The scanForBluetoothDevices() function already handles startDiscovery() and stopDiscovery() of the adapter but it seems to call findBtDevicesByIntrospection() after stopping discovery rather than before whereas the code above which works does startDiscovery() -> findBtDevicesByIntrospection() -> stopDiscovery(). I would expect the scanForBluetoothDevices() function to return RSSI values where possible.

hypfvieh commented 4 years ago

All calls for getting properties inside of BluetoothDevice will be redirected to DBus to fetch the actual property value from bluez.

RSSI is a property value, so it is not part of the information found in introspection data by default. Therefore the information is only available if bluez is in discovery mode, which is not the case when scanForBluetoothDevices() has returned.

Most of the properties can be obtained without enabled discovery mode (like MAC, name, alias...), but for RSSI, Pathloss and some power related stuff, discovery mode has to be enabled.

I don't want to change the behavior of scanForBluetoothDevices() because the properties may change at any time (e.g. the device name/alias can change as well as the RSSI changes).

If you want to store the value, you have to do it on your own e.g. using my sample code above and creating a subclass of BluetoothDevice which supports caching of some values.

srware commented 4 years ago

@hypfvieh , thanks for the explanation and understood.