sidorares/dbus-native

differences python-dbus and node-dbus

Opened this issue · 17 comments

I'm not exactly sure where the best place to talk about such things is, so I guess I'll start here.

quoting http://dbus.freedesktop.org/doc/dbus-python/doc/tutorial.html

To call a method, call the method of the same name on the proxy object, passing in the interface name via the dbus_interface keyword argument:

import dbus
bus = dbus.SystemBus()
eth0 = bus.get_object('org.freedesktop.NetworkManager',
                      '/org/freedesktop/NetworkManager/Devices/eth0')
props = eth0.getProperties(dbus_interface='org.freedesktop.NetworkManager.Devices')
# props is a tuple of properties, the first of which is the object path

As a short cut, if you're going to be calling many methods with the same interface, you can construct a dbus.Interface object and call methods on that, without needing to specify the interface again:

import dbus
bus = dbus.SystemBus()
eth0 = bus.get_object('org.freedesktop.NetworkManager',
                      '/org/freedesktop/NetworkManager/Devices/eth0')
eth0_dev_iface = dbus.Interface(eth0,
    dbus_interface='org.freedesktop.NetworkManager.Devices')
props = eth0_dev_iface.getProperties()
# props is the same as before

I definitely am not really familiar with js beyond what I do for web work, so please excuse my ignorance here.

What do you think about having the equivalent of bus.get_object and passing those around instead of having to name everything at time of call (in two different variables)

I also like being able to store the result of dbus.Interface and being able to add methods to it later instead of nesting a bunch of callbacks.

Maybe this is more of a documentation problem (and my lack of true understanding here). It would be nice to see more documentation and examples.

I think we already have this - https://github.com/sidorares/node-dbus/blob/15402ff01260931e9377aabb33997f59ba51d1d7/lib/bus.js#L260-L275 ( or maybe I not understand the question correctly )

but there seems to be no way to work without it without nesting callbacks?

Here's sandeep's first try at implementing the bluez dbus API. https://github.com/sandeepmistry/noble/blob/51c0154aa97adaaf4466fb9f886072d004336027/lib/linux-bindings.js . It's just a prototype, so it's certainly not organized super well.

I wanted to be able to store the result of getInterface here: https://github.com/sandeepmistry/noble/blob/51c0154aa97adaaf4466fb9f886072d004336027/lib/linux-bindings.js#L23 to avoid another callback nesting level, but getInterface() doesn't actually return anything.

Compare that to the equivalent python dbus code here:
https://github.com/adafruit/Adafruit_Python_BluefruitLE/blob/master/Adafruit_BluefruitLE/bluez_dbus/adapter.py (where dbus_obj is the result of bus.Interface(self._bus.get_object('org.bluez', '/'), 'org.freedesktop.DBus.ObjectManager')

Obviously, sandeep's code does a bit more than simply dealing with the adapter. But this is more about the approach of working with getInterface

depends on what we want as interface: if we want to populate all the functions from introspection data then it's a callback ( need to wait result of introspection and create proxy ). If it's dumb object ( just one that captured path/object name/interface name) then no need for callback

Also potentially possible to use js Proxy object and transform 'unknown' method calls into dbus calls ( no need to callback again, 'late binding' type of call )

I think the js proxy object you suggested is how the python API works.

unfortunately older node versions would require --harmony-proxies flag.

To build libraries like bluez wrapper I think better way is to generate client code ahead of time - there is a script for that - https://github.com/sidorares/node-dbus/blob/master/bin/dbus2js.js

On that note, what is your approach to node version compatibility? I can't see a good reason to keep 0.8.x support around, but I'm not sure of anything beyond that.

I'm ok to drop 0.8 if there is serious reason to, but would like to keep support for 0.10 for some time ( and 0.10 is not very es6 friendly )

so how long do you wanna keep 0.10 around for? looks like nodesource finally makes 4.x and 5.x available even on el6 and ubuntu precise (as of about 20 hours ago or so)

