Day 2: Poke and Destroy!

After yesterday’s small success we take a break from the big picture and
focus on harassing bluez into doing some work for us.

open System
open System.Collections.Generic
open DBus

Will I ever remove these lines of pre-amble? Perhaps, but first I’ll try
snipping out any mundane ones if it’s more than say, 5 lines… I think they’re
useful, the reader can be sure he’s not missing anything important if he wants
to run the examples and has these.

[<Literal>]
let IFDEV = "org.bluez.Device1"

This is cool! People who’ve done some C++ template programming will recognise
this as a constexpr. It’s used later, you’ll see. Minor difference in the Attribute style compared with C# vis-á-vis the <> symbols either side of the attribute name.

[<Interface ("org.freedesktop.DBus.Properties")>]
type IProperties =
    abstract member Get : string -> string -> obj 
    abstract member Set : string -> string -> obj -> unit
    abstract member GetAll : string -> IDictionary<string,obj>

The obligatory interface definiton for accessing properties, I’m not really worried about signals at this point. As an aside, why is this interface not built into DBus bindings, it wasn’t when I worked with it in Java either?

I was hoping to get some use out of F#’s built in map types but obviously we’re limited to the intersection of types available in all IL languages when working with dbus-sharp, hence the IDictionary. If you’re wondering, obj is just a type definition equivalent to System.Object.

[<Interface (IFDEV)>]
type IBluetoothDev =
    abstract Disconnect : unit -> unit
    abstract Connect : unit -> unit
    abstract ConnectProfile : string -> unit
    abstract DisconnectProfile : string -> unit
    abstract Pair : unit -> unit
    abstract CancelPairing : unit -> unit
    abstract Address : string with get
    abstract Name : string with get
    // Other Properties ommited for brevity

Spot the Literal?

Yes, I skipped the adapter since it wasn’t interesting and moved straight to the Device API. In yesterday and today’s examples there were a few unit type declarations, the concept is identical to void in other C-family languages.

At this point, we can actually grab a remote object and do some work with it. Unfortunately accessing properties doesn’t work the way I was expecting them to, generating a DBus error stating that we attempted to invoke a method which does not exist.

With some testing, in the form of stripping the MediaPlayer2 interface definition out of Banshee and making a small self contained example, it can be shown that DBus objects exported from IL langauges have accessible properties. I hypothesize that since the exported objects contain methods covered by the property system, that these methods are exported (although they do not appear in introspection data) and grant access to the properties.

The solution is nothing fancy, below is a slightly mangled decorator pattern covering the properties with the correct DBus call. In lieu of the problem being addressed in dbus-sharp, a reflecting proxy (I have only used these indirectly) may save hand-rolling a decorator for each interface that is needed.

type DevProxy (bus: Connection, name: string, obj: ObjectPath) =
    let dev = bus.GetObject<IBluetoothDev>(name, obj)
    let props = bus.GetObject<IProperties>(name, obj)
    interface IBluetoothDev with
        member x.Disconnect () = dev.Disconnect ()
        member x.Connect () = dev.Connect ()
        member x.ConnectProfile y = dev.ConnectProfile y
        member x.DisconnectProfile y = dev.DisconnectProfile y
        member x.Pair () = dev.Pair ()
        member x.CancelPairing () = dev.CancelPairing ()
        member x.Address = (props.Get IFDEV "Address").ToString ()
        member x.Name = (props.Get IFDEV "Name").ToString ()

printfn "Hello, Bluez"

let bus  = Bus.System
let name = "org.bluez"
let mac  = "<Bluetooth MAC Here>"
let path = new ObjectPath ("/org/bluez/hci0/dev_" + mac)
printfn "Requesting Remote Objects"
let dev = DevProxy(bus, name, path) :> IBluetoothDev
let props = bus.GetObject<IProperties>(name, path)
printfn "Got Remote Objects"

try
    printfn "Device: [%s] - %s" dev.Address dev.Name
with
    | ex -> printfn "%s\n%s" ex.Message ex.StackTrace
    
dev.Connect ()
dev.Disconnect ()

printfn "Bluez, Goodbye"

Finally, we grab a handle to the paired device and introduce ourselves by prodding it in a couple of places, like a teenager on IRC. Tomorrow we’ll most likely investigate some bluez interfaces available through the session bus.

Parting Concern

Prototyping like this is great, quickly displaying and importantly testing functionality that will be used in the eventual concrete implementation. But, what of that key word; one difficulty I encountered while working with a similar Java system was that since functionality depended on variables originating from a live system I found it impossible to rigourously test the system, relying on an ad-hoc approach not much different from the example above. Not only did this reduce my confidence, it also stopped me documenting the steps necessary to correct the issues. Comments and advice on how to combat this problem are welcome and appreciated.