Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to use RPC with Volttron

I would like to use RPC calls in my volttron application but I am unable to get any call working. All calls fail with a "no route to host" error

<stderr> ERROR: Unreachable: VIP error (113): No route to host: rpcserver.agent_1

Essentially there are two agents, a "server" agent that exports a RPC procedure, and a "client" agent that calls the procedure.

In the "server" agent I have exported a method of the agent class as follows:

@RPC.export('setConfig')
def setConfig(self, config):
    self.config = config                       
    self.initialize_device()

The "client" agent call the exported method as follows:

self.vip.rpc.call(sender, 'setConfig', self.config[sender]).get()

Where "sender" is the VIP identity of the "server" agent (which turns out to be "rpcserver.agent_1" and corresponds to the value of the "sender" argument when receiving a pubsub message from rpcserver.agent. I have defined the identity as rpcserver.agent_{n} in the IDENTITY file).

My questions are: 1. Am I doing something obviously wrong or missing some step to set up RPC subsystem properly? 2. Is the "peer" argument in self.vip.rpc.call(peer, method, ...) expected to be the agent's identity? This is not clear in the documentation (I have tried other options such as agent name or uuid but none worked)

I am running volttron 5.1.0 in a Ubuntu VM.

Any help with this would be very much appreciated. Best regards

DETAILS:

This is for a control application that interconnects multiple devices. Agents are loaded dynamically depending on the available devices in the network. I would like to try out RPCs instead of using only pubsub. I have already quite thoroughly searched the Volttron code and documentation for details on the RPC API to no avail so far.

Minimal "server" class:

class rpcServerAgent(Agent):    
    def __init__(self, config, **kwargs):        
        super(rpcServerAgent, self).__init__(**kwargs)
        self.config = config

    @Core.receiver('onstart')
    def onstart(self, sender, **kwargs):                     
        self.vip.rpc.export(self.setConfig, name='setConfig')           # Also tried online exporting 

        # Ask the client to call the exported procedure
        myutils.publish(self, topic='rpc/test', message={})             # myutils.publish publishes the message on pubsub 


    @RPC.export('setConfig')
    def setConfig(self, config):
        self.config = config        
        self.initialize_device()
        myutils.publish(self, topic='rpc/clientready')

    def initialize_device(self):
        pass

Minimal "client" class:

class rpcClientAgent(Agent):    
    def __init__(self, config, **kwargs):
        super(rpcClientAgent, self).__init__(**kwargs)        
        self.config = {'rpcclient.agent_1': {'a': 0, 'b': 1}}                   # dummy config for rpcclient.agent

    @Core.receiver('onstart')
    def onstart(self, sender, **kwargs):
        self.vip.pubsub.subscribe(peer='pubsub',
                                prefix='rpc',
                                callback=self.__handle_request__).get(timeout=5)      

    def call_RPC(self, sender):        
        sender = sender.strip()         # volttron adds a newline at the end        
        self.vip.rpc.call(sender, 'setConfig', self.config[sender]).get()       # assume that self.config[sender] is well-defined    

    @PubSub.subscribe('pubsub', 'rpc')
    def __handle_request__(self, peer, sender, bus, topic, headers, message):
        try:
            msg = json.loads(message)
        except:
            raise ValueError("failed to decode message")

        topics = topic.split('/')
        if len(topics) > 1:
            if topics[0] == 'rpc':                
                if topics[1] == 'test':                                                            
                    self.call_RPC(sender)

Expected behavior: exported function is called and a pubsub message with topic "rpc/clientready" is published.

Actual behavior: RPc call fails with error "Unreachable: VIP error (113): No route to host: rpcclient.agent_1"

Edit I eventually found the problem was that the agent's identity inside volttron ended with a '\n' character. This was due to gedit automatically appending it and apparently the string is not stripped by volttron.

like image 701
wawrzeniec Avatar asked Nov 06 '22 16:11

wawrzeniec


1 Answers

In terms of how to create an RPC method, I'd say you've done things properly (although this:

@RPC.export('setConfig')

could be:

@RPC.export

since you aren't changing the method name).

In terms of making RPC calls, the first argument is indeed meant to be the agent identity, or an address.

If you'd like to see more examples, check out the Modbus or Bacnet drivers in services/core/MasterDriverAgent/master_driver/interfaces.

A few other notes:

  • The config store can be used to set agent configurations. Examples of this can be found in volttron/platform/agent/base_weather.py (as well as many other agents)

  • Although I'm not intimately familiar, it seems like what you are trying to do can be accomplished by leveraging the features of the master driver. I believe the Market Service agent may share some design features with what you are trying to accomplish, it can be found at services/core/MarketServiceAgent.

like image 75
jklarson Avatar answered Nov 14 '22 19:11

jklarson