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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With