2

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?

xuman
  • 133
  • 1
  • 3
  • 11
  • Your update makes it look like there is an implementation issue with the methods or how the library is guessing the types. It is probably difficult to help without knowing more about the implementation of the service. Can you do an introspect with `busctl` to see what it things the signature is of the getter and setter methods? – ukBaz Jul 16 '21 at 10:03
  • I've added the result of the introspect to the main question. – xuman Jul 16 '21 at 11:29

1 Answers1

3

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')
ukBaz
  • 6,985
  • 2
  • 8
  • 31