mirror of
https://github.com/CopterExpress/clover.git
synced 2026-06-01 15:39:32 +00:00
Compare commits
4 Commits
master
...
roslaunch_
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c254691f80 | ||
|
|
4fe6f24d16 | ||
|
|
cda7e62dbf | ||
|
|
fa9a1794f6 |
@@ -1,7 +1,10 @@
|
|||||||
<launch>
|
<launch>
|
||||||
<arg name="aruco_detect" default="true"/>
|
<arg name="aruco_detect" default="false"/> <!-- markers recognition enabled -->
|
||||||
<arg name="aruco_map" default="false"/>
|
<arg name="aruco_map" default="false"/> <!-- markers map recognition enabled -->
|
||||||
<arg name="aruco_vpe" default="false"/>
|
<arg name="aruco_vpe" default="false"/> <!-- markers map positioning enabled -->
|
||||||
|
<arg name="placement" default="floor"/> <!-- markers placement: floor, ceiling, unknown -->
|
||||||
|
<arg name="length" default="0.33"/> <!-- not-in-map markers length, m -->
|
||||||
|
<arg name="map" default="map.txt"/> <!-- markers map file name -->
|
||||||
|
|
||||||
<!-- For additional help go to https://clover.coex.tech/aruco -->
|
<!-- For additional help go to https://clover.coex.tech/aruco -->
|
||||||
|
|
||||||
@@ -12,8 +15,9 @@
|
|||||||
<remap from="map_markers" to="aruco_map/markers" if="$(arg aruco_map)"/>
|
<remap from="map_markers" to="aruco_map/markers" if="$(arg aruco_map)"/>
|
||||||
<param name="estimate_poses" value="true"/>
|
<param name="estimate_poses" value="true"/>
|
||||||
<param name="send_tf" value="true"/>
|
<param name="send_tf" value="true"/>
|
||||||
<param name="known_tilt" value="map"/>
|
<param name="known_tilt" value="map" if="$(eval placement == 'floor')"/>
|
||||||
<param name="length" value="0.33"/>
|
<param name="known_tilt" value="map_flipped" if="$(eval position == 'ceiling')"/>
|
||||||
|
<param name="length" value="$(arg length)"/>
|
||||||
<!-- aruco detector parameters -->
|
<!-- aruco detector parameters -->
|
||||||
<param name="cornerRefinementMethod" value="2"/> <!-- contour refinement -->
|
<param name="cornerRefinementMethod" value="2"/> <!-- contour refinement -->
|
||||||
<param name="minMarkerPerimeterRate" value="0.075"/> <!-- 0.075 for 320x240, 0.0375 for 640x480 -->
|
<param name="minMarkerPerimeterRate" value="0.075"/> <!-- 0.075 for 320x240, 0.0375 for 640x480 -->
|
||||||
@@ -24,8 +28,9 @@
|
|||||||
<remap from="image_raw" to="main_camera/image_raw"/>
|
<remap from="image_raw" to="main_camera/image_raw"/>
|
||||||
<remap from="camera_info" to="main_camera/camera_info"/>
|
<remap from="camera_info" to="main_camera/camera_info"/>
|
||||||
<remap from="markers" to="aruco_detect/markers"/>
|
<remap from="markers" to="aruco_detect/markers"/>
|
||||||
<param name="map" value="$(find aruco_pose)/map/map.txt"/>
|
<param name="map" value="$(find aruco_pose)/map/$(arg map)"/>
|
||||||
<param name="known_tilt" value="map"/>
|
<param name="known_tilt" value="map" if="$(eval placement == 'floor')"/>
|
||||||
|
<param name="known_tilt" value="map_flipped" if="$(eval placement == 'ceiling')"/>
|
||||||
<param name="image_axis" value="true"/>
|
<param name="image_axis" value="true"/>
|
||||||
<param name="frame_id" value="aruco_map_detected" if="$(arg aruco_vpe)"/>
|
<param name="frame_id" value="aruco_map_detected" if="$(arg aruco_vpe)"/>
|
||||||
<param name="frame_id" value="aruco_map" unless="$(arg aruco_vpe)"/>
|
<param name="frame_id" value="aruco_map" unless="$(arg aruco_vpe)"/>
|
||||||
|
|||||||
@@ -1,16 +1,15 @@
|
|||||||
<launch>
|
<launch>
|
||||||
<arg name="fcu_conn" default="usb"/>
|
<arg name="fcu_conn" default="usb"/> <!-- FCU connection type: usb, uart, tcp, udp, sitl -->
|
||||||
<arg name="fcu_ip" default="127.0.0.1"/>
|
<arg name="fcu_ip" default="127.0.0.1"/> <!-- FCU IP adddress (if using TCP/UDP) -->
|
||||||
<arg name="fcu_sys_id" default="1"/>
|
<arg name="fcu_sys_id" default="1"/> <!-- MAVLink system ID, noeditor -->
|
||||||
<arg name="gcs_bridge" default="tcp"/>
|
<arg name="gcs_bridge" default="tcp"/> <!-- GCS bridge type: tcp, udp, udp-b, udp-pb -->
|
||||||
<arg name="web_video_server" default="true"/>
|
<arg name="web_video_server" default="true"/> <!-- web video server enabled -->
|
||||||
<arg name="rosbridge" default="true"/>
|
<arg name="rosbridge" default="true"/> <!-- rosbridge_suite enabled, noeditor -->
|
||||||
<arg name="main_camera" default="true"/>
|
<arg name="main_camera" default="true"/> <!-- main camera enabled -->
|
||||||
<arg name="optical_flow" default="true"/>
|
<arg name="optical_flow" default="true"/> <!-- optical flow enabled -->
|
||||||
<arg name="aruco" default="false"/>
|
<arg name="rangefinder_vl53l1x" default="true"/> <!-- VL53l1X rangefinder enabled -->
|
||||||
<arg name="rangefinder_vl53l1x" default="true"/>
|
<arg name="led" default="true"/> <!-- LED strip driver enabled -->
|
||||||
<arg name="led" default="true"/>
|
<arg name="rc" default="true"/> <!-- support for mobile RC enabled -->
|
||||||
<arg name="rc" default="true"/>
|
|
||||||
<arg name="shell" default="true"/>
|
<arg name="shell" default="true"/>
|
||||||
|
|
||||||
<!-- log formatting -->
|
<!-- log formatting -->
|
||||||
@@ -31,7 +30,7 @@
|
|||||||
</node>
|
</node>
|
||||||
|
|
||||||
<!-- aruco markers -->
|
<!-- aruco markers -->
|
||||||
<include file="$(find clover)/launch/aruco.launch" if="$(arg aruco)"/>
|
<include file="$(find clover)/launch/aruco.launch"/>
|
||||||
|
|
||||||
<!-- optical flow -->
|
<!-- optical flow -->
|
||||||
<node pkg="nodelet" type="nodelet" name="optical_flow" args="load clover/optical_flow nodelet_manager" if="$(arg optical_flow)" clear_params="true" output="screen">
|
<node pkg="nodelet" type="nodelet" name="optical_flow" args="load clover/optical_flow nodelet_manager" if="$(arg optical_flow)" clear_params="true" output="screen">
|
||||||
@@ -87,4 +86,11 @@
|
|||||||
<node pkg="roswww_static" name="roswww_static" type="main.py" clear_params="true">
|
<node pkg="roswww_static" name="roswww_static" type="main.py" clear_params="true">
|
||||||
<param name="default_package" value="clover"/>
|
<param name="default_package" value="clover"/>
|
||||||
</node>
|
</node>
|
||||||
|
|
||||||
|
<!-- roslaunch editor parameters -->
|
||||||
|
<group ns="roslaunch_editor">
|
||||||
|
<param name="items" type="yaml" value="[clover/clover.launch, clover/main_camera.launch, clover/aruco.launch, clover/led.launch]"/>
|
||||||
|
<param name="apply_command" value="sudo systemctl restart clover"/>
|
||||||
|
<param name="hide_uncommented" value="true"/>
|
||||||
|
</group>
|
||||||
</launch>
|
</launch>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<launch>
|
<launch>
|
||||||
<arg name="ws281x" default="true"/>
|
<arg name="ws281x" default="true"/> <!-- LED strip driver enabled -->
|
||||||
<arg name="led_effect" default="true"/>
|
<arg name="led_effect" default="true"/> <!-- LED effect API enabled -->
|
||||||
<arg name="led_notify" default="true"/>
|
<arg name="led_notify" default="all"/> <!-- LED strip notifications: all, battery, none -->
|
||||||
|
|
||||||
<!-- For additional help go to https://clover.coex.tech/led -->
|
<!-- For additional help go to https://clover.coex.tech/led -->
|
||||||
|
|
||||||
@@ -22,7 +22,7 @@
|
|||||||
<param name="fade_period" value="0.5"/>
|
<param name="fade_period" value="0.5"/>
|
||||||
<param name="rainbow_period" value="5"/>
|
<param name="rainbow_period" value="5"/>
|
||||||
<!-- events effects table -->
|
<!-- events effects table -->
|
||||||
<rosparam param="notify" if="$(arg led_notify)">
|
<rosparam param="notify" if="$(eval led_notify == 'all')">
|
||||||
startup: { r: 255, g: 255, b: 255 }
|
startup: { r: 255, g: 255, b: 255 }
|
||||||
connected: { effect: rainbow }
|
connected: { effect: rainbow }
|
||||||
disconnected: { effect: blink, r: 255, g: 50, b: 50 }
|
disconnected: { effect: blink, r: 255, g: 50, b: 50 }
|
||||||
@@ -34,5 +34,8 @@
|
|||||||
low_battery: { threshold: 3.7, effect: blink_fast, r: 255, g: 0, b: 0 }
|
low_battery: { threshold: 3.7, effect: blink_fast, r: 255, g: 0, b: 0 }
|
||||||
error: { effect: flash, r: 255, g: 0, b: 0 }
|
error: { effect: flash, r: 255, g: 0, b: 0 }
|
||||||
</rosparam>
|
</rosparam>
|
||||||
|
<rosparam param="notify" if="$(eval led_notify == 'battery')">
|
||||||
|
low_battery: { threshold: 3.7, effect: blink_fast, r: 255, g: 0, b: 0 }
|
||||||
|
</rosparam>
|
||||||
</node>
|
</node>
|
||||||
</launch>
|
</launch>
|
||||||
|
|||||||
82
roslaunch_editor/CMakeLists.txt
Normal file
82
roslaunch_editor/CMakeLists.txt
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
cmake_minimum_required(VERSION 2.8.3)
|
||||||
|
project(roslaunch_editor)
|
||||||
|
|
||||||
|
find_package(catkin REQUIRED COMPONENTS message_generation)
|
||||||
|
|
||||||
|
add_message_files(
|
||||||
|
FILES
|
||||||
|
LaunchFile.msg
|
||||||
|
)
|
||||||
|
|
||||||
|
add_service_files(
|
||||||
|
FILES
|
||||||
|
ReadLaunchFiles.srv
|
||||||
|
WriteLaunchFiles.srv
|
||||||
|
)
|
||||||
|
|
||||||
|
generate_messages(
|
||||||
|
DEPENDENCIES
|
||||||
|
# std_msgs # Or other packages containing msgs
|
||||||
|
)
|
||||||
|
|
||||||
|
catkin_package(
|
||||||
|
# INCLUDE_DIRS include
|
||||||
|
# LIBRARIES roslaunch_editor
|
||||||
|
# CATKIN_DEPENDS other_catkin_pkg
|
||||||
|
# DEPENDS system_lib
|
||||||
|
)
|
||||||
|
|
||||||
|
#############
|
||||||
|
## Install ##
|
||||||
|
#############
|
||||||
|
|
||||||
|
# all install targets should use catkin DESTINATION variables
|
||||||
|
# See http://ros.org/doc/api/catkin/html/adv_user_guide/variables.html
|
||||||
|
|
||||||
|
## Mark executable scripts (Python etc.) for installation
|
||||||
|
## in contrast to setup.py, you can choose the destination
|
||||||
|
# install(PROGRAMS
|
||||||
|
# scripts/my_python_script
|
||||||
|
# DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION}
|
||||||
|
# )
|
||||||
|
|
||||||
|
## Mark executables for installation
|
||||||
|
## See http://docs.ros.org/melodic/api/catkin/html/howto/format1/building_executables.html
|
||||||
|
# install(TARGETS ${PROJECT_NAME}_node
|
||||||
|
# RUNTIME DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION}
|
||||||
|
# )
|
||||||
|
|
||||||
|
## Mark libraries for installation
|
||||||
|
## See http://docs.ros.org/melodic/api/catkin/html/howto/format1/building_libraries.html
|
||||||
|
# install(TARGETS ${PROJECT_NAME}
|
||||||
|
# ARCHIVE DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION}
|
||||||
|
# LIBRARY DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION}
|
||||||
|
# RUNTIME DESTINATION ${CATKIN_GLOBAL_BIN_DESTINATION}
|
||||||
|
# )
|
||||||
|
|
||||||
|
## Mark cpp header files for installation
|
||||||
|
# install(DIRECTORY include/${PROJECT_NAME}/
|
||||||
|
# DESTINATION ${CATKIN_PACKAGE_INCLUDE_DESTINATION}
|
||||||
|
# FILES_MATCHING PATTERN "*.h"
|
||||||
|
# PATTERN ".svn" EXCLUDE
|
||||||
|
# )
|
||||||
|
|
||||||
|
## Mark other files for installation (e.g. launch and bag files, etc.)
|
||||||
|
# install(FILES
|
||||||
|
# # myfile1
|
||||||
|
# # myfile2
|
||||||
|
# DESTINATION ${CATKIN_PACKAGE_SHARE_DESTINATION}
|
||||||
|
# )
|
||||||
|
|
||||||
|
#############
|
||||||
|
## Testing ##
|
||||||
|
#############
|
||||||
|
|
||||||
|
## Add gtest based cpp test target and link libraries
|
||||||
|
# catkin_add_gtest(${PROJECT_NAME}-test test/test_roslaunch_editor.cpp)
|
||||||
|
# if(TARGET ${PROJECT_NAME}-test)
|
||||||
|
# target_link_libraries(${PROJECT_NAME}-test ${PROJECT_NAME})
|
||||||
|
# endif()
|
||||||
|
|
||||||
|
## Add folders to be run by python nosetests
|
||||||
|
# catkin_add_nosetests(test)
|
||||||
28
roslaunch_editor/README.md
Normal file
28
roslaunch_editor/README.md
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
# roslaunch_editor
|
||||||
|
|
||||||
|
Web-based ROS launch-files editor, created for making configuration of your robot more user-friendly for novices.
|
||||||
|
|
||||||
|
<img src="roslaunch_editor.jpg" width=450>
|
||||||
|
|
||||||
|
## Quick launch
|
||||||
|
|
||||||
|
```bash
|
||||||
|
roslaunch roslaunch_editor example.launch
|
||||||
|
```
|
||||||
|
|
||||||
|
Then, open `http://localhost:8085/roslaunch_editor/` and edit the test launch file.
|
||||||
|
|
||||||
|
## Modes
|
||||||
|
|
||||||
|
`roslaunch_editor` works in two modes: standalone mode (where running `editor` node is required), and Clover mode (where `clover`'s `shell` node, `roswww_static` and `rosbridge_suite` are utilized). The editor will read and write launch-files and restart the nodes (if configured) using one of these nodes.
|
||||||
|
|
||||||
|
The mode is determined automatically, based on advertised ROS-services.
|
||||||
|
|
||||||
|
## Parameters
|
||||||
|
|
||||||
|
* `items` (`string` or `list`) – launch files to edit, format: `package_name/launch_file_name.launch`.
|
||||||
|
* `hide_uncommented` (`boolean`, default: `false`) – don't show arguments without comments.
|
||||||
|
* `apply_command` (`string`, default: `''`) – shell command to execute after writing launch-files (e. g. to restart the systemd service).
|
||||||
|
* `backup` (`boolean`, default: `false`) – backup overwritten launch-files (backup is written to `file_name.launch.bak`).
|
||||||
|
|
||||||
|
Some parameters (`items`, `hide_uncommented`) can be overwritten over GET-parameters, e. g. `?items=package/foo.launch,package/bar.launch&hide_uncommented=1`.
|
||||||
11
roslaunch_editor/launch/example.launch
Normal file
11
roslaunch_editor/launch/example.launch
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
<launch>
|
||||||
|
<include file="$(find roswww)/launch/roswww.launch"/>
|
||||||
|
|
||||||
|
<include file="$(find rosbridge_server)/launch/rosbridge_websocket.launch"/>
|
||||||
|
|
||||||
|
<node name="roslaunch_editor" pkg="roslaunch_editor" type="editor">
|
||||||
|
<param name="items" type="yaml" value="[roslaunch_editor/test.launch]"/>
|
||||||
|
<param name="reference_frames/base_link" value="map"/>
|
||||||
|
<param name="reference_frames/navigate_target" value="map"/>
|
||||||
|
</node>
|
||||||
|
</launch>
|
||||||
3
roslaunch_editor/msg/LaunchFile.msg
Normal file
3
roslaunch_editor/msg/LaunchFile.msg
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
string package
|
||||||
|
string name
|
||||||
|
string content
|
||||||
45
roslaunch_editor/package.xml
Normal file
45
roslaunch_editor/package.xml
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
<?xml version="1.0"?>
|
||||||
|
<package format="2">
|
||||||
|
<name>roslaunch_editor</name>
|
||||||
|
<version>0.0.0</version>
|
||||||
|
<description>Web based roslaunch files editor</description>
|
||||||
|
<maintainer email="okalachev@gmail.com">Oleg Kalachev</maintainer>
|
||||||
|
<license>MIT</license>
|
||||||
|
|
||||||
|
<!-- Url tags are optional, but multiple are allowed, one per tag -->
|
||||||
|
<!-- Optional attribute type can be: website, bugtracker, or repository -->
|
||||||
|
<url type="repository">https://github.com/CopterExpress/clover</url>
|
||||||
|
|
||||||
|
<!-- Author tags are optional, multiple are allowed, one per tag -->
|
||||||
|
<!-- Authors do not have to be maintainers, but could be -->
|
||||||
|
<author email="okalachev@gmail.com">Oleg Kalachev</author>
|
||||||
|
|
||||||
|
<!-- The *depend tags are used to specify dependencies -->
|
||||||
|
<!-- Dependencies can be catkin packages or system dependencies -->
|
||||||
|
<!-- Examples: -->
|
||||||
|
<!-- Use depend as a shortcut for packages that are both build and exec dependencies -->
|
||||||
|
<!-- <depend>roscpp</depend> -->
|
||||||
|
<!-- Note that this is equivalent to the following: -->
|
||||||
|
<!-- <build_depend>roscpp</build_depend> -->
|
||||||
|
<!-- <exec_depend>roscpp</exec_depend> -->
|
||||||
|
<!-- Use build_depend for packages you need at compile time: -->
|
||||||
|
<!-- <build_depend>message_generation</build_depend> -->
|
||||||
|
<!-- Use build_export_depend for packages you need in order to build against this package: -->
|
||||||
|
<!-- <build_export_depend>message_generation</build_export_depend> -->
|
||||||
|
<!-- Use buildtool_depend for build tool packages: -->
|
||||||
|
<!-- <buildtool_depend>catkin</buildtool_depend> -->
|
||||||
|
<!-- Use exec_depend for packages you need at runtime: -->
|
||||||
|
<!-- <exec_depend>message_runtime</exec_depend> -->
|
||||||
|
<!-- Use test_depend for packages you need only for testing: -->
|
||||||
|
<!-- <test_depend>gtest</test_depend> -->
|
||||||
|
<!-- Use doc_depend for packages you need only for building documentation: -->
|
||||||
|
<!-- <doc_depend>doxygen</doc_depend> -->
|
||||||
|
<buildtool_depend>catkin</buildtool_depend>
|
||||||
|
<depend>message_generation</depend>
|
||||||
|
|
||||||
|
<!-- The export tag contains other, unspecified, tags -->
|
||||||
|
<export>
|
||||||
|
<!-- Other tools can request additional information be placed here -->
|
||||||
|
|
||||||
|
</export>
|
||||||
|
</package>
|
||||||
BIN
roslaunch_editor/roslaunch_editor.jpg
Normal file
BIN
roslaunch_editor/roslaunch_editor.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 162 KiB |
73
roslaunch_editor/src/editor
Executable file
73
roslaunch_editor/src/editor
Executable file
@@ -0,0 +1,73 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
import rospy
|
||||||
|
import os
|
||||||
|
import shutil
|
||||||
|
import rospkg
|
||||||
|
|
||||||
|
from std_srvs.srv import Trigger
|
||||||
|
from roslaunch_editor.srv import ReadLaunchFiles, ReadLaunchFilesResponse, WriteLaunchFiles
|
||||||
|
from roslaunch_editor.msg import LaunchFile
|
||||||
|
|
||||||
|
rospy.init_node('roslaunch_editor')
|
||||||
|
rospack = rospkg.RosPack()
|
||||||
|
backup = rospy.get_param('~backup', True)
|
||||||
|
apply_command = rospy.get_param('~apply_command', '')
|
||||||
|
|
||||||
|
|
||||||
|
def get_launch_file_path(package, name):
|
||||||
|
path = rospack.get_path(package)
|
||||||
|
for root, dirnames, filenames in os.walk(path):
|
||||||
|
if name in filenames:
|
||||||
|
return os.path.join(root, name)
|
||||||
|
raise Exception('Launch file %s/%s not found' % (package, name))
|
||||||
|
|
||||||
|
|
||||||
|
def read(req):
|
||||||
|
try:
|
||||||
|
res = ReadLaunchFilesResponse()
|
||||||
|
for launch_file in req.files:
|
||||||
|
path = get_launch_file_path(launch_file.package, launch_file.name)
|
||||||
|
rospy.loginfo('read file %s', path)
|
||||||
|
launch_file.content = open(path, 'r').read()
|
||||||
|
res.files.append(launch_file)
|
||||||
|
res.success = True
|
||||||
|
return res
|
||||||
|
except Exception as e:
|
||||||
|
rospy.logerr(str(e))
|
||||||
|
return {'success': False, 'message': str(e)}
|
||||||
|
|
||||||
|
|
||||||
|
def write(req):
|
||||||
|
try:
|
||||||
|
# write files
|
||||||
|
for launch_file in req.files:
|
||||||
|
if not launch_file.name.endswith('.launch'):
|
||||||
|
raise Exception('Launch file name should end with .launch')
|
||||||
|
path = get_launch_file_path(launch_file.package, launch_file.name)
|
||||||
|
rospy.loginfo('write file %s', path)
|
||||||
|
if backup:
|
||||||
|
shutil.copyfile(path, path + '.bak.launch')
|
||||||
|
with open(path, 'w') as f:
|
||||||
|
f.write(launch_file.content)
|
||||||
|
|
||||||
|
# restart the system
|
||||||
|
if not apply_command:
|
||||||
|
return {'success': True}
|
||||||
|
|
||||||
|
rospy.loginfo('apply: %s', apply_command)
|
||||||
|
res = os.system(apply_command)
|
||||||
|
if res == 0:
|
||||||
|
return {'success': True}
|
||||||
|
else:
|
||||||
|
return {'success': False, 'message': 'Error invoking %s' % apply_command}
|
||||||
|
except Exception as e:
|
||||||
|
rospy.logerr(str(e))
|
||||||
|
return {'success': False, 'message': str(e)}
|
||||||
|
|
||||||
|
|
||||||
|
rospy.Service('~read', ReadLaunchFiles, read)
|
||||||
|
rospy.Service('~write', WriteLaunchFiles, write)
|
||||||
|
|
||||||
|
|
||||||
|
rospy.spin()
|
||||||
7
roslaunch_editor/srv/ReadLaunchFiles.srv
Normal file
7
roslaunch_editor/srv/ReadLaunchFiles.srv
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
# Read launch-files
|
||||||
|
|
||||||
|
LaunchFile[] files # content field is ignored
|
||||||
|
---
|
||||||
|
bool success
|
||||||
|
string message
|
||||||
|
LaunchFile[] files
|
||||||
6
roslaunch_editor/srv/WriteLaunchFiles.srv
Normal file
6
roslaunch_editor/srv/WriteLaunchFiles.srv
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
# Write launch-files and apply (restart the system)
|
||||||
|
|
||||||
|
LaunchFile[] files
|
||||||
|
---
|
||||||
|
bool success
|
||||||
|
string message
|
||||||
34
roslaunch_editor/test/test.launch
Normal file
34
roslaunch_editor/test/test.launch
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
<launch>
|
||||||
|
<arg name="string" default="value"/> <!-- simple string value -->
|
||||||
|
<arg name="no_comment" default="value"/>
|
||||||
|
<arg name="boolean" default="true"/> <!-- simple boolean -->
|
||||||
|
<arg name="hidden" default="value"/> <!-- hiddent arg, noeditor -->
|
||||||
|
<arg name="without_default"/> <!-- without default -->
|
||||||
|
|
||||||
|
<arg name="options" default="bar"/> <!-- simple options: foo, bar, baz, killa, gorilla -->
|
||||||
|
<arg name="options_extra" default="extra"/> <!-- options with extra value: foo, bar, baz -->
|
||||||
|
|
||||||
|
<!-- arg with preceding comment -->
|
||||||
|
<arg name="preceding_comment" default="value"/>
|
||||||
|
|
||||||
|
<arg name="dirty_string" default="hello "quotes" and <tags>"/> <!-- "dirty" <arg>'s and comment value -->
|
||||||
|
|
||||||
|
<arg name="long_long_long_long_long_arg" default="very long long long long long long long long value"/> <!-- long_long_long_long_long_arg is loooooooooooooooooooooooooooooooooooooooooooooong -->
|
||||||
|
|
||||||
|
<arg name="no_subseqent_comment" default="value"/>
|
||||||
|
<!-- this comment should not be displayed -->
|
||||||
|
|
||||||
|
<arg name="integer" default="-245"/> <!-- integer value -->
|
||||||
|
|
||||||
|
<arg name="float" default="-0.045"/> <!-- float value -->
|
||||||
|
|
||||||
|
<arg name="float_dot" default=".2"/> <!-- float without leading zero -->
|
||||||
|
|
||||||
|
<arg name="float_scientific" default="-1e-5"/> <!-- scientific float value -->
|
||||||
|
|
||||||
|
<arg name="nan" default="nan"/> <!-- nan value -->
|
||||||
|
|
||||||
|
<arg name="inf" default="inf"/> <!-- inf value -->
|
||||||
|
|
||||||
|
<arg name="null" default="null"/> <!-- null value -->
|
||||||
|
</launch>
|
||||||
140
roslaunch_editor/www/index.html
Normal file
140
roslaunch_editor/www/index.html
Normal file
@@ -0,0 +1,140 @@
|
|||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<script src="roslib.js"></script>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: sans-serif;
|
||||||
|
font-size: 14px;
|
||||||
|
background: white;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
body.connected {
|
||||||
|
background: white;
|
||||||
|
}
|
||||||
|
h1 {
|
||||||
|
margin-top: 20px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.editor {
|
||||||
|
margin: 0px auto;
|
||||||
|
width: 800px;
|
||||||
|
overflow: hidden;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.editor label code {
|
||||||
|
width: 200px;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
display: inline-block;
|
||||||
|
white-space: nowrap;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
fieldset {
|
||||||
|
border: 1px solid rgb(232, 232, 232);
|
||||||
|
padding: 20px;
|
||||||
|
text-align: left;
|
||||||
|
margin: 20px 0;
|
||||||
|
}
|
||||||
|
.editor label {
|
||||||
|
display: block;
|
||||||
|
padding: 10px 0;
|
||||||
|
-webkit-user-select: none;
|
||||||
|
user-select: none;
|
||||||
|
/* margin-bottom: 20px; */
|
||||||
|
border-bottom: 1px solid rgb(232, 232, 232);
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
.editor label:last-of-type {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
.editor label input, select {
|
||||||
|
font-size: 15px;
|
||||||
|
font-family: monospace;
|
||||||
|
width: 200px;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
.editor .switch {
|
||||||
|
width: 200px;
|
||||||
|
vertical-align: middle;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
.editor .comment {
|
||||||
|
margin-left: 10px;
|
||||||
|
vertical-align: middle;
|
||||||
|
display: inline-block;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
vertical-align: middle;
|
||||||
|
max-width: 320px;
|
||||||
|
}
|
||||||
|
button {
|
||||||
|
font-size: 20px;
|
||||||
|
border-radius: 5px;
|
||||||
|
padding: 10px;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
margin: 20px;
|
||||||
|
width: 200px;
|
||||||
|
background: #4b9dd7;
|
||||||
|
color: white;
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
body.loaded button {
|
||||||
|
visibility: visible;
|
||||||
|
}
|
||||||
|
textarea {
|
||||||
|
margin-left: 50px;
|
||||||
|
font-family: monospace;
|
||||||
|
vertical-align: top;
|
||||||
|
font-size: 15px;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: auto;
|
||||||
|
font-size: 10px;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
.panel {
|
||||||
|
text-align: center;
|
||||||
|
position: sticky;
|
||||||
|
bottom: 0;
|
||||||
|
width: 100%;
|
||||||
|
background: rgba(255, 255, 255, 0.9);
|
||||||
|
}
|
||||||
|
.loader-overlay {
|
||||||
|
background: rgba(0,0,0,0.5);
|
||||||
|
position: fixed;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
.loader {
|
||||||
|
position: fixed;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
}
|
||||||
|
body.loaded.connected .loader-overlay {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<link rel="stylesheet" href="switch.css">
|
||||||
|
<link rel="stylesheet" href="loader.css">
|
||||||
|
<title>roslaunch editor</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<form class="editor">
|
||||||
|
</form>
|
||||||
|
<div class="panel">
|
||||||
|
<button id=applybtn onclick="apply()">Apply</button>
|
||||||
|
</div>
|
||||||
|
<div class="loader-overlay">
|
||||||
|
<div class="loader lds-grid"><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div></div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
<script src="ros.js"></script>
|
||||||
|
<script src="main.js"></script>
|
||||||
|
</html>
|
||||||
67
roslaunch_editor/www/loader.css
Normal file
67
roslaunch_editor/www/loader.css
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
.lds-grid {
|
||||||
|
display: inline-block;
|
||||||
|
position: relative;
|
||||||
|
width: 80px;
|
||||||
|
height: 80px;
|
||||||
|
}
|
||||||
|
.lds-grid div {
|
||||||
|
position: absolute;
|
||||||
|
width: 15px;
|
||||||
|
height: 15px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: #fff;
|
||||||
|
animation: lds-grid 1.2s linear infinite;
|
||||||
|
}
|
||||||
|
.lds-grid div:nth-child(1) {
|
||||||
|
top: 8px;
|
||||||
|
left: 8px;
|
||||||
|
animation-delay: 0s;
|
||||||
|
}
|
||||||
|
.lds-grid div:nth-child(2) {
|
||||||
|
top: 8px;
|
||||||
|
left: 32px;
|
||||||
|
animation-delay: -0.4s;
|
||||||
|
}
|
||||||
|
.lds-grid div:nth-child(3) {
|
||||||
|
top: 8px;
|
||||||
|
left: 56px;
|
||||||
|
animation-delay: -0.8s;
|
||||||
|
}
|
||||||
|
.lds-grid div:nth-child(4) {
|
||||||
|
top: 32px;
|
||||||
|
left: 8px;
|
||||||
|
animation-delay: -0.4s;
|
||||||
|
}
|
||||||
|
.lds-grid div:nth-child(5) {
|
||||||
|
top: 32px;
|
||||||
|
left: 32px;
|
||||||
|
animation-delay: -0.8s;
|
||||||
|
}
|
||||||
|
.lds-grid div:nth-child(6) {
|
||||||
|
top: 32px;
|
||||||
|
left: 56px;
|
||||||
|
animation-delay: -1.2s;
|
||||||
|
}
|
||||||
|
.lds-grid div:nth-child(7) {
|
||||||
|
top: 56px;
|
||||||
|
left: 8px;
|
||||||
|
animation-delay: -0.8s;
|
||||||
|
}
|
||||||
|
.lds-grid div:nth-child(8) {
|
||||||
|
top: 56px;
|
||||||
|
left: 32px;
|
||||||
|
animation-delay: -1.2s;
|
||||||
|
}
|
||||||
|
.lds-grid div:nth-child(9) {
|
||||||
|
top: 56px;
|
||||||
|
left: 56px;
|
||||||
|
animation-delay: -1.6s;
|
||||||
|
}
|
||||||
|
@keyframes lds-grid {
|
||||||
|
0%, 100% {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
}
|
||||||
287
roslaunch_editor/www/main.js
Normal file
287
roslaunch_editor/www/main.js
Normal file
@@ -0,0 +1,287 @@
|
|||||||
|
var editorElem = document.querySelector('form.editor');
|
||||||
|
|
||||||
|
// escape html
|
||||||
|
function esc(unsafe) {
|
||||||
|
return unsafe
|
||||||
|
.replace(/&/g, "&")
|
||||||
|
.replace(/</g, "<")
|
||||||
|
.replace(/>/g, ">")
|
||||||
|
.replace(/"/g, """)
|
||||||
|
.replace(/'/g, "'");
|
||||||
|
}
|
||||||
|
|
||||||
|
function getType(value) {
|
||||||
|
if (value == 'true' || value == 'false') {
|
||||||
|
return 'bool';
|
||||||
|
} else if (Number.isInteger(Number(value))) {
|
||||||
|
return 'int';
|
||||||
|
} else if (!isNaN(Number(value))) {
|
||||||
|
return 'float'
|
||||||
|
} else {
|
||||||
|
return 'string';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var items = {};
|
||||||
|
|
||||||
|
function generateForm(item, content) {
|
||||||
|
var parser = new DOMParser();
|
||||||
|
var doc = items[item].doc = parser.parseFromString(content, 'text/xml');
|
||||||
|
var html = '';
|
||||||
|
|
||||||
|
// go though all arg tags
|
||||||
|
var args = doc.querySelectorAll('launch > arg');
|
||||||
|
for (var arg of args) {
|
||||||
|
var name = arg.getAttribute('name');
|
||||||
|
var comment = '';
|
||||||
|
var value = arg.getAttribute('default');
|
||||||
|
if (value === null) value = '';
|
||||||
|
var type = getType(value);
|
||||||
|
|
||||||
|
// get comment from previous sibling comment with no more than one line break in-between -->
|
||||||
|
var prev = arg.previousSibling;
|
||||||
|
if (prev && prev.nodeType == Node.TEXT_NODE && prev.textContent.split('\n').length <= 2) {
|
||||||
|
prev = arg.previousSibling.previousSibling;
|
||||||
|
if (prev && prev.nodeType == Node.COMMENT_NODE && prev != next /* don't use one comment twice */) {
|
||||||
|
prevArgPrevComment = prev;
|
||||||
|
comment = prev.textContent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// get comment from next sibling comment without line breaks in-between (more priority)
|
||||||
|
var next = arg.nextSibling;
|
||||||
|
if (next.nodeType == Node.TEXT_NODE && next.textContent.indexOf('\n') == -1) {
|
||||||
|
next = next.nextSibling;
|
||||||
|
}
|
||||||
|
if (next.nodeType == Node.COMMENT_NODE) {
|
||||||
|
comment = next.textContent;
|
||||||
|
}
|
||||||
|
|
||||||
|
// get options
|
||||||
|
var options = comment.match(/^(.*?): ((.*), (.*))$/);
|
||||||
|
if (options) {
|
||||||
|
comment = options[1];
|
||||||
|
options = options[2].split(',');
|
||||||
|
options = options.map(function(option) { return option.trim(); });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (comment.indexOf('noeditor') != -1) continue;
|
||||||
|
|
||||||
|
if (p.hide_uncommented && !comment) continue;
|
||||||
|
|
||||||
|
var path = `launch > arg[name=${name}]`;
|
||||||
|
html += `<label><code title="${name}">${name}</code>`;
|
||||||
|
|
||||||
|
if (type == 'bool') {
|
||||||
|
html += `<span class=switch><input data-item="${esc(item)}" data-path="${path}" type=checkbox ${value == 'true' ? 'checked' : ''}><i></i></span>`;
|
||||||
|
// html += `<label>${unslugName}<input value=${value} list=bool></label>`;
|
||||||
|
} else if (type == 'int') {
|
||||||
|
html += `<input data-item="${esc(item)}" data-path="${path}" type=number value="${esc(value)}">`;
|
||||||
|
} else if (type == 'float') {
|
||||||
|
let step = '0.1';
|
||||||
|
try { step = '0.' + '0'.repeat(value.match(/\.(.*)$/)[1].length - 1) + '1'; } catch {}; // TODO: scientific
|
||||||
|
html += `<input data-item="${esc(item)}" data-path="${path}" type=number step="${step}" value="${esc(value)}">`;
|
||||||
|
} else if (options) {
|
||||||
|
if (!options.includes(value)) {
|
||||||
|
options.unshift(value);
|
||||||
|
}
|
||||||
|
var optionsHTML = options.map(function(option) {
|
||||||
|
return `<option${option == value ? ' selected' : ''}>${esc(option)}</option>`;
|
||||||
|
}).join('');
|
||||||
|
html += `<select data-item="${esc(item)}" data-path="${path}">${optionsHTML}</select>`;
|
||||||
|
} else {
|
||||||
|
html += `<input data-item="${esc(item)}" data-path="${path}" value="${esc(value)}">`;
|
||||||
|
}
|
||||||
|
|
||||||
|
html += `<span class=comment title="${esc(comment)}">${esc(comment)}</span></label>`
|
||||||
|
}
|
||||||
|
|
||||||
|
return html;
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateDocs() {
|
||||||
|
editorElem.querySelectorAll('input, select').forEach(function(elem) {
|
||||||
|
var type = elem.getAttribute('type');
|
||||||
|
if (type == 'checkbox') {
|
||||||
|
var value = String(elem.checked);
|
||||||
|
} else {
|
||||||
|
var value = elem.value;
|
||||||
|
}
|
||||||
|
var path = elem.getAttribute('data-path');
|
||||||
|
var item = elem.getAttribute('data-item');
|
||||||
|
var element = items[item].doc.querySelector(path);
|
||||||
|
if (element.getAttribute('default') === null && value == '') {
|
||||||
|
// don't add empty default if it's not set
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
element.setAttribute('default', value);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
editorElem.addEventListener('change', updateDocs);
|
||||||
|
|
||||||
|
function addLaunchFile(name, content) {
|
||||||
|
html = `<fieldset><legend><code>${name}</code></legend>`;
|
||||||
|
html += generateForm(name, content);
|
||||||
|
html += '</fieldset>'
|
||||||
|
editorElem.innerHTML += html;
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseItem(str) {
|
||||||
|
var parsed = str.match(/(.*?)\/(.*)/);
|
||||||
|
var package = parsed[1];
|
||||||
|
var name = parsed[2];
|
||||||
|
return items[str] = { package, name };
|
||||||
|
}
|
||||||
|
|
||||||
|
var clover;
|
||||||
|
|
||||||
|
function determineMode() {
|
||||||
|
return new Promise(function(resolve, reject) {
|
||||||
|
function errcb(err) {
|
||||||
|
alert(err);
|
||||||
|
reject();
|
||||||
|
}
|
||||||
|
// check roslaunch_editor's read service
|
||||||
|
ros.getServiceType('/roslaunch_editor/read', function(t) {
|
||||||
|
if (t == 'roslaunch_editor/ReadLaunchFiles') {
|
||||||
|
clover = false;
|
||||||
|
resolve();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// check clover's exec service
|
||||||
|
ros.getServiceType('/exec', function(t) {
|
||||||
|
if (t == 'clover/Execute') {
|
||||||
|
clover = true;
|
||||||
|
resolve();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
alert('Neither /roslaunch_editor/read nor /exec service not found');
|
||||||
|
reject();
|
||||||
|
}, errcb);
|
||||||
|
}, errcb);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function errCallback(err) {
|
||||||
|
alert('Error calling service: ' + err);
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadItems() {
|
||||||
|
editorElem.innerHTML = '';
|
||||||
|
|
||||||
|
if (typeof p.items == 'string') {
|
||||||
|
p.items = p.items.split(',');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (clover) {
|
||||||
|
const boundary = '===BOUNDARY===';
|
||||||
|
var cmd = 'bash -ic "' + p.items.map(function(item) {
|
||||||
|
parseItem(item);
|
||||||
|
return `roscat ${items[item].package} ${items[item].name}`;
|
||||||
|
}).join(` && echo -n ${boundary} && `) + '"';
|
||||||
|
exec.callService(new ROSLIB.ServiceRequest({ cmd }), function(res) {
|
||||||
|
if (res.code != 0) {
|
||||||
|
alert('Error reading launch-files');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
res.output.split(boundary).forEach(function(content, i) {
|
||||||
|
addLaunchFile(p.items[i], content);
|
||||||
|
});
|
||||||
|
document.body.classList.add('loaded');
|
||||||
|
}, errCallback);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
var req = new ROSLIB.ServiceRequest();
|
||||||
|
req.files = p.items.map(function(item) {
|
||||||
|
parseItem(item);
|
||||||
|
return { 'package': items[item].package, 'name': items[item].name }
|
||||||
|
});
|
||||||
|
readLaunchFiles.callService(req, function(res) {
|
||||||
|
res.files.forEach(function(item, i) {
|
||||||
|
addLaunchFile(p.items[i], item.content);
|
||||||
|
});
|
||||||
|
document.body.classList.add('loaded');
|
||||||
|
}, errCallback);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// load params
|
||||||
|
Promise.all([
|
||||||
|
determineMode(),
|
||||||
|
readParam('apply_command', false, ''),
|
||||||
|
readParam('items', true),
|
||||||
|
readParam('hide_uncommented', true, false),
|
||||||
|
readParam('standalone', false, false),
|
||||||
|
]).then(() => loadItems());
|
||||||
|
|
||||||
|
function shellEscape(a) {
|
||||||
|
// https://github.com/xxorax/node-shell-escape
|
||||||
|
var ret = [];
|
||||||
|
|
||||||
|
a.forEach(function (s) {
|
||||||
|
if (!['&&', '||', '|', '>', '<', '>>', '<<'].includes(s) && /[^A-Za-z0-9_\/:=-]/.test(s)) {
|
||||||
|
s = "'" + s.replace(/'/g, "'\\''") + "'";
|
||||||
|
s = s.replace(/^(?:'')+/g, '') // unduplicate single-quote at the beginning
|
||||||
|
.replace(/\\'''/g, "\\'"); // remove non-escaped single-quote if there are enclosed between 2 escaped
|
||||||
|
}
|
||||||
|
ret.push(s);
|
||||||
|
});
|
||||||
|
|
||||||
|
return ret.join(' ');
|
||||||
|
}
|
||||||
|
|
||||||
|
var applying = false;
|
||||||
|
|
||||||
|
// TODO: reread launch file on connected
|
||||||
|
function apply() {
|
||||||
|
applying = true;
|
||||||
|
document.body.classList.remove('loaded');
|
||||||
|
|
||||||
|
updateDocs();
|
||||||
|
|
||||||
|
if (clover) {
|
||||||
|
var script = '';
|
||||||
|
for (item in items) {
|
||||||
|
if (script) script += ' && ';
|
||||||
|
var content = items[item].doc.documentElement.outerHTML;
|
||||||
|
script += `_roscmd ${items[item].package} ${items[item].name} && echo ${shellEscape([content])} > $arg`;
|
||||||
|
}
|
||||||
|
if (p.apply_command) {
|
||||||
|
script + ' && ' + p.apply_command;
|
||||||
|
}
|
||||||
|
|
||||||
|
var cmd = shellEscape(['bash', '-ic', script]);
|
||||||
|
|
||||||
|
exec.callService(new ROSLIB.ServiceRequest({ cmd: cmd }), function(res) {
|
||||||
|
if (res.code != 0) {
|
||||||
|
alert('Error');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
document.body.classList.add('loaded');
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
|
||||||
|
var req = new ROSLIB.ServiceRequest();
|
||||||
|
req.files = Object.keys(items).map(function(key) {
|
||||||
|
var item = items[key];
|
||||||
|
return { package: item.package, name: item.name, content: item.doc.documentElement.outerHTML }
|
||||||
|
});
|
||||||
|
|
||||||
|
writeLaunchFiles.callService(req, function(res) {
|
||||||
|
if (!res.success) {
|
||||||
|
alert('Error writing files ' + res.message);
|
||||||
|
}
|
||||||
|
document.body.classList.add('loaded');
|
||||||
|
applying = false;
|
||||||
|
}, errCallback);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ros.on('connection', function() {
|
||||||
|
if (applying) {
|
||||||
|
// connection after applying tells that system restarted
|
||||||
|
document.body.classList.add('loaded');
|
||||||
|
applying = false;
|
||||||
|
}
|
||||||
|
})
|
||||||
46
roslaunch_editor/www/ros.js
Normal file
46
roslaunch_editor/www/ros.js
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
var url = 'ws://' + location.hostname + ':9090';
|
||||||
|
var ros = new ROSLIB.Ros({ url });
|
||||||
|
|
||||||
|
ros.on('connection', function () {
|
||||||
|
document.body.classList.add('connected');
|
||||||
|
});
|
||||||
|
|
||||||
|
ros.on('close', function () {
|
||||||
|
document.body.classList.remove('connected');
|
||||||
|
setTimeout(function() {
|
||||||
|
try {
|
||||||
|
ros.connect(url);
|
||||||
|
} catch (e) {}
|
||||||
|
}, 2000);
|
||||||
|
});
|
||||||
|
|
||||||
|
var exec = new ROSLIB.Service({ ros: ros, name : '/exec', serviceType : 'clover/Execute' });
|
||||||
|
var readLaunchFiles = new ROSLIB.Service({ ros: ros, name: '/roslaunch_editor/read', serviceType: 'roslaunch_editor/ReadLaunchFiles' });
|
||||||
|
var writeLaunchFiles = new ROSLIB.Service({ ros: ros, name: '/roslaunch_editor/write', serviceType: 'roslaunch_editor/WriteLaunchFiles' });
|
||||||
|
|
||||||
|
var p = {}; // parameters storage
|
||||||
|
|
||||||
|
function readParam(name, fromUrl, _default) {
|
||||||
|
return new Promise(function(resolve, reject) {
|
||||||
|
// read from url
|
||||||
|
if (fromUrl && ((p[name] = new URL(window.location.href).searchParams.get(name)) !== null)) {
|
||||||
|
resolve();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// read from ROS params
|
||||||
|
new ROSLIB.Param({ ros: ros, name: '/roslaunch_editor/' + name }).get(function(val) {
|
||||||
|
if (val === null) {
|
||||||
|
if (_default === undefined) {
|
||||||
|
alert('Cannot read required parameter ' + name);
|
||||||
|
reject();
|
||||||
|
} else {
|
||||||
|
p[name] = _default;
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
p[name] = val;
|
||||||
|
resolve();
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
4560
roslaunch_editor/www/roslib.js
vendored
Normal file
4560
roslaunch_editor/www/roslib.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
57
roslaunch_editor/www/switch.css
Normal file
57
roslaunch_editor/www/switch.css
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
.switch {
|
||||||
|
display: inline-block;
|
||||||
|
cursor: pointer;
|
||||||
|
-webkit-tap-highlight-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.switch i {
|
||||||
|
position: relative;
|
||||||
|
display: inline-block;
|
||||||
|
margin-right: .5rem;
|
||||||
|
width: 46px;
|
||||||
|
height: 26px;
|
||||||
|
background-color: #e6e6e6;
|
||||||
|
border-radius: 23px;
|
||||||
|
vertical-align: text-bottom;
|
||||||
|
transition: all 0.3s linear;
|
||||||
|
}
|
||||||
|
|
||||||
|
.switch i::before {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
width: 42px;
|
||||||
|
height: 22px;
|
||||||
|
background-color: #fff;
|
||||||
|
border-radius: 11px;
|
||||||
|
transform: translate3d(2px, 2px, 0) scale3d(1, 1, 1);
|
||||||
|
transition: all 0.25s linear;
|
||||||
|
}
|
||||||
|
|
||||||
|
.switch i::after {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
width: 22px;
|
||||||
|
height: 22px;
|
||||||
|
background-color: #fff;
|
||||||
|
border-radius: 11px;
|
||||||
|
box-shadow: 0 2px 2px rgba(0, 0, 0, 0.24);
|
||||||
|
transform: translate3d(2px, 2px, 0);
|
||||||
|
transition: all 0.2s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.switch:active i::after {
|
||||||
|
width: 28px;
|
||||||
|
transform: translate3d(2px, 2px, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.switch:active input:checked + i::after { transform: translate3d(16px, 2px, 0); }
|
||||||
|
|
||||||
|
.switch input { display: none; }
|
||||||
|
|
||||||
|
.switch input:checked + i { background-color: #4B9DD7; }
|
||||||
|
|
||||||
|
.switch input:checked + i::before { transform: translate3d(18px, 2px, 0) scale3d(0, 0, 0); }
|
||||||
|
|
||||||
|
.switch input:checked + i::after { transform: translate3d(22px, 2px, 0); }
|
||||||
Reference in New Issue
Block a user