/*
* Copyright (C) 2011 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package org.ros.internal.node.server;
import com.google.common.collect.Lists;
import org.ros.address.AdvertiseAddress;
import org.ros.address.BindAddress;
import org.ros.internal.node.client.MasterClient;
import org.ros.internal.node.parameter.ParameterManager;
import org.ros.internal.node.service.ServiceManager;
import org.ros.internal.node.topic.DefaultPublisher;
import org.ros.internal.node.topic.DefaultSubscriber;
import org.ros.internal.node.topic.PublisherIdentifier;
import org.ros.internal.node.topic.SubscriberIdentifier;
import org.ros.internal.node.topic.TopicDeclaration;
import org.ros.internal.node.topic.TopicParticipantManager;
import org.ros.internal.node.xmlrpc.SlaveXmlRpcEndpointImpl;
import org.ros.internal.system.Process;
import org.ros.internal.transport.ProtocolDescription;
import org.ros.internal.transport.ProtocolNames;
import org.ros.internal.transport.tcp.TcpRosProtocolDescription;
import org.ros.internal.transport.tcp.TcpRosServer;
import org.ros.namespace.GraphName;
import java.net.URI;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.ScheduledExecutorService;
/**
* An XML RPC server for the ROS slave server.
*
* @author damonkohler@google.com (Damon Kohler)
*/
public class SlaveServer extends XmlRpcServer {
private final GraphName nodeName;
private final MasterClient masterClient;
private final TopicParticipantManager topicParticipantManager;
private final ParameterManager parameterManager;
private final TcpRosServer tcpRosServer;
/**
* Construct a new slave server.
*
* @param nodeName
* the node name for the node this will be the slave server for
* @param tcpRosBindAddress
* the bind address for TCP ros connections
* @param tcpRosAdvertiseAddress
* the TCP-based ROS advertising address
* @param xmlRpcBindAddress
* the XML RPC bind address
* @param xmlRpcAdvertiseAddress
* the advertise address for the XML RPC server
* @param master
* @param topicParticipantManager
* @param serviceManager
* @param parameterManager
* @param executorService
*/
public SlaveServer(GraphName nodeName, BindAddress tcpRosBindAddress, AdvertiseAddress tcpRosAdvertiseAddress,
BindAddress xmlRpcBindAddress, AdvertiseAddress xmlRpcAdvertiseAddress, MasterClient master,
TopicParticipantManager topicParticipantManager, ServiceManager serviceManager,
ParameterManager parameterManager, ScheduledExecutorService executorService) {
super(xmlRpcBindAddress, xmlRpcAdvertiseAddress, executorService);
this.nodeName = nodeName;
this.masterClient = master;
this.topicParticipantManager = topicParticipantManager;
this.parameterManager = parameterManager;
this.tcpRosServer =
new TcpRosServer(tcpRosBindAddress, tcpRosAdvertiseAddress, topicParticipantManager, serviceManager,
executorService);
}
/**
* Get the TCP-based ROS advertise address for the server.
*
* @return the advertise address
*/
public AdvertiseAddress getTcpRosAdvertiseAddress() {
return tcpRosServer.getAdvertiseAddress();
}
/**
* Start the XML-RPC server. This start() routine requires that the {@link TcpRosServer} is initialized first so that
* the slave server returns correct information when topics are requested.
*/
public void start() {
super.start(org.ros.internal.node.xmlrpc.SlaveXmlRpcEndpointImpl.class, new SlaveXmlRpcEndpointImpl(this));
tcpRosServer.start();
}
// TODO(damonkohler): This should also shut down the Node.
@Override
public void shutdown() {
super.shutdown();
tcpRosServer.shutdown();
}
public List<Object> getBusStats(String callerId) {
throw new UnsupportedOperationException();
}
public List<Object> getBusInfo(String callerId) {
List<Object> busInfo = Lists.newArrayList();
// The connection ID field is opaque to the user. A monotonically increasing
// integer for now is sufficient.
int id = 0;
for (DefaultPublisher<?> publisher : getPublications()) {
for (SubscriberIdentifier subscriberIdentifier : topicParticipantManager.getPublisherConnections(publisher)) {
List<String> publisherBusInfo = Lists.newArrayList();
publisherBusInfo.add(Integer.toString(id));
publisherBusInfo.add(subscriberIdentifier.getNodeIdentifier().getName().toString());
// TODO(damonkohler): Pull out BusInfo constants.
publisherBusInfo.add("o");
// TODO(damonkohler): Add getter for protocol to topic participants.
publisherBusInfo.add(ProtocolNames.TCPROS);
publisherBusInfo.add(publisher.getTopicName().toString());
busInfo.add(publisherBusInfo);
id++;
}
}
for (DefaultSubscriber<?> subscriber : getSubscriptions()) {
for (PublisherIdentifier publisherIdentifer : topicParticipantManager.getSubscriberConnections(subscriber)) {
List<String> subscriberBusInfo = Lists.newArrayList();
subscriberBusInfo.add(Integer.toString(id));
// Subscriber connection PublisherIdentifiers are populated with node
// URIs instead of names. As a result, the only identifier information
// available is the URI.
subscriberBusInfo.add(publisherIdentifer.getNodeIdentifier().getUri().toString());
// TODO(damonkohler): Pull out BusInfo constants.
subscriberBusInfo.add("i");
// TODO(damonkohler): Add getter for protocol to topic participants.
subscriberBusInfo.add(ProtocolNames.TCPROS);
subscriberBusInfo.add(publisherIdentifer.getTopicName().toString());
busInfo.add(subscriberBusInfo);
id++;
}
}
return busInfo;
}
public URI getMasterUri() {
return masterClient.getRemoteUri();
}
/**
* @return PID of this process if available, throws {@link UnsupportedOperationException} otherwise.
*/
@Override
public int getPid() {
return Process.getPid();
}
public Collection<DefaultSubscriber<?>> getSubscriptions() {
return topicParticipantManager.getSubscribers();
}
public Collection<DefaultPublisher<?>> getPublications() {
return topicParticipantManager.getPublishers();
}
/**
* @param parameterName
* @param parameterValue
* @return the number of parameter subscribers that received the update
*/
public int paramUpdate(GraphName parameterName, Object parameterValue) {
return parameterManager.updateParameter(parameterName, parameterValue);
}
public void publisherUpdate(String callerId, String topicName, Collection<URI> publisherUris) {
GraphName graphName = GraphName.of(topicName);
if (topicParticipantManager.hasSubscriber(graphName)) {
DefaultSubscriber<?> subscriber = topicParticipantManager.getSubscriber(graphName);
TopicDeclaration topicDeclaration = subscriber.getTopicDeclaration();
Collection<PublisherIdentifier> identifiers =
PublisherIdentifier.newCollectionFromUris(publisherUris, topicDeclaration);
subscriber.updatePublishers(identifiers);
}
}
public ProtocolDescription requestTopic(String topicName, Collection<String> protocols) throws ServerException {
// TODO(damonkohler): Use NameResolver.
// Canonicalize topic name.
GraphName graphName = GraphName.of(topicName).toGlobal();
if (!topicParticipantManager.hasPublisher(graphName)) {
throw new ServerException("No publishers for topic: " + graphName);
}
for (String protocol : protocols) {
if (protocol.equals(ProtocolNames.TCPROS)) {
try {
return new TcpRosProtocolDescription(tcpRosServer.getAdvertiseAddress());
} catch (Exception e) {
throw new ServerException(e);
}
}
}
throw new ServerException("No supported protocols specified.");
}
/**
* @return a {@link NodeIdentifier} for this {@link SlaveServer}
*/
public NodeIdentifier toNodeIdentifier() {
return new NodeIdentifier(nodeName, getUri());
}
}