aldebaran/libqi-python

RuntimeError: disconnected (NAOqi 2.9)

Closed this issue · 3 comments

Description:
Hi, I'm trying to connect to a Pepper running on NAOqi 2.9.5 through libqi-python. I've read that Pepper 2.9 generally isn't supported but I've seen some indications that it might be possible (#7, [2]). I tried to use the authentication_with_application.py example as the starting point (see the minimised version further down below) but I can't seem to get the connection to work. With port 9503 I at least don't get a connection error outright but the program still crashes and I get:

[W] 1707753982.910001 6507 qi.FutureSync: Error in future on destruction: 'disconnected' - continuing stack unwinding...

Would this even be possible with libqi-python? #7 makes it look like it could be, but since it's almost 5 years old, the API may have changed). If it's possible, what am I doing wrong? I'm using the same credentials as when connecting via SSH (which works fine). Any advice/hints would be greatly appreciated.

Setup:

  • OS: Ubuntu 22.04.3 LTS
  • Hardware: Pepper 1.8
  • NAOqi: 2.9.5.172
  • LibQI-python: 3.1.1 (compiled from source) and 3.1.4 (taken from releases)
  • Python: 3.10.12

MRE:

import qi
import sys 

class Authenticator:

    def __init__(self, username, password):
        self.username = username
        self.password = password

    # This method is expected by libqi and must return a dictionary containing
    # login information with the keys 'user' and 'token'.
    def initialAuthData(self):
        return {'user': self.username, 'token': self.password}


class AuthenticatorFactory:

    def __init__(self, username, password):
        self.username = username
        self.password = password

    # This method is expected by libqi and must return an object with at least
    # the `initialAuthData` method.
    def newAuthenticator(self):
        return Authenticator(self.username, self.password)

# Connect to the robot fails at app.start() => RuntimeError: disconnected
app = qi.Application(sys.argv, url="tcp://192.168.1.59:9503")
logins = ("nao", "OMITTED")
factory = AuthenticatorFactory(*logins)
app.session.setClientAuthenticatorFactory(factory)
app.start()
print("started")

# This doesn't work either => RuntimeError: disconnected
# session = qi.Session()
# logins = ("nao", "OMITTED")
# factory = AuthenticatorFactory(*logins)
# session.setClientAuthenticatorFactory(factory)
# session.connect("tcp://192.168.1.59:9503")

tts = app.session.service("ALTextToSpeech")
tts.say("Hello there!")

Logs:

Terminal output:
ost@ost:~$ python3 test.py --qi-log-level verbose
[W] 1707753982.876091 6507 qi.path.sdklayout: No Application was created, trying to deduce paths
[V] 1707753982.876339 6507 qi.applicationsession: Connect URL is now: tcp://192.168.1.59:9503
[V] 1707753982.876347 6507 qi.applicationsession: Listen URLs are now: tcp://127.0.0.1:0
[V] 1707753982.876671 6507 qi.eventloop: start: thread count limits: initial (minimum, maximum) before any adjustment = (-1, 0)
[V] 1707753982.876729 6507 qi.eventloop: start: thread count limits: min <- 4 (read from environment variable QI_EVENTLOOP_MIN_THREADS, with default 4)
[V] 1707753982.876755 6507 qi.eventloop: start: thread count limits: max <- 150 (read from environment variable QI_EVENTLOOP_MAX_THREADS, with default 150)
[V] 1707753982.876758 6507 qi.eventloop: start: thread count limits: final (minimum, maximum) after potential adjustment = (4, 150)
[V] 1707753982.876760 6507 qi.eventloop: start: number of threads that will be launched = 4 (between (min, max) = (4, 150))
[V] 1707753982.877437 6507 qitype.type: Shared pointer to unknown type N2qi13MessageSocketE, assuming object not yet registered
[V] 1707753982.877517 6507 qitype.type: Shared pointer to unknown type N2qi13MessageSocketE, assuming object not yet registered
[V] 1707753982.877532 6507 qitype.type: Shared pointer to unknown type N2qi13MessageSocketE, assuming object not yet registered
[V] 1707753982.877536 6507 qitype.type: Shared pointer to unknown type N2qi13MessageSocketE, assuming object not yet registered
[V] 1707753982.877539 6507 qitype.type: Shared pointer to unknown type N2qi13MessageSocketE, assuming object not yet registered
[V] 1707753982.877760 6507 qitype.type: Shared pointer to unknown type N2qi13MessageSocketE, assuming object not yet registered
[V] 1707753982.877830 6507 qitype.type: registerType: access to type factory before registration detected for type N2qi16ServiceDirectoryE
[V] 1707753982.877872 6507 qitype.type: registerType: access to type factory before registration detected for type N2qi6FutureImEE
[V] 1707753982.878001 6507 qitype.type: registerType: access to type factory before registration detected for type N2qi6FutureINS_8AnyValueEEE
[V] 1707753982.878212 6507 qitype.type: registerType: access to type factory before registration detected for type N2qi11BoundObjectE
[V] 1707753982.878368 6507 qitype.type: registerType: access to type factory before registration detected for type N2qi10FutureSyncINS_8AnyValueEEE
[V] 1707753982.878471 6507 qitype.type: registerType: access to type factory before registration detected for type N2qi7PromiseINS_8AnyValueEEE
[V] 1707753982.878734 6507 qi.python.object: Registration of method __class__ is ignored as it is private.
[V] 1707753982.878771 6507 qi.python.object: Registration of method __delattr__ is ignored as it is private.
[V] 1707753982.878803 6507 qi.python.object: Registration of method __dir__ is ignored as it is private.
[V] 1707753982.878806 6507 qi.python.object: The object attribute '__doc__' has value 'None', and will therefore be ignored.
[V] 1707753982.878810 6507 qi.python.object: Registration of method __eq__ is ignored as it is private.
[V] 1707753982.878814 6507 qi.python.object: Registration of method __format__ is ignored as it is private.
[V] 1707753982.878817 6507 qi.python.object: Registration of method __ge__ is ignored as it is private.
[V] 1707753982.878821 6507 qi.python.object: Registration of method __getattribute__ is ignored as it is private.
[V] 1707753982.878824 6507 qi.python.object: Registration of method __gt__ is ignored as it is private.
[V] 1707753982.878828 6507 qi.python.object: Registration of method __hash__ is ignored as it is private.
[V] 1707753982.878834 6507 qi.python.object: Registration of method __init__ is ignored as it is private.
[V] 1707753982.878838 6507 qi.python.object: Registration of method __init_subclass__ is ignored as it is private.
[V] 1707753982.878841 6507 qi.python.object: Registration of method __le__ is ignored as it is private.
[V] 1707753982.878845 6507 qi.python.object: Registration of method __lt__ is ignored as it is private.
[V] 1707753982.878850 6507 qi.python.object: Registration of method __ne__ is ignored as it is private.
[V] 1707753982.878853 6507 qi.python.object: Registration of method __new__ is ignored as it is private.
[V] 1707753982.878856 6507 qi.python.object: Registration of method __reduce__ is ignored as it is private.
[V] 1707753982.878860 6507 qi.python.object: Registration of method __reduce_ex__ is ignored as it is private.
[V] 1707753982.878862 6507 qi.python.object: Registration of method __repr__ is ignored as it is private.
[V] 1707753982.878866 6507 qi.python.object: Registration of method __setattr__ is ignored as it is private.
[V] 1707753982.878869 6507 qi.python.object: Registration of method __sizeof__ is ignored as it is private.
[V] 1707753982.878872 6507 qi.python.object: Registration of method __str__ is ignored as it is private.
[V] 1707753982.878875 6507 qi.python.object: Registration of method __subclasshook__ is ignored as it is private.
[V] 1707753982.878878 6507 qi.python.object: The object attribute '__weakref__' has value 'None', and will therefore be ignored.
[V] 1707753982.879015 6507 qi.python.object: Registration of method newAuthenticator with signature () -> m.
[V] 1707753982.879175 6507 qi.python.object: Registration of method __class__ is ignored as it is private.
[V] 1707753982.879210 6507 qi.python.object: Registration of method __delattr__ is ignored as it is private.
[V] 1707753982.879218 6507 qi.python.object: Registration of method __dir__ is ignored as it is private.
[V] 1707753982.879220 6507 qi.python.object: The object attribute '__doc__' has value 'None', and will therefore be ignored.
[V] 1707753982.879224 6507 qi.python.object: Registration of method __eq__ is ignored as it is private.
[V] 1707753982.879227 6507 qi.python.object: Registration of method __format__ is ignored as it is private.
[V] 1707753982.879230 6507 qi.python.object: Registration of method __ge__ is ignored as it is private.
[V] 1707753982.879238 6507 qi.python.object: Registration of method __getattribute__ is ignored as it is private.
[V] 1707753982.879241 6507 qi.python.object: Registration of method __gt__ is ignored as it is private.
[V] 1707753982.879244 6507 qi.python.object: Registration of method __hash__ is ignored as it is private.
[V] 1707753982.879247 6507 qi.python.object: Registration of method __init__ is ignored as it is private.
[V] 1707753982.879251 6507 qi.python.object: Registration of method __init_subclass__ is ignored as it is private.
[V] 1707753982.879254 6507 qi.python.object: Registration of method __le__ is ignored as it is private.
[V] 1707753982.879258 6507 qi.python.object: Registration of method __lt__ is ignored as it is private.
[V] 1707753982.879261 6507 qi.python.object: Registration of method __ne__ is ignored as it is private.
[V] 1707753982.879265 6507 qi.python.object: Registration of method __new__ is ignored as it is private.
[V] 1707753982.879271 6507 qi.python.object: Registration of method __reduce__ is ignored as it is private.
[V] 1707753982.879280 6507 qi.python.object: Registration of method __reduce_ex__ is ignored as it is private.
[V] 1707753982.879284 6507 qi.python.object: Registration of method __repr__ is ignored as it is private.
[V] 1707753982.879287 6507 qi.python.object: Registration of method __setattr__ is ignored as it is private.
[V] 1707753982.879290 6507 qi.python.object: Registration of method __sizeof__ is ignored as it is private.
[V] 1707753982.879315 6507 qi.python.object: Registration of method __str__ is ignored as it is private.
[V] 1707753982.879321 6507 qi.python.object: Registration of method __subclasshook__ is ignored as it is private.
[V] 1707753982.879326 6507 qi.python.object: Registration of method __weakref__ is ignored as it is private.
[V] 1707753982.879380 6507 qi.python.object: Registration of method newAuthenticator with signature () -> m.
[V] 1707753982.879816 6507 qi.eventloop: start: thread count limits: initial (minimum, maximum) before any adjustment = (1, 1)
[V] 1707753982.879963 6507 qi.eventloop: start: thread count limits: final (minimum, maximum) after potential adjustment = (1, 1)
[V] 1707753982.879977 6507 qi.eventloop: start: number of threads that will be launched = 1 (between (min, max) = (1, 1))
[V] 1707753982.880133 6507 qimessaging.messagesocket: (ResolverUrlList)0x5586f8db1ec0: Trying to connect to 192.168.1.59:9503
[V] 1707753982.888069 6508 qitype.dynamicobject: Return signature might be incorrect depending on the value, from m to o
[V] 1707753982.888491 6511 qi.python.object: Registration of method __class__ is ignored as it is private.
[V] 1707753982.888648 6511 qi.python.object: Registration of method __delattr__ is ignored as it is private.
[V] 1707753982.888663 6511 qi.python.object: Registration of method __dir__ is ignored as it is private.
[V] 1707753982.888667 6511 qi.python.object: The object attribute '__doc__' has value 'None', and will therefore be ignored.
[V] 1707753982.888672 6511 qi.python.object: Registration of method __eq__ is ignored as it is private.
[V] 1707753982.888677 6511 qi.python.object: Registration of method __format__ is ignored as it is private.
[V] 1707753982.888682 6511 qi.python.object: Registration of method __ge__ is ignored as it is private.
[V] 1707753982.888687 6511 qi.python.object: Registration of method __getattribute__ is ignored as it is private.
[V] 1707753982.888691 6511 qi.python.object: Registration of method __gt__ is ignored as it is private.
[V] 1707753982.888694 6511 qi.python.object: Registration of method __hash__ is ignored as it is private.
[V] 1707753982.888699 6511 qi.python.object: Registration of method __init__ is ignored as it is private.
[V] 1707753982.888705 6511 qi.python.object: Registration of method __init_subclass__ is ignored as it is private.
[V] 1707753982.888716 6511 qi.python.object: Registration of method __le__ is ignored as it is private.
[V] 1707753982.888723 6511 qi.python.object: Registration of method __lt__ is ignored as it is private.
[V] 1707753982.888732 6511 qi.python.object: Registration of method __ne__ is ignored as it is private.
[V] 1707753982.888767 6511 qi.python.object: Registration of method __new__ is ignored as it is private.
[V] 1707753982.888797 6511 qi.python.object: Registration of method __reduce__ is ignored as it is private.
[V] 1707753982.888804 6511 qi.python.object: Registration of method __reduce_ex__ is ignored as it is private.
[V] 1707753982.888809 6511 qi.python.object: Registration of method __repr__ is ignored as it is private.
[V] 1707753982.888812 6511 qi.python.object: Registration of method __setattr__ is ignored as it is private.
[V] 1707753982.888816 6511 qi.python.object: Registration of method __sizeof__ is ignored as it is private.
[V] 1707753982.888820 6511 qi.python.object: Registration of method __str__ is ignored as it is private.
[V] 1707753982.888824 6511 qi.python.object: Registration of method __subclasshook__ is ignored as it is private.
[V] 1707753982.888828 6511 qi.python.object: The object attribute '__weakref__' has value 'None', and will therefore be ignored.
[V] 1707753982.888946 6511 qi.python.object: Registration of method initialAuthData with signature () -> m.
[V] 1707753982.889346 6508 qitype.dynamicobject: Return signature might be incorrect depending on the value, from m to {sm}
[V] 1707753982.909155 6509 qimessaging.server: Closing server...
[W] 1707753982.910001 6507 qi.FutureSync: Error in future on destruction: 'disconnected' - continuing stack unwinding...
Traceback (most recent call last):
  File "/home/ost/test.py", line 32, in <module>
    app.start()
RuntimeError: disconnected
Session meta object:
{
  "description": "",
  "methods": {
    "100": {
      "description": "",
      "name": "connect",
      "parameters": [],
      "parametersSignature": "()",
      "returnDescription": "",
      "returnSignature": "v",
      "uid": 100
    },
    "101": {
      "description": "",
      "name": "connect",
      "parameters": [],
      "parametersSignature": "(s)",
      "returnDescription": "",
      "returnSignature": "v",
      "uid": 101
    },
    "102": {
      "description": "",
      "name": "isConnected",
      "parameters": [],
      "parametersSignature": "()",
      "returnDescription": "",
      "returnSignature": "b",
      "uid": 102
    },
    "103": {
      "description": "",
      "name": "url",
      "parameters": [],
      "parametersSignature": "()",
      "returnDescription": "",
      "returnSignature": "s",
      "uid": 103
    },
    "104": {
      "description": "",
      "name": "services",
      "parameters": [],
      "parametersSignature": "(i)",
      "returnDescription": "",
      "returnSignature": "[(sIsI[s]ss)<ServiceInfo,name,serviceId,machineId,processId,endpoints,sessionId,objectUid>]",
      "uid": 104
    },
    "105": {
      "description": "",
      "name": "services",
      "parameters": [],
      "parametersSignature": "()",
      "returnDescription": "",
      "returnSignature": "[(sIsI[s]ss)<ServiceInfo,name,serviceId,machineId,processId,endpoints,sessionId,objectUid>]",
      "uid": 105
    },
    "106": {
      "description": "",
      "name": "service",
      "parameters": [],
      "parametersSignature": "(ss)",
      "returnDescription": "",
      "returnSignature": "o",
      "uid": 106
    },
    "107": {
      "description": "",
      "name": "service",
      "parameters": [],
      "parametersSignature": "(ssL)",
      "returnDescription": "",
      "returnSignature": "o",
      "uid": 107
    },
    "108": {
      "description": "",
      "name": "service",
      "parameters": [],
      "parametersSignature": "(s)",
      "returnDescription": "",
      "returnSignature": "o",
      "uid": 108
    },
    "109": {
      "description": "",
      "name": "service",
      "parameters": [],
      "parametersSignature": "(sL)",
      "returnDescription": "",
      "returnSignature": "o",
      "uid": 109
    },
    "110": {
      "description": "",
      "name": "listen",
      "parameters": [],
      "parametersSignature": "()",
      "returnDescription": "",
      "returnSignature": "v",
      "uid": 110
    },
    "111": {
      "description": "",
      "name": "listen",
      "parameters": [],
      "parametersSignature": "(s)",
      "returnDescription": "",
      "returnSignature": "v",
      "uid": 111
    },
    "112": {
      "description": "",
      "name": "listen",
      "parameters": [],
      "parametersSignature": "([s])",
      "returnDescription": "",
      "returnSignature": "v",
      "uid": 112
    },
    "113": {
      "description": "",
      "name": "endpoints",
      "parameters": [],
      "parametersSignature": "()",
      "returnDescription": "",
      "returnSignature": "[s]",
      "uid": 113
    },
    "114": {
      "description": "",
      "name": "close",
      "parameters": [],
      "parametersSignature": "()",
      "returnDescription": "",
      "returnSignature": "v",
      "uid": 114
    },
    "115": {
      "description": "",
      "name": "listenStandalone",
      "parameters": [],
      "parametersSignature": "()",
      "returnDescription": "",
      "returnSignature": "v",
      "uid": 115
    },
    "116": {
      "description": "",
      "name": "listenStandalone",
      "parameters": [],
      "parametersSignature": "(s)",
      "returnDescription": "",
      "returnSignature": "v",
      "uid": 116
    },
    "117": {
      "description": "",
      "name": "listenStandalone",
      "parameters": [],
      "parametersSignature": "([s])",
      "returnDescription": "",
      "returnSignature": "v",
      "uid": 117
    },
    "118": {
      "description": "",
      "name": "registerService",
      "parameters": [],
      "parametersSignature": "(so)",
      "returnDescription": "",
      "returnSignature": "I",
      "uid": 118
    },
    "119": {
      "description": "",
      "name": "unregisterService",
      "parameters": [],
      "parametersSignature": "(I)",
      "returnDescription": "",
      "returnSignature": "v",
      "uid": 119
    },
    "120": {
      "description": "",
      "name": "loadServiceRename",
      "parameters": [],
      "parametersSignature": "m",
      "returnDescription": "",
      "returnSignature": "m",
      "uid": 120
    },
    "121": {
      "description": "",
      "name": "callModule",
      "parameters": [],
      "parametersSignature": "m",
      "returnDescription": "",
      "returnSignature": "m",
      "uid": 121
    },
    "122": {
      "description": "",
      "name": "waitForService",
      "parameters": [],
      "parametersSignature": "(sL)",
      "returnDescription": "",
      "returnSignature": "v",
      "uid": 122
    },
    "123": {
      "description": "",
      "name": "waitForService",
      "parameters": [],
      "parametersSignature": "(s)",
      "returnDescription": "",
      "returnSignature": "v",
      "uid": 123
    },
    "128": {
      "description": "",
      "name": "setClientAuthenticatorFactory",
      "parameters": [],
      "parametersSignature": "m",
      "returnDescription": "",
      "returnSignature": "m",
      "uid": 128
    },
    "80": {
      "description": "",
      "name": "isStatsEnabled",
      "parameters": [],
      "parametersSignature": "()",
      "returnDescription": "",
      "returnSignature": "b",
      "uid": 80
    },
    "81": {
      "description": "",
      "name": "enableStats",
      "parameters": [],
      "parametersSignature": "(b)",
      "returnDescription": "",
      "returnSignature": "v",
      "uid": 81
    },
    "82": {
      "description": "",
      "name": "stats",
      "parameters": [],
      "parametersSignature": "()",
      "returnDescription": "",
      "returnSignature": "{I(I(fff)<MinMaxSum,minValue,maxValue,cumulatedValue>(fff)<MinMaxSum,minValue,maxValue,cumulatedValue>(fff)<MinMaxSum,minValue,maxValue,cumulatedValue>)<MethodStatistics,count,wall,user,system>}",
      "uid": 82
    },
    "83": {
      "description": "",
      "name": "clearStats",
      "parameters": [],
      "parametersSignature": "()",
      "returnDescription": "",
      "returnSignature": "v",
      "uid": 83
    },
    "84": {
      "description": "",
      "name": "isTraceEnabled",
      "parameters": [],
      "parametersSignature": "()",
      "returnDescription": "",
      "returnSignature": "b",
      "uid": 84
    },
    "85": {
      "description": "",
      "name": "enableTrace",
      "parameters": [],
      "parametersSignature": "(b)",
      "returnDescription": "",
      "returnSignature": "v",
      "uid": 85
    }
  },
  "properties": {},
  "signals": {
    "124": {
      "name": "serviceRegistered",
      "signature": "(Is)",
      "uid": 124
    },
    "125": {
      "name": "serviceUnregistered",
      "signature": "(Is)",
      "uid": 125
    },
    "126": {
      "name": "connected",
      "signature": "()",
      "uid": 126
    },
    "127": {
      "name": "disconnected",
      "signature": "(s)",
      "uid": 127
    },
    "86": {
      "name": "traceObject",
      "signature": "((IiIm(ll)<timeval,tv_sec,tv_usec>llII)<EventTrace,id,kind,slotId,arguments,timestamp,userUsTime,systemUsTime,callerContext,calleeContext>)",
      "uid": 86
    }
  }
}

Hello @DrR955,

I think your code is correct, and the problem is the targeted address.

On Pepper, the external TCP servers expect SSL/TLS connections so that communications are secure. This means that the server socket will expect a TLS handshake request from the client. To represent this expectation in libqi, the server address URL scheme becomes tcps instead of tcp (similarly to https vs http). If you target a tcps endpoint with a tcp address, the client socket will not request a handshake, causing the server to close the socket and your code to receive this "Socket disconnected" error.

     def newAuthenticator(self):
         return Authenticator(self.username, self.password)
 
 # Connect to the robot fails at app.start() => RuntimeError: disconnected
-app = qi.Application(sys.argv, url="tcp://192.168.1.59:9503")
+app = qi.Application(sys.argv, url="tcps://192.168.1.59:9503")
 logins = ("nao", "OMITTED")
 factory = AuthenticatorFactory(*logins)
 app.session.setClientAuthenticatorFactory(factory)

Please let me know if this solves your issue.

Yes, it really works! Thank you very much!

If a follow-up question is okay with you (as this endeavour is definitely not officially endorsed): I briefly tried to retrieve all services known to me from Pepper on NAOqi 2.5 and I noticed that ALTabletService leads to a crash ("No reachable endpoint was found for this service." - probably from libqi-src/src/messaging/transportsocketcache.cpp). I tried playing around a bit with qicli, as the service is still listed there, but more than the standard point cloud animation doesn't seem to work.

Is it possible to use the old 2.5 tablet functionality (in Python) or is it no longer functional and I've only found a few remnants? Whatever the case, thanks again!

A few things have changed with NAOqi 2.9, including services architecture. External calls now go through 2 gateways that use SSL/TLS to encrypt communications:

  • A public API gateway, available on tcps://<robot_ip>:9443. This gateway exposes a subset of services that are officially supported and may be used by clients. These services are part of the QiSDK, normally used through Android applications running on the tablet as documented here. But technically these services are also available from Python, if you connect to this gateway directly.
  • A private API gateway, available on tcps://<robot_ip>:9503 as you saw. This one exposes all services running on the robot and tablet, even those that aren't supported, and those that are inaccessible externally. It includes ALTabletService, which is deprecated and should not be used anymore.

Is it possible to use the old 2.5 tablet functionality (in Python) or is it no longer functional and I've only found a few remnants?

It should not be used, and because of the new architecture, it's most likely not usable anymore. You should try using the QiSDK instead, if possible.