Connecting to a robot ===================================== Code that drives a robot runs in one of two places: on your own machine, reaching the robot over the network, or on the robot itself, as a program the robot executes. Both use the same :class:`~pyniryo_nate.client.Nate` client and the same API. What differs is where the client gets its address and its credentials. From a remote host ------------------------------------- Address and credentials ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The robot is reached by its IP address or by a name, such as the mDNS name it announces on the local network. The API is served over HTTPS on port 8443 and the event broker over MQTTS on port 8883. Logging in takes the username and password of a user of the robot, both required. There is no API key to pass instead: the client logs in with the credentials and obtains a session token on its own. .. code-block:: python :linenos: from pyniryo_nate import Nate with Nate(hostname="10.10.10.10", login=("user", "secret")) as nate: print(nate.device.get_identity()) The same client is configured from the environment when the constructor is given nothing: .. code-block:: bash export NATE_HOSTNAME=10.10.10.10 export NATE_USERNAME=user export NATE_PASSWORD=secret .. code-block:: python :linenos: from pyniryo_nate import Nate with Nate() as nate: print(nate.device.get_identity()) .. warning:: An environment variable takes precedence over the constructor argument. With ``NATE_HOSTNAME`` set, ``Nate(hostname="10.10.10.11")`` still connects to the robot named by the variable. A process that drives several robots must leave ``NATE_HOSTNAME`` unset and pass each address to the constructor. What the constructor does ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The constructor returns a client that is ready to use, or raises. Before it returns it: 1. opens a TLS connection and checks that the robot is a Niryo robot, and the expected one when a device ID is given, see :ref:`connecting-trust`; 2. logs in and obtains a session token; 3. checks that the identity the API reports matches the certificate; 4. connects to the event broker, which carries the robot state and the subscriptions. The session token expires after ``NATE_TOKEN_VALIDITY``, one day by default. The client renews it in the background for as long as it lives, so a long-running process never has to log in again. .. _connecting-trust: Trust and identity ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Every connection is encrypted. The robot presents a certificate signed by the Niryo certificate authority, whose root ships with the package, so trusting a Niryo robot needs no setup. A robot whose certificate is signed by anyone else is refused with :class:`~pyniryo_nate.exceptions.TlsError`. To trust another root as well, point ``NATE_CA_FILE`` at a PEM file holding it. **Optional**: The robot is identified by the device ID written in its certificate. Pass the expected one and any other robot at that address is refused before a credential is sent: .. code-block:: python :linenos: nate = Nate(hostname="10.10.10.10", login=("user", "secret"), device_id="a1b2c3d") Without an expected device ID, the first robot reached binds the client for its lifetime: every later connection, the event broker included, must come from that same robot. :meth:`~pyniryo_nate.components.Device.get_identity` reports the device ID of the robot in hand, to pin it the next time. Errors ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The constructor raises: - ``ValueError`` when the username or the password is missing, before anything is sent; - :class:`~pyniryo_nate.exceptions.TlsError` when the certificate is not trusted or names another robot than the expected one; - :class:`~pyniryo_nate.exceptions.TransportError` when the robot cannot be reached, after the retries configured by ``NATE_RETRY`` and ``NATE_RETRY_DELAY_FACTOR``, or does not answer within ``NATE_HTTP_TIMEOUT``; - :class:`~pyniryo_nate.exceptions.ClientError` with a ``status_code`` of 401 when the robot refuses the credentials. .. code-block:: python :linenos: from pyniryo_nate import Nate from pyniryo_nate.exceptions import ClientError, TlsError, TransportError try: nate = Nate(hostname="10.10.10.10", login=("user", "secret")) except TlsError as error: print(f"not the robot expected, or not a Niryo robot: {error}") except TransportError as error: print(f"robot unreachable: {error}") except ClientError as error: if error.status_code == 401: print("wrong username or password") else: raise Environment variables ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. list-table:: :header-rows: 1 :widths: auto * - Variable - Meaning * - ``NATE_HOSTNAME`` - Address of the robot, default ``localhost``. * - ``NATE_USERNAME``, ``NATE_PASSWORD`` - Credentials of a user of the robot. * - ``NATE_DEVICE_ID`` - Device ID of the expected robot. * - ``NATE_CA_FILE`` - PEM file holding extra roots to trust, on top of the Niryo root shipped with the package. * - ``NATE_HTTP_PORT``, ``NATE_MQTT_PORT`` - Ports of the API and of the event broker, default ``8443`` and ``8883``. * - ``NATE_HTTP_TIMEOUT`` - Seconds to wait for an API answer, default ``10``. * - ``NATE_RETRY``, ``NATE_RETRY_DELAY_FACTOR`` - How many times a failed connection is retried, default ``5``, and the factor applied to the delay between two retries, default ``0.5``. * - ``NATE_TOKEN_VALIDITY`` - Lifetime of the session token, default ``1d``. A number followed by ``d``, ``h`` or ``s``. As a program run by the robot ------------------------------------- A program is a Python file that the robot stores and runs. Programs are run from Niryo Studio, where the Programming panel of a project holds them and Run starts one on the robot. They can also be uploaded and started from the client, with :meth:`~pyniryo_nate.components.Programs.create` and :meth:`~pyniryo_nate.components.Programs.execute`. ``pyniryo-nate`` is installed in the runtime that executes them. Connecting from the program ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Inside the program, the hostname and login provided in the constructor are ignored: .. code-block:: python :linenos: from pyniryo_nate import Nate from pyniryo_nate.models import Joints with Nate() as nate: for _ in range(3): nate.robot.move(Joints(0, 0, 0, 0, 0, 0)).wait() nate.robot.move(Joints(0.5, 0, 0, 0, 0, 0)).wait() The robot provides everything the client needs: its own address and ports, the certificate to trust, and an execution token that identifies this run. The client sends that token as its bearer credential on both transports, with no login and no renewal. No username or password is involved, and the program must not carry any. The token is only valid for that run and only while it is running or paused, so a program cannot act on the robot after it has ended, and the file holds nothing worth protecting. With the token, the moves of the program go through in automatic mode, where a remote client is refused, see :doc:`/api/direct_control`. .. warning:: An execution started from the client with an ``environment`` overrides the variables the robot sets for the program, the ``NATE_*`` ones included. ``NATE_USERNAME`` and ``NATE_PASSWORD`` both set there are the one thing that takes precedence over the execution token: the program then logs in as that user, which is how it is redirected to another robot, and only on purpose. Running the same file from your machine ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ A program built with ``Nate()`` runs unchanged on your machine once ``NATE_HOSTNAME``, ``NATE_USERNAME`` and ``NATE_PASSWORD`` are set, which is how a program is developed before being uploaded. Run that way it is a remote client like any other: its moves need the key selector on manual, and it holds nothing.