======================= A Minimal OPC-UA Server ======================= Let's start exploring the :code:`asyncua` package by building a minimal runnable server. Most of the hard work will be hidden behind the scene and we only need to implement the application specific code. The complete code will look like the code below. In the next sections we will look at the different parts of the code, so don't be overwhelmed by the code snippet! .. literalinclude:: ../../../examples/server-minimal.py :caption: server-minimal.py :linenos: Before we even look at the code in detail, let's try out what our server can do. Start the server in a terminal with :code:`python server-minimal.py` and open a new console. In the new console you now can use the CLI tools (see :ref:`usage/get-started/installation:command line tools`) provided by the package to explore the server. The following session gives you an idea how the tools can be used. .. code-block:: console $ uals --url=opc.tcp://127.0.0.1:4840 # List root node Browsing node i=84 at opc.tcp://127.0.0.1:4840 DisplayName NodeId BrowseName Value LocalizedText(Locale=None, Text='Objects') i=85 0:Objects LocalizedText(Locale=None, Text='Types') i=86 0:Types LocalizedText(Locale=None, Text='Views') i=87 0:Views $ uals --url=opc.tcp://127.0.0.1:4840 --nodeid i=85 # List 0:Objects Browsing node i=85 at opc.tcp://127.0.0.1:4840 DisplayName NodeId BrowseName Value LocalizedText(Locale=None, Text='Server') i=2253 0:Server LocalizedText(Locale=None, Text='Aliases') i=23470 0:Aliases LocalizedText(Locale=None, Text='MyObject') ns=2;i=1 2:MyObject LocalizedText(Locale=None, Text='ServerMethod') ns=2;s=ServerMethod 2:ServerMethod $ # In the last two lines we can see our own MyObject and ServerMethod $ # Lets read a value! $ uaread --url=opc.tcp://127.0.0.1:4840 --nodeid "ns=2;i=2" # By NodeId 7.599999999999997 $ uaread --url=opc.tcp://127.0.0.1:4840 --path "0:Objects,2:MyObject,2:MyVariable" # By BrowsePath 12.199999999999996 Seems like our server is working and we can browse through the nodes, read values, ... So let's start working through the code! Imports, Basic Setup & Configuration ==================================== In the first few lines the relevant packages, classes and methods are imported. While the :mod:`logging` module is optional (just remove all calls to the logging module), :mod:`asyncio` is required to actually run our main function. From the :mod:`asyncua` package we need the :class:`~asyncua.server.server.Server`, the :mod:`asyncua.ua` module and the :meth:`~asyncua.common.methods.uamethod` decorator. Ignore the :code:`@uamethod ...` part for the moment and jump straight into the :code:`async def main()` function: .. literalinclude:: ../../../examples/server-minimal.py :caption: server-minimal.py, Line 13 - 22 :lines: 13-22 .. todo:: The :meth:`~asyncua.server.server.Server.init` and :meth:`~asyncua.server.server.Server.set_endpoint` methods have no docstrings but are referenced in the next section. Here the server is created and initialized (:meth:`~asyncua.server.server.Server.init`). The endpoint is configured (:meth:`~asyncua.server.server.Server.set_endpoint`) and a custom namespace is registered (:meth:`~asyncua.server.server.Server.register_namespace`). It's recommended (:emphasis:`required??`) that all custom objects, variables and methods live in a separate namespace. We store its index as :code:`idx`. We'll need it later to add our custom objects to the namespace. Creating Objects and Variables ============================== .. literalinclude:: ../../../examples/server-minimal.py :caption: server-minimal.py, Line 26 - 29 :lines: 26-29 In the next lines, the custom object "MyObject" is created and a variable is added to this object. Note that by default all variables are read-only, so we need to be explicit and make it writable. The :meth:`~asyncua.common.node.Node.add_object` / :meth:`~asyncua.common.node.Node.add_variable` calls are actually just calling :meth:`~asyncua.common.manage_nodes.create_object`, respectively :meth:`~asyncua.common.manage_nodes.create_variable` internally. You can find more information on how nodes and variables are created in the API docs of these methods. Adding Methods ============== With the code we have written so far, we would already have a server which can be run and exposes some custom data. But to complete the example, we also add a method which is callable by clients: .. literalinclude:: ../../../examples/server-minimal.py :caption: server-minimal.py, Line 8 - 11 :lines: 8 - 11 .. literalinclude:: ../../../examples/server-minimal.py :caption: server-minimal.py, Line 30 - 36 :lines: 30 - 36 To do this, a function, decorated with the :meth:`~asyncua.common.methods.uamethod` decorator, is created and, similar to the objects and variables, registered on the server. It would also be possible to register a undecorated function on the server, but in this case the conversion from and to UA Variant types would be up to us. Starting the Server =================== .. literalinclude:: ../../../examples/server-minimal.py :caption: server-minimal.py, Line 37 - :lines: 37 - Using the server as a context manager with :code:`async with server: ...` allows us to hide starting and shutting down the server nicely. In order to keep the server alive a endless loop must be present. In this example the loop is also used to periodically update the variable in our custom object. Now that we have a working server, let's go on and write :ref:`usage/get-started/minimal-client:a minimal opc-ua client`!