The first thing to mention is that rfcomm
was deprecated back in 2017 by the BlueZ project so if you are following a tutorial, it is likely that it is out of date.
The second thing to mention is that trying to develop both ends of the connection at the same time makes life tricky. Try to develop one end at a time so it is clear where the issue is.
There isn't really a direct replacement tool for rfcomm
.
bluetoothctl
will show
what services (specified by UUID) the RPi is making available. For example:
[bluetooth]# show
Controller B8:27:EB:22:57:E0 (public)
Name: SensePi
Alias: SensePi
Class: 0x002c0000
Powered: yes
Discoverable: no
DiscoverableTimeout: 0x000000b4
Pairable: yes
UUID: A/V Remote Control (0000110e-0000-1000-8000-00805f9b34fb)
UUID: Audio Source (0000110a-0000-1000-8000-00805f9b34fb)
UUID: PnP Information (00001200-0000-1000-8000-00805f9b34fb)
UUID: Audio Sink (0000110b-0000-1000-8000-00805f9b34fb)
UUID: Headset (00001108-0000-1000-8000-00805f9b34fb)
UUID: A/V Remote Control Target (0000110c-0000-1000-8000-00805f9b34fb)
UUID: Generic Access Profile (00001800-0000-1000-8000-00805f9b34fb)
UUID: Generic Attribute Profile (00001801-0000-1000-8000-00805f9b34fb)
UUID: Device Information (0000180a-0000-1000-8000-00805f9b34fb)
UUID: Headset AG (00001112-0000-1000-8000-00805f9b34fb)
Modalias: usb:v1D6Bp0246d0537
Discovering: no
Roles: central
Roles: peripheral
Advertising Features:
ActiveInstances: 0x00 (0)
SupportedInstances: 0x05 (5)
SupportedIncludes: tx-power
SupportedIncludes: appearance
SupportedIncludes: local-name
By default there is typically not "1101" Serial Port Profile.
UUID: Serial Port (00001101-0000-1000-8000-00805f9b34fb)
which there needs to be for the type of serial connection you want to make. Below is some Python code that will create a server that echo's back any data (after reversing it) that is useful for testing.
#!/usr/bin/python3
import os
import dbus
import dbus.service
import dbus.mainloop.glib
from gi.repository import GLib
class Profile(dbus.service.Object):
fd = -1
@dbus.service.method('org.bluez.Profile1',
in_signature='',
out_signature='')
def Release(self):
print('Release')
@dbus.service.method('org.bluez.Profile1',
in_signature='oha{sv}',
out_signature='')
def NewConnection(self, path, fd, properties):
self.fd = fd.take()
print('NewConnection(%s, %d)' % (path, self.fd))
for key in properties.keys():
if key == 'Version' or key == 'Features':
print(' %s = 0x%04x' % (key, properties[key]))
else:
print(' %s = %s' % (key, properties[key]))
io_id = GLib.io_add_watch(self.fd,
GLib.PRIORITY_DEFAULT,
GLib.IO_IN | GLib.IO_PRI,
self.io_cb)
def io_cb(self, fd, conditions):
data = os.read(fd, 1024)
print('Callback Data: {0}'.format(data.decode('ascii')))
os.write(fd, bytes(list(reversed(data.rstrip()))) + b'\n')
return True
@dbus.service.method('org.bluez.Profile1',
in_signature='o',
out_signature='')
def RequestDisconnection(self, path):
print('RequestDisconnection(%s)' % (path))
if self.fd > 0:
os.close(self.fd)
self.fd = -1
def main():
dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
bus = dbus.SystemBus()
manager = dbus.Interface(bus.get_object('org.bluez',
'/org/bluez'),
'org.bluez.ProfileManager1')
mainloop = GLib.MainLoop()
adapter = dbus.Interface(bus.get_object('org.bluez',
'/org/bluez/hci0'),
dbus.PROPERTIES_IFACE)
discoverable = adapter.Get('org.bluez.Adapter1', 'Discoverable')
if not discoverable:
print('Making discoverable...')
adapter.Set('org.bluez.Adapter1', 'Discoverable', True)
profile_path = '/foo/baz/profile'
server_uuid = '00001101-0000-1000-8000-00805f9b34fb'
opts = {
'Version': dbus.UInt16(0x0102),
'AutoConnect': dbus.Boolean(True),
'Role': 'server',
'Name': 'SerialPort',
'Service': '00001101-0000-1000-8000-00805f9b34fb',
'RequireAuthentication': dbus.Boolean(False),
'RequireAuthorization': dbus.Boolean(False),
'Channel': dbus.UInt16(1),
}
print('Starting Serial Port Profile...')
profile = Profile(bus, profile_path)
manager.RegisterProfile(profile_path, server_uuid, opts)
try:
mainloop.run()
except KeyboardInterrupt:
mainloop.quit()
if __name__ == '__main__':
main()
Save this code as spp_server.py
for example. To run it python3 spp_server.py
$ python3 spp_server.py
Making discoverable...
Starting Serial Port Profile...
If you are running with a Python Virtual Environment (venv) then you will need to install a couple of extra packages into the venv. If you are running with the system wide python on a RPi then I would expect them to be installed already
They are:
$ pip install dbus-python
$ pip install vext.gi
This can now be tested with an app such as the following on an Android phone:
https://play.google.com/store/apps/details?id=de.kai_morich.serial_bluetooth_terminal&gl=DE&pli=1
Make sure you have a the python script running and you also have a window with bluetoothctl
running so you can respond to pairing requests.
Once you have that running you can move on to the Windows client. Initially I used the following Python code (In python 3.9 or newer) to establish the connection:
import socket
server_address = "B8:27:EB:22:57:E0"
server_port = 1
with socket.socket(socket.AF_BLUETOOTH,
socket.SOCK_STREAM,
socket.BTPROTO_RFCOMM) as c:
c.connect((server_address, server_port))
c.send(b'desserts')
print(c.recv(1024).decode())
Again, have a bluetoothctl
open on the RPi to respond to pairing requests.
Once I had done this once I had a device with the correct kind of port available
And could connect with putty: