Skip to content
Tobias Wellnitz edited this page Aug 21, 2019 · 4 revisions

The remote control guide explains how to use Pothos to operate on a remote environment. Pothos can speak to an RPC server to instantiate and interact with remote objects. This includes creating remote processing blocks that can be connected together in a topology. The topology automatically creates input and output sockets to connect flows of blocks that cross over network boundaries.

In fact, the RPC isn't just used to distributing across hosts, but interprocess as well. The graphical tools make considerable use of this feature, as blocks are automatically instantiated in a separate process so their segfaults, aborts, and other such bugs won't cause the GUI to crash.

The proxy server is an application that must be executed on the remote host. Once the server is active, a client can connect and interact with RPC objects. To avoid crashing the server, clients connect to the server and then automatically spawn another server to actually interact with. It is the second server which is susceptible to crashing when buggy objects are instantiated.

Spawn the proxy server on the default port, bind to all interfaces:

PothosUtil --proxy-server=""

Spawn the proxy server, specify port, and bind address:

PothosUtil --proxy-server="tcp://10.10.10.1:12345"

The proxy server only works well in local network environments that are not heavily firewalled. It makes assumptions that the client can access the server on the specified port, and that server can spawn additional servers on automatically chosen ports which must also be available to the client over the network connection.

Also, there is a similar assumption for the remote topology flows. The sockets created to cross network boundaries use automatic port allocation. Further, the server socket for the data flow may be created on the flow source or flow sink side. So there really needs to be unrestricted network access between any two hosts in the data flow. This is usually not a problem for a beowulf cluster type of situation.

The proxy API and proxy environment provide an RPC-like interface. An environment is an entry point for creating proxy objects, usually for accessing classes to construct objects within that environment. An environment could be local or remote, it could be for native C++ code, or for high-level languages like Python or Java.

Users can construct and make calls on native C++ objects through the proxy interface. To make a class, class instances, and methods available, classes are exported into the proxy system using Pothos::ManagedClass. It is typical to find a ManagedClass description at the bottom of a C++ file in the pothos.

Register FooBar with ManagedClass in a C++ file:

class FooBar
{
public:

    FooBar(int bar)
    {
       _bar = bar;
    }

    void setBar(int bar)
    {
       _bar = bar;
    }

    int getBar(void)
    {
       return _bar;
    }

    int _bar;
};

#include <Pothos/Managed.hpp>
static auto managedFooBar = Pothos::ManagedClass()
    .registerConstructor<FooBar, int>()
    .registerMethod(POTHOS_FCN_TUPLE(FooBar, setBar))
    .registerMethod(POTHOS_FCN_TUPLE(FooBar, getBar))
    .registerField(POTHOS_FCN_TUPLE(FooBar, _bar))
    .commit("FooBar");

Use environment and proxy to instantiate a FooBar:

#include <Pothos/Proxy.hpp>

auto env = Pothos::ProxyEnvironment::make("managed");
auto FooBar = env->findProxy("FooBar");

//create an instance
auto fb0 = FooBar.callProxy("new");

//make function calls
fb0.callVoid("setBar", 123);
auto bar = fb0.call<int>("getBar");

Note: The "managed" string argument represents the environment for native C++ objects (hence ManagedClass). This argument is different for other language interfaces.

For more details, check out the Doxygen docs:

The remote client API connects to a proxy server and instantiates an environment that works across the network. The environment and proxy interfaces remain exactly the same.

Example creating a remote proxy environment:

#include <Pothos/Proxy.hpp>
#include <Pothos/Remote.hpp>

Pothos::RemoteClient client("tcp://serverName");
auto env = client.makeEnvironment("managed");

//...

Caveats: The arguments passed into function calls or returned by function calls must be either remote proxies in the same environment, or registered with the serialization library. We provide serialization for most native data types and containers.

Using the same generic proxy environment API, we can also interface with other languages, provided that a wrapper has been created for that language. As of writing this wiki page, there is support for Python and Java.

Example calling into Python from Pothos::Proxy. Lets call into Python's regular expression module:

auto env = Pothos::ProxyEnvironment::make("python");
auto re = env->findProxy("re");
auto m = re.callProxy("search", "(?<=abc)def", "abcdef");
auto group = m.call<std::string>("group", 0);

Language bindings are different than language support. They allow the high-level language to call into the C++ proxy API. As of writing this wiki page, there are Python bindings to make a Pythonic block API for users who want to write processing IP in python.

The FooBar example again, but from Python:

import Pothos
env = Pothos.ProxyEnvironment("managed")

FooBar = env.findProxy("FooBar")

//create an instance
fb0 = FooBar.new()

//make function calls
fb0.setBar(123)
bar = fb0.getBar()

Caveats: The arguments passed into function calls or returned by function calls must be either proxies in the same environment, or have registered conversions. We provide converters for most native data types and containers.

It should go without saying that there is an overhead associated with using the proxy interface. Even when the objects are local and native C++, it is still more work than a simple function call. The interface is a trade-off to increase flexibility at the expense of added complexity. That being said, the practical use of the proxy RPC environment stuff is to remotely deploy and configure processing IP. It does not play an active role in the data flows themselves.