Permalink
Browse files

command line args, rosparams, and environment vars

  • Loading branch information...
1 parent a2ec555 commit e488dd9aefb35a191a01426076321373af30c728 @evanw committed Apr 30, 2012
Showing with 161 additions and 43 deletions.
  1. +29 −3 html/index.html
  2. +41 −21 html/script.js
  3. +79 −19 nodes/ride.py
  4. +6 −0 srv/NodeSettingsGet.srv
  5. +6 −0 srv/NodeSettingsSet.srv
View
@@ -9,8 +9,9 @@
<script src="bootstrap/js/bootstrap.min.js"></script>
<style>
body { overflow: hidden; }
- .modal { display: none; top: 200px; margin-top: 0; }
+ .modal { display: none; top: 200px; margin-top: 0; width: 530px; }
#terminal_output_data { background: black; color: white; border: none; }
+ #launch_settings textarea, #launch_settings input { width: 490px; }
.navbar .container { width: 100%; }
.navbar .brand { margin-left: -10px; }
.GraphBox .title { padding-right: 28px; }
@@ -48,6 +49,31 @@ <h3>Terminal Output</h3>
</div>
</div>
+ <div id="launch_settings" class="modal">
+ <div class="modal-header">
+ <a class="close" data-dismiss="modal">&times;</a>
+ <h3>Launch Settings</h3>
+ </div>
+ <div class="modal-body">
+ <p>These settings will take effect when the node is next started.</p>
+ <form onsubmit="javascript:ui.setLaunchSettings(true);return false">
+ <input id="launch_settings_node_name" type="hidden">
+ <p><b>Command-line arguments</b></p>
+ <input id="launch_settings_cmd_line_args" placeholder='--flag --key="value with spaces"'>
+ <!-- HACK: Placeholders can't have newlines, but browsers will wrap text after a lot of spaces -->
+ <p><b>ROS params</b> (key = value, one per line, JSON syntax)</p>
+ <textarea id="launch_settings_rosparams" placeholder="param1 = 0 /namespace/param2 = { a: 1, b: 2 }"></textarea>
+ <p><b>Environment variables</b> (key = value, one per line)</p>
+ <textarea id="launch_settings_env_vars" placeholder="VAR1 = 0 VAR2 = text with spaces"></textarea>
+ </form>
+ </div>
+ <div class="modal-footer">
+ <a class="btn" data-dismiss="modal">Cancel</a>
+ <a href="javascript:ui.setLaunchSettings(false)" class="btn">Change</a>
+ <a href="javascript:ui.setLaunchSettings(true)" class="btn btn-primary">Change and Relaunch</a>
+ </div>
+ </div>
+
<div id="change_connection_url" class="modal">
<div class="modal-header">
<a class="close" data-dismiss="modal">&times;</a>
@@ -62,13 +88,13 @@ <h3>Change Connection URL</h3>
<form class="form-horizontal" onsubmit="javascript:ui.setConnectionURL();return false">
<div class="control-group">
<label class="control-label" for="new_connection_url">Connection URL</label>
- <div class="controls"><input type="text" class="input-xlarge" id="new_connection_url"></div>
+ <div class="controls"><input type="text" class="input-xlarge" id="new_connection_url" placeholder="ws://localhost:9000"></div>
</div>
</form>
</div>
<div class="modal-footer">
<a class="btn" data-dismiss="modal">Cancel</a>
- <a href="javascript:ui.setConnectionURL()" class="btn btn-primary">Change URL</a>
+ <a href="javascript:ui.setConnectionURL()" class="btn btn-primary">Change</a>
</div>
</div>
View
@@ -137,28 +137,17 @@ var ride = {
case 'update_owned_node':
var node = this.graph.node(data.name);
if (!node) break;
- switch (data.status) {
- case STATUS_STARTING:
- node.detailText = 'Starting...';
- break;
- case STATUS_STARTED:
- node.detailText = '';
- break;
- case STATUS_STOPPING:
- node.detailText = 'Stopping...';
- break;
- case STATUS_STOPPED:
- node.detailText = (data.return_code === null) ? '' : 'Exited with code ' + data.return_code;
- break;
- case STATUS_ERROR:
- node.detailText = 'Could not be launched (run rosmake?)';
- break;
- }
+ node.isRunning = data.is_running;
+ node.detailText = data.status;
node.updateHTML();
- $(node.startElement).toggle(data.status == STATUS_STOPPED || data.status == STATUS_ERROR);
- $(node.stopElement).toggle(data.status != STATUS_STOPPED && data.status != STATUS_ERROR);
+ $(node.startElement).toggle(!data.is_running);
+ $(node.stopElement).toggle(data.is_running);
this.graph.updateBounds();
this.graph.draw();
+ if (!data.is_running && node.relaunchWhenStopped) {
+ node.relaunchWhenStopped = false;
+ ROS.call('/ride/node/start', { name: data.name });
+ }
break;
}
},
@@ -258,6 +247,17 @@ var ui = {
});
$(node.stopElement).show();
+ // Launch settings
+ item('Launch settings', function() {
+ ROS.call('/ride/node/settings/get', { name: node.name }, function(data) {
+ $('#launch_settings_node_name').val(node.name);
+ $('#launch_settings_cmd_line_args').val(data.cmd_line_args);
+ $('#launch_settings_rosparams').val(data.rosparams);
+ $('#launch_settings_env_vars').val(data.env_vars);
+ $('#launch_settings').modal('show');
+ });
+ });
+
// Show terminal output
item('Show terminal output', function() {
ROS.call('/ride/node/output', { name: node.name }, function(data) {
@@ -279,6 +279,26 @@ var ui = {
$('#change_connection_url').modal('hide');
},
+ setLaunchSettings: function(relaunch) {
+ var node_name = $('#launch_settings_node_name').val();
+ ROS.call('/ride/node/settings/set', {
+ name: node_name,
+ cmd_line_args: $('#launch_settings_cmd_line_args').val(),
+ rosparams: $('#launch_settings_rosparams').val(),
+ env_vars: $('#launch_settings_env_vars').val()
+ }, function() {
+ var node = ride.graph.node(node_name);
+ if (!node) return;
+ if (node.isRunning) {
+ ROS.call('/ride/node/stop', { name: node_name });
+ node.relaunchWhenStopped = true;
+ } else {
+ ROS.call('/ride/node/start', { name: node_name });
+ }
+ });
+ $('#launch_settings').modal('hide');
+ },
+
setConnected: function(connected) {
if (connected) {
$('#connection_status').text('Connected to ' + ROS.url);
@@ -331,8 +351,8 @@ ride.graph.draw();
// Compute which input has focus
var inputWithFocus = null;
-$('input').focus(function() { inputWithFocus = this; });
-$('input').blur(function() { inputWithFocus = null; });
+$('input, textarea').focus(function() { inputWithFocus = this; });
+$('input, textarea').blur(function() { inputWithFocus = null; });
// Deselect input when the graph is clicked
$(ride.graph.element).click(function() {
View
@@ -5,6 +5,7 @@
import re
import json
import fcntl
+import shlex
import rospy
import signal
import pickle
@@ -13,12 +14,6 @@
from ros import rosnode
from std_msgs.msg import String
-STATUS_STARTING = 0
-STATUS_STARTED = 1
-STATUS_STOPPING = 2
-STATUS_STOPPED = 3
-STATUS_ERROR = 4
-
TOPIC_NAMES_TO_IGNORE = ['/rosout']
NODE_NAMES_TO_IGNORE = ['/rosout', '/rosbridge', '/ride']
@@ -98,10 +93,12 @@ def __init__(self, ride, name, path, display_name):
self.path = path
self.display_name = display_name
self.stdout = ''
- self.status = STATUS_STARTING
- self.return_code = None
+ self.status = 'Not running'
self.process = None
self.remappings = {}
+ self.cmd_line_args = ''
+ self.rosparams = ''
+ self.env_vars = ''
self.ride.updates.create_node(self)
self.ride.names_to_avoid.add(name)
self.start()
@@ -170,20 +167,65 @@ def start(self):
for topic in map:
command.append(topic + ':=' + map[topic])
- # Start the node again
+ # Prepare for an early return
self.stdout = '$ ' + self.path + '\n'
+ self.status = 'Failed to launch'
+
+ # Shared parser logic for multiline key=value pairs
+ def parse_multiline(text):
+ for line in text.split('\n'):
+ line = line.strip()
+ if not line:
+ continue
+ if '=' not in line:
+ raise Exception('Line missing "="')
+ key, value = line.split('=', 1)
+ yield key.strip(), value.strip()
+
+ # Attempt to append command-line arguments
+ try:
+ command += shlex.split(self.cmd_line_args)
+ self.stdout = '$ ' + self.path + ' ' + self.cmd_line_args + '\n'
+ except Exception as e:
+ self.status = 'Failed to launch: Could not parse command-line arguments (%s)' % str(e)
+ self.ride.updates.update_owned_node(self)
+ return
+
+ # Attempt to parse environment variables
+ env_vars = dict(os.environ)
+ try:
+ for key, value in parse_multiline(self.env_vars):
+ env_vars[key] = value
+ except Exception as e:
+ self.status = 'Failed to launch: Could not parse environment variables (%s)' % str(e)
+ self.ride.updates.update_owned_node(self)
+ return
+
+ # Attempt to parse rosparams
+ try:
+ for key, value in parse_multiline(self.rosparams):
+ if key and '/' not in key:
+ key = self.name + '/' + key # Handle private names
+ value = json.loads('{ "value": %s }' % value)['value']
+ rospy.set_param(key, value)
+ except Exception as e:
+ self.status = 'Failed to launch: Could not parse ROS params (%s)' % str(e)
+ self.ride.updates.update_owned_node(self)
+ return
+
+ # Start the node again
try:
# Start the node as a child process
- self.process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
- self.status = STATUS_STARTING
+ self.process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, env=env_vars)
+ self.status = 'Starting...'
# Make sure self.process.stdout.read() won't block
f = self.process.stdout
fcntl.fcntl(f, fcntl.F_SETFL, fcntl.fcntl(f, fcntl.F_GETFL) | os.O_NONBLOCK)
except Exception as e:
# This will fail if the file doesn't exist (need to use rosmake again)
self.stdout += str(e)
- self.status = STATUS_ERROR
+ self.status = 'Failed to launch: popen() failed (run rosmake?)'
# Send the new status
self.ride.updates.update_owned_node(self)
@@ -192,11 +234,11 @@ def stop(self):
if self.process:
# Don't use self.ride.soft_kill_process(self.process) because we
# are still checking for the return code in self.poll()
- if self.status == STATUS_STOPPING:
+ if self.status == 'Stopping...':
self.process.kill()
else:
self.process.send_signal(signal.SIGINT)
- self.status = STATUS_STOPPING
+ self.status = 'Stopping...'
self.ride.updates.update_owned_node(self)
def poll(self):
@@ -207,8 +249,7 @@ def poll(self):
except:
pass
if self.process.returncode is not None:
- self.status = STATUS_STOPPED
- self.return_code = self.process.returncode
+ self.status = 'Exited with return code %d' % self.process.returncode
self.process = None
self.ride.updates.update_owned_node(self)
@@ -293,7 +334,7 @@ def update_owned_node(self, node, send=True):
'type': 'update_owned_node',
'name': node.name,
'status': node.status,
- 'return_code': node.return_code,
+ 'is_running': node.status in ['Starting...', '', 'Stopping...'],
}, send)
class RIDE:
@@ -326,6 +367,8 @@ def __init__(self):
rospy.Service('/ride/node/start', ride.srv.NodeStart, self.node_start_service)
rospy.Service('/ride/node/stop', ride.srv.NodeStop, self.node_stop_service)
rospy.Service('/ride/node/output', ride.srv.NodeOutput, self.node_output_service)
+ rospy.Service('/ride/node/settings/get', ride.srv.NodeSettingsGet, self.node_settings_get_service)
+ rospy.Service('/ride/node/settings/set', ride.srv.NodeSettingsSet, self.node_settings_set_service)
rospy.Service('/ride/link/create', ride.srv.LinkCreate, self.link_create_service)
rospy.Service('/ride/link/destroy', ride.srv.LinkDestroy, self.link_destroy_service)
@@ -413,6 +456,23 @@ def node_output_service(self, request):
return ride.srv.NodeOutputResponse(self.owned_nodes[request.name].stdout, True)
return ride.srv.NodeOutputResponse('', False)
+ def node_settings_get_service(self, request):
+ '''Implements the /ride/node/settings/get service'''
+ if request.name in self.owned_nodes:
+ node = self.owned_nodes[request.name]
+ return ride.srv.NodeSettingsGetResponse(node.cmd_line_args, node.rosparams, node.env_vars, True)
+ return ride.srv.NodeSettingsGetResponse('', '', '', False)
+
+ def node_settings_set_service(self, request):
+ '''Implements the /ride/node/settings/set service'''
+ if request.name in self.owned_nodes:
+ node = self.owned_nodes[request.name]
+ node.cmd_line_args = request.cmd_line_args
+ node.rosparams = request.rosparams
+ node.env_vars = request.env_vars
+ return ride.srv.NodeSettingsSetResponse(True)
+ return ride.srv.NodeSettingsSetResponse(False)
+
def link_create_service(self, request):
'''Implements the /ride/link/create service'''
key = request.from_topic, request.to_topic
@@ -504,8 +564,8 @@ def poll(self):
node = self.owned_nodes[name]
# Check on the process
- if node.status == STATUS_STARTING and node.name in node_names:
- node.status = STATUS_STARTED
+ if node.status == 'Starting...' and node.name in node_names:
+ node.status = ''
self.updates.update_owned_node(node)
node.poll()
View
@@ -0,0 +1,6 @@
+string name
+----
+string cmd_line_args
+string rosparams
+string env_vars
+bool worked
View
@@ -0,0 +1,6 @@
+string name
+string cmd_line_args
+string rosparams
+string env_vars
+----
+bool worked

0 comments on commit e488dd9

Please sign in to comment.