How to pass a dictionary that maps strings to structures to a d-bus method with busctl?

1.3k views Asked by At

I have a D-Bus method that receives as input a dictionary that maps strings to structures:

<method name="myMethod">
  <arg name="input_dict" type="a{s(ss)}" direction="in"/>
  ...
</method>

I have tried to invoke such method using busctl without success. So far I've tried using the following arguments:

busctl call ${SERVICE} ${OBJECT} ${INTERFACE} myMethod "a{s(ss)}" 1 "str1" \( "str2" "str3" \)
busctl call ${SERVICE} ${OBJECT} ${INTERFACE} myMethod "a{s(ss)}" 1 str1 str2 str3
busctl call ${SERVICE} ${OBJECT} ${INTERFACE} myMethod "a{s(ss)}" str1 str2 str3
busctl call ${SERVICE} ${OBJECT} ${INTERFACE} myMethod "a{s(ss)}" 1 str1 '(ss)' str2 str3
busctl call ${SERVICE} ${OBJECT} ${INTERFACE} myMethod "a{s(ss)}" 1 str1 \(ss\) str2 str3
busctl call ${SERVICE} ${OBJECT} ${INTERFACE} myMethod "a{s(ss)}" 1 str1 "ss" str2 str3
busctl call ${SERVICE} ${OBJECT} ${INTERFACE} myMethod "a{s(ss)}" 1 str1 \"ss\" str2 str3
busctl call ${SERVICE} ${OBJECT} ${INTERFACE} myMethod "a{s(ss)}" 1 str1 \(str2 str3\)
busctl call ${SERVICE} ${OBJECT} ${INTERFACE} myMethod "a{s(ss)}" 1 str1 \(ss\) \(str2 str3\)

Depending on the call, I'm getting different errors:

No such method 'WakeUp' in interface 'com.verisure.cuxscored.DECT' at object path '/com/verisure/cuxscored/dect' (signature 'a{s(ss)}')

Too many parameters for signature.

Therefore, my question is: How can I pass the arguments to myMethod when using busctl?

EDIT

Thanks to ukBaz's answer, I got an insight. I have available a getter method as well, which I invoke previously, and I get a result a little bit different than ukBaz's:

<method name="myGetMethod">
  <arg name="output_dict" type="a{s(ss)}" direction="out"/>
  ...
</method>

Running the busctl command it gave me this:

a(s(ss)) 1 "str1" "str2" "str3"

Notice the difference with the expected solution (a(s(ss)) instead of a{s(ss)}, parentheses instead of braces). If I got it right, the output should then be interpreted as an array of structures which contain a string, and another structure with two strings.

I'm not quite sure why the result is different, but I have kind of overcome the problem using the output of the getter straight away as input of the other method:

busctl call ${SERVICE} ${OBJECT} ${INTERFACE} myMethod `busctl call ${SERVICE} ${OBJECT} ${INTERFACE} myGetMethod`

Likewise, if I use the output of the myGetMethod 'as is', the method also works:

busctl call ${SERVICE} ${OBJECT} ${INTERFACE} myMethod "a(s(ss))" 1 "str1" "str2" "str3"

So the only question remaining would be why the D-bus method returns an array of structs even when it is declared as returning a dictionary.

EDIT 2

If I run dbusctl introspect, this is what I get:

NAME            TYPE      SIGNATURE RESULT/VALUE FLAGS
${INTERFACE}    interface -         -            -
.myGetMethod    method    -         a{s(ss)}     -
.myMethod       method    a{s(ss)}  -            -
...

As an aside question, I have also tried using D-Feet, with no better result. I've called a method that returns a value of type a{s(ss)}, and D-Feet returns the following structure:

[('str1', ('str2', 'str3'))]

If I then call myMethod on D-Feet using that very same structure, I got a DBus.Error.UnknownMethod:

No '
 "such method 'myMethod' in interface '${INTERFACE}' at object "
 "path '${OBJECT}' (signature 'a{s(ss)}') (19)")

However, if I play around with the arguments, braces and so on, I got different error messages, but I'm not able to get the method properly working. So, what's the appropriate way of passing this kind of argument to myMethod when using D-Feet?

1

There are 1 answers

0
ukBaz On

I created myself a DBus service with that signature to test this:

from gi.repository import GLib
from pydbus import SessionBus

loop = GLib.MainLoop()

class MyDBUSService(object):
    """
        <node>
            <interface name='net.lew21.pydbus.ClientServerExample'>
                <method name='Hello'>
                    <arg type='a{s(ss)}' name='response' direction='in'/>
                </method>
                <method name='Echo'>
                    <arg type='a{s(ss)}' name='response' direction='out'/>
                </method>
                <method name='Quit'/>
            </interface>
        </node>
    """

    def Hello(self, data):
        """Prints the data sent to it"""
        for my_key in data:
            print(f'key: {my_key} - Value: {data[my_key]}')

    def Echo(self):
        """Sends a known string back"""
        return {"str1" : ("str2", "str3")}

    def Quit(self):
        """removes this object from the DBUS connection and exits"""
        loop.quit()

bus = SessionBus()
bus.publish("net.lew21.pydbus.ClientServerExample", MyDBUSService())
loop.run()

Which in busctl introspection gave:

$ busctl --user introspect net.lew21.pydbus.ClientServerExample /net/lew21/pydbus/ClientServerExample net.lew21.pydbus.ClientServerExample 
NAME                                 TYPE      SIGNATURE RESULT/VALUE FLAGS
.Echo                                method    -         a{s(ss)}     -
.Hello                               method    a{s(ss)}  -            -
.Quit                                method    -         -            -

The result output from Echo was:

$ busctl --user call net.lew21.pydbus.ClientServerExample /net/lew21/pydbus/ClientServerExample net.lew21.pydbus.ClientServerExample Echo
a{s(ss)} 1 "str1" "str2" "str3"

I used that output to workout how to put the values in:

$ busctl --user call net.lew21.pydbus.ClientServerExample /net/lew21/pydbus/ClientServerExample net.lew21.pydbus.ClientServerExample Hello "a{s(ss)}" 1 str str2 str3

Which gave the correct output from my Python script:

 $ python3 dbus_service_struct.py
key: str - Value: ('str2', 'str3')

So I don't know why your second attempt didn't work

And for completeness, here is an example sending two values in the dictionary:

$ busctl --user call net.lew21.pydbus.ClientServerExample /net/lew21/pydbus/ClientServerExample net.lew21.pydbus.ClientServerExample Hello 'a{s(ss)}' 2 "key1" "value1:1" "value1:2" "key2" "value2:1" "value2:1"
$ python3 dbus_service_struct.py
key: key1 - Value: ('value1:1', 'value1:2')
key: key2 - Value: ('value2:1', 'value2:1')