pythonbluetoothrfcomm

Bind a Bluetooth device to a rfcomm via Python


I was able to search and pair a Raspberry Pi 4 to a Bluetooth 4.0 thermal printer using the BlueZ API.

In order to use the python-escpos library, I need to create a serial port /dev/rfcomm, which I can do with sudo rfcomm bind /dev/rfcomm0 XX:XX:XX:XX:XX:XX 1.

How can I do it programatically in Python (without having to use python-subprocess)?


Solution

  • rfcomm was one of the tools deprecated by the BlueZ project in 2017. I would suggest that you avoid using functionality related to that.

    The python-escpos library takes the location of a serial port device so I would suggest using the Python pty library which creates two pseudo-devices, the pts, emulates a hardware serial port device.

    How exactly you want to architect this will depend on your project but I did a simple test where I had a script creating a pseudo serial port that it listened on and sent to the BLE device if data was received:

    import os
    import pty
    from time import sleep
    
    import pydbus
    
    dev_addr = 'xx:xx:xx:xx:xx:xx'
    
    ptmx_fd, pts_fd = pty.openpty()
    pts_name = os.ttyname(pts_fd)
    print(f"Printer port: {pts_name}")
    
    bus = pydbus.SystemBus()
    
    mngr = bus.get('org.bluez', '/')
    
    
    def get_characteristic_path(dev_path, uuid):
        mng_objs = mngr.GetManagedObjects()
        for path in mng_objs:
            chr_uuid = mng_objs[path].get('org.bluez.GattCharacteristic1', {}).get('UUID')
            if path.startswith(dev_path) and chr_uuid == uuid.casefold():
                return path
    
    
    class MyRemoteDevice:
        # CHAR_UUID = '0000ff02-0000-1000-8000-00805f9b34fb'  # Real printer
        CHAR_UUID = '6E400003-B5A3-F393-E0A9-E50E24DCCA9E'  # my test device
    
        def __init__(self, mac_addr):
            device_path = f"/org/bluez/hci0/dev_{mac_addr.replace(':', '_')}"
            self.device = bus.get('org.bluez', device_path)
    
            # Placeholder for characteristic details
            self.characteristic = None
    
        def _get_gatt_details(self):
            char_path = get_characteristic_path(self.device._path,
                                                MyRemoteDevice.CHAR_UUID)
            self.characteristic = bus.get('org.bluez', char_path)
    
        def connect(self):
            self.device.Connect()
            # Check GATT services have been resolved before continuing
            while not self.device.ServicesResolved:
                sleep(0.25)
            self._get_gatt_details()
    
        def disconnect(self):
            self.device.Disconnect()
    
        def read(self):
            return self.characteristic.ReadValue({})
    
        def write(self, new_value):
            self.characteristic.WriteValue(new_value, {})
    
    
    my_first_dev = MyRemoteDevice(dev_addr)
    
    my_first_dev.connect()
    try:
        while True:
            data = os.read(ptmx_fd, 1000)
            if data:
                print("data received:", data)
                my_first_dev.write(data)
    except:
        pass
    finally:
        my_first_dev.disconnect()
    
    

    In a Python prompt I then sent values to the serial port using the escpos library:

    from escpos.printer import Serial
    # 9600 Baud, 8N1, Flow Control Enabled
    p = Serial(devfile='/dev/pts/3',
               baudrate=9600,
               bytesize=8,
               parity='N',
               stopbits=1,
               timeout=1.00,
    
    p.text("Hello Printer#")