Trouble Registering BLE GATT Characteristic on VenusOS using velib_python

I’m trying to register a BLE GATT characteristic on a Victron controller running VenusOS using the velib_python library.

My goal: In the end, I would like to have a BLE Characteristic (to put it simply) that I can write to from the outside. When a new value is written via BLE, I want to execute a Python or .sh script with the received value as an argument.

I’ve written a Python script that creates a BLE GATT service and a characteristic using the D-Bus org.bluez.GattManager1 interface, but I’m encountering an issue when attempting to register the application.

Here’s the error message I receive:

Failed to register application: org.bluez.Error.Failed: No object received

From the D-Bus monitor, I can see that a call is made to RegisterApplication on the GattManager1 interface, but the registration still fails:

method call time=XXX sender=:1.637 -> destination=:1.0 serial=4 path=/org/bluez/hci0; interface=org.bluez.GattManager1; member=RegisterApplication
object path "/org/bluez/example/service0"

The error seems related to BlueZ not receiving the necessary objects (e.g., characteristics) under the service. Here’s a summary of what I’ve tried so far:

  • I’ve defined a GATT service class and a characteristic class within my Python script.
  • The service contains a method to handle writes to the characteristic (for receiving SSID and password data).
  • I’m using the system D-Bus for communication and bluez is available on D-Bus.

Has anyone successfully registered a BLE GATT service and characteristic using VenusOS and velib_python? Any insights or examples of working setups would be greatly appreciated!

Thanks in advance!

Python Code
from dbus.service import method, Object
from dbus.mainloop.glib import DBusGMainLoop
from gi.repository import GLib
import dbus
import os
import sys

class MyCharacteristic(Object):
    PATH_BASE = '/org/bluez/example/service/char'

    def __init__(self, bus, index, service):
        self.path = self.PATH_BASE + str(index)
        Object.__init__(self, bus, self.path)
        self.service = service

        self.value = []

    @method(dbus_interface="org.bluez.GattCharacteristic1",
            in_signature="ay", out_signature="")
    def WriteValue(self, value):
        self.value = value
        print(f"Received WriteValue: {value}")
        # Handle logic for parameter
        data = ''.join([chr(byte) for byte in value])
        if "parameter:" in data:
            self.service.parameter = data.replace("parameter:", "").strip()

        if self.service.parameter:
            self.service.execute_script()


class MyService(Object):
    PATH_BASE = '/org/bluez/example/service'

    def __init__(self, bus, index):
        self.path = self.PATH_BASE + str(index)
        Object.__init__(self, bus, self.path)

        self.parameter = None
        self.characteristics = []

        # Add the MyCharacteristic
        self.add_characteristic(MyCharacteristic(bus, 0, self))

    def add_characteristic(self, characteristic):
        self.characteristics.append(characteristic)

    def execute_script(self):
        print(f"Executing script.")
        # Example shell command
        script_command = f"/path/to/my.sh {self.parameter}"
        os.system(script_command)


def register_app_cb():
    print("GATT application registered")


def register_app_error_cb(error):
    print("Failed to register application:", str(error))
    mainloop.quit()


def main():
    DBusGMainLoop(set_as_default=True)

    # Use the system bus for BlueZ interaction
    bus = dbus.SystemBus()

    # Get the GattManager1 interface on the Bluetooth adapter
    service_manager = dbus.Interface(bus.get_object('org.bluez', '/org/bluez/hci0'), 'org.bluez.GattManager1')

    # Create and register the custom GATT Service
    service = MyService(bus, 0)

    # Register the service with BlueZ
    service_manager.RegisterApplication(service.path, {},
                                        reply_handler=register_app_cb,
                                        error_handler=register_app_error_cb)

    global mainloop
    mainloop = GLib.MainLoop()
    mainloop.run()


if __name__ == "__main__":
    main()