Have you used the results of dbus2js to build something before? it doesn't seem to handle something like this well, as the path is encoded. I guess the code generator should turn that into a variable/property, but I wonder if it's possible to do in a generic way.

bin/dbus2js.js --bus system --service org.bluez --path /org/bluez/hci0/dev_C4_06_E7_C7_5D_5D
    property: 
 { '@': { name: 'Address', type: 's', access: 'read' } }
    property: 
 { '@': { name: 'Name', type: 's', access: 'read' } }
    property: 
 { '@': { name: 'Alias', type: 's', access: 'readwrite' } }
    property: 
 { '@': { name: 'Class', type: 'u', access: 'read' } }
    property: 
 { '@': { name: 'Appearance', type: 'q', access: 'read' } }
    property: 
 { '@': { name: 'Icon', type: 's', access: 'read' } }
    property: 
 { '@': { name: 'Paired', type: 'b', access: 'read' } }
    property: 
 { '@': { name: 'Trusted', type: 'b', access: 'readwrite' } }
    property: 
 { '@': { name: 'Blocked', type: 'b', access: 'readwrite' } }
    property: 
 { '@': { name: 'LegacyPairing', type: 'b', access: 'read' } }
    property: 
 { '@': { name: 'RSSI', type: 'n', access: 'read' } }
    property: 
 { '@': { name: 'Connected', type: 'b', access: 'read' } }
    property: 
 { '@': { name: 'UUIDs', type: 'as', access: 'read' } }
    property: 
 { '@': { name: 'Modalias', type: 's', access: 'read' } }
    property: 
 { '@': { name: 'Adapter', type: 'o', access: 'read' } }
    property: 
 { '@': { name: 'ManufacturerData', type: 'a{qv}', access: 'read' } }
    property: 
 { '@': { name: 'ServiceData', type: 'a{sv}', access: 'read' } }
    property: 
 { '@': { name: 'TxPower', type: 'n', access: 'read' } }
    property: 
 { '@': { name: 'GattServices', type: 'ao', access: 'read' } }
