Search code examples
pythonmacosbluetoothpyobjc

How to add an SDP Record to MacOS with Python?


My current goal is to add an SDP record to the Bluetooth service running on a MacBook Pro, such that I can advertise this service to other Bluetooth devices. Ideally, I would like to accomplish this task in Python.

At this point in time, I have successfully created, added, and advertised with an SDP record on Linux under the BlueZ Bluetooth stack, however, I'm having a bit of trouble getting a similar process to work on MacOS.

After a bit of research, my understanding is that interacting with the Bluetooth API on MacOS involves using the IOBluetooth Objective-C framework, which can be accomplished in Python through pyobjc. On a side-note, I also understand that the CoreBluetooth framework exists, however, the functionality of this framework isn't a suitable fit since it lacks SDP record add/remove functionality.

I'm currently dealing with two problems:

1. Creating a MacOS suitable SDP record

MacOS uses the PLIST format for loading/specifying SDP records. I've got an existing record in XML format that's loadable by BlueZ. Would it be possible to convert this record to a PLIST format or should I be looking into rewriting the record from scratch?

2. Adding the SDP record through pyobjc

I'm able to interact/query basic Bluetooth functionality through pyobjc/IOBluetooth. The problem occurs when I attempt to use the IOBluetoothSDPServiceRecord class. From some examples I've seen elsewhere (one example here), this class seems to be the one you would use to add a new record? When I attempt to load/use this class, I'm not able to access any of its functions.

I apologize in advance if this seems like a trivial/or amateur problem to those familiar with Objective-C! Python is where my expertise lies, so interfacing with this language is a bit out of my wheelhouse.

I'll also provide a minimal working example of what I've been working at below:

IOBluetooth.py

import objc as _objc

_objc.loadBundle('IOBluetooth', globals(),\
  bundle_path=u'/System/Library/Frameworks/IOBluetooth.framework')

bluetooth_test.py

from IOBluetooth import *


sdp = None
with open("record.plist", "r") as f:
    sdp = f.read()

# This functions correctly
devs = IOBluetoothDevice.recentDevices_(0)
print(devs[0].getNameOrAddress())

# This does not
sdp_sr = IOBluetoothSDPServiceRecord.alloc().init()
sdp_sr.publishedServiceRecord(sdp)

Output of bluetooth_test.py

Apple Watch
Traceback (most recent call last):
  File "bluetooth_test.py", line 12, in <module>
    sdp_sr.publishedServiceRecord(sdp)
AttributeError: 'IOBluetoothSDPServiceRecord' object has no attribute 'publishedServiceRecord'

Any input on this would be much appreciated!


Solution

  • After working on this a bit more, I was able to come up with an answer. For those who happen to be working on something similar, I'll post how I solved this:

    Answer to Question 1

    After a bit of searching, I deemed it better to rewrite the existing XML SDP record by hand. I couldn't find any utility that would handle the conversion of 16 bit Bluetooth UUIDs from XML to PLIST format.

    Answer to Question 2

    The above IOBluetooth.py file was unchanged from my question.

    bluetooth_test.py

    from IOBluetooth import *
    from Cocoa import NSDictionary
    import time
    
    
    plist = NSDictionary.dictionaryWithContentsOfFile_("service.plist")
    sdp_sr = IOBluetoothSDPServiceRecord.publishedServiceRecordWithDictionary_(plist)
    
    time.sleep(10)
    
    sdp_sr.removeServiceRecord()
    print("Removed Service Record")
    

    What really helped with exploring the IOBluetooth API was using the dir() function in Python. You can actually toss a pyobjc Instance or Class at it and you'll receive a list of all the available methods.

    Hope this helps someone in the future!