Bluetooth mesh blues

2019

Introduction to BlueZ mesh, via Python/asyncio

What is D-Bus

(baby don't hurt me)

Operating System

App

Kernel

User

Some syscalls require
privileged acess!

What is D-Bus

(baby don't hurt me)

Service

Service

Service

Operating System

App

App

App

API

API

API

Root

What is D-Bus

(baby don't hurt me)

Message bus

Service

Service

Service

Operating System

App

App

App

API

API

API

D-Bus API: BlueZ

Message bus

BlueZ

org.bluez.mesh

/org/bluez/mesh

/org/bluez/mesh/node<uuid>

org.bluez.mesh.Network1

org.bluez.mesh.Node1
org.bluez.mesh.Management1

D-Bus API: Application

Message bus

App

<unnamed>

/com/silvair/app

/com/silvair/app/element<n>

org.bluez.mesh.Application1
org.bluez.mesh.ProvisionAgent1

org.bluez.mesh.Element1

D-Bus API: Provisioning

BlueZ

App

Provisioner

Join "/com/silvair/app", <uuid>

JoinComplete <token>

Provisioning

Attach <token>

GetManagedObjects

Send ...

<configuration>

D-Bus API: Importing

BlueZ

App

Provisioner

ImportLocalNode ...

<token>

Attach <token>

GetManagedObjects

Send ...

<configuration>

Application Interface

BlueZ

App

Provisioner

class Application:
    def __init__(self):
        self.bus = ravel.system_bus(managed_objects=True)

        self.path = "/com/silvair/app"
        self.bus.register(self.path, interface=ApplicationInterface(self))
        self.bus.object_added(self.path)
@ravel.interface(ravel.ITERFACE_SERVER, name='org.bluez.mesh.Application1')
class ApplicationInterface:
    def __init__(self, application):
        self.application = application

    @ravel.method(name='JoinComplete',
                  in_signature='t', out_signature='')
    def join_complete(self, token):
        return self.application.join_complete(token)

    # ...

Element Interface

BlueZ

App

Provisioner

class Element:
    def __init__(self, application, index):
        self.path = '%s/element%02d' % (application.path, index)
        # ...

class Application:
    def __init__(self):
        # ...

        self.element = Element(self, index=0)
        self.bus.register(self.element.path, interface=ElementInterface(self.element)
        self.bus.object_added(path)
@ravel.interface(ravel.ITERFACE_SERVER, name='org.bluez.mesh.Element1')
class ElementInterface:
    def __init__(self, element):
        self.element = element

    @ravel.method(name='MessageReceived',
                  in_signature='qqbay', out_signature='')
    def message_received(self, source, application_key, subscription, data):
        return self.element.message_received(source, application_key,
                                             subscription, bytes(data))

Talking to BlueZ

BlueZ

App

Provisioner

class Application:
    def __init__(self):
        self.mesh_service = self.bus['org.bluez.mesh']
        self.token = ...

    async def connect(self):
        network_object = self.mesh_service['/org/bluez/mesh']

        network_interface = await network_object \
            .get_async_interface('org.bluez.mesh.Network1')

        node_path, configuration = \
            await network_interface.Attach(self.PATH, token)

        node_object = self.mesh_service[node_path]

        self.node_interface = await node_object \
            .get_async_interface('org.bluez.mesh.Node1')        

Talking to BlueZ: sending

BlueZ

App

Provisioner

class Application:
    async def send(self):
        await self.node_interface.Send(self.element.path,
                                       destination=0x0042,
                                       application_key=0,
                                       bytes.fromhex('800501'))
        

Talking to BlueZ: receiving

BlueZ

App

Provisioner

class Element:
    def message_received(self, src, app_key, sub, msg):
        self.application.message_received(self, src, app_key, sub, msg)


class Application:
    def __init__(self):
        self.message_callbacks = defaultdict(set)

    def message_received(self, element, src, app_key, sub, msg):
        opcode, params = parse(msg)
        
        for cb in self.message_callbacks(opcode):
            cb(element, src, app_key, sub, params)        

Talking to BlueZ: receiving

BlueZ

App

Provisioner

class Application:
    async def attention(self):
        status = asyncio.Future()

        def message_received(element, src, app_key, sub, params):
            if element.path != self.element.path or src != 0x0042 or app_key != 0:
                return

            status.set_result(params)

        opcode = 0x8007
        self.message_callbacks[].add(message_received)

        await self.node_interface.Send(self.element.path,
                                       destination=0x0042,
                                       application_key=0,
                                       bytes.fromhex('800501'))

        params = await status
        

homersoft/bluez-tests

AKA pymeshd

Thank you

Questions?

Bluetooth mesh blues @ Silvair

By Michał Lowas-Rzechonek

Bluetooth mesh blues @ Silvair

Introduction to BlueZ mesh, via Python/asyncio

  • 781