module.exports['org.freedesktop.DBus.Introspectable'] = function(bus) {
    this.addListener = this.on = function(signame, callback) {
        bus.addMatch('type=\'signal\',member=\'' + signame + '\'', function(err, result) {
            if (err) throw new Error(err);
        });
        var signalFullName = bus.mangle('/org/bluez/hci0/dev_C4_06_E7_C7_5D_5D', 'org.freedesktop.DBus.Introspectable', signame);
        bus.signals.on(signalFullName, function(messageBody) {
             callback.apply(null, messageBody);
        });
    };
    this.Introspect = function(callback) {
        bus.invoke({
            destination: 'org.bluez',
            path: '/org/bluez/hci0/dev_C4_06_E7_C7_5D_5D',
            interface: 'org.freedesktop.DBus.Introspectable',
            member: 'Introspect',
        }, callback);
    };
}
module.exports['org.bluez.Device1'] = function(bus) {
    this.addListener = this.on = function(signame, callback) {
        bus.addMatch('type=\'signal\',member=\'' + signame + '\'', function(err, result) {
            if (err) throw new Error(err);
        });
        var signalFullName = bus.mangle('/org/bluez/hci0/dev_C4_06_E7_C7_5D_5D', 'org.bluez.Device1', signame);
        bus.signals.on(signalFullName, function(messageBody) {
             callback.apply(null, messageBody);
        });
    };
    this.Disconnect = function(callback) {
        bus.invoke({
            destination: 'org.bluez',
            path: '/org/bluez/hci0/dev_C4_06_E7_C7_5D_5D',
            interface: 'org.bluez.Device1',
            member: 'Disconnect',
        }, callback);
    };
    this.Connect = function(callback) {
        bus.invoke({
            destination: 'org.bluez',
            path: '/org/bluez/hci0/dev_C4_06_E7_C7_5D_5D',
            interface: 'org.bluez.Device1',
            member: 'Connect',
        }, callback);
    };
    this.ConnectProfile = function(UUID, callback) {
        bus.invoke({
            destination: 'org.bluez',
            path: '/org/bluez/hci0/dev_C4_06_E7_C7_5D_5D',
            interface: 'org.bluez.Device1',
            member: 'ConnectProfile',
            body: [UUID], 
            signature: 's',
        }, callback);
    };
    this.DisconnectProfile = function(UUID, callback) {
        bus.invoke({
            destination: 'org.bluez',
            path: '/org/bluez/hci0/dev_C4_06_E7_C7_5D_5D',
            interface: 'org.bluez.Device1',
            member: 'DisconnectProfile',
            body: [UUID], 
            signature: 's',
        }, callback);
    };
    this.Pair = function(callback) {
        bus.invoke({
            destination: 'org.bluez',
            path: '/org/bluez/hci0/dev_C4_06_E7_C7_5D_5D',
            interface: 'org.bluez.Device1',
            member: 'Pair',
        }, callback);
    };
    this.CancelPairing = function(callback) {
        bus.invoke({
            destination: 'org.bluez',
            path: '/org/bluez/hci0/dev_C4_06_E7_C7_5D_5D',
            interface: 'org.bluez.Device1',
            member: 'CancelPairing',
        }, callback);
    };
}
module.exports['org.freedesktop.DBus.Properties'] = function(bus) {
    this.addListener = this.on = function(signame, callback) {
        bus.addMatch('type=\'signal\',member=\'' + signame + '\'', function(err, result) {
            if (err) throw new Error(err);
        });
        var signalFullName = bus.mangle('/org/bluez/hci0/dev_C4_06_E7_C7_5D_5D', 'org.freedesktop.DBus.Properties', signame);
        bus.signals.on(signalFullName, function(messageBody) {
             callback.apply(null, messageBody);
        });
    };
    this.Get = function(interface, name, callback) {
        bus.invoke({
            destination: 'org.bluez',
            path: '/org/bluez/hci0/dev_C4_06_E7_C7_5D_5D',
            interface: 'org.freedesktop.DBus.Properties',
            member: 'Get',
            body: [interface, name], 
            signature: 'ss',
        }, callback);
    };
    this.Set = function(interface, name, value, callback) {
        bus.invoke({
            destination: 'org.bluez',
            path: '/org/bluez/hci0/dev_C4_06_E7_C7_5D_5D',
            interface: 'org.freedesktop.DBus.Properties',
            member: 'Set',
            body: [interface, name, value], 
            signature: 'ssv',
        }, callback);
    };
    this.GetAll = function(interface, callback) {
        bus.invoke({
            destination: 'org.bluez',
            path: '/org/bluez/hci0/dev_C4_06_E7_C7_5D_5D',
            interface: 'org.freedesktop.DBus.Properties',
            member: 'GetAll',
            body: [interface], 
            signature: 's',
        }, callback);
    };
}

not a lot, but yes - https://github.com/sidorares/node-gday/blob/master/lib/index.js

probably need to add 'specify what should go as input parameter' functionality

so how long do you wanna keep 0.10 around for
probably until 6.0

re Proxy vs async Introspection results: do you think we should keep both? or you'd prefer to see getInterface() to return proxy object?

I'm also thinking about Promise api in addition ( or instead of ) errbacks when async/await is available ( for methods and properties )

probably need to add 'specify what should go as input parameter' functionality

Not just that, but also set some properties so stuff like destination: 'org.bluez' is instead destination: this._serviceName or something to that effect.

I should have also asked about how long you plan on keeping 0.12.x around too.

about proxy vs async.. I feel like I'm not the right person to answer this. My judgment is too clouded by not working enough with js, and spending most of my time with languages like ruby, php, and python (and a little C/C++).

I should have also asked about how long you plan on keeping 0.12.x around too.

as long as node-abstractsocket supports it

definitely different than my feeling of js without es6! I'm itching to move to any node version that supports es6 modules out of the box. Coding js feels without es6 feels like coding php 4.x + first class functions.

I was thinking about this module as a relatively low level layer, maybe make dbus2js script to generate whatever modern es6/7 flavor user wants ( Promise/generators/async-await based )