Skip to content

Instantly share code, notes, and snippets.

@sebgod
Last active November 25, 2023 08:02
Show Gist options
  • Save sebgod/ff5667b204e8fb8a2317d10e54797806 to your computer and use it in GitHub Desktop.
Save sebgod/ff5667b204e8fb8a2317d10e54797806 to your computer and use it in GitHub Desktop.
Starts a Bluetooth SPP server that forwards traffic to a locally connected serial device that uses the SkyWatcher serial protocol

SkyWatcher Serial Protocol Bluetooth Server

Bluetooth server supporting SPP profile for relaying SkyWatcher serial protocol commands to a locally connected mount. This has been tested on a Raspberry PI 4, might work on older models/different Linux systems.

Server setup

  • Create a virtual environment (venv), activate via ./venv/bin/activate
  • Install required modules via requirements.txt via pip3 install -r requirements.txt (might require installing native deps)
  • Open a different terminal with bluetoothctl
  • Run the Python script and wait for a connection, you might have to trust it first via the open bluetoothctl session

Client setup (Windows)

  • Open Bluetooth settings
  • More Bluetooth Settings
  • Com Ports
  • Add outgoing
  • Browse
  • Serial Port service should appear (after trusting on the server side)
  • A COM port will be assigned
  • Configure COM port in Device Manager, tested with 115200 baud

TODO

Make device and baud rate configurable via command line arguments

#!/usr/bin/python3
import os
import dbus
import dbus.service
import dbus.mainloop.glib
import serial
from gi.repository import GLib
class Profile(dbus.service.Object):
fd = -1
ser = None
@dbus.service.method('org.bluez.Profile1',
in_signature='',
out_signature='')
def Release(self):
if self.ser is not None:
self.ser.close()
print('Release')
@dbus.service.method('org.bluez.Profile1',
in_signature='oha{sv}',
out_signature='')
def NewConnection(self, path, fd, properties):
if self.ser is None:
self.ser = serial.Serial('/dev/ttyUSB0', 9600, timeout=1)
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):
ser = self.ser
data = os.read(fd, 64)
# print(data)
ser.write(data)
res = bytes()
eol = False
while not eol:
if ser.inWaiting() > 0:
char = ser.read()
if char == b'\r':
eol = True
res += char
# print(res)
os.write(fd, res)
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()
bluedot==2.0.0
certifi==2023.11.17
charset-normalizer==3.3.2
dbus-python==1.3.2
future==0.18.3
idna==3.4
iso8601==2.1.0
pyserial==3.5
PyYAML==6.0.1
requests==2.31.0
urllib3==2.1.0
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment