linuxraspberry-pibluetoothbluetooth-lowenergyinput-devices

How to identify a Bluetooth peripheral related to an input device?


I want to use multiple Bluetooth input devices on a Linux as /dev/input and write a program to handle their input event, but I need to vary its behavior depending on the source peripheral. How do we identify which peripheral originates the received input event?

Background:

I'm working on a personal project to develop a call button for a home robot without using its official smartphone app. The robot will come for you if you push the button arranged in every room.

I make use of Smartphone Shutter Remote Controller (BT Shutter), a $1 Bluetooth peripheral to tap a shutter button on your smartphone from a distance. It is actually a one-button Bluetooth keyboard that can only send a VolumeUp keyboard event to the paired central device. I think it is ideal to implement my use case regarding cost and battery life.

I plan to develop a gateway server in Python on a Linux device like Rasberry Pi that acts as a Bluetooth central and sends a control command to the robot via its API in response to receiving keyboard events from the BT Shutters, like the following:

import evdev

device = evdev.InputDevice('/dev/input/event1')

for event in device.read_loop():
    if (
        event.type == evdev.ecodes.EV_KEY
        and event.code == evdev.ecodes.KEY_VOLUMEUP
        and event.value == 1:  # KEYDOWN
    ):
        # Move the robot to the defined location in the house.
        ...

However, the server cannot distinguish the events from different rooms because BT Shutter is just a single-function keyboard and cannot upgrade its firmware to make it send a unique identifier. It would be helpful if it could retrieve the MAC address or something of the input device to identify the source of the events. Note that the device name /event? is not reliable because evdev does not ensure its consistency between re-connection.

Smartphone Shutter Remote Controller


Solution

  • Each /dev/input/event* provides a UNIQ identifier in response to the EVIOCGUNIQ ioctl request. In the case of Bluetooth, UNIQ represents a MAC address to identify the underlying peripheral uniquely. evdev also exposes PRODUCT via EVIOCGID request.

    We can easily get the value in Python by using python-evdev, that provides a convenient wrapper function of ioctl:

    >>> import evdev
    >>> dev = evdev.InputDevice('/dev/input/event1')
    >>> dev.name
    'BT Shutter Keyboard'
    >>> dev.info
    DeviceInfo(bustype=5, vendor=9354, product=33382, version=1)
    >>> dev.phys  # Controller MAC address
    'xx:xx:xx:xx:xx:xx'
    >>> dev.uniq  # Device MAC address
    'xx:xx:xx:xx:xx:xx'
    

    udevadm also provides the event device path that includes the path to the underlying input device on sysfs:

    $ udevadm info /dev/input/event1
    P: /devices/virtual/misc/uhid/XXXX:XXXX:XXXX.XXXX/input/input1/event1
    N: input/event1
    L: 0
    E: DEVPATH=/devices/virtual/misc/uhid/XXXX:XXXX:XXXX.XXXX/input/input1/event1
    E: DEVNAME=/dev/input/event1
    ...
    
    $ udevadm info /sys/devices/virtual/misc/uhid/XXXX:XXXX:XXXX.XXXX/input/input1
    PRODUCT=5/248a/8266/1
    NAME="BT Shutter Keyboard"
    PHYS="xx:xx:xx:xx:xx:xx"
    UNIQ="xx:xx:xx:xx:xx:xx"
    ...