diff --git a/.travis.rosinstall.melodic b/.travis.rosinstall.melodic index d9add05bce..278b28a7f6 100644 --- a/.travis.rosinstall.melodic +++ b/.travis.rosinstall.melodic @@ -54,3 +54,15 @@ local-name: DENSORobot/denso_cobotta_ros uri: https://github.com/DENSORobot/denso_cobotta_ros.git version: bb60e75adb8477ed3402561b4ec3ba687af3f397 +# switchbot_ros is not correctly released. +# see: https://github.com/jsk-ros-pkg/jsk_3rdparty/issues/356 +- git: + local-name: jsk-ros-pkg/jsk_3rdparty + uri: https://github.com/jsk-ros-pkg/jsk_3rdparty.git + version: f0ab7bba54523b8f9945ed4a3e9c0efec0c8dde9 +# for jsk_spot_teleop in jsk_robot +# spot_msgs is not released +- git: + local-name: clearpathrobotics/spot_ros + uri: https://github.com/clearpathrobotics/spot_ros.git + version: 8e1554b95cf2875a53f14fda8165957559b0ffb7 \ No newline at end of file diff --git a/jsk_robot_common/jsk_robot_startup/lifelog/mongodb_replication_params.yaml b/jsk_robot_common/jsk_robot_startup/lifelog/mongodb_replication_params.yaml index 966a90f87d..2a8b99f9ea 100644 --- a/jsk_robot_common/jsk_robot_startup/lifelog/mongodb_replication_params.yaml +++ b/jsk_robot_common/jsk_robot_startup/lifelog/mongodb_replication_params.yaml @@ -1,4 +1,4 @@ -mongodb_store_extras: [["robot-database.jsk.imi.i.u-tokyo.ac.jp", 27017],["musca.jsk.imi.i.u-tokyo.ac.jp",27017]] +mongodb_store_extras: [["robot-database.jsk.imi.i.u-tokyo.ac.jp", 27017]] replication_client: interval: 3600 delete_after_move: true diff --git a/jsk_robot_common/jsk_robot_startup/package.xml b/jsk_robot_common/jsk_robot_startup/package.xml index 8b1e644b85..c008fbdf44 100644 --- a/jsk_robot_common/jsk_robot_startup/package.xml +++ b/jsk_robot_common/jsk_robot_startup/package.xml @@ -19,6 +19,7 @@ sensor_msgs dynamic_reconfigure urdf + actionlib_msgs app_manager dynamic_reconfigure diff --git a/jsk_robot_common/jsk_robot_startup/scripts/head_teleop_joy_completion.py b/jsk_robot_common/jsk_robot_startup/scripts/head_teleop_joy_completion.py index cc323c7ee1..a1637a517a 100755 --- a/jsk_robot_common/jsk_robot_startup/scripts/head_teleop_joy_completion.py +++ b/jsk_robot_common/jsk_robot_startup/scripts/head_teleop_joy_completion.py @@ -58,9 +58,9 @@ def callback(self,msg): rospy.loginfo("raw: x={:5.2f}, y={:5.2f}, v={:5.2f}, r={:5.2f} -> cmd: x={:5.2f}, y={:5.2f}, r={:5.2f}".format(x, y, v, r, xx, yy, rr)) msg.axes = list(msg.axes) # tuple to list - msg.axes[self.axis_linear_x] = xx - msg.axes[self.axis_linear_y] = yy - msg.axes[self.axis_angular] = rr + msg.axes[self.axis_linear_x] = max(min(xx, self.axis_linear_x_max), self.axis_linear_x_min) + msg.axes[self.axis_linear_y] = max(min(yy, self.axis_linear_y_max), self.axis_linear_y_min) + msg.axes[self.axis_angular] = max(min(rr, self.axis_angular_max), self.axis_angular_min) self.pub.publish(msg) @@ -74,6 +74,13 @@ def spin(self): if rospy.Time.now() - self.last_publish_time > rospy.Duration(3.0): self.pub.publish(msg) + def get_param(self, key1, key2, value): + p = rospy.get_param(key1) + if type(p) == dict and p.has_key(key2): + return p[key2] + else: + return value + def __init__(self): rospy.init_node('joy_topic_completion') self.pass_through = rospy.get_param('~pass_through', True) @@ -81,6 +88,12 @@ def __init__(self): self.axis_linear_x = int(rospy.get_param('~axis_linear', {'x': 1})['x']) self.axis_linear_y = int(rospy.get_param('~axis_linear', {'y': 2})['y']) self.axis_angular = int(rospy.get_param('~axis_angular', {'yaw': 0})['yaw']) + self.axis_linear_x_min = float(self.get_param('~axis_linear', 'x_min', -1)) + self.axis_linear_x_max = float(self.get_param('~axis_linear', 'x_max', 1)) + self.axis_linear_y_min = float(self.get_param('~axis_linear', 'y_min', -1)) + self.axis_linear_y_max = float(self.get_param('~axis_linear', 'y_max', 1)) + self.axis_angular_min = float(self.get_param('~axis_angular', 'yaw_min', -0.5)) + self.axis_angular_max = float(self.get_param('~axis_angular', 'yaw_max', 0.5)) self.last_publish_time = rospy.Time.now() diff --git a/jsk_robot_common/jsk_robot_startup/src/quadruped_joystick_teleop.cpp b/jsk_robot_common/jsk_robot_startup/src/quadruped_joystick_teleop.cpp index 9a35c227a7..d0077eec62 100644 --- a/jsk_robot_common/jsk_robot_startup/src/quadruped_joystick_teleop.cpp +++ b/jsk_robot_common/jsk_robot_startup/src/quadruped_joystick_teleop.cpp @@ -67,7 +67,7 @@ class TeleopManager sound_play::SoundRequest msg; msg.sound = sound_play::SoundRequest::SAY; msg.command = sound_play::SoundRequest::PLAY_ONCE; - msg.volume = 1.0; + msg.volume = 0.3; msg.arg = message; pub_sound_play_.publish(msg); } @@ -92,7 +92,7 @@ class TeleopManager if ( client_estop_hard_.call(srv) && srv.response.success ) { ROS_INFO_STREAM("Service " << client_estop_hard_.getService() << " succeeded."); } else { - ROS_ERROR_STREAM("Service " << client_estop_hard_.getService() << " failed."); + ROS_ERROR_STREAM("Service " << client_estop_hard_.getService() << " failed. (" << srv.response.message << ")"); } pressed_[button_estop_hard_] = true; } @@ -115,7 +115,7 @@ class TeleopManager if ( client_estop_gentle_.call(srv) && srv.response.success ) { ROS_INFO_STREAM("Service " << client_estop_gentle_.getService() << " succeeded."); } else { - ROS_ERROR_STREAM("Service " << client_estop_gentle_.getService() << " failed."); + ROS_ERROR_STREAM("Service " << client_estop_gentle_.getService() << " failed. (" << srv.response.message << ")"); } pressed_[button_estop_gentle_] = true; } @@ -138,7 +138,7 @@ class TeleopManager if ( client_power_off_.call(srv) && srv.response.success ) { ROS_INFO_STREAM("Service " << client_power_off_.getService() << " succeeded."); } else { - ROS_ERROR_STREAM("Service " << client_power_off_.getService() << " failed."); + ROS_ERROR_STREAM("Service " << client_power_off_.getService() << " failed. (" << srv.response.message << ")"); } pressed_[button_power_off_] = true; } @@ -161,7 +161,7 @@ class TeleopManager if ( client_power_on_.call(srv) && srv.response.success ) { ROS_INFO_STREAM("Service " << client_power_on_.getService() << " succeeded."); } else { - ROS_ERROR_STREAM("Service " << client_power_on_.getService() << " failed."); + ROS_ERROR_STREAM("Service " << client_power_on_.getService() << " failed. (" << srv.response.message << ")"); } pressed_[button_power_on_] = true; } @@ -184,7 +184,7 @@ class TeleopManager if ( client_self_right_.call(srv) && srv.response.success ) { ROS_INFO_STREAM("Service " << client_self_right_.getService() << " succeeded."); } else { - ROS_ERROR_STREAM("Service " << client_self_right_.getService() << " failed."); + ROS_ERROR_STREAM("Service " << client_self_right_.getService() << " failed. (" << srv.response.message << ")"); } pressed_[button_self_right_] = true; } @@ -207,7 +207,7 @@ class TeleopManager if ( client_sit_.call(srv) && srv.response.success ) { ROS_INFO_STREAM("Service " << client_sit_.getService() << " succeeded."); } else { - ROS_ERROR_STREAM("Service " << client_sit_.getService() << " failed."); + ROS_ERROR_STREAM("Service " << client_sit_.getService() << " failed. (" << srv.response.message << ")"); } pressed_[button_sit_] = true; } @@ -230,7 +230,7 @@ class TeleopManager if ( client_stand_.call(srv) && srv.response.success ) { ROS_INFO_STREAM("Service " << client_stand_.getService() << " succeeded."); } else { - ROS_ERROR_STREAM("Service " << client_stand_.getService() << " failed."); + ROS_ERROR_STREAM("Service " << client_stand_.getService() << " failed. (" << srv.response.message << ")"); } pressed_[button_stand_] = true; } @@ -253,7 +253,7 @@ class TeleopManager if ( client_stop_.call(srv) && srv.response.success ) { ROS_INFO_STREAM("Service " << client_stop_.getService() << " succeeded."); } else { - ROS_ERROR_STREAM("Service " << client_stop_.getService() << " failed."); + ROS_ERROR_STREAM("Service " << client_stop_.getService() << " failed. (" << srv.response.message << ")"); } pressed_[button_stop_] = true; } @@ -276,7 +276,7 @@ class TeleopManager if ( client_release_.call(srv) && srv.response.success ) { ROS_INFO_STREAM("Service " << client_release_.getService() << " succeeded."); } else { - ROS_ERROR_STREAM("Service " << client_release_.getService() << " failed."); + ROS_ERROR_STREAM("Service " << client_release_.getService() << " failed. (" << srv.response.message << ")"); } pressed_[button_release_] = true; } @@ -299,7 +299,7 @@ class TeleopManager if ( client_claim_.call(srv) && srv.response.success ) { ROS_INFO_STREAM("Service " << client_claim_.getService() << " succeeded."); } else { - ROS_ERROR_STREAM("Service " << client_claim_.getService() << " failed."); + ROS_ERROR_STREAM("Service " << client_claim_.getService() << " failed. (" << srv.response.message << ")"); } pressed_[button_claim_] = true; } @@ -328,7 +328,7 @@ class TeleopManager ROS_INFO_STREAM("Service " << client_stair_mode_.getService() << " succeeded. set to " << req_next_stair_mode_.data); req_next_stair_mode_.data = not req_next_stair_mode_.data; } else { - ROS_ERROR_STREAM("Service " << client_stair_mode_.getService() << " failed."); + ROS_ERROR_STREAM("Service " << client_stair_mode_.getService() << " failed. (" << srv.response.message << ")"); } pressed_[button_stair_mode_] = true; } @@ -350,7 +350,7 @@ class TeleopManager if (client_dock_.call(srv) && srv.response.success ){ ROS_INFO_STREAM("Service '" << client_dock_.getService() << "' succeeded."); } else { - ROS_ERROR_STREAM("Service '" << client_dock_.getService() << "' failed."); + ROS_ERROR_STREAM("Service '" << client_dock_.getService() << "' failed. (" << srv.response.message << ")"); } pressed_axes_[axe_dock_] = true; } @@ -358,9 +358,9 @@ class TeleopManager if (not pressed_axes_[axe_dock_]){ this->say("undock calling"); if (client_undock_.call(srv) && srv.response.success ){ - ROS_INFO("Service 'undock' succeeded."); + ROS_INFO_STREAM("Service '" << client_undock_.getService() << "' succeeded."); } else { - ROS_ERROR("Service 'undock' failed."); + ROS_ERROR_STREAM("Service '" << client_undock_.getService() << "' failed. (" << srv.response.message << ")"); } pressed_axes_[axe_dock_] = true; } @@ -384,7 +384,7 @@ class TeleopManager if (client_tuck_.call(srv) && srv.response.success ){ ROS_INFO_STREAM("Service '" << client_tuck_.getService() << "' succeeded."); } else { - ROS_ERROR_STREAM("Service '" << client_tuck_.getService() << "' failed."); + ROS_ERROR_STREAM("Service '" << client_tuck_.getService() << "' failed. (" << srv.response.message << ")"); } pressed_axes_[axe_tuck_] = true; } @@ -394,7 +394,7 @@ class TeleopManager if (client_untuck_.call(srv) && srv.response.success ){ ROS_INFO_STREAM("Service '" << client_untuck_.getService() << "' succeeded."); } else { - ROS_ERROR_STREAM("Service '" << client_untuck_.getService() << "' failed."); + ROS_ERROR_STREAM("Service '" << client_untuck_.getService() << "' failed. (" << srv.response.message << ")"); } pressed_axes_[axe_tuck_] = true; } diff --git a/jsk_spot_robot/README.md b/jsk_spot_robot/README.md new file mode 100644 index 0000000000..13696b4be1 --- /dev/null +++ b/jsk_spot_robot/README.md @@ -0,0 +1,149 @@ +jsk_spot_robot +============== + +## Manuals + +- [Supported Documents of Boston Dynamics](https://www.bostondynamics.com/spot/training/documentation) +- [Spot ROS User Documentation](http://www.clearpathrobotics.com/assets/guides/melodic/spot-ros/ros_usage.html#taking-control-of-the-robot) + +## Installation + +To setup a new internal PC and spot user, Please see [this page](./SetupInternalPCAndSpotUser.md). + +### How to set up a catkin workspace for a remote PC + +Create a workspace + +```bash +source /opt/ros/melodic/setup.bash +mkdir ~/spot_ws/src -p +cd ~/spot_ws/src +wstool init . +wstool set jsk-ros-pkg/jsk_robot https://github.com/k-okada/jsk_robot.git --git -v spot_arm +wstool update +wstool merge -t . jsk-ros-pkg/jsk_robot/jsk_spot_robot/jsk_spot_user.rosinstall +wstool update +rosdep update +rosdep install -y -r --from-paths . --ignore-src +cd $HOME/spot_ws +catkin init +catkin build -j4 jsk_spot_startup spoteus +``` + +## How to run + +### Bringup spot + +First, please turn on spot and turn on motors according to [the OPERATION section of spot user guide](https://www.bostondynamics.com/sites/default/files/inline-files/spot-user-guide.pdf) and power on the internal PC. + +Basically, ros systemd services will start automatically. So you can use spot now. + + +#### superviosur + +URI: http://:9001 + +#### rwt_app_chooser + +URI: http://:8000/rwt_app_chooser + +### Development with a remote PC + +Please create `spot_ws` to your PC. + +First, connect your development PC's wifi adapter to Access point of the robot. + +And for every terminals in this section, Set your ROS_IP and ROS_MASTER_URI and source spot_ws. + +``` +rossetmaster +rossetip +source ~/spot_ws/devel/setup.bash +``` + +##### spoteus + +spoteus is roseus client for ROS Interface of spot_ros. + +Open a terminal, setting up it for spot and run roseus repl. ( emacs shell or euslime are recommended. ) + +Then initialize it for spot + +``` +(load "package://spoteus/spot-interface.l") +(spot-init) +``` + +You can now control spot from roseus. +for example, when you want to move spot 1m forward, type. + +``` +(send *ri* :undock) ;; undock robot +(send *spot* :reset-pose) ;; change robot pose, use (objects (list *spot*)) to visuailze +(send *ri* :angle-vector (send *spot* :angle-vector) 2000 :default-controller) ;; send to real robot +(send *ri* :stow-arm) ;; contorl onbody-api +(send *ri* :sit) +``` + +### How to set up a catkin workspace in on-bodyPC + +First create user account into internal PC +``` +ssh spotcore -l spot +sudo adduser k-okada +sudo adduser k-okada spot +sudo adduser k-okada sudo +``` + +Create a workspace. Make sure that you need to login to spotcore with your account + +```bash +ssh spotcore -l k-okada +source /opt/ros/melodic/setup.bash +source ~spot/spot_driver_ws/devel/setup.bash +mkdir ~/spot_ws/src -p +cd ~/spot_ws/src +cd $HOME/spot_ws +catkin init +catkin config --cmake-args -DCMAKE_BUILD_TYPE=Release -DPYTHON_EXECUTABLE=/usr/bin/python3 -DPYTHON_INCLUDE_DIR=/usr/include/python3.6m -DPYTHON_LIBRARY=/usr/lib/x86_64-linux-gnu/libpython3.6m.so +catkin build -j4 jsk_spot_startup spoteus +echo "source /opt/ros/melodic/setup.bash" >> ~/.bashrc +echo "source ~/spot_ws/devel/setup.bash" >> ~/.bashrc +``` + +#### Run apps + +You can run apps manually, this is good for debugging your applications. +```bash +roscd jsk_spot_startup/apps/head_lead_demo roslaunch head_lead_demo.xml +``` + +#### Install apps +If you would like to call your apps from rwt_app_chooser, you can + +## Connect Spot Robot + +There are 3 network interface in Spot Robot + +- Ethernet +- WiFi +- Payload + +### Ethernet + +Spot has ethernet port on its rear. its ip address is `10.0.0.3/24` by default. See [this article](https://support.bostondynamics.com/s/article/Spot-network-setup) for more details. + +You can open web console by accessing `https://-p.jsk.imi.i.u-tokyo.ac.jp` . You can connect `https://spot-BD--p.jsk.imi.i.u-tokyo.ac.jp`. + +### WiFi + +Spot has WiFi interface and it is usually used for table control. Its accesspoint is `spot-BD-` by default. +You can connect web console by connecting its AP and connect ip of robot. + +### Payload + +Payload has ethernet port `192.168.50.3` by default. So if you have access to SpotCore and do port foward to `192.168.50.3:443` with some way ( e.g. Local forward of SSH ) You can connect robot web console through payload. + +For examples, if you can connect SpotCore by `ssh spot@spotcore.local`, you can forward access of robot to your local machine by `ssh spot@sporcore.local -L 443:192.168.50.3:443` and you can open webconsole by accesing `https://localhost:443`. diff --git a/jsk_spot_robot/SetupInternalPCAndSpotUser.md b/jsk_spot_robot/SetupInternalPCAndSpotUser.md new file mode 100644 index 0000000000..ab7f5a886a --- /dev/null +++ b/jsk_spot_robot/SetupInternalPCAndSpotUser.md @@ -0,0 +1,258 @@ +# How to setup a internal PC and spot user + +This page describes how to setup a internal pc and spot user. + +## How to setup a [CORE I/O](https://dev.bostondynamics.com/docs/payload/coreio_documentation) + +The CORE I/O is desgined to use filesystem is read only and user Docekr or Spot Extensions for user applications. + + +### Setup core-io users + +login with default password (ex: ssh -p 20022 10.0.0.3 -el spot) + +``` +# better to reboot on every command ???? +core-io$ passwd ... # change password +core-io$ sudo usermod -a -G docker spot # add spot to docker group +``` + +### Setup development environment + +``` +core-io$ mkdir ~/spot_driver_ws/src -p +core-io$ cd ~/spot_driver_ws/src +core-io$ git clone https://github.com/k-okada/jsk_robot.git -b spot_arm +core-io$ cd ~/spot_driver_ws/src/jsk_robot/jsk_spot_robot/coreio/base +core-io$ make pre_build catkin_build dev_build all +``` +This procedure create `~/bash.sh`. Please use this script when you start development. + +### Setup wifi interfaces + +Use [TP-Link AC600 wireless Realtek RTL8811AU Archer T2U Nano](https://www.amazon.co.jp/gp/product/B07MXHJ6KB/ref=ppx_yo_dt_b_asin_title_o01_s00?ie=UTF8&psc=1) +``` +$ dmesg +[ 7.305827] usb 1-2.1.1: new high-speed USB device number 5 using tegra-xusb +[ 7.326404] usb 1-2.1.1: New USB device found, idVendor=2357, idProduct=011e +[ 7.326553] usb 1-2.1.1: New USB device strings: Mfr=1, Product=2, SerialNumber=3 +[ 7.326681] usb 1-2.1.1: Product: 802.11ac WLAN Adapter +[ 7.326781] usb 1-2.1.1: Manufacturer: Realtek +$ lsusb +Bus 001 Device 005: ID 2357:011e +``` + +Make sure that the roming configuration works correctly (from core-io) +``` +core-io$ sudo wpa_cli get_network 0 bgscan +Selected interface 'wlxac15a246e382' +"simple:30:-80:86400" +``` +This scan every 30 seconds, when signal is below -80, and 86400 seconds (24 hours) otherwise. + +### Setup Joystick + +[Push PS and (left-top small) create button to enter pairing mode](https://www.playstation.com/en-us/support/hardware/pair-dualsense-controller-bluetooth/#blue) +``` +$ hcitool dev +Devices: + hci0 00:1B:DC:0D:D0:AD +$ hcitool -i hci0 scan +Scanning ... + D0:BC:C1:CB:48:37 Wireless Controller +$ sudo bluetoothctl +[bluetooth]# pair D0:BC:C1:CB:48:37 +[bluetooth]# trust D0:BC:C1:CB:48:37 +[bluetooth]# connect D0:BC:C1:CB:48:37 + +``` + + +## Settings for SPOT CORE (x86_64) + +## wifi settings [Wi-Fi roaming aggressiveness configuration](https://github.com/jsk-ros-pkg/jsk_robot/issues/1598#issuecomment-1247533330) + +``` +$ git clone https://github.com/cilynx/rtl88x2bu.git +cd rtl88x2bu +./deply.sh +echo 88x2bu | sudo tee /etc/modules-load.d/88x2bu.conf # to startup on boot time +echo 'install 88x2bu /sbin/modprobe -i 88x2bu && { /sbin/wpa_cli set_network 0 bgscan "\\"simple:5:-50:3000\\"";}' | sudo tee /etc/modprobe.d/88x2bu.conf # run set_network when load module + +sudo nmtui # to configure network +``` + +### Setup timezone + +``` +sudo timedatectl set-timezone Asia/Tokyo +``` + + +## How to set up the spot user + +First, create your user account to internal PC. + +``` +ssh spot-BD-xxxxxxxx-p.jsk.imi.i.u-tokyo.ac.jp -l spot -p 20022 +sudo adduser k-okada +sudo adduser k-okada spot + +``` + +If you are the first user to use spotcore, add `spot` user to sudo group. If someone already using the spotcore, you can skip this section + +``` +sudo gpasswd -a spot sudo +``` + +To install systemd service, run following commands. Note that this script start launch file of user's workspace, so usually we expect to run from spot users. +``` +rosrun robot_upstart install --provider supervisor --supervisor-priority 10 --roscore +rosrun robot_upstart install --provider supervisor --supervisor-priority 300 --symlink --wait --job jsk_spot_startup jsk_spot_startup/launch/jsk_spot_bringup.launch credential_config:=$(rospack find jsk_spot_startup)/auth/spot_credential.yaml use_app_manager:=false +rosrun robot_upstart install --provider supervisor --supervisor-priority 400 --symlink --wait --job app_manager jsk_robot_startup/lifelog/app_manager.launch use_applist:=false respawn:=false +``` + +To check output of roslaunch output, please try +``` +sudo supervisorctl tail -f jsk_spot_startup stdout +sudo supervisorctl tail -f jsk_spot_startup stderr +``` + +You can connect to the supervisor console from https://spotcore.jsk.imi.i.u-tokyo.ac.jp:9001/ + +systemd services of JSK Spot system use workspaces in `spot` user. + +- `spot_driver_ws`: a workspace to run driver.launch. which requires python3 build version of geometry3 + +### Prerequities + +Install necessary packages for workspace building + +``` +sudo apt-get install python3-catkin-pkg-modules python3-rospkg-modules python3-venv python3-empy python3-opencv python3-sip-dev python3-defusedxml +sudo apt-get install ros-melodic-catkin ros-melodic-vision-msgs +``` + + +### setting up `spot_driver_ws` + +Create a workspace for `spot_driver`. + +```bash +source /opt/ros/melodic/setup.bash +mkdir ~/spot_driver_ws/src -p +cd ~/spot_driver_ws/src +wstool init . +wstool set jsk-ros-pkg/jsk_robot https://github.com/jsk-ros-pkg/jsk_robot.git --git +wstool update +wstool merge -t . jsk-ros-pkg/jsk_robot/jsk_spot_robot/jsk_spot_driver.rosinstall +wstool update +rosdep update +rosdep install -y -r --from-paths . --ignore-src +pip3 install -r jsk-ros-pkg/jsk_robot/jsk_spot_robot/requirements.txt +cd ~/spot_driver_ws +catkin init +catkin config --cmake-args -DCMAKE_BUILD_TYPE=Release -DPYTHON_EXECUTABLE=/usr/bin/python3 -DPYTHON_INCLUDE_DIR=/usr/include/python3.6m -DPYTHON_LIBRARY=/usr/lib/x86_64-linux-gnu/libpython3.6m.so +catkin build -j4 tf2_ros cv_bridge jsk_spot_startup spoteus robot_upstart +``` + +After this, please modify the credential files for spot_driver. + +```bash +roscd jsk_spot_startup +# modify auth/credential_config.yaml +git update-index --skip-worktree auth/spot_credential.yaml +``` + +Note that `rosdep install ...` did not install all dependencies, you need to install `python3-` modules manualy. or run `ROS_PYTHON_VERSION=3 rosdep install ...`. But this will install `python3-catkin-pkg` which remove `python2-catkin-pkg` package and all `ros-melodic-*` packages. + +Workaround is to install only jsk_robot direcotry. (But you still re-install `ros-melodic-jsk-tools`) +``` +ROS_PYTHON_VERSION=3 rosdep install -r --from-paths ~/spot_driver_ws/src/jsk_robot --ignore-src +``` +or create dummy package that did not conflict each other. + +``` +#!/bin/bash + +set -x +set -e + +TMPDIR=/tmp/tmp-$$ + +mkdir ${TMPDIR} +cd ${TMPDIR} +apt download python3-catkin-pkg +dpkg-deb -R python3-catkin-pkg_0.5.2-100_all.deb ${TMPDIR}/src-python3-catkin-pkg +rm -fr ${TMPDIR}/src-python3-catkin-pkg/usr/bin/ +sed -i /^Conflicts:/d ${TMPDIR}/src-python3-catkin-pkg/DEBIAN/control +sed -i '/^Version:/ s/$/0/' ${TMPDIR}/src-python3-catkin-pkg/DEBIAN/control +cat ${TMPDIR}/src-python3-catkin-pkg/DEBIAN/control +# edit DEBIAN/postinst +dpkg-deb -b ${TMPDIR}/src-python3-catkin-pkg python3-catkin-pkg-dummy.deb + +apt download python-catkin-pkg +dpkg-deb -R python-catkin-pkg_0.5.2-100_all.deb ${TMPDIR}/src-python2-catkin-pkg +rm -fr ${TMPDIR}/src-python2-catkin-pkg/usr/bin/ +sed -i 's/, python3-catkin-pkg//' ${TMPDIR}/src-python2-catkin-pkg/DEBIAN/control +sed -i '/^Version:/ s/$/0/' ${TMPDIR}/src-python2-catkin-pkg/DEBIAN/control +cat ${TMPDIR}/src-python2-catkin-pkg/DEBIAN/control +# edit DEBIAN/postinst +dpkg-deb -b ${TMPDIR}/src-python2-catkin-pkg python2-catkin-pkg-no-conflict.deb + +apt download python3-rosdep +dpkg-deb -R python3-rosdep_0.22.1-1_all.deb ${TMPDIR}/src-python3-rosdep +rm -fr ${TMPDIR}/src-python3-rosdep/usr/bin/ +sed -i /^Conflicts:/d ${TMPDIR}/src-python3-rosdep/DEBIAN/control +sed -i '/^Version:/ s/$/0/' ${TMPDIR}/src-python3-rosdep/DEBIAN/control +cat ${TMPDIR}/src-python3-rosdep/DEBIAN/control +# edit DEBIAN/postinst +dpkg-deb -b ${TMPDIR}/src-python3-rosdep python3-rosdep-dummy.deb + +apt download python-rosdep +dpkg-deb -R python-rosdep_0.22.1-1_all.deb ${TMPDIR}/src-python-rosdep +rm -fr ${TMPDIR}/src-python-rosdep/usr/bin/ +sed -i 's/, python3-rosdep//' ${TMPDIR}/src-python-rosdep/DEBIAN/control +sed -i '/^Version:/ s/$/0/' ${TMPDIR}/src-python-rosdep/DEBIAN/control +cat ${TMPDIR}/src-python-rosdep/DEBIAN/control +# edit DEBIAN/postinst +dpkg-deb -b ${TMPDIR}/src-python-rosdep python-rosdep-no-conflict.deb + +set +x +echo "scp ${TMPDIR}/python2-catkin-pkg-no-conflict.deb to your environment" +echo "scp ${TMPDIR}/python3-catkin-pkg-dummy.deb to your environment" +echo "scp ${TMPDIR}/python-rosdep-no-conflict.deb to your environment" +echo "scp ${TMPDIR}/python3-rosdep-dummy.deb to your environment" +``` + +## Troubleshootig + +### Joystick did not respond + +Check `Setup Joystick` section and reconnet bluetooth + +### Wifi did not connec + +Check [roaming aggressiveness configuration](https://github.com/jsk-ros-pkg/jsk_robot/issues/1598) +``` +$ wpa_cli get_network 0 bgscan +``` + +This tells roaming configuation, for example below setting means it scan every 5 seconds when the signal is weak (below -50), and every 3600 seconds otherwise. +``` +$ wpa_cli set_network 0 bgscan "\"simple:5:-50:3000\"" +``` + +You can also check the output of wpa_supplicant. +``` +$ journalctl -u wpa_supplicant -f +``` + +### rwt_app_chooser did not respond + +http://spotcore:8000/rwt_app_chooser/#!task/ did not show any apps, and following command did not returns any apps, make sure that you have run `rosdep update`. + +``` +$ rosservice call /SpotCORE/list_apps +``` diff --git a/jsk_spot_robot/coreio/base/Dockerfile b/jsk_spot_robot/coreio/base/Dockerfile new file mode 100644 index 0000000000..0464b2e58d --- /dev/null +++ b/jsk_spot_robot/coreio/base/Dockerfile @@ -0,0 +1,80 @@ +##### +## Setup development environment +##### +##### Use sha256 as of 2023/03/16 +FROM arm64v8/ros:melodic-robot@sha256:c0a0a8a25dc822ea48b40c45d74b267ddf4dbe22b8accc9fd66bb146c34eb974 AS base_build +ENV DEBIAN_FRONTEND noninteractive + +# development tools +RUN apt-get update && apt install -y -q python3-catkin-pkg-modules python3-rospkg-modules python3-venv python3-pip python3-empy python3-opencv python3-sip-dev python3-defusedxml cython3 + +# network tools +RUN apt-get update && apt install -y -q net-tools iputils-ping dnsutils traceroute curl + +# ROS packages +RUN apt-get update && apt install -y -q ros-melodic-catkin ros-melodic-vision-msgs + +# setup user space +RUN sed -i 's@%sudo\s*ALL=(ALL:ALL)\s*ALL@%sudo ALL=(ALL:ALL) NOPASSWD: ALL@' /etc/sudoers +RUN useradd -ms /bin/bash spot +RUN echo spot:spot | chpasswd +RUN adduser spot sudo +USER spot +WORKDIR /home/spot +RUN rosdep update -y + +# get dummy workspace to install dpeendent packages +RUN mkdir -p /tmp/ws/src/jsk_robot/jsk_spot_robot/jsk_spot_startup /tmp/ws/src/jsk_robot/jsk_spot_robot/spoteus +COPY src/jsk_robot/jsk_spot_robot/jsk_spot_startup/package.xml /tmp/ws/src/jsk_robot/jsk_spot_robot/jsk_spot_startup/ +COPY src/jsk_robot/jsk_spot_robot/spoteus/package.xml /tmp/ws/src/jsk_robot/jsk_spot_robot/spoteus/ +COPY src/jsk_robot/jsk_spot_robot/requirements.txt /tmp/ws/src/jsk_robot/jsk_spot_robot/ + +# install pip3 dependencies +RUN pip3 install pip==21.3.1 +RUN pip3 install wrapt==1.12.1 six==1.15.0 PyJWT==2.0.0 numpy==1.19.4 grpcio==1.34.0 + +# install python requirements +RUN pip3 install -r /tmp/ws/src/jsk_robot/jsk_spot_robot/requirements.txt + +# rosdep install +RUN rosdep install -y -r --from-paths /tmp/ws/src --ignore-src --rosdistro melodic || echo "OK" + +##### +## Initialize catkin workspace after wstool merge and update with jsk_spot_robot/jsk_spot_driver.rosinstall +##### +FROM base_build AS pre_build + +# setup catkin develeopment environment with mounted workspace +COPY package.xml.tar /tmp/ +RUN tar -C /tmp/ws/ -xvf /tmp/package.xml.tar +RUN rosdep install -y -r --from-paths /tmp/ --ignore-src --rosdistro melodic || echo "OK" +## +## 'RUN --mount' called everytime when conttnes of spot/ws changed, so use 'COPY' +#RUN --mount=type=bind,source=/,target=/home/spot/ws cd ws && rosdep install -y -r --from-paths src --ignore-src --rosdistro melodic || echo "OK" + + +##### +## User preferences for developent tools +##### +FROM pre_build AS dev_build +RUN sudo apt install -y -q python-catkin-tools less emacs vim + +RUN sudo apt install -y -q bluez bluez-tools + +RUN echo "#!/bin/bash" > /home/spot/ros_entrypoint.sh && \ + echo "set -e" >> /home/spot/ros_entrypoint.sh && \ + echo "source /opt/ros/melodic/setup.bash --" >> /home/spot/ros_entrypoint.sh && \ + echo "source /home/spot/ws/devel/setup.bash --" >> /home/spot/ros_entrypoint.sh && \ + echo "sudo service dbus start" >> /home/spot/ros_entrypoint.sh && \ + echo "sudo bluetoothd &" >> /home/spot/ros_entrypoint.sh && \ + echo "exec \"\$@\"" >> /home/spot/ros_entrypoint.sh + +RUN chmod u+x /home/spot/ros_entrypoint.sh + +### FIXME ros_entrypoint did not work with roscd +RUN echo "source /opt/ros/melodic/share/rosbash/rosbash" >> ~/.bashrc + +ENV TZ=Asia/Tokyo + +ENTRYPOINT ["/home/spot/ros_entrypoint.sh"] +CMD ["bash"] diff --git a/jsk_spot_robot/coreio/base/Makefile b/jsk_spot_robot/coreio/base/Makefile new file mode 100644 index 0000000000..ed00da7df5 --- /dev/null +++ b/jsk_spot_robot/coreio/base/Makefile @@ -0,0 +1,49 @@ +all: $(HOME)/bash.sh + @echo "make build: build docker for spot user" + +$(HOME)/bash.sh: + echo "#!/bin/bash\n\ncd ~/spot_driver_ws/src/jsk_robot/jsk_spot_robot/coreio/base\nmake shell\n" > $(HOME)/bash.sh + chmod u+x $(HOME)/bash.sh + + +WS_ROOT=$(abspath $(CURDIR)/../../../../../) + +define run +docker run --rm --privileged --hostname strelka --add-host strelka:133.11.216.166 --add-host strelka.jsk.imi.i.u-tokyo.ac.jp:133.11.216.166 --network=host --device=/dev/input -v $(WS_ROOT):/home/spot/ws -v $(HOME)/.ros:/home/spot/.ros -w /home/spot/ws -ti dev_env ${1}; +endef +define docker_run +${1} +endef + +pre_build: + # make .ros file + mkdir -p ~/.ros + # build base file + cp Dockerfile $(WS_ROOT) + cd $(WS_ROOT); DOCKER_BUILDKIT=1 docker build --target base_build --progress=plain --network=host -t dev_env -f Dockerfile . + $(call run, /bin/ls -al src) + # copy workspaces + if [ ! -e $(WS_ROOT)/src/.rosinstall ]; then $(call run, wstool init src) fi + $(call run, wstool merge -t src src/jsk_robot/jsk_spot_robot/jsk_spot_driver.rosinstall) + $(call run, wstool update -t src) + # rosdep install all + cd $(WS_ROOT); DOCKER_BUILDKIT=1 docker build --target pre_build --progress=plain --network=host -t dev_env -f Dockerfile . + rm $(WS_ROOT)/Dockerfile + +dev_build: + cp Dockerfile $(WS_ROOT) + cd $(WS_ROOT); [ -e package.xml.tar ] || find src -iname 'package.xml' | tar cf package.xml.tar -T - + cd $(WS_ROOT); DOCKER_BUILDKIT=1 docker build --target dev_build --progress=plain --network=host -t dev_env -f Dockerfile . + rm $(WS_ROOT)/Dockerfile $(WS_ROOT)/package.xml.tar + +catkin_build: + $(call run, catkin init) + $(call run, catkin config --cmake-args -DCMAKE_BUILD_TYPE=Release -DPYTHON_EXECUTABLE=/usr/bin/python3 -DPYTHON_INCLUDE_DIR=/usr/include/python3.6m -DPYTHON_LIBRARY=/usr/lib/aarch64-linux-gnu/libpython3.6m.so.1) + $(call run, catkin build) + + +shell: + $(call run, bash) + +emacs: + $(call run, emacs -nw) diff --git a/jsk_spot_robot/coreio/network_compute_server/0_setup.sh b/jsk_spot_robot/coreio/network_compute_server/0_setup.sh new file mode 100755 index 0000000000..4b555ad992 --- /dev/null +++ b/jsk_spot_robot/coreio/network_compute_server/0_setup.sh @@ -0,0 +1,5 @@ +#!/bin/bash + +## see https://dev.bostondynamics.com/docs/python/fetch_tutorial/fetch1 + +. ~/my_spot_env/bin/activate diff --git a/jsk_spot_robot/coreio/network_compute_server/1_capture.sh b/jsk_spot_robot/coreio/network_compute_server/1_capture.sh new file mode 100755 index 0000000000..e96128ac60 --- /dev/null +++ b/jsk_spot_robot/coreio/network_compute_server/1_capture.sh @@ -0,0 +1,17 @@ +#!/bin/bash + +## see https://dev.bostondynamics.com/docs/python/fetch_tutorial/fetch1 + +set -x +rm -fr dogtoy/images/* + +trap "exit" INT TERM ERR +trap "kill 0" EXIT + +python capture_images.py 10.0.0.3 --image-source frontleft_fisheye_image --folder dogtoy/images & +python capture_images.py 10.0.0.3 --image-source frontright_fisheye_image --folder dogtoy/images & +python capture_images.py 10.0.0.3 --image-source left_fisheye_image --folder dogtoy/images & +python capture_images.py 10.0.0.3 --image-source right_fisheye_image --folder dogtoy/images & +python capture_images.py 10.0.0.3 --image-source back_fisheye_image --folder dogtoy/images & + +wait diff --git a/jsk_spot_robot/coreio/network_compute_server/2_split.sh b/jsk_spot_robot/coreio/network_compute_server/2_split.sh new file mode 100755 index 0000000000..201830d8b1 --- /dev/null +++ b/jsk_spot_robot/coreio/network_compute_server/2_split.sh @@ -0,0 +1,4 @@ +#!/bin/bash + +mv dogtoy/images/*.xml dogtoy/annotations/ +python split_dataset.py --labels-dir dogtoy/annotations/ --output-dir dogtoy/annotations/ --ratio 0.9 diff --git a/jsk_spot_robot/coreio/network_compute_server/3_convert.sh b/jsk_spot_robot/coreio/network_compute_server/3_convert.sh new file mode 100755 index 0000000000..155949ff77 --- /dev/null +++ b/jsk_spot_robot/coreio/network_compute_server/3_convert.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +## see https://dev.bostondynamics.com/docs/python/fetch_tutorial/fetch1 + +python generate_tfrecord.py --xml_dir dogtoy/annotations/train --image_dir dogtoy/images --labels_path dogtoy/annotations/label_map.pbtxt --output_path dogtoy/annotations/train.record +python generate_tfrecord.py --xml_dir dogtoy/annotations/test --image_dir dogtoy/images --labels_path dogtoy/annotations/label_map.pbtxt --output_path dogtoy/annotations/test.record + diff --git a/jsk_spot_robot/coreio/network_compute_server/4_train.sh b/jsk_spot_robot/coreio/network_compute_server/4_train.sh new file mode 100755 index 0000000000..ece266b850 --- /dev/null +++ b/jsk_spot_robot/coreio/network_compute_server/4_train.sh @@ -0,0 +1,8 @@ +#!/bin/bash + +## see https://dev.bostondynamics.com/docs/python/fetch_tutorial/fetch1 + +python model_main_tf2.py --model_dir=dogtoy/models/dogtoy_ssd_resnet50_v1_fpn --pipeline_config_path=dogtoy/models/dogtoy_ssd_resnet50_v1_fpn/pipeline.config --num_train_steps=20000 +# CUDA_VISIBLE_DEVICES="-1" python model_main_tf2.py --model_dir=dogtoy/models/dogtoy_ssd_resnet50_v1_fpn --pipeline_config_path=dogtoy/models/dogtoy_ssd_resnet50_v1_fpn/pipeline.config --checkpoint_dir=dogtoy/models/dogtoy_ssd_resnet50_v1_fpn +mkdir -p dogtoy/exported-models/dogtoy-model +python exporter_main_v2.py --input_type image_tensor --pipeline_config_path dogtoy/models/dogtoy_ssd_resnet50_v1_fpn/pipeline.config --trained_checkpoint_dir dogtoy/models/dogtoy_ssd_resnet50_v1_fpn/ --output_directory dogtoy/exported-models/dogtoy-model diff --git a/jsk_spot_robot/coreio/network_compute_server/5_run.sh b/jsk_spot_robot/coreio/network_compute_server/5_run.sh new file mode 100755 index 0000000000..03b51fd747 --- /dev/null +++ b/jsk_spot_robot/coreio/network_compute_server/5_run.sh @@ -0,0 +1,6 @@ +#!/bin/bash + +## see https://dev.bostondynamics.com/docs/python/fetch_tutorial/fetch1 + +python network_compute_server.py -m dogtoy/exported-models/dogtoy-model/saved_model dogtoy/annotations/label_map.pbtxt 10.0.0.3 + diff --git a/jsk_spot_robot/coreio/network_compute_server/6_build_ext.sh b/jsk_spot_robot/coreio/network_compute_server/6_build_ext.sh new file mode 100755 index 0000000000..ed260c2d1b --- /dev/null +++ b/jsk_spot_robot/coreio/network_compute_server/6_build_ext.sh @@ -0,0 +1,31 @@ +#!/bin/bash -ex + +## https://dev.bostondynamics.com/docs/python/fetch_tutorial/fetch6 +## https://dev.bostondynamics.com/docs/python/fetch_tutorial/files/coreio_extension/create_extension.sh + +mkdir -p data +rm -rf data/* +# Dogtoy model +cp -r ./dogtoy/exported-models/dogtoy-model data/. +# and its label map +cp ./dogtoy/annotations/label_map.pbtxt data/dogtoy-model/. +# coco model (includes its label map) +cp -r ./dogtoy/pre-trained-models/ssd_resnet50_v1_fpn_640x640_coco17_tpu-8 data/. +cp ./models-with-protos/research/object_detection/data/mscoco_label_map.pbtxt data/ssd_resnet50_v1_fpn_640x640_coco17_tpu-8/ + +# Build the image +docker build -t object_detection_coco:l4t -f Dockerfile.l4t . + +# Exports the image, uses pigz +docker save object_detection_coco:l4t | pigz > object_detection_coco_image.tar.gz + +# Built the Spot Extension by taring all the files together +tar -cvzf object_detection_coco.spx \ + object_detection_coco_image.tar.gz \ + manifest.json \ + docker-compose.yml \ + data + +# Cleanup intermediate image +rm object_detection_coco_image.tar.gz +rm -fr data diff --git a/jsk_spot_robot/coreio/network_compute_server/Dockerfile.l4t b/jsk_spot_robot/coreio/network_compute_server/Dockerfile.l4t new file mode 100644 index 0000000000..f5ba4ae1bc --- /dev/null +++ b/jsk_spot_robot/coreio/network_compute_server/Dockerfile.l4t @@ -0,0 +1,22 @@ +# Use a base image provided by nvidia that already contains tensorflow 2.5 +FROM nvcr.io/nvidia/l4t-tensorflow:r32.7.1-tf2.7-py3 + +# Do some basic apt and pip updating +RUN apt-get update && \ + apt-get install -y --no-install-recommends python3-pip && \ + apt-get clean + +# Copy over the python requirements file and our prebuilt models API library +COPY docker-requirements.txt prebuilt/*.whl ./ +COPY models-with-protos models-with-protos + +# Install the python requirements +RUN python3 -m pip install pip==21.3.1 setuptools==59.6.0 wheel==0.37.1 && \ + python3 -m pip install -r docker-requirements.txt --find-links . + +# Copy over our main script +COPY network_compute_server.py /app/ +WORKDIR /app + +# Set our script as the main entrypoint for the container +ENTRYPOINT ["python3", "network_compute_server.py"] diff --git a/jsk_spot_robot/coreio/network_compute_server/docker-compose.yml b/jsk_spot_robot/coreio/network_compute_server/docker-compose.yml new file mode 100644 index 0000000000..72a9437ea1 --- /dev/null +++ b/jsk_spot_robot/coreio/network_compute_server/docker-compose.yml @@ -0,0 +1,26 @@ +version: "3.5" +services: + object_detection_coco: + image: object_detection_coco:l4t + network_mode: host + restart: unless-stopped + environment: + # This package couldn't be installed, but putting it on the path allows required access to the protos. + - PYTHONPATH=/models-with-protos/research/ + volumes: + # Mount payload credentials. + - /opt/payload_credentials/payload_guid_and_secret:/opt/payload_credentials/payload_guid_and_secret + # and ML models + - /data/.extensions/object_detection_coco/data:/data + # The command below is partial because the docker image is already configured with an entrypoint. + command: > + -m /data/dogtoy-model/saved_model /data/dogtoy-model/label_map.pbtxt + -m /data/ssd_resnet50_v1_fpn_640x640_coco17_tpu-8/saved_model /data/ssd_resnet50_v1_fpn_640x640_coco17_tpu-8/mscoco_label_map.pbtxt + --payload-credentials-file /opt/payload_credentials/payload_guid_and_secret + 192.168.50.3 + deploy: + resources: + reservations: + devices: + - driver: nvidia + capabilities: [gpu] diff --git a/jsk_spot_robot/coreio/network_compute_server/docker-requirements.txt b/jsk_spot_robot/coreio/network_compute_server/docker-requirements.txt new file mode 100644 index 0000000000..12c96e14fe --- /dev/null +++ b/jsk_spot_robot/coreio/network_compute_server/docker-requirements.txt @@ -0,0 +1,40 @@ +bosdyn-api==3.2.0 + # via + # bosdyn-client + # bosdyn-core +bosdyn-client==3.2.0 + # via -r requirements.txt +bosdyn-core==3.2.0 + # via bosdyn-client +certifi==2022.5.18.1 + # via requests +charset-normalizer==2.0.12 + # via requests +deprecated==1.2.13 + # via + # bosdyn-client + # bosdyn-core +grpcio + #==1.46.3 (conflicts with base image) + # via bosdyn-client +idna==3.3 + # via requests +numpy==1.19.4 + # via bosdyn-client +protobuf==3.19.4 + # via bosdyn-api +pyjwt==2.4.0 + # via bosdyn-client +requests==2.27.1 + # via bosdyn-client +six + #==1.16.0 (conflicts with base image) + # via grpcio +urllib3==1.26.9 + # via requests +wrapt + #==1.14.1 (conflicts with base image) + # via deprecated +Pillow==8.4.0 +opencv-python==4.6.0.66 + # via network_compute_server.py \ No newline at end of file diff --git a/jsk_spot_robot/coreio/network_compute_server/manifest.json b/jsk_spot_robot/coreio/network_compute_server/manifest.json new file mode 100644 index 0000000000..d762e573d0 --- /dev/null +++ b/jsk_spot_robot/coreio/network_compute_server/manifest.json @@ -0,0 +1,7 @@ +{ + "description": "object_detection_coco", + "version": "3.2.0", + "images": [ + "object_detection_coco_image.tar.gz" + ] +} diff --git a/jsk_spot_robot/coreio/network_compute_server/network_compute_server.py b/jsk_spot_robot/coreio/network_compute_server/network_compute_server.py new file mode 100644 index 0000000000..d600d51577 --- /dev/null +++ b/jsk_spot_robot/coreio/network_compute_server/network_compute_server.py @@ -0,0 +1,312 @@ +# Copyright (c) 2022 Boston Dynamics, Inc. All rights reserved. +# +# Downloading, reproducing, distributing or otherwise using the SDK Software +# is subject to the terms and conditions of the Boston Dynamics Software +# Development Kit License (20191101-BDSDK-SL). + +import argparse +import io +import os +import sys +import time +import logging + +import cv2 +from PIL import Image +import numpy as np + +from bosdyn.api import network_compute_bridge_service_pb2_grpc +from bosdyn.api import network_compute_bridge_pb2 +from bosdyn.api import image_pb2 +from bosdyn.api import header_pb2 +import bosdyn.client +import bosdyn.client.util +import grpc +from concurrent import futures +import tensorflow as tf + +import queue +import threading +from google.protobuf import wrappers_pb2 +from object_detection.utils import label_map_util + +kServiceAuthority = "object-detection-coco-worker.spot.robot" + + +class TensorFlowObjectDetectionModel: + def __init__(self, model_path, label_path): + self.detect_fn = tf.saved_model.load(model_path) + self.category_index = label_map_util.create_category_index_from_labelmap(label_path, use_display_name=True) + self.name = os.path.basename(os.path.dirname(model_path)) + + def predict(self, image): + input_tensor = tf.convert_to_tensor(image) + input_tensor = input_tensor[tf.newaxis, ...] + detections = self.detect_fn(input_tensor) + + return detections + +def process_thread(args, request_queue, response_queue): + # Load the model(s) + models = {} + for model in args.model: + this_model = TensorFlowObjectDetectionModel(model[0], model[1]) + models[this_model.name] = this_model + + print('') + print('Service ' + args.name + ' running on port: ' + str(args.port)) + + print('Loaded models:') + for model_name in models: + print(' ' + model_name) + + while True: + request = request_queue.get() + + if isinstance(request, network_compute_bridge_pb2.ListAvailableModelsRequest): + out_proto = network_compute_bridge_pb2.ListAvailableModelsResponse() + for model_name in models: + out_proto.available_models.append(model_name) + if models[model_name].category_index is not None: + labels_msg = out_proto.labels.add() + labels_msg.model_name = model_name + for n in models[model_name].category_index.keys(): + labels_msg.available_labels.append(models[model_name].category_index[n]['name']) + response_queue.put(out_proto) + continue + else: + out_proto = network_compute_bridge_pb2.NetworkComputeResponse() + + # Find the model + if request.input_data.model_name not in models: + err_str = 'Cannot find model "' + request.input_data.model_name + '" in loaded models.' + print(err_str) + + # Set the error in the header. + out_proto.header.error.code = header_pb2.CommonError.CODE_INVALID_REQUEST + out_proto.header.error.message = err_str + response_queue.put(out_proto) + continue + + model = models[request.input_data.model_name] + + # Unpack the incoming image. + if request.input_data.image.format == image_pb2.Image.FORMAT_RAW: + pil_image = Image.open(io.BytesIO(request.input_data.image.data)) + if request.input_data.image.pixel_format == image_pb2.Image.PIXEL_FORMAT_GREYSCALE_U8: + # If the input image is grayscale, convert it to RGB. + image = cv2.cvtColor(pil_image, cv2.COLOR_GRAY2RGB) + + elif request.input_data.image.pixel_format == image_pb2.Image.PIXEL_FORMAT_RGB_U8: + # Already an RGB image. + image = pil_image + + else: + print('Error: image input in unsupported pixel format: ', request.input_data.image.pixel_format) + response_queue.put(out_proto) + continue + + elif request.input_data.image.format == image_pb2.Image.FORMAT_JPEG: + dtype = np.uint8 + jpg = np.frombuffer(request.input_data.image.data, dtype=dtype) + image = cv2.imdecode(jpg, -1) + + if len(image.shape) < 3: + # If the input image is grayscale, convert it to RGB. + image = cv2.cvtColor(image, cv2.COLOR_GRAY2RGB) + + image_width = image.shape[0] + image_height = image.shape[1] + + detections = model.predict(image) + + num_objects = 0 + + # All outputs are batches of tensors. + # Convert to numpy arrays, and take index [0] to remove the batch dimension. + # We're only interested in the first num_detections. + num_detections = int(detections.pop('num_detections')) + detections = {key: value[0, :num_detections].numpy() + for key, value in detections.items()} + + boxes = detections['detection_boxes'] + classes = detections['detection_classes'] + scores = detections['detection_scores'] + + + for i in range(boxes.shape[0]): + if scores[i] < request.input_data.min_confidence: + continue + + box = tuple(boxes[i].tolist()) + + # Boxes come in with normalized coordinates. Convert to pixel values. + box = [box[0] * image_width, box[1] * image_height, box[2] * image_width, box[3] * image_height] + + score = scores[i] + + if classes[i] in model.category_index.keys(): + label = model.category_index[classes[i]]['name'] + else: + label = 'N/A' + + num_objects += 1 + + print('Found object with label: "' + label + '" and score: ' + str(score)) + + + point1 = np.array([box[1], box[0]]) + point2 = np.array([box[3], box[0]]) + point3 = np.array([box[3], box[2]]) + point4 = np.array([box[1], box[2]]) + + # Add data to the output proto. + out_obj = out_proto.object_in_image.add() + out_obj.name = "obj" + str(num_objects) + "_label_" + label + + vertex1 = out_obj.image_properties.coordinates.vertexes.add() + vertex1.x = point1[0] + vertex1.y = point1[1] + + vertex2 = out_obj.image_properties.coordinates.vertexes.add() + vertex2.x = point2[0] + vertex2.y = point2[1] + + vertex3 = out_obj.image_properties.coordinates.vertexes.add() + vertex3.x = point3[0] + vertex3.y = point3[1] + + vertex4 = out_obj.image_properties.coordinates.vertexes.add() + vertex4.x = point4[0] + vertex4.y = point4[1] + + # Pack the confidence value. + confidence = wrappers_pb2.FloatValue(value=score) + out_obj.additional_properties.Pack(confidence) + + if not args.no_debug: + polygon = np.array([point1, point2, point3, point4], np.int32) + polygon = polygon.reshape((-1, 1, 2)) + cv2.polylines(image, [polygon], True, (0, 255, 0), 2) + + caption = "{}: {:.3f}".format(label, score) + left_x = min(point1[0], min(point2[0], min(point3[0], point4[0]))) + top_y = min(point1[1], min(point2[1], min(point3[1], point4[1]))) + cv2.putText(image, caption, (int(left_x), int(top_y)), cv2.FONT_HERSHEY_SIMPLEX, 0.5, + (0, 255, 0), 2) + + print('Found ' + str(num_objects) + ' object(s)') + + if not args.no_debug: + debug_image_filename = 'network_compute_server_output.jpg' + cv2.imwrite(debug_image_filename, image) + print('Wrote debug image output to: "' + debug_image_filename + '"') + + response_queue.put(out_proto) + + +class NetworkComputeBridgeWorkerServicer( + network_compute_bridge_service_pb2_grpc.NetworkComputeBridgeWorkerServicer): + + def __init__(self, thread_input_queue, thread_output_queue): + super(NetworkComputeBridgeWorkerServicer, self).__init__() + + self.thread_input_queue = thread_input_queue + self.thread_output_queue = thread_output_queue + + def NetworkCompute(self, request, context): + print('Got NetworkCompute request') + self.thread_input_queue.put(request) + out_proto = self.thread_output_queue.get() + return out_proto + + def ListAvailableModels(self, request, context): + print('Got ListAvailableModels request') + self.thread_input_queue.put(request) + out_proto = self.thread_output_queue.get() + return out_proto + + +def register_with_robot(options): + """ Registers this worker with the robot's Directory.""" + ip = bosdyn.client.common.get_self_ip(options.hostname) + print('Detected IP address as: ' + ip) + + sdk = bosdyn.client.create_standard_sdk("tensorflow_server") + + robot = sdk.create_robot(options.hostname) + + # Authenticate robot before being able to use it + if options.payload_credentials_file: + robot.authenticate_from_payload_credentials( + *bosdyn.client.util.get_guid_and_secret(options)) + else: + bosdyn.client.util.authenticate(robot) + + directory_client = robot.ensure_client( + bosdyn.client.directory.DirectoryClient.default_service_name) + directory_registration_client = robot.ensure_client( + bosdyn.client.directory_registration.DirectoryRegistrationClient.default_service_name) + + # Check to see if a service is already registered with our name + services = directory_client.list() + for s in services: + if s.name == options.name: + print("WARNING: existing service with name, \"" + options.name + "\", removing it.") + directory_registration_client.unregister(options.name) + break + + # Register service + print('Attempting to register ' + ip + ':' + options.port + ' onto ' + options.hostname + ' directory...') + directory_registration_client.register(options.name, "bosdyn.api.NetworkComputeBridgeWorker", kServiceAuthority, ip, int(options.port)) + + + +def main(argv): + default_port = '50051' + + parser = argparse.ArgumentParser() + parser.add_argument('-m', '--model', help='[MODEL_DIR] [LABELS_FILE.pbtxt]: Path to a model\'s directory and path to its labels .pbtxt file', action='append', nargs=2, required=True) + parser.add_argument('-p', '--port', help='Server\'s port number, default: ' + default_port, + default=default_port) + parser.add_argument('-d', '--no-debug', help='Disable writing debug images.', action='store_true') + parser.add_argument('-n', '--name', help='Service name', default='object-detection-coco') + bosdyn.client.util.add_payload_credentials_arguments(parser, required=False) + bosdyn.client.util.add_base_arguments(parser) + + options = parser.parse_args(argv) + + print(options.model) + + for model in options.model: + if not os.path.isdir(model[0]): + print('Error: model directory (' + model[0] + ') not found or is not a directory.') + sys.exit(1) + + # Perform registration. + register_with_robot(options) + + # Thread-safe queues for communication between the GRPC endpoint and the ML thread. + request_queue = queue.Queue() + response_queue = queue.Queue() + + # Start server thread + thread = threading.Thread(target=process_thread, args=([options, request_queue, response_queue])) + thread.start() + + # Set up GRPC endpoint + server = grpc.server(futures.ThreadPoolExecutor(max_workers=10)) + network_compute_bridge_service_pb2_grpc.add_NetworkComputeBridgeWorkerServicer_to_server( + NetworkComputeBridgeWorkerServicer(request_queue, response_queue), server) + server.add_insecure_port('[::]:' + options.port) + server.start() + + print('Running...') + thread.join() + + return True + +if __name__ == '__main__': + logging.basicConfig() + if not main(sys.argv[1:]): + sys.exit(1) diff --git a/jsk_spot_robot/coreio_extension/Dockerfile b/jsk_spot_robot/coreio_extension/Dockerfile new file mode 100644 index 0000000000..bb60defe90 --- /dev/null +++ b/jsk_spot_robot/coreio_extension/Dockerfile @@ -0,0 +1,42 @@ +# Use a base image provided by nvidia that already contains tensorflow 2.5 +## FROM nvcr.io/nvidia/l4t-tensorflow:r32.6.1-tf2.5-py3 +## https://docs.nvidia.com/deeplearning/frameworks/support-matrix/index.html#framework-matrix-2019 +FROM nvcr.io/nvidia/tensorflow:19.10-py3 + + +# Do some basic apt and pip updating +RUN apt-get update && \ + apt-get install -y --no-install-recommends python3-pip libgl1 &&\ + apt-get clean + +# Copy over the python requirements file and our prebuilt models API library +COPY docker-requirements.txt prebuilt/*.whl ./ +COPY models-with-protos models-with-protos + + +# Install the python requirements +RUN python3 -m pip install pip==21.3.1 setuptools==59.6.0 wheel==0.37.1 && \ + python3 -m pip install -r docker-requirements.txt --find-links . + +# https://dev.bostondynamics.com/docs/python/fetch_tutorial/fetch2 +RUN python3 -m pip install opencv-python==4.5.* +RUN python3 -m pip install --upgrade pip && \ + python3 -m pip install tensorflow-gpu==2.3.1 tensorflow==2.3.1 tensorboard==2.3.0 tf-models-official==2.3.0 pycocotools lvis && \ + python3 -m pip uninstall -y opencv-python-headless + + +RUN python3 -m pip install models-with-protos/research + +# copy pre-trained model +COPY ssd_resnet50_v1_fpn_640x640_coco17_tpu-8 /data/ssd_resnet50_v1_fpn_640x640_coco17_tpu-8 +RUN cp models-with-protos/research/object_detection/data/mscoco_label_map.pbtxt /data/ssd_resnet50_v1_fpn_640x640_coco17_tpu-8/ + +# copy trained model +COPY dogtoy-model /data/dogtoy-model + +# Copy over our main script +COPY network_compute_server.py /app/ +WORKDIR /app + +# Set our script as the main entrypoint for the container +ENTRYPOINT ["python3", "network_compute_server.py"] diff --git a/jsk_spot_robot/coreio_extension/Makefile b/jsk_spot_robot/coreio_extension/Makefile new file mode 100644 index 0000000000..ab1675d527 --- /dev/null +++ b/jsk_spot_robot/coreio_extension/Makefile @@ -0,0 +1,29 @@ +HOST_IP=192.168.50.3 # Or 10.0.0.3 + +all: + [ -e models-with-protos.zip ] || wget https://dev.bostondynamics.com/docs/python/fetch_tutorial/files/models-with-protos.zip + [ -e models-with-protos ] || unzip models-with-protos.zip + [ -e ssd_resnet50_v1_fpn_640x640_coco17_tpu-8.tar.gz ] || wget http://download.tensorflow.org/models/object_detection/tf2/20200711/ssd_resnet50_v1_fpn_640x640_coco17_tpu-8.tar.gz + [ -e ssd_resnet50_v1_fpn_640x640_coco17_tpu-8 ] || tar -xvzf ssd_resnet50_v1_fpn_640x640_coco17_tpu-8.tar.gz + [ -e dogtoy-model ] || cp -r ../dogtoy/exported-models/dogtoy-model . + [ -e dogtoy-model/label_map.pbtxt ] || cp ../dogtoy/annotations/label_map.pbtxt dogtoy-model/ + docker build -t object_detection_coco -f Dockerfile . + +run: + docker run --network host \ + -v /opt/payload_credentials/payload_guid_and_secret:/opt/payload_credentials/payload_guid_and_secret \ + --rm object_detection_coco \ + -m /data/dogtoy-model/saved_model /data/dogtoy-model/label_map.pbtxt \ + -m /data/ssd_resnet50_v1_fpn_640x640_coco17_tpu-8/saved_model \ + /data/ssd_resnet50_v1_fpn_640x640_coco17_tpu-8/mscoco_label_map.pbtxt \ + --payload-credentials-file /opt/payload_credentials/payload_guid_and_secret \ + --name ssd-resnet-coco-server \ + $(HOST_IP) + +export: + [ -e object_detection_coco.tar.gz ] || docker save object_detection_coco | pigz > object_detection_coco.tar.gz + # scp -P 20022 *.tgz to spot@10.0.0.3:/opt + # scp -P 20022 *.service spot@10.0.0.3:/lib/systemd/system/ + +bash: + docker run --gpus all --rm -it --entrypoint /bin/bash object_detection_coco diff --git a/jsk_spot_robot/coreio_extension/README.md b/jsk_spot_robot/coreio_extension/README.md new file mode 100644 index 0000000000..aae571ffdc --- /dev/null +++ b/jsk_spot_robot/coreio_extension/README.md @@ -0,0 +1,23 @@ +coreio extensions +----------------- + +Please follow https://dev.bostondynamics.com/docs/python/fetch_tutorial/fetch1 to get images, annotate target, training network. After that, we expect following directory structure. + +``` +- dogtoy/ + |- annotations/ + |- label_map.pbtxt + |- exported-models/ + |- dogtoy-model/ + |- checkpoint/ + |- pipeline.config + |- saved_model/ + |- models/ + |- pre-trained-models/ +``` + + +Then copy `dogtoy-model` directory within `dogtoy/exported-models/` to this directory and put `annotations/label_map.pbtxt` under `dogtoy-model`. See `Makefile` for detail. + +To build docker run `make all`. To export docker image, run `make export`. After that copy `object_detection_coco.tar.gz` to `/opt/` directory of SpotCORE. To copy the data, we recommend to connect the ethernet cable to the robot or dock and use `scp -P 20022 object_detection_coco.tar.gz spot@10.0.0.3:/tmp/` to copy data and then, login to to spotcore and run `sudo cp /tmp/object_detection_coco.tar.gz /opt`. Please keep backup before you overwrite `tar.gz` file. + diff --git a/jsk_spot_robot/coreio_extension/docker-compose.yml b/jsk_spot_robot/coreio_extension/docker-compose.yml new file mode 100644 index 0000000000..3d194245f2 --- /dev/null +++ b/jsk_spot_robot/coreio_extension/docker-compose.yml @@ -0,0 +1,20 @@ +version: "3.5" +services: + object_detection_coco: + image: object_detection_coco + network_mode: host + restart: unless-stopped + environment: + # This package couldn't be installed, but putting it on the path allows required access to the protos. + - PYTHONPATH=/models-with-protos/research/ + volumes: + # Mount payload credentials. + - /opt/payload_credentials:/opt/payload_credentials + # and ML models + - /data/.extensions/object_detection_coco/data:/data + # The command below is partial because the docker image is already configured with an entrypoint. + command: > + -m /data/ssd_resnet50_v1_fpn_640x640_coco17_tpu-8/saved_model /data/ssd_resnet50_v1_fpn_640x640_coco17_tpu-8/mscoco_label_map.pbtxt + --payload-credentials-file /opt/payload_credentials/payload_guid_and_secret + 10.0.0.3 +# 192.168.50.3 diff --git a/jsk_spot_robot/coreio_extension/docker-requirements.txt b/jsk_spot_robot/coreio_extension/docker-requirements.txt new file mode 100644 index 0000000000..12c96e14fe --- /dev/null +++ b/jsk_spot_robot/coreio_extension/docker-requirements.txt @@ -0,0 +1,40 @@ +bosdyn-api==3.2.0 + # via + # bosdyn-client + # bosdyn-core +bosdyn-client==3.2.0 + # via -r requirements.txt +bosdyn-core==3.2.0 + # via bosdyn-client +certifi==2022.5.18.1 + # via requests +charset-normalizer==2.0.12 + # via requests +deprecated==1.2.13 + # via + # bosdyn-client + # bosdyn-core +grpcio + #==1.46.3 (conflicts with base image) + # via bosdyn-client +idna==3.3 + # via requests +numpy==1.19.4 + # via bosdyn-client +protobuf==3.19.4 + # via bosdyn-api +pyjwt==2.4.0 + # via bosdyn-client +requests==2.27.1 + # via bosdyn-client +six + #==1.16.0 (conflicts with base image) + # via grpcio +urllib3==1.26.9 + # via requests +wrapt + #==1.14.1 (conflicts with base image) + # via deprecated +Pillow==8.4.0 +opencv-python==4.6.0.66 + # via network_compute_server.py \ No newline at end of file diff --git a/jsk_spot_robot/coreio_extension/ncb_object_detection_coco.service b/jsk_spot_robot/coreio_extension/ncb_object_detection_coco.service new file mode 100644 index 0000000000..40f869e6d8 --- /dev/null +++ b/jsk_spot_robot/coreio_extension/ncb_object_detection_coco.service @@ -0,0 +1,18 @@ +[Unit] +Description=Coco object detection container +After=docker.service +Wants=network-online.target docker.socket +Requires=docker.socket + +[Service] +Restart=always +User=root +ExecStartPre=/bin/bash -c "/usr/bin/docker container inspect object_detection_coco 2> /dev/null || /usr/bin/docker load < /opt/object_detection_coco.tar.gz;" +ExecStartPre=-/usr/bin/docker stop object_detection_coco +ExecStartPre=-/usr/bin/docker rm object_detection_coco +ExecStart=/usr/bin/docker run --name object_detection_coco --network host --rm -v /opt/payload_credentials/payload_guid_and_secret:/opt/payload_credentials/payload_guid_and_secret object_detection_coco -m /data/dogtoy-model/saved_model /data/dogtoy-model/label_map.pbtxt -m /data/ssd_resnet50_v1_fpn_640x640_coco17_tpu-8/saved_model /data/ssd_resnet50_v1_fpn_640x640_coco17_tpu-8/mscoco_label_map.pbtxt --payload-credentials-file /opt/payload_credentials/payload_guid_and_secret --name ssd-resnet-coco-server --port 50055 192.168.50.3 +ExecStop=/usr/bin/docker stop -t 10 object_detection_coco +TimeoutSec=900 + +[Install] +WantedBy=multi-user.target diff --git a/jsk_spot_robot/coreio_extension/network_compute_server.py b/jsk_spot_robot/coreio_extension/network_compute_server.py new file mode 100644 index 0000000000..9b697b8383 --- /dev/null +++ b/jsk_spot_robot/coreio_extension/network_compute_server.py @@ -0,0 +1,312 @@ +# Copyright (c) 2022 Boston Dynamics, Inc. All rights reserved. +# +# Downloading, reproducing, distributing or otherwise using the SDK Software +# is subject to the terms and conditions of the Boston Dynamics Software +# Development Kit License (20191101-BDSDK-SL). + +import argparse +import io +import os +import sys +import time +import logging + +import cv2 +from PIL import Image +import numpy as np + +from bosdyn.api import network_compute_bridge_service_pb2_grpc +from bosdyn.api import network_compute_bridge_pb2 +from bosdyn.api import image_pb2 +from bosdyn.api import header_pb2 +import bosdyn.client +import bosdyn.client.util +import grpc +from concurrent import futures +import tensorflow as tf + +import queue +import threading +from google.protobuf import wrappers_pb2 +from object_detection.utils import label_map_util + +kServiceAuthority = "fetch-tutorial-worker.spot.robot" + + +class TensorFlowObjectDetectionModel: + def __init__(self, model_path, label_path): + self.detect_fn = tf.saved_model.load(model_path) + self.category_index = label_map_util.create_category_index_from_labelmap(label_path, use_display_name=True) + self.name = os.path.basename(os.path.dirname(model_path)) + + def predict(self, image): + input_tensor = tf.convert_to_tensor(image) + input_tensor = input_tensor[tf.newaxis, ...] + detections = self.detect_fn(input_tensor) + + return detections + +def process_thread(args, request_queue, response_queue): + # Load the model(s) + models = {} + for model in args.model: + this_model = TensorFlowObjectDetectionModel(model[0], model[1]) + models[this_model.name] = this_model + + print('') + print('Service ' + args.name + ' running on port: ' + str(args.port)) + + print('Loaded models:') + for model_name in models: + print(' ' + model_name) + + while True: + request = request_queue.get() + + if isinstance(request, network_compute_bridge_pb2.ListAvailableModelsRequest): + out_proto = network_compute_bridge_pb2.ListAvailableModelsResponse() + for model_name in models: + out_proto.available_models.append(model_name) + if models[model_name].category_index is not None: + labels_msg = out_proto.labels.add() + labels_msg.model_name = model_name + for n in models[model_name].category_index.keys(): + labels_msg.available_labels.append(models[model_name].category_index[n]['name']) + response_queue.put(out_proto) + continue + else: + out_proto = network_compute_bridge_pb2.NetworkComputeResponse() + + # Find the model + if request.input_data.model_name not in models: + err_str = 'Cannot find model "' + request.input_data.model_name + '" in loaded models.' + print(err_str) + + # Set the error in the header. + out_proto.header.error.code = header_pb2.CommonError.CODE_INVALID_REQUEST + out_proto.header.error.message = err_str + response_queue.put(out_proto) + continue + + model = models[request.input_data.model_name] + + # Unpack the incoming image. + if request.input_data.image.format == image_pb2.Image.FORMAT_RAW: + pil_image = Image.open(io.BytesIO(request.input_data.image.data)) + if request.input_data.image.pixel_format == image_pb2.Image.PIXEL_FORMAT_GREYSCALE_U8: + # If the input image is grayscale, convert it to RGB. + image = cv2.cvtColor(pil_image, cv2.COLOR_GRAY2RGB) + + elif request.input_data.image.pixel_format == image_pb2.Image.PIXEL_FORMAT_RGB_U8: + # Already an RGB image. + image = pil_image + + else: + print('Error: image input in unsupported pixel format: ', request.input_data.image.pixel_format) + response_queue.put(out_proto) + continue + + elif request.input_data.image.format == image_pb2.Image.FORMAT_JPEG: + dtype = np.uint8 + jpg = np.frombuffer(request.input_data.image.data, dtype=dtype) + image = cv2.imdecode(jpg, -1) + + if len(image.shape) < 3: + # If the input image is grayscale, convert it to RGB. + image = cv2.cvtColor(image, cv2.COLOR_GRAY2RGB) + + image_width = image.shape[0] + image_height = image.shape[1] + + detections = model.predict(image) + + num_objects = 0 + + # All outputs are batches of tensors. + # Convert to numpy arrays, and take index [0] to remove the batch dimension. + # We're only interested in the first num_detections. + num_detections = int(detections.pop('num_detections')) + detections = {key: value[0, :num_detections].numpy() + for key, value in detections.items()} + + boxes = detections['detection_boxes'] + classes = detections['detection_classes'] + scores = detections['detection_scores'] + + + for i in range(boxes.shape[0]): + if scores[i] < request.input_data.min_confidence: + continue + + box = tuple(boxes[i].tolist()) + + # Boxes come in with normalized coordinates. Convert to pixel values. + box = [box[0] * image_width, box[1] * image_height, box[2] * image_width, box[3] * image_height] + + score = scores[i] + + if classes[i] in model.category_index.keys(): + label = model.category_index[classes[i]]['name'] + else: + label = 'N/A' + + num_objects += 1 + + print('Found object with label: "' + label + '" and score: ' + str(score)) + + + point1 = np.array([box[1], box[0]]) + point2 = np.array([box[3], box[0]]) + point3 = np.array([box[3], box[2]]) + point4 = np.array([box[1], box[2]]) + + # Add data to the output proto. + out_obj = out_proto.object_in_image.add() + out_obj.name = "obj" + str(num_objects) + "_label_" + label + + vertex1 = out_obj.image_properties.coordinates.vertexes.add() + vertex1.x = point1[0] + vertex1.y = point1[1] + + vertex2 = out_obj.image_properties.coordinates.vertexes.add() + vertex2.x = point2[0] + vertex2.y = point2[1] + + vertex3 = out_obj.image_properties.coordinates.vertexes.add() + vertex3.x = point3[0] + vertex3.y = point3[1] + + vertex4 = out_obj.image_properties.coordinates.vertexes.add() + vertex4.x = point4[0] + vertex4.y = point4[1] + + # Pack the confidence value. + confidence = wrappers_pb2.FloatValue(value=score) + out_obj.additional_properties.Pack(confidence) + + if not args.no_debug: + polygon = np.array([point1, point2, point3, point4], np.int32) + polygon = polygon.reshape((-1, 1, 2)) + cv2.polylines(image, [polygon], True, (0, 255, 0), 2) + + caption = "{}: {:.3f}".format(label, score) + left_x = min(point1[0], min(point2[0], min(point3[0], point4[0]))) + top_y = min(point1[1], min(point2[1], min(point3[1], point4[1]))) + cv2.putText(image, caption, (int(left_x), int(top_y)), cv2.FONT_HERSHEY_SIMPLEX, 0.5, + (0, 255, 0), 2) + + print('Found ' + str(num_objects) + ' object(s)') + + if not args.no_debug: + debug_image_filename = 'network_compute_server_output.jpg' + cv2.imwrite(debug_image_filename, image) + print('Wrote debug image output to: "' + debug_image_filename + '"') + + response_queue.put(out_proto) + + +class NetworkComputeBridgeWorkerServicer( + network_compute_bridge_service_pb2_grpc.NetworkComputeBridgeWorkerServicer): + + def __init__(self, thread_input_queue, thread_output_queue): + super(NetworkComputeBridgeWorkerServicer, self).__init__() + + self.thread_input_queue = thread_input_queue + self.thread_output_queue = thread_output_queue + + def NetworkCompute(self, request, context): + print('Got NetworkCompute request') + self.thread_input_queue.put(request) + out_proto = self.thread_output_queue.get() + return out_proto + + def ListAvailableModels(self, request, context): + print('Got ListAvailableModels request') + self.thread_input_queue.put(request) + out_proto = self.thread_output_queue.get() + return out_proto + + +def register_with_robot(options): + """ Registers this worker with the robot's Directory.""" + ip = bosdyn.client.common.get_self_ip(options.hostname) + print('Detected IP address as: ' + ip) + + sdk = bosdyn.client.create_standard_sdk("tensorflow_server") + + robot = sdk.create_robot(options.hostname) + + # Authenticate robot before being able to use it + if options.payload_credentials_file: + robot.authenticate_from_payload_credentials( + *bosdyn.client.util.get_guid_and_secret(options)) + else: + bosdyn.client.util.authenticate(robot) + + directory_client = robot.ensure_client( + bosdyn.client.directory.DirectoryClient.default_service_name) + directory_registration_client = robot.ensure_client( + bosdyn.client.directory_registration.DirectoryRegistrationClient.default_service_name) + + # Check to see if a service is already registered with our name + services = directory_client.list() + for s in services: + if s.name == options.name: + print("WARNING: existing service with name, \"" + options.name + "\", removing it.") + directory_registration_client.unregister(options.name) + break + + # Register service + print('Attempting to register ' + ip + ':' + options.port + ' onto ' + options.hostname + ' directory...') + directory_registration_client.register(options.name, "bosdyn.api.NetworkComputeBridgeWorker", kServiceAuthority, ip, int(options.port)) + + + +def main(argv): + default_port = '50051' + + parser = argparse.ArgumentParser() + parser.add_argument('-m', '--model', help='[MODEL_DIR] [LABELS_FILE.pbtxt]: Path to a model\'s directory and path to its labels .pbtxt file', action='append', nargs=2, required=True) + parser.add_argument('-p', '--port', help='Server\'s port number, default: ' + default_port, + default=default_port) + parser.add_argument('-d', '--no-debug', help='Disable writing debug images.', action='store_true') + parser.add_argument('-n', '--name', help='Service name', default='fetch-server') + bosdyn.client.util.add_payload_credentials_arguments(parser, required=False) + bosdyn.client.util.add_base_arguments(parser) + + options = parser.parse_args(argv) + + print(options.model) + + for model in options.model: + if not os.path.isdir(model[0]): + print('Error: model directory (' + model[0] + ') not found or is not a directory.') + sys.exit(1) + + # Perform registration. + register_with_robot(options) + + # Thread-safe queues for communication between the GRPC endpoint and the ML thread. + request_queue = queue.Queue() + response_queue = queue.Queue() + + # Start server thread + thread = threading.Thread(target=process_thread, args=([options, request_queue, response_queue])) + thread.start() + + # Set up GRPC endpoint + server = grpc.server(futures.ThreadPoolExecutor(max_workers=10)) + network_compute_bridge_service_pb2_grpc.add_NetworkComputeBridgeWorkerServicer_to_server( + NetworkComputeBridgeWorkerServicer(request_queue, response_queue), server) + server.add_insecure_port('[::]:' + options.port) + server.start() + + print('Running...') + thread.join() + + return True + +if __name__ == '__main__': + logging.basicConfig() + if not main(sys.argv[1:]): + sys.exit(1) diff --git a/jsk_spot_robot/jsk_spot_behavior_manager/CMakeLists.txt b/jsk_spot_robot/jsk_spot_behavior_manager/CMakeLists.txt new file mode 100644 index 0000000000..46b773ac6b --- /dev/null +++ b/jsk_spot_robot/jsk_spot_behavior_manager/CMakeLists.txt @@ -0,0 +1,9 @@ +cmake_minimum_required(VERSION 2.8.3) +project(jsk_spot_behavior_manager) + +find_package(catkin REQUIRED) + +catkin_python_setup() + +catkin_package( +) diff --git a/jsk_spot_robot/jsk_spot_behavior_manager/README.md b/jsk_spot_robot/jsk_spot_behavior_manager/README.md new file mode 100644 index 0000000000..96d7c3e0fd --- /dev/null +++ b/jsk_spot_robot/jsk_spot_behavior_manager/README.md @@ -0,0 +1,93 @@ +# jsk_spot_behaviors + +These packages enable for Spot to execute locomotoion behaviors to reach a desired position. + +## Concept + +In this framework, knowledge about positions and transtion behaviors between them are represented as a digraph like below. + +Each node represents specified positions and each edge represents a behavior to transition between them. + +![example_graph](https://user-images.githubusercontent.com/9410362/124147589-cc8ce700-dac9-11eb-930f-1c00c2a4777e.png) + +A graph is defined by a yaml file ( e.g. [node.yaml in spot_behavior_graph](./spot_behavior_graph/config/node.yaml) and [edge.yaml in spot_behavior_graph](./spot_behavior_graph/config/edge.yaml) ) +Please see nodes and edges format section below. + +Knowledge representation and execution process of behaviors are separated from actual behavior implementation. +The implementation of former is in spot_behavior_manager and spot_behavior_manager_server, but actual behaviors like walk and elevator are in spot_basic_behaviors. +behavior_manager_server node will dynamically load each behaviors defined in map.yaml so you can easilly add your behavior without editing these core implementation. +Please see spot_basic_behaviors package for behavior examples. + +## How to use it + +To run demo, please make Spot stand in front of the fiducial in 73B2 and run + +```bash +roslaunch spot_behavior_graph demo.launch +``` + +```bash +$ rostopic pub -1 /spot_behavior_manager_server/lead_person/execute_behaviors spot_behavior_manager_msgs/LeadPersonActionGoal "header: + seq: 0 + stamp: + secs: 0 + nsecs: 0 + frame_id: '' +goal_id: + stamp: + secs: 0 + nsecs: 0 + id: '' +goal: + target_node_id: 'eng2_2FElevator'" +``` + +Then Spot will go to 2FElevator of eng2 by walk behavior and elevator behavior implemented in spot_basic_behaviors packages. + +https://user-images.githubusercontent.com/9410362/124338016-aad25380-dbe0-11eb-962f-b9a27e1e08cb.mp4 + +For more details, please see [spot_behavior_manager](./spot_behavior_manager), [spot_behavior_manager_server](./spot_behavior_manager_server) and each behavior documentation. (e.g. [spot_basic_behaviors](./spot_basic_behaviors) ) + +## Behavior Graph + + +This package provides graph and node data for spot_behavior_manager. + +### scripts + +#### visualize_map.py + +Visualizer script of map file of spot_behavior_manager + +Befor using this script, you need to install + +``` +pip3 install graphviz xdot +``` + +##### Usage + +``` +rosrun spot_behavior_graph visualize_map.py --filename +``` + +##### Example Output + +![map](https://user-images.githubusercontent.com/9410362/132942120-4a4e652b-3d25-43df-a678-fd3c09782284.png) + +## behavior manager server + +This package provides ros nodes for spot_behavior_manager. + +### ROS Node + +#### behavior_manager_server.py + +ROS Node script for spot_behavior_manager. + +See [the source of behavior_manager_node](../spot_behavior_manager/src/spot_behavior_manager/behavior_manager_node.py). + +#### interactive_behavior_executor.py + +ROS Node script to execute action of spot_behavior_manager by language. + diff --git a/jsk_spot_robot/jsk_spot_behavior_manager/behavior_graph/edges.yaml b/jsk_spot_robot/jsk_spot_behavior_manager/behavior_graph/edges.yaml new file mode 100644 index 0000000000..13455ad03f --- /dev/null +++ b/jsk_spot_robot/jsk_spot_behavior_manager/behavior_graph/edges.yaml @@ -0,0 +1,389 @@ +edges: + - from: 'eng2_JSKEntrance' + to: 'eng2_73A2' + behavior_type: 'spot_basic_behaviors.walk_behavior.WalkBehavior' + cost: 10 + args: + graph: '$(find spot_autowalk_data)/autowalk/eng2_73B2_to_7FElevator.walk' + - from: 'eng2_73A2' + to: 'eng2_JSKEntrance' + behavior_type: 'spot_basic_behaviors.walk_behavior.WalkBehavior' + cost: 10 + args: + graph: '$(find spot_autowalk_data)/autowalk/eng2_73B2_to_7FElevator.walk' + - from: 'eng2_JSKEntrance' + to: 'eng2_73B1' + behavior_type: 'spot_basic_behaviors.walk_behavior.WalkBehavior' + cost: 10 + args: + graph: '$(find spot_autowalk_data)/autowalk/eng2_73B2_to_7FElevator.walk' + - from: 'eng2_73B1' + to: 'eng2_JSKEntrance' + behavior_type: 'spot_basic_behaviors.walk_behavior.WalkBehavior' + cost: 10 + args: + graph: '$(find spot_autowalk_data)/autowalk/eng2_73B2_to_7FElevator.walk' + - from: 'eng2_JSKEntrance' + to: 'eng2_73B2' + behavior_type: 'spot_basic_behaviors.walk_behavior.WalkBehavior' + cost: 10 + args: + graph: '$(find spot_autowalk_data)/autowalk/eng2_73B2_to_7FElevator.walk' + - from: 'eng2_73B2' + to: 'eng2_JSKEntrance' + behavior_type: 'spot_basic_behaviors.walk_behavior.WalkBehavior' + cost: 10 + args: + graph: '$(find spot_autowalk_data)/autowalk/eng2_73B2_to_7FElevator.walk' + - from: 'eng2_JSKEntrance' + to: 'eng2_73A4' + behavior_type: 'spot_basic_behaviors.walk_behavior.WalkBehavior' + cost: 10 + args: + graph: '$(find spot_autowalk_data)/autowalk/eng2_73B2_to_7FElevator.walk' + - from: 'eng2_73A4' + to: 'eng2_JSKEntrance' + behavior_type: 'spot_basic_behaviors.walk_behavior.WalkBehavior' + cost: 10 + args: + graph: '$(find spot_autowalk_data)/autowalk/eng2_73B2_to_7FElevator.walk' + - from: 'eng2_73B2' + to: 'eng2_73B2_belka_home' + behavior_type: 'spot_basic_behaviors.walk_behavior.WalkBehavior' + cost: 10 + args: + graph: '$(find spot_autowalk_data)/autowalk/eng2_73B2_to_7FElevator.walk' + - from: 'eng2_73B2_belka_home' + to: 'eng2_73B2' + behavior_type: 'spot_basic_behaviors.walk_behavior.WalkBehavior' + cost: 10 + args: + graph: '$(find spot_autowalk_data)/autowalk/eng2_73B2_to_7FElevator.walk' + - from: 'eng2_73B2' + to: 'eng2_73B2_strelka_home' + behavior_type: 'spot_basic_behaviors.walk_behavior.WalkBehavior' + cost: 10 + args: + graph: '$(find spot_autowalk_data)/autowalk/eng2_73B2_to_7FElevator.walk' + - from: 'eng2_73B2_strelka_home' + to: 'eng2_73B2' + behavior_type: 'spot_basic_behaviors.walk_behavior.WalkBehavior' + cost: 10 + args: + graph: '$(find spot_autowalk_data)/autowalk/eng2_73B2_to_7FElevator.walk' + - from: 'eng2_73B2' + to: 'eng2_73B2_shinjo_desk' + behavior_type: 'spot_basic_behaviors.walk_behavior.WalkBehavior' + cost: 10 + args: + graph: '$(find spot_autowalk_data)/autowalk/73b2_inside.walk' + - from: 'eng2_73B2_shinjo_desk' + to: 'eng2_73B2' + behavior_type: 'spot_basic_behaviors.walk_behavior.WalkBehavior' + cost: 10 + args: + graph: '$(find spot_autowalk_data)/autowalk/73b2_inside.walk' + - from: 'eng2_73B2' + to: 'eng2_81C1' + behavior_type: 'spot_basic_behaviors.walk_behavior.WalkBehavior' + cost: 10 + args: + graph: '$(find spot_autowalk_data)/autowalk/eng2_73B2_to_81C1.walk' + - from: 'eng2_81C1' + to: 'eng2_73B2' + behavior_type: 'spot_basic_behaviors.walk_behavior.WalkBehavior' + cost: 10 + args: + graph: '$(find spot_autowalk_data)/autowalk/eng2_73B2_to_81C1.walk' + - from: 'eng2_73B2' + to: 'eng2_83A2' + behavior_type: 'spot_basic_behaviors.walk_behavior.WalkBehavior' + cost: 10 + args: + graph: '$(find spot_autowalk_data)/autowalk/eng2_73B2_to_83A2.walk' + - from: 'eng2_83A2' + to: 'eng2_73B2' + behavior_type: 'spot_basic_behaviors.walk_behavior.WalkBehavior' + cost: 10 + args: + graph: '$(find spot_autowalk_data)/autowalk/eng2_73B2_to_83A2.walk' + - from: 'eng2_JSKEntrance' + to: 'eng2_7FElevator' + behavior_type: 'spot_basic_behaviors.walk_behavior.WalkBehavior' + cost: 10 + args: + graph: '$(find spot_autowalk_data)/autowalk/eng2_73B2_to_7FElevator.walk' + - from: 'eng2_7FElevator' + to: 'eng2_JSKEntrance' + behavior_type: 'spot_basic_behaviors.walk_behavior.WalkBehavior' + cost: 10 + args: + graph: '$(find spot_autowalk_data)/autowalk/eng2_73B2_to_7FElevator.walk' + - from: 'eng2_7FElevator' + to: 'eng2_2FElevator' + behavior_type: 'spot_basic_behaviors.elevator_behavior.ElevatorBehavior' + cost: 10 + args: + graph: '$(find spot_autowalk_data)/autowalk/eng2_elevator_7FElevator_to_2FElevator.walk' + rest_waypoint_id: 'unsaid-collie-jvatvS.7KX9jpzQz61GL4A==' + goal_waypoint_id: 'shelfy-dassie-vK9Tf3cAQpdA.25gPJl8OQ==' + - from: 'eng2_2FElevator' + to: 'eng2_7FElevator' + behavior_type: 'spot_basic_behaviors.elevator_behavior.ElevatorBehavior' + cost: 10 + args: + graph: '$(find spot_autowalk_data)/autowalk/eng2_elevator_7FElevator_to_2FElevator.walk' + rest_waypoint_id: 'unsaid-collie-jvatvS.7KX9jpzQz61GL4A==' + goal_waypoint_id: 'sly-chetah-IZ4pVY7vrqO36OoKCYk9Zg==' + - from: 'eng2_2FElevator' + to: 'eng_TelephoneBox' + behavior_type: 'spot_basic_behaviors.walk_behavior.WalkBehavior' + cost: 10 + args: + graph: '$(find spot_autowalk_data)/autowalk/eng2_2FElevator_to_eng8_2FElevator.walk' + - from: 'eng_TelephoneBox' + to: 'eng2_2FElevator' + behavior_type: 'spot_basic_behaviors.walk_behavior.WalkBehavior' + cost: 10 + args: + graph: '$(find spot_autowalk_data)/autowalk/eng2_2FElevator_to_eng8_2FElevator.walk' + - from: 'eng_TelephoneBox' + to: 'eng8_Piloti_Center' + behavior_type: 'spot_basic_behaviors.walk_behavior.WalkBehavior' + cost: 10 + args: + graph: '$(find spot_autowalk_data)/autowalk/eng2_2FElevator_to_eng8_2FElevator.walk' + - from: 'eng8_Piloti_Center' + to: 'eng_TelephoneBox' + behavior_type: 'spot_basic_behaviors.walk_behavior.WalkBehavior' + cost: 10 + args: + graph: '$(find spot_autowalk_data)/autowalk/eng2_2FElevator_to_eng8_2FElevator.walk' + - from: 'eng2_3FElevator' + to: 'eng2_2FElevator' + behavior_type: 'spot_basic_behaviors.elevator_behavior.ElevatorBehavior' + cost: 10 + args: + graph: '$(find spot_autowalk_data)/autowalk/eng2_elevator_3FElevator_to_2FElevator.walk' + rest_waypoint_id: 'akimbo-scarab-RoO1pzYYARw+EquQk1IIbg==' + goal_waypoint_id: 'dashed-virus-QgetwIkinypWecGNMSS4Yw==' + - from: 'eng2_2FElevator' + to: 'eng2_3FElevator' + behavior_type: 'spot_basic_behaviors.elevator_behavior.ElevatorBehavior' + cost: 10 + args: + graph: '$(find spot_autowalk_data)/autowalk/eng2_elevator_3FElevator_to_2FElevator.walk' + rest_waypoint_id: 'akimbo-scarab-RoO1pzYYARw+EquQk1IIbg==' + goal_waypoint_id: 'eighth-ant-0Ot3VoMm0uiJNBHnbJOF9w==' + - from: 'eng2_3FElevator' + to: 'eng2_3F_door_inside' + behavior_type: 'spot_basic_behaviors.walk_behavior.WalkBehavior' + cost: 10 + args: + graph: '$(find spot_autowalk_data)/autowalk/eng2_3FElevator_to_Mech_Office.walk' + - from: 'eng2_3F_door_inside' + to: 'eng2_3FElevator' + behavior_type: 'spot_basic_behaviors.walk_behavior.WalkBehavior' + cost: 10 + args: + graph: '$(find spot_autowalk_data)/autowalk/eng2_3FElevator_to_Mech_Office.walk' + - from: 'eng2_3F_door_inside' + to: 'eng2_3F_door_outside' + behavior_type: 'spot_basic_behaviors.walk_behavior.WalkBehavior' + cost: 10 + args: + graph: '$(find spot_autowalk_data)/autowalk/eng2_3FElevator_to_Mech_Office.walk' + - from: 'eng2_3F_door_outside' + to: 'eng2_3F_door_inside' + behavior_type: 'spot_basic_behaviors.walk_behavior.WalkBehavior' + cost: 10 + args: + graph: '$(find spot_autowalk_data)/autowalk/eng2_3FElevator_to_Mech_Office.walk' + - from: 'eng2_3F_door_outside' + to: 'eng2_Mech_Office' + behavior_type: 'spot_basic_behaviors.walk_behavior.WalkBehavior' + cost: 10 + args: + graph: '$(find spot_autowalk_data)/autowalk/eng2_3FElevator_to_Mech_Office.walk' + - from: 'eng2_Mech_Office' + to: 'eng2_3F_door_outside' + behavior_type: 'spot_basic_behaviors.walk_behavior.WalkBehavior' + cost: 10 + args: + graph: '$(find spot_autowalk_data)/autowalk/eng2_3FElevator_to_Mech_Office.walk' + - from: 'eng2_Mech_Office' + to: 'eng2_Mailbox' + behavior_type: 'spot_basic_behaviors.walk_behavior.WalkBehavior' + cost: 10 + args: + graph: '$(find spot_autowalk_data)/autowalk/eng2_MechOffice_inside.walk' + - from: 'eng2_Mailbox' + to: 'eng2_Mech_Office' + behavior_type: 'spot_basic_behaviors.walk_behavior.WalkBehavior' + cost: 10 + args: + graph: '$(find spot_autowalk_data)/autowalk/eng2_MechOffice_inside.walk' + - from: 'eng2_Mech_Office' + to: 'eng2_CampusMail' + behavior_type: 'spot_basic_behaviors.walk_behavior.WalkBehavior' + cost: 10 + args: + graph: '$(find spot_autowalk_data)/autowalk/eng2_MechOffice_inside.walk' + - from: 'eng2_CampusMail' + to: 'eng2_Mech_Office' + behavior_type: 'spot_basic_behaviors.walk_behavior.WalkBehavior' + cost: 10 + args: + graph: '$(find spot_autowalk_data)/autowalk/eng2_MechOffice_inside.walk' + - from: 'eng2_Mailbox' + to: 'eng2_CampusMail' + behavior_type: 'spot_basic_behaviors.walk_behavior.WalkBehavior' + cost: 10 + args: + graph: '$(find spot_autowalk_data)/autowalk/eng2_MechOffice_inside.walk' + - from: 'eng2_CampusMail' + to: 'eng2_Mailbox' + behavior_type: 'spot_basic_behaviors.walk_behavior.WalkBehavior' + cost: 10 + args: + graph: '$(find spot_autowalk_data)/autowalk/eng2_MechOffice_inside.walk' + - from: 'eng2_3FElevator' + to: 'eng2_7FElevator' + behavior_type: 'spot_basic_behaviors.elevator_behavior.ElevatorBehavior' + cost: 10 + args: + graph: '$(find spot_autowalk_data)/autowalk/eng2_elevator_3FElevator_to_2FElevator.walk' + rest_waypoint_id: 'akimbo-scarab-RoO1pzYYARw+EquQk1IIbg==' + goal_waypoint_id: 'dashed-virus-QgetwIkinypWecGNMSS4Yw==' + - from: 'eng2_7FElevator' + to: 'eng2_3FElevator' + behavior_type: 'spot_basic_behaviors.elevator_behavior.ElevatorBehavior' + cost: 10 + args: + graph: '$(find spot_autowalk_data)/autowalk/eng2_elevator_7FElevator_to_2FElevator.walk' + rest_waypoint_id: 'unsaid-collie-jvatvS.7KX9jpzQz61GL4A==' + goal_waypoint_id: 'shelfy-dassie-vK9Tf3cAQpdA.25gPJl8OQ==' + - from: 'eng2_2FElevator' + to: 'eng2_subway_pillar' + behavior_type: 'spot_basic_behaviors.walk_behavior.WalkBehavior' + cost: 10 + args: + graph: '$(find spot_autowalk_data)/autowalk/eng2_2FElevator_to_subway.walk' + - from: 'eng2_subway_pillar' + to: 'eng2_2FElevator' + behavior_type: 'spot_basic_behaviors.walk_behavior.WalkBehavior' + cost: 10 + args: + graph: '$(find spot_autowalk_data)/autowalk/eng2_2FElevator_to_subway.walk' + - from: 'eng2_subway_pillar' + to: 'eng2_subway_initial' + behavior_type: 'spot_basic_behaviors.walk_behavior.WalkBehavior' + cost: 10 + args: + graph: '$(find spot_autowalk_data)/autowalk/eng2_subway_tablenavigation.walk' + - from: 'eng2_subway_initial' + to: 'eng2_subway_pillar' + behavior_type: 'spot_basic_behaviors.walk_behavior.WalkBehavior' + cost: 10 + args: + graph: '$(find spot_autowalk_data)/autowalk/eng2_subway_tablenavigation.walk' + - from: 'eng2_subway_initial' + to: 'eng2_subway_table_00' + behavior_type: 'spot_basic_behaviors.walk_behavior.WalkBehavior' + cost: 10 + args: + graph: '$(find spot_autowalk_data)/autowalk/eng2_subway_tablenavigation.walk' + - from: 'eng2_subway_table_00' + to: 'eng2_subway_initial' + behavior_type: 'spot_basic_behaviors.walk_behavior.WalkBehavior' + cost: 10 + args: + graph: '$(find spot_autowalk_data)/autowalk/eng2_subway_tablenavigation.walk' + - from: 'eng2_2FElevator' + to: 'eng2_MainEntrance_upper' + behavior_type: 'spot_basic_behaviors.walk_behavior.WalkBehavior' + cost: 10 + args: + graph: '$(find spot_autowalk_data)/autowalk/eng2_2FElevator_to_MainEntrance.walk' + - from: 'eng2_MainEntrance_upper' + to: 'eng2_2FElevator' + behavior_type: 'spot_basic_behaviors.walk_behavior.WalkBehavior' + cost: 10 + args: + graph: '$(find spot_autowalk_data)/autowalk/eng2_2FElevator_to_MainEntrance.walk' + - from: 'eng2_MainEntrance_upper' + to: 'eng2_MainEntrance_lower' + behavior_type: 'spot_basic_behaviors.walk_behavior.WalkBehavior' + cost: 10 + args: + graph: '$(find spot_autowalk_data)/autowalk/eng2_MainEntrance_to_HongoMainGate_Daylight.walk' + - from: 'eng2_MainEntrance_lower' + to: 'eng2_MainEntrance_upper' + behavior_type: 'spot_basic_behaviors.walk_behavior.WalkBehavior' + cost: 10 + args: + graph: '$(find spot_autowalk_data)/autowalk/eng2_MainEntrance_to_HongoMainGate_Daylight.walk' + - from: 'eng2_MainEntrance_lower' + to: 'eng2_MainEntrance_outer' + behavior_type: 'spot_basic_behaviors.crosswalk_behavior.CrosswalkBehavior' + cost: 10 + args: + graph: '$(find spot_autowalk_data)/autowalk/eng2_MainEntrance_to_HongoMainGate_Daylight.walk' + - from: 'eng2_MainEntrance_outer' + to: 'eng2_MainEntrance_lower' + behavior_type: 'spot_basic_behaviors.crosswalk_behavior.CrosswalkBehavior' + cost: 10 + args: + graph: '$(find spot_autowalk_data)/autowalk/eng2_MainEntrance_to_HongoMainGate_Daylight.walk' + - from: 'eng2_MainEntrance_outer' + to: 'HongoMainGate' + behavior_type: 'spot_basic_behaviors.walk_behavior.WalkBehavior' + cost: 10 + args: + graph: '$(find spot_autowalk_data)/autowalk/eng2_MainEntrance_to_HongoMainGate_Daylight.walk' + - from: 'HongoMainGate' + to: 'eng2_MainEntrance_outer' + behavior_type: 'spot_basic_behaviors.walk_behavior.WalkBehavior' + cost: 10 + args: + graph: '$(find spot_autowalk_data)/autowalk/eng2_MainEntrance_to_HongoMainGate_Daylight.walk' + - from: 'eng2_7FElevator' + to: 'eng2_1FElevator' + behavior_type: 'spot_basic_behaviors.elevator_behavior.ElevatorBehavior' + cost: 10 + args: + graph: '$(find spot_autowalk_data)/autowalk/eng2_elevator_7FElevator_to_1FElevator.walk' + rest_waypoint_id: 'snubbed-puma-5v8XtjdA17XhPNLhsH2WEg==' + goal_waypoint_id: 'meager-fox-bT4DU3uCHBJFWuYILkopVg==' + - from: 'eng2_1FElevator' + to: 'eng2_7FElevator' + behavior_type: 'spot_basic_behaviors.elevator_behavior.ElevatorBehavior' + cost: 10 + args: + graph: '$(find spot_autowalk_data)/autowalk/eng2_elevator_7FElevator_to_1FElevator.walk' + rest_waypoint_id: 'snubbed-puma-5v8XtjdA17XhPNLhsH2WEg==' + goal_waypoint_id: 'meager-fox-bT4DU3uCHBJFWuYILkopVg==' + - from: 'eng2_1FElevator' + to: 'eng2_GarbageCollectionArea_east' + behavior_type: 'spot_basic_behaviors.walk_behavior.WalkBehavior' + cost: 10 + args: + graph: '$(find spot_autowalk_data)/autowalk/eng2_1FElevator_to_1FGarbageCollectionAreaEast.walk' + - from: 'eng2_1FElevator' + to: 'eng2_GarbageCollectionArea_south' + behavior_type: 'spot_basic_behaviors.walk_behavior.WalkBehavior' + cost: 10 + args: + graph: '$(find spot_autowalk_data)/autowalk/eng2_1FElevator_to_1FGarbageCollectionAreaSouth.walk' + - from: 'eng2_GarbageCollectionArea_east' + to: 'eng2_1FElevator' + behavior_type: 'spot_basic_behaviors.walk_behavior.WalkBehavior' + cost: 10 + args: + graph: '$(find spot_autowalk_data)/autowalk/eng2_1FElevator_to_1FGarbageCollectionAreaEast.walk' + - from: 'eng2_GarbageCollectionArea_south' + to: 'eng2_1FElevator' + behavior_type: 'spot_basic_behaviors.walk_behavior.WalkBehavior' + cost: 10 + args: + graph: '$(find spot_autowalk_data)/autowalk/eng2_1FElevator_to_1FGarbageCollectionAreaSouth.walk' diff --git a/jsk_spot_robot/jsk_spot_behavior_manager/behavior_graph/edges_person_lead.yaml b/jsk_spot_robot/jsk_spot_behavior_manager/behavior_graph/edges_person_lead.yaml new file mode 100644 index 0000000000..64632e982f --- /dev/null +++ b/jsk_spot_robot/jsk_spot_behavior_manager/behavior_graph/edges_person_lead.yaml @@ -0,0 +1,295 @@ +edges: + - from: 'eng2_73B2' + to: 'eng2_7FElevator' + behavior_type: 'spot_person_lead_behaviors.narrow_behavior.NarrowBehavior' + cost: 10 + args: + graph: '$(find spot_autowalk_data)/autowalk/eng2_73B2_to_7FElevator.walk' + - from: 'eng2_7FElevator' + to: 'eng2_73B2' + behavior_type: 'spot_person_lead_behaviors.narrow_behavior.NarrowBehavior' + cost: 10 + args: + graph: '$(find spot_autowalk_data)/autowalk/eng2_73B2_to_7FElevator.walk' + - from: 'eng2_7FElevator' + to: 'eng2_2FElevator' + behavior_type: 'spot_person_lead_behaviors.elevator_behavior.ElevatorBehavior' + cost: 10 + args: + graph: '$(find spot_autowalk_data)/autowalk/eng2_elevator_7FElevator_to_2FElevator.walk' + rest_waypoint_id: 'unsaid-collie-jvatvS.7KX9jpzQz61GL4A==' + goal_waypoint_id: 'shelfy-dassie-vK9Tf3cAQpdA.25gPJl8OQ==' + - from: 'eng2_2FElevator' + to: 'eng2_7FElevator' + behavior_type: 'spot_person_lead_behaviors.elevator_behavior.ElevatorBehavior' + cost: 10 + args: + graph: '$(find spot_autowalk_data)/autowalk/eng2_elevator_7FElevator_to_2FElevator.walk' + rest_waypoint_id: 'unsaid-collie-jvatvS.7KX9jpzQz61GL4A==' + goal_waypoint_id: 'sly-chetah-IZ4pVY7vrqO36OoKCYk9Zg==' + - from: 'eng2_2FElevator' + to: 'eng2_MainEntrance_upper' + behavior_type: 'spot_person_lead_behaviors.narrow_behavior.NarrowBehavior' + cost: 10 + args: + graph: '$(find spot_autowalk_data)/autowalk/eng2_2FElevator_to_MainEntrance.walk' + - from: 'eng2_MainEntrance_upper' + to: 'eng2_2FElevator' + behavior_type: 'spot_person_lead_behaviors.narrow_behavior.NarrowBehavior' + cost: 10 + args: + graph: '$(find spot_autowalk_data)/autowalk/eng2_2FElevator_to_MainEntrance.walk' + - from: 'eng2_MainEntrance_upper' + to: 'eng2_MainEntrance_lower' + behavior_type: 'spot_person_lead_behaviors.stair_behavior.StairBehavior' + cost: 10 + args: + graph: '$(find spot_autowalk_data)/autowalk/eng2_MainEntrance_to_HongoMainGate_Daylight.walk' + stair_nums: + - -4 + - -8 + - from: 'eng2_MainEntrance_lower' + to: 'eng2_MainEntrance_upper' + behavior_type: 'spot_person_lead_behaviors.stair_behavior.StairBehavior' + cost: 10 + args: + graph: '$(find spot_autowalk_data)/autowalk/eng2_MainEntrance_to_HongoMainGate_Daylight.walk' + stair_nums: + - 8 + - 4 + - from: 'eng2_MainEntrance_lower' + to: 'eng2_MainEntrance_outer' + behavior_type: 'spot_person_lead_behaviors.crosswalk_behavior.CrosswalkBehavior' + cost: 10 + args: + graph: '$(find spot_autowalk_data)/autowalk/eng2_MainEntrance_to_HongoMainGate_Daylight.walk' + - from: 'eng2_MainEntrance_outer' + to: 'eng2_MainEntrance_lower' + behavior_type: 'spot_person_lead_behaviors.crosswalk_behavior.CrosswalkBehavior' + cost: 10 + args: + graph: '$(find spot_autowalk_data)/autowalk/eng2_MainEntrance_to_HongoMainGate_Daylight.walk' + - from: 'eng2_MainEntrance_outer' + to: 'HongoMainGate' + behavior_type: 'spot_person_lead_behaviors.walk_behavior.WalkBehavior' + cost: 10 + args: + graph: '$(find spot_autowalk_data)/autowalk/eng2_MainEntrance_to_HongoMainGate_Daylight.walk' + - from: 'HongoMainGate' + to: 'eng2_MainEntrance_outer' + behavior_type: 'spot_person_lead_behaviors.walk_behavior.WalkBehavior' + cost: 10 + args: + graph: '$(find spot_autowalk_data)/autowalk/eng2_MainEntrance_to_HongoMainGate_Daylight.walk' + - from: 'eng2_3FElevator' + to: 'eng2_2FElevator' + behavior_type: 'spot_person_lead_behaviors.elevator_behavior.ElevatorBehavior' + cost: 10 + args: + graph: '$(find spot_autowalk_data)/autowalk/eng2_elevator_3FElevator_to_2FElevator.walk' + rest_waypoint_id: 'akimbo-scarab-RoO1pzYYARw+EquQk1IIbg==' + goal_waypoint_id: 'dashed-virus-QgetwIkinypWecGNMSS4Yw==' + - from: 'eng2_2FElevator' + to: 'eng2_3FElevator' + behavior_type: 'spot_person_lead_behaviors.elevator_behavior.ElevatorBehavior' + cost: 10 + args: + graph: '$(find spot_autowalk_data)/autowalk/eng2_elevator_3FElevator_to_2FElevator.walk' + rest_waypoint_id: 'akimbo-scarab-RoO1pzYYARw+EquQk1IIbg==' + goal_waypoint_id: 'eighth-ant-0Ot3VoMm0uiJNBHnbJOF9w==' + - from: 'eng2_3FElevator' + to: 'eng2_3F_door_inside' + behavior_type: 'spot_person_lead_behaviors.walk_behavior.WalkBehavior' + cost: 10 + args: + graph: '$(find spot_autowalk_data)/autowalk/eng2_3FElevator_to_Mech_Office.walk' + - from: 'eng2_3F_door_inside' + to: 'eng2_3FElevator' + behavior_type: 'spot_person_lead_behaviors.walk_behavior.WalkBehavior' + cost: 10 + args: + graph: '$(find spot_autowalk_data)/autowalk/eng2_3FElevator_to_Mech_Office.walk' + - from: 'eng2_3F_door_inside' + to: 'eng2_3F_door_outside' + behavior_type: 'spot_person_lead_behaviors.walk_behavior.WalkBehavior' + cost: 10 + args: + graph: '$(find spot_autowalk_data)/autowalk/eng2_3FElevator_to_Mech_Office.walk' + - from: 'eng2_3F_door_outside' + to: 'eng2_3F_door_inside' + behavior_type: 'spot_person_lead_behaviors.walk_behavior.WalkBehavior' + cost: 10 + args: + graph: '$(find spot_autowalk_data)/autowalk/eng2_3FElevator_to_Mech_Office.walk' + - from: 'eng2_3F_door_outside' + to: 'eng2_Mech_Office' + behavior_type: 'spot_person_lead_behaviors.walk_behavior.WalkBehavior' + cost: 10 + args: + graph: '$(find spot_autowalk_data)/autowalk/eng2_3FElevator_to_Mech_Office.walk' + - from: 'eng2_Mech_Office' + to: 'eng2_3F_door_outside' + behavior_type: 'spot_person_lead_behaviors.walk_behavior.WalkBehavior' + cost: 10 + args: + graph: '$(find spot_autowalk_data)/autowalk/eng2_3FElevator_to_Mech_Office.walk' + - from: 'eng2_3FElevator' + to: 'eng2_7FElevator' + behavior_type: 'spot_person_lead_behaviors.elevator_behavior.ElevatorBehavior' + cost: 10 + args: + graph: '$(find spot_autowalk_data)/autowalk/eng2_elevator_3FElevator_to_2FElevator.walk' + rest_waypoint_id: 'akimbo-scarab-RoO1pzYYARw+EquQk1IIbg==' + goal_waypoint_id: 'dashed-virus-QgetwIkinypWecGNMSS4Yw==' + - from: 'eng2_7FElevator' + to: 'eng2_3FElevator' + behavior_type: 'spot_person_lead_behaviors.elevator_behavior.ElevatorBehavior' + cost: 10 + args: + graph: '$(find spot_autowalk_data)/autowalk/eng2_elevator_7FElevator_to_2FElevator.walk' + rest_waypoint_id: 'unsaid-collie-jvatvS.7KX9jpzQz61GL4A==' + goal_waypoint_id: 'shelfy-dassie-vK9Tf3cAQpdA.25gPJl8OQ==' + - from: 'eng2_2FElevator' + to: 'eng2_subway_pillar' + behavior_type: 'spot_person_lead_behaviors.walk_behavior.WalkBehavior' + cost: 10 + args: + graph: '$(find spot_autowalk_data)/autowalk/eng2_2FElevator_to_subway.walk' + - from: 'eng2_subway_pillar' + to: 'eng2_2FElevator' + behavior_type: 'spot_person_lead_behaviors.walk_behavior.WalkBehavior' + cost: 10 + args: + graph: '$(find spot_autowalk_data)/autowalk/eng2_2FElevator_to_subway.walk' + - from: 'eng2_subway_pillar' + to: 'eng2_subway_initial' + behavior_type: 'spot_person_lead_behaviors.walk_behavior.WalkBehavior' + cost: 10 + args: + graph: '$(find spot_autowalk_data)/autowalk/eng2_subway_tablenavigation.walk' + - from: 'eng2_subway_initial' + to: 'eng2_subway_pillar' + behavior_type: 'spot_person_lead_behaviors.walk_behavior.WalkBehavior' + cost: 10 + args: + graph: '$(find spot_autowalk_data)/autowalk/eng2_subway_tablenavigation.walk' + - from: 'eng2_subway_initial' + to: 'eng2_subway_table_00' + behavior_type: 'spot_person_lead_behaviors.walk_behavior.WalkBehavior' + cost: 10 + args: + graph: '$(find spot_autowalk_data)/autowalk/eng2_subway_tablenavigation.walk' + - from: 'eng2_subway_table_00' + to: 'eng2_subway_initial' + behavior_type: 'spot_person_lead_behaviors.walk_behavior.WalkBehavior' + cost: 10 + args: + graph: '$(find spot_autowalk_data)/autowalk/eng2_subway_tablenavigation.walk' + - from: 'eng2_subway_initial' + to: 'eng2_subway_table_01' + behavior_type: 'spot_person_lead_behaviors.walk_behavior.WalkBehavior' + cost: 10 + args: + graph: '$(find spot_autowalk_data)/autowalk/eng2_subway_tablenavigation.walk' + - from: 'eng2_subway_table_01' + to: 'eng2_subway_initial' + behavior_type: 'spot_person_lead_behaviors.walk_behavior.WalkBehavior' + cost: 10 + args: + graph: '$(find spot_autowalk_data)/autowalk/eng2_subway_tablenavigation.walk' + - from: 'eng2_subway_initial' + to: 'eng2_subway_table_02' + behavior_type: 'spot_person_lead_behaviors.walk_behavior.WalkBehavior' + cost: 10 + args: + graph: '$(find spot_autowalk_data)/autowalk/eng2_subway_tablenavigation_02.walk' + - from: 'eng2_subway_table_02' + to: 'eng2_subway_initial' + behavior_type: 'spot_person_lead_behaviors.walk_behavior.WalkBehavior' + cost: 10 + args: + graph: '$(find spot_autowalk_data)/autowalk/eng2_subway_tablenavigation_02.walk' + - from: 'eng2_subway_initial' + to: 'eng2_subway_table_03' + behavior_type: 'spot_person_lead_behaviors.walk_behavior.WalkBehavior' + cost: 10 + args: + graph: '$(find spot_autowalk_data)/autowalk/eng2_subway_tablenavigation_02.walk' + - from: 'eng2_subway_table_03' + to: 'eng2_subway_initial' + behavior_type: 'spot_person_lead_behaviors.walk_behavior.WalkBehavior' + cost: 10 + args: + graph: '$(find spot_autowalk_data)/autowalk/eng2_subway_tablenavigation_02.walk' + - from: 'eng2_subway_initial' + to: 'eng2_subway_table_04' + behavior_type: 'spot_person_lead_behaviors.walk_behavior.WalkBehavior' + cost: 10 + args: + graph: '$(find spot_autowalk_data)/autowalk/eng2_subway_tablenavigation_02.walk' + - from: 'eng2_subway_table_04' + to: 'eng2_subway_initial' + behavior_type: 'spot_person_lead_behaviors.walk_behavior.WalkBehavior' + cost: 10 + args: + graph: '$(find spot_autowalk_data)/autowalk/eng2_subway_tablenavigation_02.walk' + - from: 'eng2_subway_initial' + to: 'eng2_subway_table_10' + behavior_type: 'spot_person_lead_behaviors.walk_behavior.WalkBehavior' + cost: 10 + args: + graph: '$(find spot_autowalk_data)/autowalk/eng2_subway_tablenavigation.walk' + - from: 'eng2_subway_table_10' + to: 'eng2_subway_initial' + behavior_type: 'spot_person_lead_behaviors.walk_behavior.WalkBehavior' + cost: 10 + args: + graph: '$(find spot_autowalk_data)/autowalk/eng2_subway_tablenavigation.walk' + - from: 'eng2_subway_initial' + to: 'eng2_subway_table_11' + behavior_type: 'spot_person_lead_behaviors.walk_behavior.WalkBehavior' + cost: 10 + args: + graph: '$(find spot_autowalk_data)/autowalk/eng2_subway_tablenavigation.walk' + - from: 'eng2_subway_table_11' + to: 'eng2_subway_initial' + behavior_type: 'spot_person_lead_behaviors.walk_behavior.WalkBehavior' + cost: 10 + args: + graph: '$(find spot_autowalk_data)/autowalk/eng2_subway_tablenavigation.walk' + - from: 'eng2_subway_initial' + to: 'eng2_subway_table_12' + behavior_type: 'spot_person_lead_behaviors.walk_behavior.WalkBehavior' + cost: 10 + args: + graph: '$(find spot_autowalk_data)/autowalk/eng2_subway_tablenavigation_02.walk' + - from: 'eng2_subway_table_12' + to: 'eng2_subway_initial' + behavior_type: 'spot_person_lead_behaviors.walk_behavior.WalkBehavior' + cost: 10 + args: + graph: '$(find spot_autowalk_data)/autowalk/eng2_subway_tablenavigation_02.walk' + - from: 'eng2_subway_initial' + to: 'eng2_subway_table_13' + behavior_type: 'spot_person_lead_behaviors.walk_behavior.WalkBehavior' + cost: 10 + args: + graph: '$(find spot_autowalk_data)/autowalk/eng2_subway_tablenavigation_02.walk' + - from: 'eng2_subway_table_13' + to: 'eng2_subway_initial' + behavior_type: 'spot_person_lead_behaviors.walk_behavior.WalkBehavior' + cost: 10 + args: + graph: '$(find spot_autowalk_data)/autowalk/eng2_subway_tablenavigation_02.walk' + - from: 'eng2_subway_initial' + to: 'eng2_subway_table_14' + behavior_type: 'spot_person_lead_behaviors.walk_behavior.WalkBehavior' + cost: 10 + args: + graph: '$(find spot_autowalk_data)/autowalk/eng2_subway_tablenavigation_02.walk' + - from: 'eng2_subway_table_14' + to: 'eng2_subway_initial' + behavior_type: 'spot_person_lead_behaviors.walk_behavior.WalkBehavior' + cost: 10 + args: + graph: '$(find spot_autowalk_data)/autowalk/eng2_subway_tablenavigation_02.walk' diff --git a/jsk_spot_robot/jsk_spot_behavior_manager/behavior_graph/nodes.yaml b/jsk_spot_robot/jsk_spot_behavior_manager/behavior_graph/nodes.yaml new file mode 100644 index 0000000000..49006a7fde --- /dev/null +++ b/jsk_spot_robot/jsk_spot_behavior_manager/behavior_graph/nodes.yaml @@ -0,0 +1,421 @@ +nodes: + 'dummy': + name_en: 'dummy' + name_jp: 'ダミー' + 'eng2_JSKEntrance': + name_en: '73B1' + name_jp: + - 'JSK' + waypoints_on_graph: + - graph: '$(find spot_autowalk_data)/autowalk/eng2_73B2_to_7FElevator.walk' + id: 'hated-salmon-+oAmVbVKzwfer6BexXKNog==' + localization_method: 'waypoint' + 'eng2_73B1': + name_en: '73B1' + name_jp: + - '73 B 1' + - '73B1' + waypoints_on_graph: + - graph: '$(find spot_autowalk_data)/autowalk/eng2_73B2_to_7FElevator.walk' + id: 'unkept-scarab-pfr5U1CBRCXx9EOt6eQDCw==' + localization_method: 'waypoint' + 'eng2_73B2': + name_en: '73B2' + name_jp: + - '73 B 2' + - '73B2' + waypoints_on_graph: + - graph: '$(find spot_autowalk_data)/autowalk/eng2_73B2_to_7FElevator.walk' + id: 'arty-hyrax-hiViwXJwUrkJ2LR5UAuKyw==' + localization_method: 'fiducial' + - graph: '$(find spot_autowalk_data)/autowalk/73b2_inside.walk' + id: 'goodly-hogg-cEjt+0aMeZdksVRXrP7.uw==' + localization_method: 'fiducial' + - graph: '$(find spot_autowalk_data)/autowalk/eng2_73B2_to_81C1.walk' + id: 'fuzzed-medusa-wyXWCEjmWas1kMo8EJW8Bw==' + localization_method: 'fiducial' + - graph: '$(find spot_autowalk_data)/autowalk/eng2_73B2_to_83A2.walk' + id: 'dyed-viper-HVQRLVeklEUaE2aXJW+d6Q==' + localization_method: 'fiducial' + - graph: '$(find spot_autowalk_data)/autowalk/eng2_7FElevator_to_73B2_trash.walk' + id: 'tined-mouse-D.2ws5hStb8AE.C.J5lKEw==' + localization_method: 'fiducial' + 'eng2_73B2_belka_home': + name_en: 'belka' + name_jp: + - 'ベルカ' + waypoints_on_graph: + - graph: '$(find spot_autowalk_data)/autowalk/eng2_73B2_to_7FElevator.walk' + id: 'uppity-cayman-rm8aTrilycp7soPeorOdUw==' + localization_method: 'waypoint' + 'eng2_73B2_strelka_home': + name_en: 'strelka' + name_jp: + - 'ストレルカ' + waypoints_on_graph: + - graph: '$(find spot_autowalk_data)/autowalk/eng2_73B2_to_7FElevator.walk' + id: 'proof-llama-9slE0zml9BYcNFbpQkmNcg==' + localization_method: 'waypoint' + 'eng2_73B2_shinjo_desk': + name_en: 'shinjo' + name_jp: + - '新城' + - '新城の席' + waypoints_on_graph: + - graph: '$(find spot_autowalk_data)/autowalk/73b2_inside.walk' + id: 'cogent-vole-wTZdgPPyjZIObFh2dr7EqA==' + localization_method: 'waypoint' + 'eng2_73A2': + name_en: '73A2' + name_jp: + - '73 A 2' + - '73A2' + waypoints_on_graph: + - graph: '$(find spot_autowalk_data)/autowalk/eng2_73B2_to_7FElevator.walk' + id: 'enough-python-pqYBEhDZHuCEapcDxLK32w==' + localization_method: 'waypoint' + 'eng2_73A4': + name_en: '73A4' + name_jp: + - '73 A 4' + - '73A4' + waypoints_on_graph: + - graph: '$(find spot_autowalk_data)/autowalk/eng2_73B2_to_7FElevator.walk' + id: 'dashed-kodiak-bKNHSR3S7fXE11UeXeeekA==' + localization_method: 'waypoint' + 'eng2_81C1': + name_en: '81C1' + name_jp: + - '81 C 1' + - '81C1' + - 'プロジェクト部屋' + - 'プロジェクト 部屋' + waypoints_on_graph: + - graph: '$(find spot_autowalk_data)/autowalk/eng2_73B2_to_81C1.walk' + id: 'sable-narwal-o7qEo7543BUTj4sNKWcQ6g==' + localization_method: 'waypoint' + 'eng2_83A2': + name_en: '83A2' + name_jp: + - '83 A 2' + - '83A2' + - '工作室' + waypoints_on_graph: + - graph: '$(find spot_autowalk_data)/autowalk/eng2_73B2_to_83A2.walk' + id: 'shaved-koala-aLy+bUIQpAHBQbi5DxeKJA==' + localization_method: 'waypoint' + 'eng2_7FElevator': + name_en: 'Elevator' + name_jp: + - '7階エレベーター' + - '7階 エレベーター' + waypoints_on_graph: + - graph: '$(find spot_autowalk_data)/autowalk/eng2_73B2_to_7FElevator.walk' + id: 'looted-cougar-RWT2C0zLJXG.ezYnCuLUeQ==' + localization_method: 'fiducial' + - graph: '$(find spot_autowalk_data)/autowalk/eng2_7FElevator_to_2FElevator.walk' + id: 'larger-mudcat-0x3bt3Dif.1QU9JfXpyQPw==' + localization_method: 'fiducial' + - graph: '$(find spot_autowalk_data)/autowalk/eng2_elevator_7FElevator_to_2FElevator.walk' + id: 'sly-chetah-IZ4pVY7vrqO36OoKCYk9Zg==' + localization_method: 'fiducial' + - graph: '$(find spot_autowalk_data)/autowalk/eng2_elevator_7FElevator_to_1FElevator.walk' + id: 'meager-fox-bT4DU3uCHBJFWuYILkopVg==' + localization_method: 'fiducial' + - graph: '$(find spot_autowalk_data)/autowalk/eng2_elevator_7FElevator_to_73B2_trash.walk' + id: 'spruce-frog-bNtHHEv469wvlNxlIxBV5w==' + localization_method: 'fiducial' + switchbot_device: '/eng2/7f/elevator_bot_down' + floor: 7 + floor_height: 24 + 'eng2_2FElevator': + name_en: 'Elevator' + name_jp: + - '2階 エレベーター' + - '2階エレベーター' + waypoints_on_graph: + - graph: '$(find spot_autowalk_data)/autowalk/eng2_7FElevator_to_2FElevator.walk' + id: 'rainy-collie-uwFW2KVYg2YCUi8FF6N00g==' + localization_method: 'fiducial' + - graph: '$(find spot_autowalk_data)/autowalk/eng2_2FElevator_to_2FEntrance.walk' + id: 'snaky-beagle-.fmRm2JflzlV6Dv5fTdnIQ==' + localization_method: 'fiducial' + - graph: '$(find spot_autowalk_data)/autowalk/eng2_2FElevator_to_MainGate.walk' + id: 'banner-oxen-UrEwTUbZYL0IQnY5TCFwrA==' + localization_method: 'fiducial' + - graph: '$(find spot_autowalk_data)/autowalk/eng2_elevator_7FElevator_to_2FElevator.walk' + id: 'shelfy-dassie-vK9Tf3cAQpdA.25gPJl8OQ==' + localization_method: 'fiducial' + - graph: '$(find spot_autowalk_data)/autowalk/eng2_2FElevator_to_eng8_2FElevator.walk' + id: 'inland-remora-FqtX8ScP+.inEd4xFYKq4A==' + localization_method: 'fiducial' + - graph: '$(find spot_autowalk_data)/autowalk/eng2_elevator_3FElevator_to_2FElevator.walk' + id: 'dashed-virus-QgetwIkinypWecGNMSS4Yw==' + localization_method: 'fiducial' + - graph: '$(find spot_autowalk_data)/autowalk/eng2_2FElevator_to_MainEntrance.walk' + id: 'mythic-leech-Uslc00eTIhygFsCdH2q1Qg==' + localization_method: 'fiducial' + - graph: '$(find spot_autowalk_data)/autowalk/eng2_2FElevator_to_subway.walk' + id: 'brumal-gerbil-NLVgQc0oBXbc8BCZev57LQ==' + localization_method: 'fiducial' + - graph: '$(find spot_autowalk_data)/autowalk/eng2_2fentrance_to_1fentrance.walk' + id: 'felted-weasel-kFVXZpw1.DvEm6FCSgC5XQ==' + localization_method: 'fiducial' + switchbot_device: '/eng2/2f/elevator_bot_up' + floor: 2 + floor_height: 0 + 'eng2_3FElevator': + name_en: 'Elevator' + name_jp: + - '3階 エレベーター' + - '3階エレベーター' + waypoints_on_graph: + - graph: '$(find spot_autowalk_data)/autowalk/eng2_elevator_3FElevator_to_2FElevator.walk' + id: 'eighth-ant-0Ot3VoMm0uiJNBHnbJOF9w==' + localization_method: 'fiducial' + - graph: '$(find spot_autowalk_data)/autowalk/eng2_3FElevator_to_Mech_Office.walk' + id: 'doughy-vulture-kMie2F0bAno9STnKFjNadA==' + localization_method: 'fiducial' + switchbot_device: '/eng2/3f/elevator_bot_up' + floor: 3 + floor_height: 4.5 + 'eng2_1FElevator': + name_en: 'Elevator' + name_jp: + - '1階 エレベーター' + - '1階エレベーター' + waypoints_on_graph: + - graph: '$(find spot_autowalk_data)/autowalk/eng2_elevator_7FElevator_to_1FElevator.walk' + id: 'brag-eagle-Of.kPg1gvRBhSWzrP+G3Yw==' + localization_method: 'fiducial' + - graph: '$(find spot_autowalk_data)/autowalk/eng2_1FElevator_to_1FGarbageCollectionAreaEast.walk' + id: 'sunlit-iguana-cXGwqooA99O95Hzqas19dw==' + localization_method: 'fiducial' + - graph: '$(find spot_autowalk_data)/autowalk/eng2_1FElevator_to_1FGarbageCollectionAreaSouth.walk' + id: 'gilled-bee-ve3JJ8B2sMTnRL3s8p.SZA==' + localization_method: 'fiducial' + - graph: '$(find spot_autowalk_data)/autowalk/eng2_1FElevator_to_1FGarbageCollectionAreaSouth_via213.walk' + id: 'oafish-vervet-w.bCe7FjEILCP8oBQg6Miw==' + localization_method: 'fiducial' + - graph: '$(find spot_autowalk_data)/autowalk/eng2_1fentrance_to_1FElevator.walk' + id: 'anemic-spider-TimPFBe0OMBdd+kWxYBW2Q==' + localization_method: 'fiducial' + switchbot_device: '/eng2/1f/elevator_bot_up' + floor: 1 + floor_height: -4.5 + 'eng2_Mech_Office': + name_en: 'Mech Office' + name_jp: + - '機械系 事務室' + - '機械系事務室' + - '事務室' + waypoints_on_graph: + - graph: '$(find spot_autowalk_data)/autowalk/eng2_3FElevator_to_Mech_Office.walk' + id: 'foiled-ibis-dAio3Ld6rCSWQgy9ERDulw==' + localization_method: 'fiducial' + - graph: '$(find spot_autowalk_data)/autowalk/eng2_MechOffice_inside.walk' + id: 'askant-beef-JQLXogu60cJgJiSXcyjKUQ==' + localization_method: 'fiducial' + 'eng2_Mailbox': + name_en: 'Mailbox' + name_jp: + - '郵便入れ' + waypoints_on_graph: + - graph: '$(find spot_autowalk_data)/autowalk/eng2_MechOffice_inside.walk' + id: 'sacked-tomcat-wpzX0UCSooUeKol.CShTqQ==' + localization_method: 'waypoint' + 'eng2_CampusMail': + name_en: 'CampusMail' + name_jp: + - '学内便' + waypoints_on_graph: + - graph: '$(find spot_autowalk_data)/autowalk/eng2_MechOffice_inside.walk' + id: 'jovial-civet-OPcK4EyGMTkxjZg04nuO.Q==' + localization_method: 'waypoint' + 'eng2_3F_door_inside': + waypoints_on_graph: + - graph: '$(find spot_autowalk_data)/autowalk/eng2_3FElevator_to_Mech_Office.walk' + id: 'silty-llama-gdnA1Y.5ugUuDuZ9RL9upQ==' + localization_method: 'waypoint' + 'eng2_3F_door_outside': + waypoints_on_graph: + - graph: '$(find spot_autowalk_data)/autowalk/eng2_3FElevator_to_Mech_Office.walk' + id: 'plane-marlin-gaEqSGH8WX35X1619X0NwQ==' + localization_method: 'waypoint' + 'eng_TelephoneBox': + name_en: 'telephone box' + name_jp: '電話ボックス' + waypoints_on_graph: + - graph: '$(find spot_autowalk_data)/autowalk/eng2_2FElevator_to_eng8_2FElevator.walk' + id: 'slain-pika-VdtPz3lWcp5T+pEArUB3PA==' + localization_method: 'fiducial' + - graph: '$(find spot_autowalk_data)/autowalk/eng_TelephoneBox_to_HongoMainGate.walk' + id: 'aired-hyrax-BF+hXHaUufi.DLd3pEWm5Q==' + localization_method: 'fiducial' + 'eng8_Piloti_Center': + name_en: 'Piloti' + name_jp: '8号館 ピロティ' + waypoints_on_graph: + - graph: '$(find spot_autowalk_data)/autowalk/eng2_2FElevator_to_eng8_2FElevator.walk' + id: 'azoic-lion-Iapbto7rzlzuFF0MvXdyPA==' + localization_method: 'fiducial' + 'eng2_subway_pillar': + name_en: 'Piloti' + name_jp: 'サブウェイ' + waypoints_on_graph: + - graph: '$(find spot_autowalk_data)/autowalk/eng2_2FElevator_to_subway.walk' + id: 'quack-snail-Zm7LgLSyql.xcoqi9OeCQA==' + localization_method: 'fiducial' + - graph: '$(find spot_autowalk_data)/autowalk/eng2_subway_tablenavigation.walk' + id: 'risen-bowfin-2he29MpsZLJRToJDhZ3.3g==' + localization_method: 'fiducial' + - graph: '$(find spot_autowalk_data)/autowalk/eng2_subway_tablenavigation_02.walk' + id: 'unread-donkey-2pdTiY6qfMnAfHD8RNH2ZA==' + localization_method: 'fiducial' + 'eng2_subway_initial': + waypoints_on_graph: + - graph: '$(find spot_autowalk_data)/autowalk/eng2_subway_tablenavigation.walk' + id: 'skimpy-vole-2plogvKFUrjXTCoxsCsNGA==' + localization_method: 'waypoint' + - graph: '$(find spot_autowalk_data)/autowalk/eng2_subway_tablenavigation_02.walk' + id: 'fired-minnow-Pw+F4RFHFQwQolGeuHgkEQ==' + localization_method: 'waypoint' + 'eng2_subway_table_00': + waypoints_on_graph: + - graph: '$(find spot_autowalk_data)/autowalk/eng2_subway_tablenavigation.walk' + id: 'perky-crake-iZWT3e+cQtLKUCo8iVTu7Q==' + localization_method: 'waypoint' + 'eng2_subway_table_01': + waypoints_on_graph: + - graph: '$(find spot_autowalk_data)/autowalk/eng2_subway_tablenavigation.walk' + id: 'spacy-eland-WlC6wgdJeo5u.TY6T3hA8Q==' + localization_method: 'waypoint' + 'eng2_subway_table_02': + waypoints_on_graph: + - graph: '$(find spot_autowalk_data)/autowalk/eng2_subway_tablenavigation.walk' + id: 'apodal-drum-o2pzrnIBJdLVWMX.ElaQOQ==' + localization_method: 'waypoint' + 'eng2_subway_table_03': + waypoints_on_graph: + - graph: '$(find spot_autowalk_data)/autowalk/eng2_subway_tablenavigation.walk' + id: 'formed-gerbil-yZerC+XIahSrvCJK5Duk6A==' + localization_method: 'waypoint' + 'eng2_subway_table_04': + waypoints_on_graph: + - graph: '$(find spot_autowalk_data)/autowalk/eng2_subway_tablenavigation.walk' + id: 'ruled-otter-2zSijP2KPICZpqU80utSPw==' + localization_method: 'waypoint' + 'eng2_subway_table_10': + waypoints_on_graph: + - graph: '$(find spot_autowalk_data)/autowalk/eng2_subway_tablenavigation.walk' + id: 'spiral-falcon-bG1t8To8x.p7BWwOWjH2Lg==' + localization_method: 'waypoint' + 'eng2_subway_table_11': + waypoints_on_graph: + - graph: '$(find spot_autowalk_data)/autowalk/eng2_subway_tablenavigation.walk' + id: 'manky-mite-EvhV0ln+iiG+pTCfeEnArQ==' + localization_method: 'waypoint' + 'eng2_subway_table_12': + waypoints_on_graph: + - graph: '$(find spot_autowalk_data)/autowalk/eng2_subway_tablenavigation.walk' + id: 'brash-sheep-NYE7iE0HuaqR0i5xnzYE1A==' + localization_method: 'waypoint' + 'eng2_subway_table_13': + waypoints_on_graph: + - graph: '$(find spot_autowalk_data)/autowalk/eng2_subway_tablenavigation.walk' + id: 'swampy-jay-jEYEQ+FOe4eMKEhoGco9LA==' + localization_method: 'waypoint' + 'eng2_subway_table_14': + waypoints_on_graph: + - graph: '$(find spot_autowalk_data)/autowalk/eng2_subway_tablenavigation.walk' + id: 'terse-crest-Toj82ugJt2Y029d7Afhq0A==' + localization_method: 'waypoint' + 'eng2_MainEntrance_upper': + waypoints_on_graph: + - graph: '$(find spot_autowalk_data)/autowalk/eng2_2FElevator_to_MainEntrance.walk' + id: 'violet-ibis-HtCPm39SIDSzoJFVX88S+A==' + localization_method: 'fiducial' + - graph: '$(find spot_autowalk_data)/autowalk/eng2_MainEntrance_to_HongoMainGate.walk' + id: 'about-goat-EHRLqkk7XWmMXAdfcXHP5g==' + localization_method: 'fiducial' + - graph: '$(find spot_autowalk_data)/autowalk/eng2_MainEntrance_to_HongoMainGate_Daylight.walk' + id: 'ahead-mink-Fml31k5vw.Y2XR.7v1BSeg==' + localization_method: 'fiducial' + 'eng2_MainEntrance_lower': + name_en: 'Entrance' + name_jp: + - '2号館 正面入口' + - '2号館正面入口' + waypoints_on_graph: + - graph: '$(find spot_autowalk_data)/autowalk/eng2_MainEntrance_to_HongoMainGate.walk' + id: 'aft-tick-clWAq8obxtWm64bTthYwMQ==' + localization_method: 'waypoint' + - graph: '$(find spot_autowalk_data)/autowalk/eng2_MainEntrance_to_HongoMainGate_Daylight.walk' + id: 'rested-grouse-GzVp6htKyt6kdIcS+Tu6aA==' + localization_method: 'waypoint' + 'eng2_MainEntrance_outer': + waypoints_on_graph: + - graph: '$(find spot_autowalk_data)/autowalk/eng2_MainEntrance_to_HongoMainGate.walk' + id: 'tilled-weevil-slAZLtX3U2NcqaINkGY0Sw==' + localization_method: 'waypoint' + - graph: '$(find spot_autowalk_data)/autowalk/eng2_MainEntrance_to_HongoMainGate_Daylight.walk' + id: 'sown-pincer-NcgfKhWf9H+B3yWIKA8W7A==' + localization_method: 'waypoint' + 'eng2_1FEntrance_outer': + name_en: '1F Entrance' + name_jp: + - '2号館1階入口' + - '2号館 1階入口' + waypoints_on_graph: + - graph: '$(find spot_autowalk_data)/autowalk/eng2_1fentrance_to_1FElevator.walk' + id: 'madcap-racoon-WmDiG93hDjlU6eDOUX2Jbg==' + localization_method: 'fiducial' + - graph: '$(find spot_autowalk_data)/autowalk/eng2_2fentrance_to_1fentrance.walk' + id: 'jammed-deer-L6mHZb2lmu+iQwGexsoAvw==' + localization_method: 'fiducial' + - graph: '$(find spot_autowalk_data)/autowalk/eng2_outside.walk' + id: 'bully-walrus-xsZTCuUHJ0dvzFGGmEZJxQ==' + localization_method: 'fiducial' + 'eng2_corner_northwest': + waypoints_on_graph: + - graph: '$(find spot_autowalk_data)/autowalk/eng2_outside.walk' + id: 'setose-angus-zflUwvXh2LTV96LNrrAHFg==' + localization_method: 'fiducial' + - graph: '$(find spot_autowalk_data)/autowalk/eng2_2fentrance_to_1fentrance.walk' + id: 'honey-agouti-3CfK3qU9XiErSV9gttqSKA==' + localization_method: 'fiducial' + 'eng2_GarbageCollectionArea_east': + name_en: 'Garbage collection area' + name_jp: 'ゴミ捨て場' + waypoints_on_graph: + - graph: '$(find spot_autowalk_data)/autowalk/eng2_1FElevator_to_1FGarbageCollectionAreaEast.walk' + id: 'podgy-oxen-t+KIiX.QiVaFGcphBsrekA==' + localization_method: 'waypoint' + 'eng2_GarbageCollectionArea_south': + name_en: 'Garbage collection area' + name_jp: 'ゴミ捨て場' + waypoints_on_graph: + - graph: '$(find spot_autowalk_data)/autowalk/eng2_1FElevator_to_1FGarbageCollectionAreaSouth.walk' + id: 'atonic-frog-uCPybjcZAN3fQZHNx71GRw==' + localization_method: 'waypoint' + - graph: '$(find spot_autowalk_data)/autowalk/eng2_1FElevator_to_1FGarbageCollectionAreaSouth_via213.walk' + id: 'boxy-hound-2E2IbEf63lXIM91zP.P2Ww==' + localization_method: 'waypoint' + 'eng2_7F_trashcan': + waypoints_on_graph: + - graph: '$(find spot_autowalk_data)/autowalk/eng2_7FElevator_to_73B2_trash.walk' + id: 'faddy-cuckoo-ZSHi6wGNKFCQhYetW6iLew==' + localization_method: 'waypoint' + - graph: '$(find spot_autowalk_data)/autowalk/eng2_7f_around.walk' + id: '' + localization_method: 'waypoint' + 'HongoMainGate': + name_en: 'Maingate' + name_jp: '正門' + waypoints_on_graph: + - graph: '$(find spot_autowalk_data)/autowalk/eng2_MainEntrance_to_HongoMainGate.walk' + id: 'aglow-gadfly-tSusAUqUNtqVaKFO49pDbQ==' + localization_method: 'waypoint' + - graph: '$(find spot_autowalk_data)/autowalk/eng2_MainEntrance_to_HongoMainGate_Daylight.walk' + id: 'regal-bison-qmAA7fyZh10L2bzoTNqUFQ==' + localization_method: 'waypoint' diff --git a/jsk_spot_robot/jsk_spot_behavior_manager/behavior_graph/simple_behavior_graph.yaml b/jsk_spot_robot/jsk_spot_behavior_manager/behavior_graph/simple_behavior_graph.yaml new file mode 100644 index 0000000000..cba2b070a0 --- /dev/null +++ b/jsk_spot_robot/jsk_spot_behavior_manager/behavior_graph/simple_behavior_graph.yaml @@ -0,0 +1,28 @@ +edges: + - from: 'nodeA' + to: 'nodeB' + behavior_type: 'jsk_spot_behavior_manager.base_behavior.SimpleBehavior' + cost: 10 + args: {} + - from: 'nodeB' + to: 'nodeC' + behavior_type: 'jsk_spot_behavior_manager.base_behavior.SimpleBehavior' + cost: 10 + args: {} + - from: 'nodeC' + to: 'nodeB' + behavior_type: 'jsk_spot_behavior_manager.base_behavior.SimpleBehavior' + cost: 10 + args: {} + - from: 'nodeB' + to: 'nodeA' + behavior_type: 'jsk_spot_behavior_manager.base_behavior.SimpleBehavior' + cost: 10 + args: {} +nodes: + 'nodeA': + name_en: 'nodeA' + 'nodeB': + name_en: 'nodeB' + 'nodeC': + name_en: 'nodeC' diff --git a/jsk_spot_robot/jsk_spot_behavior_manager/launch/demo.launch b/jsk_spot_robot/jsk_spot_behavior_manager/launch/demo.launch new file mode 100644 index 0000000000..8986335870 --- /dev/null +++ b/jsk_spot_robot/jsk_spot_behavior_manager/launch/demo.launch @@ -0,0 +1,23 @@ + + + + + + + + + + initial_node_id: '$(arg initial_node_id)' + silent_mode: true + + + diff --git a/jsk_spot_robot/jsk_spot_behavior_manager/launch/simple_demo.launch b/jsk_spot_robot/jsk_spot_behavior_manager/launch/simple_demo.launch new file mode 100644 index 0000000000..4cc59161ec --- /dev/null +++ b/jsk_spot_robot/jsk_spot_behavior_manager/launch/simple_demo.launch @@ -0,0 +1,22 @@ + + + + + + + + initial_node_id: 'nodeA' + silent_mode: true + dry_mode: true + + + diff --git a/jsk_spot_robot/jsk_spot_behavior_manager/node_scripts/behavior_manager_server.py b/jsk_spot_robot/jsk_spot_behavior_manager/node_scripts/behavior_manager_server.py new file mode 100755 index 0000000000..af70e08639 --- /dev/null +++ b/jsk_spot_robot/jsk_spot_behavior_manager/node_scripts/behavior_manager_server.py @@ -0,0 +1,14 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +import rospy +from jsk_spot_behavior_manager.behavior_manager_node import BehaviorManagerNode + +def main(): + + rospy.init_node('behavior_manager_node') + node = BehaviorManagerNode() + node.run() + +if __name__ == '__main__': + main() diff --git a/jsk_spot_robot/jsk_spot_behavior_manager/node_scripts/interactive_behavior_executor.py b/jsk_spot_robot/jsk_spot_behavior_manager/node_scripts/interactive_behavior_executor.py new file mode 100755 index 0000000000..85333b6ba6 --- /dev/null +++ b/jsk_spot_robot/jsk_spot_behavior_manager/node_scripts/interactive_behavior_executor.py @@ -0,0 +1,66 @@ +#!/usr/bin/env python +# -*- encoding: utf-8 -*- + +import actionlib +import rospy + +from jsk_spot_behavior_msgs.msg import NavigationAction, NavigationGoal + +from sound_play.libsoundplay import SoundClient +from ros_speech_recognition import SpeechRecognitionClient + + +def main(): + + rospy.init_node('interactive_behavior_executor') + + speech_recognition_client = SpeechRecognitionClient() + sound_client = SoundClient(sound_action='/robotsound_jp', sound_topic='/robotsound_jp') + action_server_lead_to = actionlib.SimpleActionClient('~execute_behaviors', NavigationAction) + + node_list = rospy.get_param('~nodes', {}) + + while not rospy.is_shutdown(): + + rospy.loginfo('Asking package information') + while not rospy.is_shutdown(): + sound_client.say('行き先を教えてください。', blocking=True) + recogntion_result = speech_recognition_client.recognize() + if len(recogntion_result.transcript) == 0: + rospy.logerr('No matching node found from spoken \'{}\''.format(recogntion_result)) + sound_client.say('行き先がわかりませんでした', blocking=True) + continue + recognized_destination = recogntion_result.transcript[0] + target_node_candidates = {} + for node_id, value in node_list.items(): + try: + if not value.has_key('name_jp'): + continue + if type(value['name_jp']) is list: + # DO HOGE + for name in value['name_jp']: + if name.encode('utf-8') == recognized_destination: + target_node_candidates[node_id] = value + else: + if value['name_jp'].encode('utf-8') == recognized_destination: + target_node_candidates[node_id] = value + except Exception as e: + rospy.logerr('Error: {}'.format(e)) + if len(target_node_candidates) == 0: + rospy.logerr('No matching node found from spoken \'{}\''.format(recogntion_result)) + sound_client.say('行き先がわかりませんでした', blocking=True) + else: + rospy.loginfo('target_node_candidates: {}'.format(target_node_candidates)) + break + + target_node_id = target_node_candidates.keys()[0] + target_node_name_jp = node_list[target_node_id]['name_jp'].encode('utf-8') + + sound_client.say('{} へ移動します'.format(target_node_name_jp)) + + rospy.loginfo('executing behaviors to {}'.format(target_node_name_jp)) + action_server_lead_to.send_goal_and_wait(NavigationGoal(target_node_id=target_node_id)) + + +if __name__ == '__main__': + main() diff --git a/jsk_spot_robot/jsk_spot_behavior_manager/package.xml b/jsk_spot_robot/jsk_spot_behavior_manager/package.xml new file mode 100644 index 0000000000..bc941c558b --- /dev/null +++ b/jsk_spot_robot/jsk_spot_behavior_manager/package.xml @@ -0,0 +1,39 @@ + + + jsk_spot_behavior_manager + 1.1.0 + The spot_behavior_manager package + + Kei Okada + Koki Shinjo + Koki Shinjo + + BSD + + catkin + + rospy + + actionlib + networkx + roslaunch + rospkg + rospy + sound_play + spot_behavior_manager_msgs + spot_ros_client + std_msgs + spot_autowalk_data + + actionlib + networkx + rospkg + rospy + roslaunch + sound_play + spot_behavior_manager_msgs + spot_ros_client + std_msgs + + + diff --git a/jsk_spot_robot/jsk_spot_behavior_manager/scripts/visualize_map.py b/jsk_spot_robot/jsk_spot_behavior_manager/scripts/visualize_map.py new file mode 100755 index 0000000000..f26b7742da --- /dev/null +++ b/jsk_spot_robot/jsk_spot_behavior_manager/scripts/visualize_map.py @@ -0,0 +1,84 @@ +#!/usr/bin/env python3 +# -*- encoding: utf-8 -*- + +import argparse +import yaml + +from graphviz import Digraph + +import gi +gi.require_version('Gtk','3.0') +from gi.repository import Gtk + +import xdot + + +class MyDotwindow(xdot.DotWindow): + + def __init__(self): + xdot.DotWindow.__init__(self) + self.dotwidget.connect('clicked', self.on_url_clicked) + + def on_url_clicked(self, widget, url, event): + dialog = Gtk.MessageDialog( + parent=self, + buttons=Gtk.ButtonsType.OK, + message_format='{} clicked'.format(url)) + dialog.connect('response', lambda dialog, response: dialog.destroy()) + dialog.run() + return True + + +def main(): + + parser = argparse.ArgumentParser() + parser.add_argument('filename') + parser.add_argument('--output') + args = parser.parse_args() + + filename = args.filename + + with open(filename, 'r') as f: + map_data = yaml.load(f, Loader=yaml.FullLoader) + + edges = map_data['edges'] + + list_node = [] + list_behavior_type = [] + list_color_for_bt = [] + + for edge in edges: + + if edge['from'] not in list_node: + list_node.append(edge['from']) + + if edge['to'] not in list_node: + list_node.append(edge['to']) + + if edge['behavior_type'] not in list_behavior_type: + list_behavior_type.append(edge['behavior_type']) + + for index, behavior_type in enumerate(list_behavior_type): + list_color_for_bt.append('{} 1.0 1.0'.format(1.0 * index / len(list_behavior_type))) + + dg = Digraph(format='svg') + + for node in list_node: + dg.node(node) + + for edge in edges: + dg.attr('edge', color=list_color_for_bt[list_behavior_type.index(edge['behavior_type'])]) + dg.edge(edge['from'], edge['to'], label=edge['behavior_type'].split('.')[2]) + + + if args.output: + dg.render(args.output) + else: + window = MyDotwindow() + window.set_dotcode(dg.source.encode('utf-8')) + window.connect('delete-event', Gtk.main_quit) + Gtk.main() + + +if __name__ == '__main__': + main() diff --git a/jsk_spot_robot/jsk_spot_behavior_manager/setup.py b/jsk_spot_robot/jsk_spot_behavior_manager/setup.py new file mode 100644 index 0000000000..799b412b34 --- /dev/null +++ b/jsk_spot_robot/jsk_spot_behavior_manager/setup.py @@ -0,0 +1,9 @@ +from setuptools import setup +from catkin_pkg.python_setup import generate_distutils_setup + +d = generate_distutils_setup( + packages=['jsk_spot_behavior_manager'], + package_dir={'': 'src'} +) + +setup(**d) diff --git a/jsk_spot_robot/jsk_spot_behavior_manager/src/jsk_spot_behavior_manager/__init__.py b/jsk_spot_robot/jsk_spot_behavior_manager/src/jsk_spot_behavior_manager/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/jsk_spot_robot/jsk_spot_behavior_manager/src/jsk_spot_behavior_manager/base_behavior.py b/jsk_spot_robot/jsk_spot_behavior_manager/src/jsk_spot_behavior_manager/base_behavior.py new file mode 100644 index 0000000000..5c3fa4266f --- /dev/null +++ b/jsk_spot_robot/jsk_spot_behavior_manager/src/jsk_spot_behavior_manager/base_behavior.py @@ -0,0 +1,51 @@ +# -*- coding: utf-8 -*- + +import rospy +import roslaunch + + +def load_behavior_class(class_string): + package_name = class_string.split('.')[0] + module_name = class_string.split('.')[1] + class_name = class_string.split('.')[2] + behavior_class = getattr( + getattr(__import__(package_name), module_name), class_name) + return behavior_class + + +class BaseBehavior(object): + + def __init__(self, spot_client, sound_client): + + self.spot_client = spot_client + self.sound_client = sound_client + + def run_initial(self, start_node, end_node, edge, pre_edge): + pass + + def run_main(self, start_node, end_node, edge, pre_edge): + pass + + def run_final(self, start_node, end_node, edge, pre_edge): + pass + + +class SimpleBehavior(BaseBehavior): + + def run_initial(self, start_node, end_node, edge, pre_edge): + + rospy.loginfo('__run_initial() called') + return True + + def run_main(self, start_node, end_node, edge, pre_edge): + + rospy.loginfo('__run_main() called') + rospy.loginfo('start_node: {}'.format(start_node)) + rospy.loginfo('end_node: {}'.format(end_node)) + rospy.loginfo('edge: {}'.format(edge)) + rospy.loginfo('pre_edge: {}'.format(pre_edge)) + return True + + def run_final(self, start_node, end_node, edge, pre_edge): + + rospy.loginfo('__run_finalize() called') diff --git a/jsk_spot_robot/jsk_spot_behavior_manager/src/jsk_spot_behavior_manager/behavior_graph.py b/jsk_spot_robot/jsk_spot_behavior_manager/src/jsk_spot_behavior_manager/behavior_graph.py new file mode 100644 index 0000000000..861dcf3c00 --- /dev/null +++ b/jsk_spot_robot/jsk_spot_behavior_manager/src/jsk_spot_behavior_manager/behavior_graph.py @@ -0,0 +1,99 @@ +# -*- coding: utf-8 -*- + +import networkx as nx + + +class GraphEdge: + + def __init__(self, + node_id_from, + node_id_to, + behavior_type, + cost, + properties + ): + self.node_id_from = node_id_from + self.node_id_to = node_id_to + self.behavior_type = behavior_type + self.cost = cost + self.properties = properties + + +class GraphNode: + + def __init__(self, + node_id, + properties + ): + self.node_id = node_id + self.properties = properties + + +class BehaviorGraph: + + # 現在の BehaviorGraph の仕様 + # 重み付きの有向グラフ + # あるノードからあるノードまでのエッジの数は 0 or 1 + + def __init__(self, raw_edges=[], raw_nodes={}): + + self.edges = {} + self.nodes = {} + self.network = nx.DiGraph() + + for raw_edge in raw_edges: + self.add_edge(raw_edge['from'], + raw_edge['to'], + raw_edge['behavior_type'], + int(raw_edge['cost']), + raw_edge['args']) + + for key, raw_node in raw_nodes.items(): + self.add_node(key, raw_node) + + def calcPath(self, node_id_from, node_id_to): + + try: + node_id_list = nx.shortest_path( + self.network, node_id_from, node_id_to) + except nx.NetworkXNoPath as e: + return None + path = [] + for index in range(len(node_id_list)-1): + path.append(self.edges[node_id_list[index], node_id_list[index+1]]) + return path + + def add_node(self, + node_id, + properties): + # TODO: もしすでにnodeがあれば,適切に上書きするようにする + # NOTICE: node と edge で整合性を保つようには実装されていない + self.nodes[node_id] = GraphNode(node_id, properties) + + def add_edge(self, + node_id_from, + node_id_to, + behavior_type, + cost, + args): + # TODO: もしすでにedgeがあれば,適切に上書きするようにする + # NOTICE: node と edge で整合性を保つようには実装されていない + edge = GraphEdge(node_id_from, + node_id_to, + behavior_type, + cost, + args) + self.edges[node_id_from, node_id_to] = edge + self.network.add_edge(node_id_from, + node_id_to, + weight=cost) + + def remove_node(self, + node_id): + del self.nodes[node_id] + + def remove_edge(self, + node_id_from, + node_id_to): + del self.edges[node_id_from, node_id_to] + self.network.remove_edge(node_id_from, node_id_to) diff --git a/jsk_spot_robot/jsk_spot_behavior_manager/src/jsk_spot_behavior_manager/behavior_manager_node.py b/jsk_spot_robot/jsk_spot_behavior_manager/src/jsk_spot_behavior_manager/behavior_manager_node.py new file mode 100644 index 0000000000..abb4049ef8 --- /dev/null +++ b/jsk_spot_robot/jsk_spot_behavior_manager/src/jsk_spot_behavior_manager/behavior_manager_node.py @@ -0,0 +1,236 @@ +# -*- coding: utf-8 -*- + +import copy +import sys +import traceback + +import actionlib +import rospy +import roslaunch +import PyKDL + +from sound_play.libsoundplay import SoundClient +from spot_ros_client.libspotros import SpotRosClient + +from jsk_spot_behavior_manager.behavior_graph import BehaviorGraph +from jsk_spot_behavior_manager.base_behavior import BaseBehavior, load_behavior_class + +from std_msgs.msg import String +from jsk_spot_behavior_msgs.msg import NavigationAction, NavigationFeedback, NavigationResult, NavigationActionFeedback +from jsk_spot_behavior_msgs.srv import ResetCurrentNode, ResetCurrentNodeResponse + + +class BehaviorManagerNode(object): + + def __init__(self): + + # navigation dictonary + raw_edges = rospy.get_param('~map/edges') + raw_nodes = rospy.get_param('~map/nodes') + self.graph = BehaviorGraph(raw_edges, raw_nodes) + self.current_node_id = rospy.get_param('~initial_node_id') + self.pre_edge = None + self.anchor_pose = None + + # silent mode / dry mode + self.silent_mode = rospy.get_param('~silent_mode', False) + self.dry_mode = rospy.get_param('~dry_mode', False) + + # get target action name list for synchronizer + self.list_action_name_synchronizer = rospy.get_param( + '~action_names_synchronizer', []) + + # action clients + if self.dry_mode: + self.spot_client = None + self.sound_client = None + else: + self.spot_client = SpotRosClient() + self.sound_client = SoundClient( + blocking=False, + sound_action='/robotsound_jp', + sound_topic='/robotsound_jp' + ) + + # publisher + self.pub_current_node_id = rospy.Publisher( + '~current_node_id', String, queue_size=1) + + # reset service + self.service_reset_current_node_id = rospy.Service( + '~reset_current_node_id', + ResetCurrentNode, + self.handler_reset_current_node_id + ) + + # + self.set_anchor_pose() + + # + roslaunch.pmon._init_signal_handlers() + + # subscribers + self.list_behaviors_execution_actions = [] + for action_name in self.list_action_name_synchronizer: + self.list_behaviors_execution_actions.append( + rospy.Subscriber('{}/feedback'.format(action_name), NavigationActionFeedback, self.callback_synchronizer) + ) + + # action server + self.server_execute_behaviors = actionlib.SimpleActionServer( + '~execute_behaviors', + NavigationAction, + execute_cb=self.handler_execute_behaviors, + auto_start=False + ) + self.server_execute_behaviors.start() + + rospy.loginfo('Initialized!') + + def say(self, message, blocking=True): + + rospy.loginfo('Saying: {}'.format(message)) + if not self.silent_mode and not self.dry_mode: + self.sound_client.say(message, blocking=blocking) + + def set_anchor_pose(self): + + rospy.loginfo('Set new anchor pose') + if not self.dry_mode: + self.anchor_pose = self.spot_client.get_robot_pose() + + def go_back_to_anchor_pose(self): + + rospy.loginfo('Going back to an anchor pose') + if not self.dry_mode: + current_pose = self.spot_client.get_robot_pose() + if current_pose is None or self.anchor_pose is None: + return False + frame_current_to_anchor = current_pose.Inverse() * self.anchor_pose + self.spot_client.go_pose(frame_current_to_anchor) + + def callback_synchronizer(self, msg): + + rospy.loginfo('Current node is updated to {}'.format(msg.feedback.current_node_id)) + self.current_node_id = msg.feedback.current_node_id + self.pre_edge = None + + def run(self): + + rate = rospy.Rate(1) + while not rospy.is_shutdown(): + rate.sleep() + self.pub_current_node_id.publish(String(data=self.current_node_id)) + + def handler_reset_current_node_id(self, req): + + rospy.loginfo('current_node_id is reset to {}'.format( + req.current_node_id)) + self.current_node_id = req.current_node_id + self.pre_edge = None + self.set_anchor_pose() + return ResetCurrentNodeResponse(success=True) + + def handler_execute_behaviors(self, goal): + + rospy.loginfo('Behavior Action started. goal: {}'.format(goal)) + + current_graph = copy.deepcopy(self.graph) + while True: + # path calculation + path = current_graph.calcPath( + self.current_node_id, goal.target_node_id) + if path is None: + rospy.logerr('No path from {} to {}'.format( + self.current_node_id, goal.target_node_id)) + self.say('パスが見つかりませんでした') + result = NavigationResult( + success=False, message='No path found') + self.server_execute_behaviors.set_aborted(result) + return + else: + # navigation of edges in the path + self.say('目的地に向かいます', blocking=True) + self.go_back_to_anchor_pose() + success_navigation = True + for edge in path: + rospy.loginfo('Navigating Edge {}...'.format(edge)) + try: + if self.navigate_edge(edge): + rospy.loginfo('Edge {} succeeded.'.format(edge)) + self.current_node_id = edge.node_id_to + self.server_execute_behaviors.publish_feedback(NavigationFeedback(current_node_id=self.current_node_id)) + self.pre_edge = edge + self.set_anchor_pose() + else: + rospy.logwarn('Edge {} failed'.format(edge)) + self.say( + '移動に失敗しました。経路を探索し直します。', blocking=True) + self.server_execute_behaviors.publish_feedback(NavigationFeedback(current_node_id=self.current_node_id)) + current_graph.remove_edge( + edge.node_id_from, edge.node_id_to) + success_navigation = False + break + except Exception as e: + rospy.logerr( + 'Got an error while navigating edge {}: {}'.format(edge, sys.exc_info())) + traceback.print_exc() + self.say('エラーが発生しました', blocking=True) + self.pre_edge = None + result = NavigationResult( + success=False, + message='Got an error while navigating edge {}: {}'.format(edge, e)) + self.server_execute_behaviors.set_aborted(result) + self.anchor_pose = None + return + + if success_navigation: + break + + rospy.loginfo('Goal Reached!') + self.say('目的地に到着しました.', blocking=True) + result = NavigationResult(success=True, message='Goal Reached.') + self.server_execute_behaviors.set_succeeded(result) + self.set_anchor_pose() + return + + def navigate_edge(self, edge): + + # start node id validation + if self.current_node_id != edge.node_id_from: + rospy.logwarn( + 'current_node_id {} does not match node_id_from of edge ({})'.format( + self.current_node_id, + edge.node_id_from) + ) + return False + + # load behavior class + try: + behavior_class = load_behavior_class(edge.behavior_type) + behavior = behavior_class( + self.spot_client, + self.sound_client + ) + except Exception as e: + rospy.logerr( + 'Failed to load and initialize behavior class: {}'.format(sys.exc_info())) + traceback.print_exc() + self.say('行動クラスを読み込めませんでした', blocking=True) + self.pre_edge = None + return False + + node_from = self.graph.nodes[edge.node_id_from] + node_to = self.graph.nodes[edge.node_id_to] + + # Exception from behavior will be caught in handler + success_initial = behavior.run_initial( + node_from, node_to, edge, self.pre_edge) + if success_initial is False: + behavior.run_final(node_from, node_to, edge, self.pre_edge) + return False + else: + success_main = behavior.run_main( + node_from, node_to, edge, self.pre_edge) + behavior.run_final(node_from, node_to, edge, self.pre_edge) + return success_main diff --git a/jsk_spot_robot/jsk_spot_behavior_msgs/CMakeLists.txt b/jsk_spot_robot/jsk_spot_behavior_msgs/CMakeLists.txt new file mode 100644 index 0000000000..30e82ce82f --- /dev/null +++ b/jsk_spot_robot/jsk_spot_behavior_msgs/CMakeLists.txt @@ -0,0 +1,21 @@ +cmake_minimum_required(VERSION 2.8.3) +project(jsk_spot_behavior_msgs) + +find_package(catkin REQUIRED COMPONENTS genmsg actionlib actionlib_msgs) + +add_action_files( + DIRECTORY action + FILES Navigation.action +) + +add_service_files( + DIRECTORY srv + FILES ResetCurrentNode.srv +) + +generate_messages(DEPENDENCIES actionlib_msgs) + +catkin_package( + LIBRARIES jsk_spot_behavior_msgs + CATKIN_DEPENDS message_runtime +) diff --git a/jsk_spot_robot/jsk_spot_behavior_msgs/action/Navigation.action b/jsk_spot_robot/jsk_spot_behavior_msgs/action/Navigation.action new file mode 100644 index 0000000000..8f41dfdaf9 --- /dev/null +++ b/jsk_spot_robot/jsk_spot_behavior_msgs/action/Navigation.action @@ -0,0 +1,6 @@ +string target_node_id +--- +bool success +string message +--- +string current_node_id diff --git a/jsk_spot_robot/jsk_spot_behavior_msgs/package.xml b/jsk_spot_robot/jsk_spot_behavior_msgs/package.xml new file mode 100644 index 0000000000..be66e24a55 --- /dev/null +++ b/jsk_spot_robot/jsk_spot_behavior_msgs/package.xml @@ -0,0 +1,24 @@ + + + jsk_spot_behavior_msgs + 1.1.0 + The jsk_spot_behavior_msgs package + + Koki Shinjo + Koki Shinjo + + BSD + + catkin + + actionlib + actionlib_msgs + message_generation + + actionlib + actionlib_msgs + message_runtime + + + + diff --git a/jsk_spot_robot/jsk_spot_behavior_msgs/srv/ResetCurrentNode.srv b/jsk_spot_robot/jsk_spot_behavior_msgs/srv/ResetCurrentNode.srv new file mode 100644 index 0000000000..8fa01bd4c0 --- /dev/null +++ b/jsk_spot_robot/jsk_spot_behavior_msgs/srv/ResetCurrentNode.srv @@ -0,0 +1,3 @@ +string current_node_id +--- +bool success diff --git a/jsk_spot_robot/jsk_spot_behaviors/spot_basic_behaviors/CMakeLists.txt b/jsk_spot_robot/jsk_spot_behaviors/spot_basic_behaviors/CMakeLists.txt new file mode 100644 index 0000000000..fd9652fb99 --- /dev/null +++ b/jsk_spot_robot/jsk_spot_behaviors/spot_basic_behaviors/CMakeLists.txt @@ -0,0 +1,13 @@ +cmake_minimum_required(VERSION 2.8.3) +project(spot_basic_behaviors) + +find_package(catkin REQUIRED) + +catkin_python_setup() + +catkin_package( +) + +install(DIRECTORY config launch + DESTINATION ${CATKIN_PACKAGE_SHARE_DESTINATION} +) diff --git a/jsk_spot_robot/jsk_spot_behaviors/spot_basic_behaviors/README.md b/jsk_spot_robot/jsk_spot_behaviors/spot_basic_behaviors/README.md new file mode 100644 index 0000000000..fa341940b3 --- /dev/null +++ b/jsk_spot_robot/jsk_spot_behaviors/spot_basic_behaviors/README.md @@ -0,0 +1,124 @@ +# spot_basic_behaviors + +This package includes exaple implementation of base_behavior for spot_behavior_manager. + +## walk behavior + +This behavior enables Spot to walk a specified route in a autowalk data from a given waypoint to another waypoint. + +behavior name: `spot_basic_behaviors.walk_behavior.WalkBehavior` + +https://user-images.githubusercontent.com/9410362/124337233-896f6880-dbdc-11eb-9588-30d4a5193bbd.mp4 + +### Required Configuration + +Before using this behavior, belows are required. + +- autowalk data containing a route for the behavior +- edge and nodes configuration for the behavior + +#### Edge + +Fields below are required in args of a edge + +- `graph`: path to autowalk data + +e.g. + +```yaml +- from: 'eng2_73B2' + to: 'eng2_73A2' + behavior_type: 'spot_basic_behavior.walk_behavior.WalkBehavior' + cost: 10 + args: + graph: '$(find spot_autowalk_data)/autowalk/eng2_73B2_to_7FElevator.walk' +``` + +#### Start and End Node + +Fields below are required for nodes + +- `waypoints_on_graph`: list of dict. each dict has `graph`, `id`, `localization_method` + + `graph`: path to a autowalk data + + `id`: waypoint id of the start node in the graph + + `localization_method`: localization_method to be used when starting autowalk, this must be 'fiducial' or 'waypoint' + +e.g. + +```yaml + 'eng2_73B2': + waypoints_on_graph: + - graph: '$(find spot_autowalk_data)/autowalk/eng2_73B2_to_7FElevator.walk' + id: 'dyed-bat-t00VKo5XixLihCvpsZPRqw==' + localization_method: 'fiducial' +``` + +## elevator behavior + +This behavior enables Spot to use an elevator to move to anothor floor. + +behavior name: `spot_basic_behaviors.elevator_behavior.ElevatorBehavior` + +https://user-images.githubusercontent.com/9410362/124337679-cd636d00-dbde-11eb-8db9-c5fedfda4ad9.mp4 + +### Required Configuration + +Before using this behavior, belows are required. + +- autowalk data containing a route for the behavior. +- apriltag pose information for elevator door frame detection. +- edge and nodes configuration for the behavior + +#### autowalk data + +To use this behavior, you need to record autowalk data while Spot riding on the elevator and getting off. + +Here is an example. + +![124134162-86318b00-dabd-11eb-86e3-092fcc5e8719](https://user-images.githubusercontent.com/9410362/124337765-364ae500-dbdf-11eb-83d3-a99e4025102e.png) + +#### apriltag pose information + +To use this behavior, an Fiducial (april tag) must be placed on the wall near the elevator doors. +And for elevator door opening and closing detection, transform from elevator foor frame ( center point on the ground ) to the fiducial is required. +Please see https://github.com/sktometometo/jsk_robot/blob/b166ef04cb954b175bedd5653af808be35e42121/jsk_spot_robot/jsk_spot_behaviors/spot_basic_behaviors/config/apriltag/tags.yaml#L44-L54 for examples + +#### Edge + +Fields below are required in args of a edge + +- `graph`: path to autowalk data +- `rest_waypoint_id`: waypoint of rest position in a elevator + +e.g. + +```yaml +- from: 'eng2_7FElevator' + to: 'eng2_2FElevator' + behavior_type: 'spot_basic_behavior.elevator_behavior.ElevatorBehavior' + cost: 10 + args: + graph: '$(find spot_autowalk_data)/autowalk/eng2_elevator_7FElevator_to_2FElevator.walk' + rest_waypoint_id: 'unsaid-collie-jvatvS.7KX9jpzQz61GL4A==' +``` + +#### Start and End Node + +Fields below are required for nodes + +- `waypoints_on_graph`: list of dict. each dict has `graph`, `id`, `localization_method` + + `graph`: path to a autowalk data + + `id`: waypoint id of the start node in the graph + + `localization_method`: localization_method to be used when starting autowalk, this must be 'fiducial' or 'waypoint' +- `switchbot_device`: switchbot device name for elevator button + +e.g. + +```yaml + 'eng2_7FElevator': + waypoints_on_graph: + - graph: '$(find spot_autowalk_data)/autowalk/eng2_elevator_7FElevator_to_2FElevator.walk' + id: 'sly-chetah-IZ4pVY7vrqO36OoKCYk9Zg==' + localization_method: 'fiducial' + switchbot_device: '/eng2/7f/elevator/down/button' +``` diff --git a/jsk_spot_robot/jsk_spot_behaviors/spot_basic_behaviors/config/apriltag/settings.yaml b/jsk_spot_robot/jsk_spot_behaviors/spot_basic_behaviors/config/apriltag/settings.yaml new file mode 100644 index 0000000000..9bd12af3d9 --- /dev/null +++ b/jsk_spot_robot/jsk_spot_behaviors/spot_basic_behaviors/config/apriltag/settings.yaml @@ -0,0 +1,9 @@ +tag_family: "tag36h11" # options: tagStandard52h13, tagStandard41h12, tag36h11, tag25h9, tag16h5, tagCustom48h12, tagCircle21h7, tagCircle49h12 +tag_threads: 2 # default: 2 +tag_decimate: 1.0 # default: 1.0 +tag_blur: 0.0 # default: 0.0 +tag_refine_edges: 1 # default: 1 +tag_debug: 0 # default: 0 +max_hamming_dist: 2 # default: 2 (Tunable parameter with 2 being a good choice - values >=3 consume large amounts of memory. Choose the largest value possible.) +# Other parameters +publish_tf: true # default: false diff --git a/jsk_spot_robot/jsk_spot_behaviors/spot_basic_behaviors/config/apriltag/tags.yaml b/jsk_spot_robot/jsk_spot_behaviors/spot_basic_behaviors/config/apriltag/tags.yaml new file mode 100644 index 0000000000..6828fdd222 --- /dev/null +++ b/jsk_spot_robot/jsk_spot_behaviors/spot_basic_behaviors/config/apriltag/tags.yaml @@ -0,0 +1,94 @@ +# # Definitions of tags to detect +# +# ## General remarks +# +# - All length in meters +# - Ellipsis (...) signifies that the previous element can be repeated multiple times. +# +# ## Standalone tag definitions +# ### Remarks +# +# - name is optional +# +# ### Syntax +# +# standalone_tags: +# [ +# {id: ID, size: SIZE, name: NAME}, +# ... +# ] +standalone_tags: [] +# ## Tag bundle definitions +# ### Remarks +# +# - name is optional +# - x, y, z have default values of 0 thus they are optional +# - qw has default value of 1 and qx, qy, qz have default values of 0 thus they are optional +# +# ### Syntax +# +# tag_bundles: +# [ +# { +# name: 'CUSTOM_BUNDLE_NAME', +# layout: +# [ +# {id: ID, size: SIZE, x: X_POS, y: Y_POS, z: Z_POS, qw: QUAT_W_VAL, qx: QUAT_X_VAL, qy: QUAT_Y_VAL, qz: QUAT_Z_VAL}, +# ... +# ] +# }, +# ... +# ] +tag_bundles: + [ + { + name: "elevator_door_frame_raw", + layout: + [ + { + id: 219, + size: 0.146, + x: -1.7, + y: -1.0, + z: 0.6, + qx: 0.0, + qy: 0.7071067811865475, + qz: 0.7071067811865476, + qw: 0.0, + }, + { + id: 211, + size: 0.146, + x: -1.7, + y: -1.0, + z: 0.4, + qx: 0.0, + qy: 0.7071067811865475, + qz: 0.7071067811865476, + qw: 0.0, + }, + { + id: 202, + size: 0.146, + x: -1.7, + y: -1.0, + z: 0.4, + qx: 0.0, + qy: 0.7071067811865475, + qz: 0.7071067811865476, + qw: 0.0, + }, + { + id: 216, + size: 0.146, + x: -1.7, + y: -1.0, + z: 0.4, + qx: 0.0, + qy: 0.7071067811865475, + qz: 0.7071067811865476, + qw: 0.0, + }, + ], + }, + ] diff --git a/jsk_spot_robot/jsk_spot_behaviors/spot_basic_behaviors/config/switchbot_ros/token.yaml b/jsk_spot_robot/jsk_spot_behaviors/spot_basic_behaviors/config/switchbot_ros/token.yaml new file mode 100644 index 0000000000..bfc61225f6 --- /dev/null +++ b/jsk_spot_robot/jsk_spot_behaviors/spot_basic_behaviors/config/switchbot_ros/token.yaml @@ -0,0 +1 @@ +token: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX diff --git a/jsk_spot_robot/jsk_spot_behaviors/spot_basic_behaviors/launch/elevator_detection.launch b/jsk_spot_robot/jsk_spot_behaviors/spot_basic_behaviors/launch/elevator_detection.launch new file mode 100644 index 0000000000..e5c9f2d921 --- /dev/null +++ b/jsk_spot_robot/jsk_spot_behaviors/spot_basic_behaviors/launch/elevator_detection.launch @@ -0,0 +1,214 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + max_duration: 5.0 + timeout_duration: 0.05 + timer_duration: 0.1 + reference_frame_id: elevator_door_frame_raw + output_frame_id: elevator_door_frame + fixed_frame_id: odom + + + + + + + + approximate_sync: true + output_frame: body + input_topics: + - $(arg TOPIC_DEPTH_FRONT_RIGHT)/points + - $(arg TOPIC_DEPTH_FRONT_LEFT)/points + - $(arg TOPIC_DEPTH_RIGHT)/points + - $(arg TOPIC_DEPTH_LEFT)/points + - $(arg TOPIC_DEPTH_BACK)/points + + + + + + + + initial_pos: [0, 0, 0.5] + initial_rot: [0, 0, 0] + dimension_x: 0.5 + dimension_y: 0.5 + dimension_z: 0.8 + frame_id: elevator_door_frame + + + + + + + + + + + + + + + + + diff --git a/jsk_spot_robot/jsk_spot_behaviors/spot_basic_behaviors/package.xml b/jsk_spot_robot/jsk_spot_behaviors/spot_basic_behaviors/package.xml new file mode 100644 index 0000000000..d11b5b7d7f --- /dev/null +++ b/jsk_spot_robot/jsk_spot_behaviors/spot_basic_behaviors/package.xml @@ -0,0 +1,32 @@ + + + spot_basic_behaviors + 1.1.0 + The spot_basic_behaviors package + + Kei Okada + Koki Shinjo + Koki Shinjo + BSD + + catkin + + spot_behavior_manager + + apriltag_ros + depth_image_proc + geometry_msgs + jsk_pcl_ros + jsk_recognition_msgs + message_filters + sensor_msgs + std_msgs + switchbot_ros + tf2_geometry_msgs + tf2_ros + tf_relay + elevator_operation + + + + diff --git a/jsk_spot_robot/jsk_spot_behaviors/spot_basic_behaviors/setup.py b/jsk_spot_robot/jsk_spot_behaviors/spot_basic_behaviors/setup.py new file mode 100644 index 0000000000..5518911cd8 --- /dev/null +++ b/jsk_spot_robot/jsk_spot_behaviors/spot_basic_behaviors/setup.py @@ -0,0 +1,9 @@ +from setuptools import setup +from catkin_pkg.python_setup import generate_distutils_setup + +d = generate_distutils_setup( + packages=['spot_basic_behaviors'], + package_dir={'': 'src'} +) + +setup(**d) diff --git a/jsk_spot_robot/jsk_spot_behaviors/spot_basic_behaviors/src/spot_basic_behaviors/__init__.py b/jsk_spot_robot/jsk_spot_behaviors/spot_basic_behaviors/src/spot_basic_behaviors/__init__.py new file mode 100644 index 0000000000..077d19e4ad --- /dev/null +++ b/jsk_spot_robot/jsk_spot_behaviors/spot_basic_behaviors/src/spot_basic_behaviors/__init__.py @@ -0,0 +1,2 @@ +import spot_basic_behaviors.walk_behavior as walk_behavior +import spot_basic_behaviors.elevator_behavior as elevator_behavior diff --git a/jsk_spot_robot/jsk_spot_behaviors/spot_basic_behaviors/src/spot_basic_behaviors/elevator_behavior.py b/jsk_spot_robot/jsk_spot_behaviors/spot_basic_behaviors/src/spot_basic_behaviors/elevator_behavior.py new file mode 100644 index 0000000000..68d212fcbe --- /dev/null +++ b/jsk_spot_robot/jsk_spot_behaviors/spot_basic_behaviors/src/spot_basic_behaviors/elevator_behavior.py @@ -0,0 +1,279 @@ +# -*- coding: utf-8 -*- + +from jsk_spot_behavior_manager.base_behavior import BaseBehavior + +import actionlib +import roslaunch +import rospkg +import rospy + +import math + +from switchbot_ros.msg import SwitchBotCommandGoal, SwitchBotCommandAction +from sensor_msgs.msg import PointCloud2 +from geometry_msgs.msg import Quaternion +from std_msgs.msg import Float32, Bool, Int16 + + +class ElevatorBehavior(BaseBehavior): + + def door_point_cloud_callback(self, msg): + if len(msg.data) == 0: + self.door_is_open = True + else: + self.door_is_open = False + + def current_floor_callback(self, msg): + self.current_floor = msg.data + + def rest_elevator_callback(self, msg): + self.rest_elevator = msg.data + + def run_initial(self, start_node, end_node, edge, pre_edge): + + rospy.logdebug('run_initial() called') + + self.silent_mode = rospy.get_param('~silent_mode', True) + + start_floor = start_node.properties['floor'] + rospy.loginfo("Start floor: {}".format(start_floor)) + + # launch recognition launch + uuid = roslaunch.rlutil.get_or_generate_uuid(None, True) + roslaunch_path = rospkg.RosPack().get_path('spot_basic_behaviors') +\ + '/launch/elevator_detection.launch' + cli_args = [roslaunch_path, "initial_floor:={}".format(start_floor)] + roslaunch_args = cli_args[1:] + roslaunch_file = [(roslaunch.rlutil.resolve_launch_arguments(cli_args)[0], roslaunch_args)] + self.roslaunch_parent = roslaunch.parent.ROSLaunchParent( + uuid, + roslaunch_file + ) + self.roslaunch_parent.start() + + # value for door openring checker + self.door_is_open = False + self.subscriber_door_check = None + + # elevator state + self.current_floor = None + self.rest_elevator = None + + self.subscriber_current_floor = rospy.Subscriber("/elevator_state_publisher/current_floor", Int16, self.current_floor_callback) + self.subscriber_rest_elevator = rospy.Subscriber("/elevator_state_publisher/rest_elevator", Bool, self.rest_elevator_callback) + + # value for switchbot + self.action_client_switchbot = actionlib.SimpleActionClient( + '/switchbot_ros/switch', + SwitchBotCommandAction + ) + + if not self.action_client_switchbot.wait_for_server(rospy.Duration(30)): + rospy.logerr('switchbot server seems to fail.') + return False + + try: + end_time = rospy.Time.now() + rospy.Duration(30) + except rospy.ROSException: + rospy.logerr("Door points topics are not published.") + return False + + try: + rospy.wait_for_message("/elevator_state_publisher/current_floor", Int16, timeout=rospy.Duration(30)) + except rospy.ROSException: + rospy.logerr("Current floor topics are not published.") + return False + + try: + rospy.wait_for_message("/elevator_state_publisher/rest_elevator", Bool, timeout=rospy.Duration(30)) + except rospy.ROSException: + rospy.logerr("Elevator state topic are not published.") + return False + + return True + + def run_main(self, start_node, end_node, edge, pre_edge): + + rospy.logdebug('run_main() called') + + start_floor = start_node.properties['floor'] + + graph_name = edge.properties['graph'] + start_id = list(filter( + lambda x: x['graph'] == graph_name, + start_node.properties['waypoints_on_graph'] + ))[0]['id'] + end_id = edge.properties['goal_waypoint_id'] + rest_waypoint_id = edge.properties['rest_waypoint_id'] + localization_method = list(filter( + lambda x: x['graph'] == graph_name, + start_node.properties['waypoints_on_graph'] + ))[0]['localization_method'] + + end_floor = end_node.properties['floor'] + + # graph uploading and localization + if pre_edge is not None and \ + graph_name == pre_edge.properties['graph']: + rospy.loginfo('graph upload and localization skipped.') + else: + # Upload + ret = self.spot_client.upload_graph(graph_name) + if ret[0]: + rospy.loginfo('graph {} uploaded.'.format(graph_name)) + else: + rospy.logerr('graph uploading failed: {}'.format(ret[1])) + return False + # Localization + if localization_method == 'fiducial': + ret = self.spot_client.set_localization_fiducial() + elif localization_method == 'waypoint': + ret = self.spot_client.set_localization_waypoint(start_id) + else: + ret = (False, 'Unknown localization method') + if ret[0]: + rospy.loginfo('robot is localized on the graph.') + else: + rospy.logwarn('Localization failed: {}'.format(ret[1])) + return False + + # start door opening check from outside + self.subscriber_door_check = rospy.Subscriber( + '/spot_recognition/elevator_door_points', + PointCloud2, + self.door_point_cloud_callback) + + # push button with switchbot + rospy.loginfo('calling elevator when riding...') + success_calling = False + switchbot_goal = SwitchBotCommandGoal() + switchbot_goal.device_name = start_node.properties['switchbot_device_up'] if end_floor > start_floor else start_node.properties['switchbot_device_down'] + switchbot_goal.command = 'press' + count = 0 + while True: + self.action_client_switchbot.send_goal(switchbot_goal) + if self.action_client_switchbot.wait_for_result(rospy.Duration(10)): + break + count += 1 + if count >= 3: + rospy.logerr('switchbot calling failed.') + return False + result = self.action_client_switchbot.get_result() + rospy.loginfo('switchbot result: {}'.format(result)) + if not result.done: + rospy.logerr('switchbot calling failed.') + return False + rospy.loginfo('elevator calling when riding on has succeeded') + + # wait for elevator + rate = rospy.Rate(1) + door_open_count = 0 + while not rospy.is_shutdown(): + rate.sleep() + if self.door_is_open: + door_open_count += 1 + else: + door_open_count = 0 + if door_open_count >= 2: + break + rospy.loginfo('elevator door opened.') + self.subscriber_door_check.unregister() + self.subscriber_door_check = None + + # start navigation to rest point + rate = rospy.Rate(10) + if not self.silent_mode: + self.sound_client.say('エレベータに乗り込みます。ご注意ください。', blocking=False) + self.spot_client.navigate_to(rest_waypoint_id, blocking=False) + # call elevator from destination floor while + rospy.loginfo('calling elevator when getting off...') + switchbot_goal = SwitchBotCommandGoal() + switchbot_goal.device_name = end_node.properties['switchbot_device_up'] if end_floor > start_floor else end_node.properties['switchbot_device_down'] + switchbot_goal.command = 'press' + self.action_client_switchbot.send_goal(switchbot_goal) + ## + if not self.action_client_switchbot.wait_for_result(timeout=rospy.Duration(20)): + rospy.logerr('Switchbot timeout') + self.spot_client.wait_for_navigate_to_result() + self.spot_client.navigate_to(start_id, blocking=True) + self.spot_client.wait_for_navigate_to_result() + return False + result_switchbot = self.action_client_switchbot.get_result() + while not rospy.is_shutdown(): + switchbot_goal = SwitchBotCommandGoal() + switchbot_goal.device_name = start_node.properties['switchbot_device_up'] if end_floor > start_floor else end_node.properties['switchbot_device_down'] + switchbot_goal.command = 'press' + self.action_client_switchbot.send_goal_and_wait(switchbot_goal, execute_timeout=rospy.Duration(10)) + if self.spot_client.wait_for_navigate_to_result(rospy.Duration(5)): + break + result_navigation = self.spot_client.get_navigate_to_result() + # recovery when riding on + if not result_navigation.success or not result_switchbot.done: + rospy.logerr('Failed to ride on a elevator. result_navigation: {}, result_switchbot: {}'.format( + result_navigation, result_switchbot)) + self.spot_client.navigate_to(start_id, blocking=True) + self.spot_client.wait_for_navigate_to_result() + return False + else: + rospy.loginfo('Riding on succeded.') + + if not self.silent_mode: + self.sound_client.say('{}階に行きます'.format(end_floor), blocking=False) + + # start door openning check from inside + self.subscriber_door_check = rospy.Subscriber( + '/spot_recognition/elevator_door_points', + PointCloud2, + self.door_point_cloud_callback) + + # check if the door is closed + rate = rospy.Rate(2) + while not rospy.is_shutdown(): + rate.sleep() + if not self.door_is_open: + break + rospy.loginfo('elevator door closed') + + # check if the door is open and at the target floor + rate = rospy.Rate(1) + while not rospy.is_shutdown(): + rate.sleep() + rospy.loginfo('door_is_open: {}, is_target_floor: {}, elevator stop: {}'.format( + self.door_is_open, end_floor == self.current_floor, self.rest_elevator)) + if self.door_is_open and end_floor == self.current_floor and self.rest_elevator: + break + rospy.loginfo('elevator door opened and at the target_floor') + + # dance before starting to move + self.spot_client.pub_body_pose(0.0, Quaternion(w=1)) + self.spot_client.stand() + rospy.sleep(0.5) + self.spot_client.pub_body_pose(-0.2, Quaternion(w=1)) + self.spot_client.stand() + rospy.sleep(0.5) + self.spot_client.pub_body_pose(0.0, Quaternion(w=1)) + self.spot_client.stand() + + # get off the elevator + self.spot_client.navigate_to(end_id, blocking=True) + result = self.spot_client.get_navigate_to_result() + + return result.success + + def run_final(self, start_node, end_node, edge, pre_edge): + + rospy.logdebug('run_finalize() called') + + if self.subscriber_door_check != None: + self.subscriber_door_check.unregister() + self.subscriber_door_check = None + + if self.subscriber_current_floor != None: + self.subscriber_current_floor.unregister() + self.subscriber_current_floor = None + + if self.subscriber_rest_elevator != None: + self.subscriber_rest_elevator.unregister() + self.subscriber_rest_elevator = None + + self.roslaunch_parent.shutdown() diff --git a/jsk_spot_robot/jsk_spot_behaviors/spot_basic_behaviors/src/spot_basic_behaviors/walk_behavior.py b/jsk_spot_robot/jsk_spot_behaviors/spot_basic_behaviors/src/spot_basic_behaviors/walk_behavior.py new file mode 100644 index 0000000000..9f595d0255 --- /dev/null +++ b/jsk_spot_robot/jsk_spot_behaviors/spot_basic_behaviors/src/spot_basic_behaviors/walk_behavior.py @@ -0,0 +1,94 @@ +# -*- coding: utf-8 -*- + +from jsk_spot_behavior_manager.base_behavior import BaseBehavior + +import rospy + + +class WalkBehavior(BaseBehavior): + + def run_initial(self, start_node, end_node, edge, pre_edge): + + rospy.logdebug('run_initial() called') + self.silent_mode = rospy.get_param('~silent_mode', True) + return True + + def run_main(self, start_node, end_node, edge, pre_edge): + + rospy.logdebug('run_main() called') + + graph_name = edge.properties['graph'] + start_id = list(filter( + lambda x: x['graph'] == graph_name, + start_node.properties['waypoints_on_graph'] + ))[0]['id'] + end_id = list(filter( + lambda x: x['graph'] == graph_name, + end_node.properties['waypoints_on_graph'] + ))[0]['id'] + localization_method = list(filter( + lambda x: x['graph'] == graph_name, + start_node.properties['waypoints_on_graph'] + ))[0]['localization_method'] + + # graph uploading and localization + if pre_edge is not None and \ + graph_name == pre_edge.properties['graph']: + rospy.loginfo('graph upload and localization skipped.') + else: + # Upload + ret = self.spot_client.upload_graph(graph_name) + if ret[0]: + rospy.loginfo('graph {} uploaded.'.format(graph_name)) + else: + rospy.logerr('graph uploading failed: {}'.format(ret[1])) + return False + # Localization + if localization_method == 'fiducial': + ret = self.spot_client.set_localization_fiducial() + elif localization_method == 'waypoint': + ret = self.spot_client.set_localization_waypoint(start_id) + else: + ret = (False, 'Unknown localization method') + if ret[0]: + rospy.loginfo('robot is localized on the graph.') + else: + rospy.logwarn('Localization failed: {}'.format(ret[1])) + return False + + # start navigation + success = False + rate = rospy.Rate(10) + velocity_limit_linear_x = rospy.get_param('/spot_basic_behaviors/walk_behaviors/velocity_limit_linear_x', 1.0) + velocity_limit_linear_y = rospy.get_param('/spot_basic_behaviors/walk_behaviors/velocity_limit_linear_y', 1.0) + velocity_limit_angular_z = rospy.get_param('/spot_basic_behaviors/walk_behaviors/velocity_limit_angular_z', 1.0) + if not self.silent_mode: + self.sound_client.say('移動します', blocking=True) + self.spot_client.navigate_to( + end_id, + velocity_limit=( + velocity_limit_linear_x, + velocity_limit_linear_y, + velocity_limit_angular_z + ), + blocking=False) + while not rospy.is_shutdown(): + rate.sleep() + if self.spot_client.wait_for_navigate_to_result(rospy.Duration(0.1)): + result = self.spot_client.get_navigate_to_result() + success = result.success + rospy.loginfo('result: {}'.format(result)) + break + + # recovery on failure + if not success: + if not self.silent_mode: + self.sound_client.say('失敗したので元に戻ります', blocking=True) + self.spot_client.navigate_to(start_id, blocking=True) + self.spot_client.wait_for_navigate_to_result() + + return success + + def run_final(self, start_node, end_node, edge, pre_edge): + + rospy.logdebug('run_finalize() called') diff --git a/jsk_spot_robot/jsk_spot_driver.rosinstall b/jsk_spot_robot/jsk_spot_driver.rosinstall new file mode 100644 index 0000000000..49d25f998d --- /dev/null +++ b/jsk_spot_robot/jsk_spot_driver.rosinstall @@ -0,0 +1,96 @@ +# spot-ros is required for spot +# This is a develop branch for jsk version. +# We need to use it until it is merged to master +# - git: +# local-name: spot-ros +# uri: https://github.com/sktometometo/spot_ros.git +# version: develop/spot +## use https://github.com/clearpathrobotics/spot_ros/pull/67 (Commands for the Arm and gripper, and URDF and simulation of the arm added) +# - git: +# local-name: spot-ros +# uri: https://github.com/estherRay/spot_ros.git +# version: b5fe5e5c4a732c1a724e4f86abc2be6395088d02 +## use https://github.com/cst0/spot_ros/pull/4 for Arm and gripper +- git: + local-name: spot-ros + uri: https://github.com/k-okada/spot_ros-arm.git + version: 0a8c4b29768ae946d7c84aec9a2ae2d185ed97a9 +## to support custom limb name, we need https://github.com/jsk-ros-pkg/jsk_model_tools/pull/249 +- git: + local-name: jsk_model_tools + uri: https://github.com/k-okada/jsk_model_tools.git + version: custom_limb +# +# geometry2 have to be built for python3 +- git: + local-name: geometry2 + uri: https://github.com/ros/geometry2.git + version: 0.6.5 +# vision_opencv have to be build for python3 +- git: + local-name: vision_opencv + uri: https://github.com/ros-perception/vision_opencv.git + version: 1.13.0 +# +# pykdl have to be built for python3 +- git: + local-name: orocos_kinematics_dynamics + uri: https://github.com/orocos/orocos_kinematics_dynamics.git + version: v1.4.0 +# +# mongodb_store need to be built for python3 +- git: + local-name: mongodb_store + uri: https://github.com/strands-project/mongodb_store.git + version: noetic-devel +# +# to avtivate python_catkin_install, we need newer catkin +- git: + local-name: catkin + uri: https://github.com/ros/catkin.git + version: 0.8.10 +# +# use robot_upstart to install supervisor (https://github.com/clearpathrobotics/robot_upstart/pull/113) +- git: + local-name: robot_upstart + uri: https://github.com/k-okada/robot_upstart.git + version: develop +# aques_talk needs to be compiled from source +- tar: + local-name: aques_talk + uri: https://github.com/tork-a/jsk_3rdparty-release/archive/refs/tags/release/melodic/aques_talk/2.1.24-2.tar.gz +# use parallel app_manager +- git: + local-name: app_manager + uri: https://github.com/PR2/app_manager.git + version: kinetic-devel +# wait for https://github.com/jsk-ros-pkg/jsk_pr2eus/pull/443 (add volume key arg for robot-interface :speak) to be released +- git: + local-name: jsk_pr2eus + uri: https://github.com/jsk-ros-pkg/jsk_pr2eus.git + version: master +# +# +# need to add following code to ublox_gps/config/zed_f9p.yaml +#config_on_startup: true +# +#rate: 0.016 +## TMODE3 Config +#tmode3: 1 # Survey-In Mode +#sv_in: +# reset: false # True: disables and re-enables survey-in (resets) +# # False: Disables survey-in only if TMODE3 is +# # disabled +# min_dur: 300 # Survey-In Minimum Duration [s] +# acc_lim: 3.0 # Survey-In Accuracy Limit [m] +- git: + local-name: ublox + uri: https://github.com/KumarRobotics/ublox.git +# insta360 need latest jsk_perception +- git: + local-name: jsk-ros-pkg/jsk_recognition + uri: jsk-ros-pkg/jsk_recognition.git +# Panoram image recognition need coral_usb, which is not release as apt package. +- git: + local-name: jsk-ros-pkg/coral_usb_ros + uri: https://github.com/jsk-ros-pkg/coral_usb_ros.git diff --git a/jsk_spot_robot/jsk_spot_driver.rosinstall.noetic b/jsk_spot_robot/jsk_spot_driver.rosinstall.noetic new file mode 100644 index 0000000000..9ad27be062 --- /dev/null +++ b/jsk_spot_robot/jsk_spot_driver.rosinstall.noetic @@ -0,0 +1,68 @@ +# spot-ros is required for spot +# This is a develop branch for jsk version. +# We need to use it until it is merged to master +# - git: +# local-name: spot-ros +# uri: https://github.com/sktometometo/spot_ros.git +# version: develop/spot +## use https://github.com/clearpathrobotics/spot_ros/pull/67 (Commands for the Arm and gripper, and URDF and simulation of the arm added) +# - git: +# local-name: spot-ros +# uri: https://github.com/estherRay/spot_ros.git +# version: b5fe5e5c4a732c1a724e4f86abc2be6395088d02 +## use https://github.com/cst0/spot_ros/pull/4 for Arm and gripper +- git: + local-name: spot-ros + uri: https://github.com/k-okada/spot_ros-arm.git + version: 0a8c4b29768ae946d7c84aec9a2ae2d185ed97a9 +## to support custom limb name, we need https://github.com/jsk-ros-pkg/jsk_model_tools/pull/249 +- git: + local-name: jsk_model_tools + uri: https://github.com/k-okada/jsk_model_tools.git + version: custom_limb +# +# use robot_upstart to install supervisor (https://github.com/clearpathrobotics/robot_upstart/pull/113) +- git: + local-name: robot_upstart + uri: https://github.com/k-okada/robot_upstart.git + version: develop +# aques_talk needs to be compiled from source +- tar: + local-name: aques_talk + uri: https://github.com/tork-a/jsk_3rdparty-release/archive/refs/tags/release/melodic/aques_talk/2.1.24-2.tar.gz +# use parallel app_manager +- git: + local-name: app_manager + uri: https://github.com/PR2/app_manager.git + version: kinetic-devel +# wait for https://github.com/jsk-ros-pkg/jsk_pr2eus/pull/443 (add volume key arg for robot-interface :speak) to be released +- git: + local-name: jsk_pr2eus + uri: https://github.com/jsk-ros-pkg/jsk_pr2eus.git + version: master +# +# +# need to add following code to ublox_gps/config/zed_f9p.yaml +#config_on_startup: true +# +#rate: 0.016 +## TMODE3 Config +#tmode3: 1 # Survey-In Mode +#sv_in: +# reset: false # True: disables and re-enables survey-in (resets) +# # False: Disables survey-in only if TMODE3 is +# # disabled +# min_dur: 300 # Survey-In Minimum Duration [s] +# acc_lim: 3.0 # Survey-In Accuracy Limit [m] +- git: + local-name: ublox + uri: https://github.com/KumarRobotics/ublox.git +# insta360 need latest jsk_perception +- git: + local-name: jsk-ros-pkg/jsk_recognition + uri: jsk-ros-pkg/jsk_recognition.git + version: master +# Panoram image recognition need coral_usb, which is not release as apt package. +- git: + local-name: jsk-ros-pkg/coral_usb_ros + uri: https://github.com/jsk-ros-pkg/coral_usb_ros.git diff --git a/jsk_spot_robot/jsk_spot_startup/CMakeLists.txt b/jsk_spot_robot/jsk_spot_startup/CMakeLists.txt new file mode 100644 index 0000000000..7552ce0555 --- /dev/null +++ b/jsk_spot_robot/jsk_spot_startup/CMakeLists.txt @@ -0,0 +1,44 @@ +cmake_minimum_required(VERSION 2.8.3) +project(jsk_spot_startup) + +find_package(catkin REQUIRED) + + +################################### +## catkin specific configuration ## +################################### +catkin_package() + + +############# +## Install ## +############# +install(DIRECTORY auth config launch scripts services udev_rules template + DESTINATION ${CATKIN_PACKAGE_SHARE_DESTINATION} + USE_SOURCE_PERMISSIONS +) + +file(GLOB_RECURSE SCRIPT_PROGRAMS node_scripts/*) +foreach(SCRIPT_PROGRAM ${SCRIPT_PROGRAMS}) + if("${SCRIPT_PROGRAM}" MATCHES ".*\\.py$") + catkin_install_python(PROGRAMS ${SCRIPT_PROGRAM} DESTINATION ${CATKIN_PACKAGE_SHARE_DESTINATION}/node_scripts) + else() + install(PROGRAMS ${SCRIPT_PROGRAM} DESTINATION ${CATKIN_PACKAGE_SHARE_DESTINATION}/node_scripts) + endif() +endforeach() + + +############# +## Testing ## +############# +if(CATKIN_ENABLE_TESTING) + find_package(catkin REQUIRED COMPONENTS roslaunch roslint) + file(GLOB LAUNCH_FILES launch/*.launch) + foreach(LAUNCH_FILE ${LAUNCH_FILES}) + roslaunch_add_file_check(${LAUNCH_FILE}) + endforeach() + + set(ROSLINT_PYTHON_OPTS --max-line-length=180 --ignore=E221,E222,E241) # skip multiple spaces before/after operator + roslint_python() + roslint_add_test() +endif() diff --git a/jsk_spot_robot/jsk_spot_startup/apps/app.installed b/jsk_spot_robot/jsk_spot_startup/apps/app.installed new file mode 100644 index 0000000000..eb14ab712a --- /dev/null +++ b/jsk_spot_robot/jsk_spot_startup/apps/app.installed @@ -0,0 +1,7 @@ +apps: +- app: jsk_spot_startup/hello_world + dispay: Hello World +- app: jsk_spot_startup/head_lead_demo + dispay: Head Lead Demo +- app: jsk_spot_startup/sample_elevator + dispay: Sample Elevator diff --git a/jsk_spot_robot/jsk_spot_startup/apps/head_lead_demo/head-lead-teleop.l b/jsk_spot_robot/jsk_spot_startup/apps/head_lead_demo/head-lead-teleop.l new file mode 100755 index 0000000000..8548d7f372 --- /dev/null +++ b/jsk_spot_robot/jsk_spot_startup/apps/head_lead_demo/head-lead-teleop.l @@ -0,0 +1,297 @@ +#!/usr/bin/env roseus + +(ros::roseus-add-msgs "std_msgs") + +(setq *continue* t) +;; control head-lead +(setq *head-lead* t) +;; (setq *head-lead* nil) +(load "package://spoteus/spot-interface.l") +(spot-init) +(if (eq (send *ri* :state :power-state-shore-power-state) 'on-shore-power) + (send *ri* :undock)) +;; (send *ri* :set-impedance-param :linear-stiffness #f(250 250 250) :rotational-stiffness #f(30 30 30) :linear-damping #f(1.5 1.5 1.5) :rotational-damping #f(0.5 0.5 0.5)) +;; (send *ri* :set-impedance-param :linear-stiffness #f(250 25 25) :rotational-stiffness #f(30 5 5) :linear-damping #f(1.5 1.5 1.5) :rotational-damping #f(0.5 0.5 0.5)) + +(setq *default-av* #f(0.0 -130.0 120.0 0.0 10.0 0.0)) + +;; sample +(setq *person* nil) +(setq *ball* nil) +(setq *ball-name* nil) +(setq *last-detected-time* (ros::time-now)) +(defclass ncb-result-synchronizer + :super exact-time-message-filter) + +(defmethod ncb-result-synchronizer + (:callback (bbox rects result) + (setq *last-detected-time* (ros::time-now)) + (let (len ret) + (setq len (length (send bbox :boxes))) + (dotimes (i len) + (ros::ros-info (format nil "found ~A (~A) in ~A sec ago in ~A" + (elt (send result :label_names) i) + (elt (send result :label_proba) i) + (send (ros::time- (ros::time-now) (send bbox :header :stamp)) :to-sec) + (cdr (assoc "topic" (send result :connection-header) :test #'string=)))) + (cond ((string= (elt (send result :label_names) i) "person") + (send *ri* :speak "p") + (push (list (send result :header :frame_id) (elt (send rects :rects) i) (elt (send bbox :boxes) i)) *person*)) + ((or (string= (elt (send result :label_names) i) "soccer ball") + (string= (elt (send result :label_names) i) "pokemon ball") + (string= (elt (send result :label_names) i) "dice")) + (send *ri* :speak "ball") + (push (list (send result :header :frame_id) + (elt (send rects :rects) i) + (elt (send bbox :boxes) i) + (elt (send result :label_names) i)) + *ball*)))) + ;; results are (list image rect box); to get box user (elt x 2) or (third x) + (setq *person* (sort *person* #'< #'(lambda (x) (send (elt x 2) :value)))) + (setq *ball* (sort *ball* #'< #'(lambda (x) (send (elt x 2) :value)))) + )) ;; :callback + ) +(ros::roseus-add-msgs "jsk_recognition_msgs") + +(setq synchronizers nil) +(dolist (image (list "left_fisheye_image" "right_fisheye_image" + "frontleft_fisheye_image" "frontright_fisheye_image" + "hand_color_image" "back_fisheye_image")) + (push (instance ncb-result-synchronizer :init + (list (list (format nil "/spot/ncb_provider/~A/bbox_array" image) jsk_recognition_msgs::BoundingBoxArray) + (list (format nil "/spot/ncb_provider/~A/rects" image) jsk_recognition_msgs::RectArray) + (list (format nil "/spot/ncb_provider/~A/class" image) jsk_recognition_msgs::ClassificationResult))) synchronizers)) + +(defun init-walk-arm-setting () + (send *spot* :arm :angle-vector #f(0.0 -130.0 120.0 0.0 10.0 0.0)) + (send *ri* :angle-vector (send *spot* :angle-vector) 1000 :default-controller) + (send *ri* :stop-grasp) + (send *ri* :wait-interpolation) + (send *ri* :set-impedance-param + :linear-stiffness #f(250 25 75) ;; z(up) y(side) x(front) + :rotational-stiffness #f(3 30 30) + :linear-damping #f(1.5 1.0 1.0) + :rotational-damping #f(0.1 0.5 0.5)) + (setq hand-pos (ros::pos->tf-point (send *spot* :hand :end-coords :worldpos))) + (ros::set-param "end_effector_to_joy/center_x" (send hand-pos :x)) + (ros::set-param "end_effector_to_joy/center_y" (send hand-pos :y)) + (unless (ros::wait-for-service "end_effector_to_joy/set_enabled" 10) + (ros::ros-error "end_effector_to_joy/set_enabled not found, so quitting") + (return-from init-walk-arm-setting :finished)) + (ros::service-call "end_effector_to_joy/set_enabled" (instance std_srvs::SetBoolRequest :init :data *head-lead*)) + ) + +(defun start-func (args) + (when (not (eq (send *ri* :state :power-state-shore-power-state) 'off-shore-power)) + (send *ri* :speak "Robot is on dock") + (ros::ros-error "Robot is on dock or powered, quit from apps") + ;(return-from start-func :finished) + ) + (send *ri* :speak "Hello, let's start walking") + (if *head-lead* + (init-walk-arm-setting) + (progn + (send *spot* :arm :angle-vector #f(0 -180 180 90 0 -90)) + (send *ri* :angle-vector (send *spot* :angle-vector) 2000) + (send *ri* :stow-arm) + (send *ri* :stop-grasp) + (send *ri* :wait-interpolation))) + :started) + +(defun end-func (args) + (ros::ros-info "end-func") + (send *ri* :speak "Thank You") + (send *ri* :stow-arm) + (unless (ros::wait-for-service "end_effector_to_joy/set_enabled" 10) + (ros::ros-error "end_effector_to_joy/set_enabled not found, so quitting") + (return-from end-func :finished)) + (ros::service-call "end_effector_to_joy/set_enabled" (instance std_srvs::SetBoolRequest :init :data nil)) + :finished) + +;; *cmd-vel* is (cons (ros::time-now) msg) +(defun walk-func (args) + (let ((still-sec 0) + (countdown 0)) + (ros::ros-info "walk-func") + (do-until-key + (ros::rate 2) + (ros::spin-once) + (if *cmd-vel* + (setq still-sec (send (ros::time- (ros::time-now) (car *cmd-vel*)) :to-sec))) + (ros::ros-info "continue... ~A, still-sec ~A" *continue* still-sec) + (if (null *continue*) (return-from walk-func :finished)) + (if *cmd-vel* + (ros::ros-info "cmd-vel ... ~A sec ago ~A (~A)" + still-sec + (float-vector (send (cdr *cmd-vel*) :linear :x) (send (cdr *cmd-vel*) :linear :y) (send (cdr *cmd-vel*) :angular :z)) + (norm (float-vector (send (cdr *cmd-vel*) :linear :x) (send (cdr *cmd-vel*) :linear :y) (send (cdr *cmd-vel*) :angular :z))))) + (when (> still-sec countdown) + (send *ri* :speak (format nil "~A" (case countdown (0 "z") (2 "fu") (4 "shi") (6 "mu") (8 "ya") (10 "tou") (t countdown)))) + (setq countdown (+ (ceiling still-sec) 1))) + (if (and *cmd-vel* (> still-sec 10)) + (return-from walk-func :not-pull)) + (ros::sleep)) + :finished)) + +(setq *distance-thre* 2000) +(defun ball-play-func (args) + (ros::ros-info "ball-play") + (ros::spin-once) + (block + :cond + (cond ((send *ri* :interpolatingp) + (ros::ros-info "robot is moving...")) + (*ball* + (send *ri* :speak "found ball") + (ros::service-call "end_effector_to_joy/set_enabled" (instance std_srvs::SetBoolRequest :init :data nil)) + (ros::ros-info "found ball ~A" *ball*) + (ros::ros-info "found ~A" (mapcar #'ros::tf-pose->coords (send-all (mapcar #'third *ball*) :pose))) + (ros::ros-info " ~A" (mapcar #'second *ball*)) + (ros::ros-info " ~A" (mapcar #'fourth *ball*)) + (send *ri* :stow-arm) + (let ((image-source (first (car *ball*))) + (bbox (third (car *ball*))) + (rect (second (car *ball*))) + (ball-name (fourth (car *ball*))) + result) + (ros::ros-info "distance ~A (< ~A)" (norm (send (ros::tf-pose->coords (send bbox :pose)) :worldpos)) *distance-thre*) + (when (> (norm (send (ros::tf-pose->coords (send bbox :pose)) :worldpos)) *distance-thre*) + (send *ri* :speak "too far") + (setq *ball* nil) + (return-from :cond nil)) + ;; look at the ball + (send *spot* :head :look-at + (v+ (float-vector 0 0 (/ (send bbox :dimensions :z) 2)) + (send (ros::tf-pose->coords (send bbox :pose)) :worldpos))) + (send *ri* :angle-vector (send *spot* :angle-vector) 1000) + (send *ri* :wait-interpolation) + (unix::sleep 1) + (send *ri* :pick-object-in-image image-source + (+ (send rect :x) (/ (send rect :width) 2)) + (+ (send rect :y) (/ (send rect :height) 2))) + (ros::publish "/spot/pick_object_in_image/pick_object" + (instance std_msgs::String :init :data ball-name)) + (while (= (send *ri* :pick-object-in-image-get-state) actionlib_msgs::GoalStatus::*active*) + (ros::ros-info "wait for pick... ~A" + (if (send *ri* :pick-object-in-image-feedback-msg) + (send *ri* :pick-object-in-image-feedback-msg :feedback :status))) + (ros::duration-sleep 1)) + (setq result (send *ri* :pick-object-in-image-wait-for-result)) + (if (send result :success) + (progn + (send *ri* :speak "good") + (ros::ros-info "succeeded to pick object")) + (progn + (send *ri* :speak "too bad") + (ros::ros-info "failed to pick object") + (send *ri* :stop-grasp) + ;;(unix::sleep 2) ;; ?? prevent from walking???? + )) + (if *head-lead* + (progn + (send *spot* :arm :angle-vector *default-av*) + (send *ri* :angle-vector (send *spot* :angle-vector) 1000) + (send *ri* :wait-interpolation) + ) + (progn + (send *spot* :arm :angle-vector #f(0 -180 180 90 0 -90)) + (send *ri* :angle-vector (send *spot* :angle-vector) 2000) + (send *ri* :wait-interpolation) + (send *ri* :stow-arm) + )) + (when (send result :success) + (send *ri* :go-pos -0.5 0 0) + (when *head-lead* + (send *spot* :arm :wrist-p :joint-angle 30) + (send *ri* :angle-vector (send *spot* :angle-vector) 500)) + (send *ri* :stop-grasp)) + (setq *ball* nil) + (ros::rate 1) + (if *head-lead* (init-walk-arm-setting)) + )) + (*person* + (send *ri* :speak "found person") + (ros::ros-info "found person ~A" *person*) + (ros::ros-info "found person ~A" (mapcar #'ros::tf-pose->coords (send-all (mapcar #'third *person*) :pose))) + (ros::ros-info " ~A" (mapcar #'second *person*)) + (ros::service-call "end_effector_to_joy/set_enabled" (instance std_srvs::SetBoolRequest :init :data nil)) + (send *spot* :arm :angle-vector *default-av*) + (send *spot* :head :look-at (v+ + (float-vector 0 0 (/ (send (third (car *person*)) :dimensions :z) 2)) + (send (ros::tf-pose->coords (send (third (car *person*)) :pose)) :worldpos))) + (send *ri* :angle-vector (send *spot* :angle-vector) 500) + (setq *person* nil) + (send *ri* :wait-interpolation) + (if *head-lead* (init-walk-arm-setting))) + (t + (ros::ros-info (format nil "nothing found ..... last detected object is ~A sec ago" (send (ros::time- (ros::time-now) *last-detected-time*) :to-sec))) + (when (and + *head-lead* + (> (send (ros::time- (ros::time-now) *last-detected-time*) :to-sec) 10) + (> (norm (v- (coerce (mapcar #'(lambda (x) (elt (send *ri* :state :angle-vector) x)) (mapcar #'(lambda (x) (position x (send *spot* :joint-list))) (send *spot* :arm :joint-list))) float-vector) *default-av*)) 10)) + (ros::service-call "end_effector_to_joy/set_enabled" (instance std_srvs::SetBoolRequest :init :data nil)) + (send *spot* :arm :angle-vector *default-av*) + (send *ri* :angle-vector (send *spot* :angle-vector) 500) + (send *ri* :wait-interpolation) + (init-walk-arm-setting) + )) + )) ;; cond + :walk) +;; +(load "package://roseus_smach/src/state-machine-ros.l") +(defun walk-sm () + (let (sm) + (setq sm + (make-state-machine + '((:start :started :walk) ;; transitions (node transition node) + (:start :finished :end) + (:walk :finished :end) + (:walk :not-pull :ball-play) + (:ball-play :walk :walk) + (:ball-play :finished :end) + (:end :finished :goal) + ) + '((:start 'start-func) ;; node-to-function maps + (:end 'end-func) + (:walk 'walk-func) + (:ball-play 'ball-play-func) + ) + '(:start) ;; initial node + '(:goal) ;; goal node + )) + (send sm :arg-keys 'description) + sm)) + +;; create robot interface +(unless (boundp '*ri*) (spot-init)) +(objects (list *spot*)) + +;; + +;; this does not work... +;; (unix:signal unix::sigint '(lambda-closure nil 0 0 (sig code) (setq *continue* nil))) + +;; https://github.com/jsk-ros-pkg/jsk_roseus/pull/717 +;; did not work, when we subscribe image data ??? + +(defun ros::roseus-sigint-handler (sig code) + (ros::ros-warn (format nil "ros::roseus-sigint-handler ~A" sig)) + (setq *continue* nil)) +(unix:signal unix::sigint 'ros::roseus-sigint-handler) + +;; send start-func (set-impedance) when R Stick button is pressed +(ros::subscribe "/joy_dualsense" sensor_msgs::Joy + #'(lambda (msg) + (if (and (> (length (send msg :buttons)) 11) + (= (elt (send msg :buttons) 11) 1)) + (start-func nil))) + 20) +(setq *cmd-vel* (cons (ros::time-now) (instance geometry_msgs::Twist :init))) +(ros::subscribe "/spot/cmd_vel" geometry_msgs::Twist + #'(lambda (msg) (setq *cmd-vel* (cons (ros::time-now) msg))) 20) + +(ros::advertise "/spot/pick_object_in_image/pick_object" std_msgs::String 1 t) + +;; state machine +(exec-state-machine (walk-sm) '((description . "お散歩しました!")(image . ""))) +(exit) diff --git a/jsk_spot_robot/jsk_spot_startup/apps/head_lead_demo/head_lead_demo.app b/jsk_spot_robot/jsk_spot_startup/apps/head_lead_demo/head_lead_demo.app new file mode 100644 index 0000000000..500d55e80a --- /dev/null +++ b/jsk_spot_robot/jsk_spot_startup/apps/head_lead_demo/head_lead_demo.app @@ -0,0 +1,5 @@ +display: Head Lead Demo +platform: spot +launch: jsk_spot_startup/head_lead_demo.xml +interface: jsk_spot_startup/head_lead_demo.interface + diff --git a/jsk_spot_robot/jsk_spot_startup/apps/head_lead_demo/head_lead_demo.interface b/jsk_spot_robot/jsk_spot_startup/apps/head_lead_demo/head_lead_demo.interface new file mode 100644 index 0000000000..c27c9c296e --- /dev/null +++ b/jsk_spot_robot/jsk_spot_startup/apps/head_lead_demo/head_lead_demo.interface @@ -0,0 +1,3 @@ +published_topics: {} +subscribed_topics: {} + diff --git a/jsk_spot_robot/jsk_spot_startup/apps/head_lead_demo/head_lead_demo.xml b/jsk_spot_robot/jsk_spot_startup/apps/head_lead_demo/head_lead_demo.xml new file mode 100644 index 0000000000..bee6b6b5e4 --- /dev/null +++ b/jsk_spot_robot/jsk_spot_startup/apps/head_lead_demo/head_lead_demo.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/jsk_spot_robot/jsk_spot_startup/apps/hello_world/hello-world.l b/jsk_spot_robot/jsk_spot_startup/apps/hello_world/hello-world.l new file mode 100755 index 0000000000..3f6ad67077 --- /dev/null +++ b/jsk_spot_robot/jsk_spot_startup/apps/hello_world/hello-world.l @@ -0,0 +1,78 @@ +#!/usr/bin/env roseus + +(setq *continue* t) +(load "package://spoteus/spot-interface.l") +(spot-init) +(if (eq (send *ri* :state :power-state-shore-power-state) 'on-shore-power) + (send *ri* :undock)) + +(defun init-pose () + (send *spot* :reset-pose) + (send *spot* :arm :move-end-pos #f(0 0 250) :world) + ) + +(defun start-func (args) + (init-pose) + (send *ri* :angle-vector (send *spot* :angle-vector) 500 :default-controller) + (send *ri* :wait-interpolation) + :started) + +(defun end-func (args) + (send *ri* :stow-arm) + :finished) + +(defun hello-func (args) + (send *ri* :speak "Hello" :volume 1.0) + (init-pose) + (send *spot* :arm :move-end-rot -20 :z :parent) + (send *spot* :arm :move-end-rot -10 :x :parent) + (send *ri* :angle-vector (send *spot* :angle-vector) 150 :default-controller) + (send *ri* :wait-interpolation) + ;; + (send *ri* :speak "World" :volume 1.0) + (init-pose) + (send *spot* :arm :move-end-rot 20 :z :parent) + (send *spot* :arm :move-end-rot -10 :x :parent) + (send *ri* :angle-vector (send *spot* :angle-vector) 150 :default-controller) + (send *ri* :wait-interpolation) + :finished) +;; +(load "package://roseus_smach/src/state-machine-ros.l") +(defun hello-sm () + (let (sm) + (setq sm + (make-state-machine + '((:start :started :hello) ;; transitions (node transition node) + (:hello :finished :end) + (:end :finished :goal) + ) + '((:start 'start-func) ;; node-to-function maps + (:end 'end-func) + (:hello 'hello-func) + ) + '(:start) ;; initial node + '(:goal) ;; goal node + )) + (send sm :arg-keys 'description) + sm)) + +;; create robot interface +(unless (boundp '*ri*) (spot-init)) +(objects (list *spot*)) + +;; + +;; this does not work... +;; (unix:signal unix::sigint '(lambda-closure nil 0 0 (sig code) (setq *continue* nil))) + +;; https://github.com/jsk-ros-pkg/jsk_roseus/pull/717 +;; did not work, when we subscribe image data ??? + +(defun ros::roseus-sigint-handler (sig code) + (ros::ros-warn (format nil "ros::roseus-sigint-handler ~A" sig)) + (setq *continue* nil)) +(unix:signal unix::sigint 'ros::roseus-sigint-handler) + +;; state machine +(exec-state-machine (hello-sm) '((description . "Hello World")(image . ""))) +(exit) diff --git a/jsk_spot_robot/jsk_spot_startup/apps/hello_world/hello_world.app b/jsk_spot_robot/jsk_spot_startup/apps/hello_world/hello_world.app new file mode 100644 index 0000000000..ae8dc86a34 --- /dev/null +++ b/jsk_spot_robot/jsk_spot_startup/apps/hello_world/hello_world.app @@ -0,0 +1,5 @@ +display: Hello World +platform: spot +launch: jsk_spot_startup/hello_world.xml +interface: jsk_spot_startup/hello_world.interface + diff --git a/jsk_spot_robot/jsk_spot_startup/apps/hello_world/hello_world.interface b/jsk_spot_robot/jsk_spot_startup/apps/hello_world/hello_world.interface new file mode 100644 index 0000000000..c27c9c296e --- /dev/null +++ b/jsk_spot_robot/jsk_spot_startup/apps/hello_world/hello_world.interface @@ -0,0 +1,3 @@ +published_topics: {} +subscribed_topics: {} + diff --git a/jsk_spot_robot/jsk_spot_startup/apps/hello_world/hello_world.xml b/jsk_spot_robot/jsk_spot_startup/apps/hello_world/hello_world.xml new file mode 100644 index 0000000000..15a5b233e7 --- /dev/null +++ b/jsk_spot_robot/jsk_spot_startup/apps/hello_world/hello_world.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/jsk_spot_robot/jsk_spot_startup/apps/sample_elevator/sample_elevator.app b/jsk_spot_robot/jsk_spot_startup/apps/sample_elevator/sample_elevator.app new file mode 100644 index 0000000000..2fdff70260 --- /dev/null +++ b/jsk_spot_robot/jsk_spot_startup/apps/sample_elevator/sample_elevator.app @@ -0,0 +1,75 @@ +display: Sample Elevator +platform: spot +launch: jsk_spot_startup/sample_elevator.xml +interface: jsk_spot_startup/sample_elevator.interface +plugins: + - name: panorama_video_recorder_plugin + type: app_recorder/video_recorder_plugin + launch_args: + video_path: /tmp + video_title: sample_elevator_panorama.avi + video_topic_name: /dual_fisheye_to_panorama/output + video_fps: 1.0 + - name: rosbag_recorder_plugin + type: app_recorder/rosbag_recorder_plugin + launch_args: + rosbag_path: /tmp + rosbag_title: sample_elevator_rosbag.bag + compress: true + rosbag_topic_names: + - /rosout + - /tf + - /tf_static + - /joint_states + - /odom + - /elevator_accel + - /elevator_accel_filtered + - /elevator_altitude + - /elevator_state_publisher/current_floor + - /elevator_state_publisher/elevator_movement + - /elevator_state_publisher/rest_elevator + - /m5stack_core2_driver/imu + - /m5stack_core2_driver/pressure + - /m5stack_core2_driver/temperature + - name: result_recorder_plugin + type: app_recorder/result_recorder_plugin + plugin_args: + result_path: /tmp + result_title: sample_elevator_result.yaml + - name: gdrive_uploader_plugin + type: app_uploader/gdrive_uploader_plugin + plugin_args: + upload_file_paths: + - /tmp/sample_elevator_result.yaml + - /tmp/sample_elevator_panorama.avi + - /tmp/sample_elevator_rosbag.bag + upload_file_titles: + - sample_elevator_result.yaml + - sample_elevator_panorama.avi + - sample_elevator_rosbag.bag + upload_parents_path: spot_sample_elevator + upload_server_name: /gdrive_server + - name: speech_notifier_plugin + type: app_notifier/speech_notifier_plugin + plugin_args: + client_name: /sound_play + - name: mail_notifier_plugin + type: app_notifier/mail_notifier_plugin + plugin_args: + mail_title: Spot Sample Elevator demo + use_timestamp_title: true + sender_address: belka@jsk.imi.i.u-tokyo.ac.jp + receiver_address: spot@jsk.imi.i.u-tokyo.ac.jp +plugin_order: + start_plugin_order: + - panorama_video_recorder_plugin + - rosbag_recorder_plugin + - result_recorder_plugin + - gdrive_uploader_plugin + - mail_notifier_plugin + stop_plugin_order: + - panorama_video_recorder_plugin + - rosbag_recorder_plugin + - result_recorder_plugin + - gdrive_uploader_plugin + - mail_notifier_plugin diff --git a/jsk_spot_robot/jsk_spot_startup/apps/sample_elevator/sample_elevator.interface b/jsk_spot_robot/jsk_spot_startup/apps/sample_elevator/sample_elevator.interface new file mode 100644 index 0000000000..c27c9c296e --- /dev/null +++ b/jsk_spot_robot/jsk_spot_startup/apps/sample_elevator/sample_elevator.interface @@ -0,0 +1,3 @@ +published_topics: {} +subscribed_topics: {} + diff --git a/jsk_spot_robot/jsk_spot_startup/apps/sample_elevator/sample_elevator.py b/jsk_spot_robot/jsk_spot_startup/apps/sample_elevator/sample_elevator.py new file mode 100755 index 0000000000..00372e57e8 --- /dev/null +++ b/jsk_spot_robot/jsk_spot_startup/apps/sample_elevator/sample_elevator.py @@ -0,0 +1,32 @@ +#!/usr/bin/env python3 + +import rospy +from spot_ros_client.libspotros import SpotRosClient +import actionlib +from jsk_spot_behavior_msgs.msg import NavigationAction +from jsk_spot_behavior_msgs.msg import NavigationGoal + + +if __name__ == '__main__': + + rospy.init_node("sample_elevator") + client = SpotRosClient() + + behavior_client = actionlib.SimpleActionClient("/spot_behavior_manager_server/execute_behaviors", NavigationAction) + behavior_client.wait_for_server() + + client.claim() + client.power_on() + client.undock() + + client.reset_current_node('eng2_73B2_dock') + client.execute_behaviors(target_node_id="eng2_3FElevator") + client.wait_execute_behaviors_result() + result = client.get_execute_behaviors_result() + rospy.loginfo("Result forward: {} {}".format(result.success, result.message)) + + client.execute_behaviors(NavigationGoal(target_node_id="eng2_73B2_dock")) + result = client.get_execute_behaviors_result() + rospy.loginfo("Result backward: {} {}".format(result.success, result.message)) + + client.dock(521) diff --git a/jsk_spot_robot/jsk_spot_startup/apps/sample_elevator/sample_elevator.xml b/jsk_spot_robot/jsk_spot_startup/apps/sample_elevator/sample_elevator.xml new file mode 100644 index 0000000000..f3497f43d5 --- /dev/null +++ b/jsk_spot_robot/jsk_spot_startup/apps/sample_elevator/sample_elevator.xml @@ -0,0 +1,9 @@ + + + + + + + + diff --git a/jsk_spot_robot/jsk_spot_startup/auth/spot_credential.yaml b/jsk_spot_robot/jsk_spot_startup/auth/spot_credential.yaml new file mode 100644 index 0000000000..a9e90c9bf1 --- /dev/null +++ b/jsk_spot_robot/jsk_spot_startup/auth/spot_credential.yaml @@ -0,0 +1,4 @@ +username: "dummyusername" +password: "dummypassword" +hostname: "192.168.50.3" +dock_id: 521 diff --git a/jsk_spot_robot/jsk_spot_startup/config/dualsense_teleop_twist_joy.yaml b/jsk_spot_robot/jsk_spot_startup/config/dualsense_teleop_twist_joy.yaml new file mode 100644 index 0000000000..bfd30e14b0 --- /dev/null +++ b/jsk_spot_robot/jsk_spot_startup/config/dualsense_teleop_twist_joy.yaml @@ -0,0 +1,17 @@ +axis_linear: + x: 1 + y: 0 +scale_linear: + x: 1.0 + y: 0.8 +scale_linear_turbo: + x: 1.5 + y: 1.2 +axis_angular: + yaw: 2 +scale_angular: + yaw: 1.0 +scale_angular_turbo: + yaw: 2.0 +enable_button: 4 +enable_turbo_button: 10 diff --git a/jsk_spot_robot/jsk_spot_startup/config/head_teleop_twist_joy.yaml b/jsk_spot_robot/jsk_spot_startup/config/head_teleop_twist_joy.yaml new file mode 100644 index 0000000000..364aaf2941 --- /dev/null +++ b/jsk_spot_robot/jsk_spot_startup/config/head_teleop_twist_joy.yaml @@ -0,0 +1,13 @@ +axis_linear: + x: 0 + y: 1 + x_min: -0.5 + x_max: 1.25 + y_min: -0.5 + y_max: 0.5 +axis_angular: + yaw: 2 + yaw_min: -0.75 + yaw_max: 0.75 +enable_button: 0 +enable_turbo_button: -1 diff --git a/jsk_spot_robot/jsk_spot_startup/config/spot.rviz b/jsk_spot_robot/jsk_spot_startup/config/spot.rviz new file mode 100644 index 0000000000..c732bf2766 --- /dev/null +++ b/jsk_spot_robot/jsk_spot_startup/config/spot.rviz @@ -0,0 +1,505 @@ +Panels: + - Class: rviz/Displays + Help Height: 0 + Name: Displays + Property Tree Widget: + Expanded: + - /DepthClouds1/FrontLeftDepthCloud1/Autocompute Value Bounds1 + - /DepthClouds1/FrontRightDepthCloud1/Autocompute Value Bounds1 + - /DepthClouds1/LeftDepthCloud1/Autocompute Value Bounds1 + - /DepthClouds1/RightDepthCloud1/Autocompute Value Bounds1 + - /DepthClouds1/RearDepthCloud1/Autocompute Value Bounds1 + Splitter Ratio: 0.6832844614982605 + Tree Height: 441 + - Class: rviz/Selection + Name: Selection + - Class: rviz/Tool Properties + Expanded: + - /2D Pose Estimate1 + - /2D Nav Goal1 + - /Publish Point1 + Name: Tool Properties + Splitter Ratio: 0.5886790156364441 + - Class: rviz/Views + Expanded: + - /Current View1 + - /Current View1/Focal Point1 + Name: Views + Splitter Ratio: 0.5 + - Class: rviz/Time + Experimental: false + Name: Time + SyncMode: 0 + SyncSource: FrontLeftDepthCloud +Preferences: + PromptSaveOnExit: true +Toolbars: + toolButtonStyle: 2 +Visualization Manager: + Class: "" + Displays: + - Alpha: 1 + Class: rviz/RobotModel + Collision Enabled: false + Enabled: true + Links: + All Links Enabled: true + Expand Joint Details: false + Expand Link Details: false + Expand Tree: false + Link Tree Style: Links in Alphabetic Order + base_link: + Alpha: 1 + Show Axes: false + Show Trail: false + body: + Alpha: 1 + Show Axes: false + Show Trail: false + Value: true + front_left_hip: + Alpha: 1 + Show Axes: false + Show Trail: false + Value: true + front_left_lower_leg: + Alpha: 1 + Show Axes: false + Show Trail: false + Value: true + front_left_upper_leg: + Alpha: 1 + Show Axes: false + Show Trail: false + Value: true + front_rail: + Alpha: 1 + Show Axes: false + Show Trail: false + front_right_hip: + Alpha: 1 + Show Axes: false + Show Trail: false + Value: true + front_right_lower_leg: + Alpha: 1 + Show Axes: false + Show Trail: false + Value: true + front_right_upper_leg: + Alpha: 1 + Show Axes: false + Show Trail: false + Value: true + insta360_link: + Alpha: 1 + Show Axes: false + Show Trail: false + jsk_head_mount_frame: + Alpha: 1 + Show Axes: false + Show Trail: false + jsk_payload_frame: + Alpha: 1 + Show Axes: false + Show Trail: false + rear_left_hip: + Alpha: 1 + Show Axes: false + Show Trail: false + Value: true + rear_left_lower_leg: + Alpha: 1 + Show Axes: false + Show Trail: false + Value: true + rear_left_upper_leg: + Alpha: 1 + Show Axes: false + Show Trail: false + Value: true + rear_rail: + Alpha: 1 + Show Axes: false + Show Trail: false + rear_right_hip: + Alpha: 1 + Show Axes: false + Show Trail: false + Value: true + rear_right_lower_leg: + Alpha: 1 + Show Axes: false + Show Trail: false + Value: true + rear_right_upper_leg: + Alpha: 1 + Show Axes: false + Show Trail: false + Value: true + Name: RobotModel + Robot Description: robot_description + TF Prefix: "" + Update Interval: 0 + Value: true + Visual Enabled: true + - Class: rviz/TF + Enabled: false + Frame Timeout: 15 + Frames: + All Enabled: true + Marker Scale: 0.25 + Name: TF + Show Arrows: true + Show Axes: true + Show Names: true + Tree: + {} + Update Interval: 0 + Value: false + - Class: rviz/Group + Displays: + - Alpha: 1 + Auto Size: + Auto Size Factor: 1 + Value: true + Autocompute Intensity Bounds: true + Autocompute Value Bounds: + Max Value: 2.072983741760254 + Min Value: 0.4096323251724243 + Value: true + Axis: Z + Channel Name: intensity + Class: rviz/DepthCloud + Color: 255; 255; 255 + Color Image Topic: "" + Color Transformer: AxisColor + Color Transport Hint: raw + Decay Time: 0 + Depth Map Topic: /spot/depth/frontleft/image + Depth Map Transport Hint: raw + Enabled: true + Invert Rainbow: false + Max Color: 255; 255; 255 + Max Intensity: 4096 + Min Color: 0; 0; 0 + Min Intensity: 0 + Name: FrontLeftDepthCloud + Occlusion Compensation: + Occlusion Time-Out: 30 + Value: false + Position Transformer: XYZ + Queue Size: 5 + Selectable: true + Size (Pixels): 3 + Style: Flat Squares + Topic Filter: true + Use Fixed Frame: true + Use rainbow: true + Value: true + - Alpha: 1 + Auto Size: + Auto Size Factor: 1 + Value: true + Autocompute Intensity Bounds: true + Autocompute Value Bounds: + Max Value: 3.060708999633789 + Min Value: 0.31034958362579346 + Value: true + Axis: Z + Channel Name: intensity + Class: rviz/DepthCloud + Color: 255; 255; 255 + Color Image Topic: "" + Color Transformer: AxisColor + Color Transport Hint: raw + Decay Time: 0 + Depth Map Topic: /spot/depth/frontright/image + Depth Map Transport Hint: raw + Enabled: true + Invert Rainbow: false + Max Color: 255; 255; 255 + Max Intensity: 4096 + Min Color: 0; 0; 0 + Min Intensity: 0 + Name: FrontRightDepthCloud + Occlusion Compensation: + Occlusion Time-Out: 30 + Value: false + Position Transformer: XYZ + Queue Size: 5 + Selectable: true + Size (Pixels): 3 + Style: Flat Squares + Topic Filter: true + Use Fixed Frame: true + Use rainbow: true + Value: true + - Alpha: 1 + Auto Size: + Auto Size Factor: 1 + Value: true + Autocompute Intensity Bounds: true + Autocompute Value Bounds: + Max Value: 1.9569889307022095 + Min Value: 0.40128064155578613 + Value: true + Axis: Z + Channel Name: intensity + Class: rviz/DepthCloud + Color: 255; 255; 255 + Color Image Topic: "" + Color Transformer: AxisColor + Color Transport Hint: raw + Decay Time: 0 + Depth Map Topic: /spot/depth/left/image + Depth Map Transport Hint: raw + Enabled: true + Invert Rainbow: false + Max Color: 255; 255; 255 + Max Intensity: 4096 + Min Color: 0; 0; 0 + Min Intensity: 0 + Name: LeftDepthCloud + Occlusion Compensation: + Occlusion Time-Out: 30 + Value: false + Position Transformer: XYZ + Queue Size: 5 + Selectable: true + Size (Pixels): 3 + Style: Flat Squares + Topic Filter: true + Use Fixed Frame: true + Use rainbow: true + Value: true + - Alpha: 1 + Auto Size: + Auto Size Factor: 1 + Value: true + Autocompute Intensity Bounds: true + Autocompute Value Bounds: + Max Value: 2.684413433074951 + Min Value: 0.4553413391113281 + Value: true + Axis: Z + Channel Name: intensity + Class: rviz/DepthCloud + Color: 255; 255; 255 + Color Image Topic: "" + Color Transformer: AxisColor + Color Transport Hint: raw + Decay Time: 0 + Depth Map Topic: /spot/depth/right/image + Depth Map Transport Hint: raw + Enabled: true + Invert Rainbow: false + Max Color: 255; 255; 255 + Max Intensity: 4096 + Min Color: 0; 0; 0 + Min Intensity: 0 + Name: RightDepthCloud + Occlusion Compensation: + Occlusion Time-Out: 30 + Value: false + Position Transformer: XYZ + Queue Size: 5 + Selectable: true + Size (Pixels): 3 + Style: Flat Squares + Topic Filter: true + Use Fixed Frame: true + Use rainbow: true + Value: true + - Alpha: 1 + Auto Size: + Auto Size Factor: 1 + Value: true + Autocompute Intensity Bounds: true + Autocompute Value Bounds: + Max Value: 4.3172607421875 + Min Value: 0.1980423927307129 + Value: true + Axis: Z + Channel Name: intensity + Class: rviz/DepthCloud + Color: 255; 255; 255 + Color Image Topic: "" + Color Transformer: AxisColor + Color Transport Hint: raw + Decay Time: 0 + Depth Map Topic: /spot/depth/back/image + Depth Map Transport Hint: raw + Enabled: true + Invert Rainbow: false + Max Color: 255; 255; 255 + Max Intensity: 4096 + Min Color: 0; 0; 0 + Min Intensity: 0 + Name: RearDepthCloud + Occlusion Compensation: + Occlusion Time-Out: 30 + Value: false + Position Transformer: XYZ + Queue Size: 5 + Selectable: true + Size (Pixels): 3 + Style: Flat Squares + Topic Filter: true + Use Fixed Frame: true + Use rainbow: true + Value: true + Enabled: true + Name: DepthClouds + - Class: jsk_rviz_plugin/OverlayImage + Enabled: true + Name: PanoramaImage + Topic: /dual_fisheye_to_panorama/output + Value: true + alpha: 0.800000011920929 + height: 128 + keep aspect ratio: true + left: 0 + overwrite alpha value: false + top: 0 + transport hint: raw + width: 700 + - Class: jsk_rviz_plugin/OverlayImage + Enabled: true + Name: PanoramaDetection + Topic: /spot_recognition/object_detection_image + Value: true + alpha: 0.800000011920929 + height: 128 + keep aspect ratio: true + left: 0 + overwrite alpha value: false + top: 0 + transport hint: raw + width: 700 + - Class: jsk_rviz_plugin/TFTrajectory + Enabled: true + Name: BodyTrajectory + Value: true + color: 25; 255; 240 + duration: 1000 + frame: base_link + line_width: 0.009999999776482582 + - Class: jsk_rviz_plugin/PieChart + Enabled: true + Name: SpotBattery + Topic: /spot/status/battery_percentage + Value: true + auto color change: false + background color: 0; 0; 0 + backround alpha: 0 + clockwise rotate direction: false + foreground alpha: 0.699999988079071 + foreground alpha 2: 0.4000000059604645 + foreground color: 25; 255; 240 + left: 750 + max color: 255; 0; 0 + max value: 100 + min value: 0 + show caption: true + size: 128 + text size: 10 + top: 50 + - Class: jsk_rviz_plugin/PieChart + Enabled: true + Name: LaptopBattery + Topic: /spot/status/laptop_battery_percentage + Value: true + auto color change: false + background color: 0; 0; 0 + backround alpha: 0 + clockwise rotate direction: false + foreground alpha: 0.699999988079071 + foreground alpha 2: 0.4000000059604645 + foreground color: 25; 255; 240 + left: 900 + max color: 255; 0; 0 + max value: 100 + min value: 0 + show caption: true + size: 128 + text size: 10 + top: 50 + - Class: jsk_rviz_plugin/BoundingBoxArray + Enabled: true + Name: DetectedObjects + Topic: /spot_recognition/bbox_array + Unreliable: false + Value: true + alpha: 0.800000011920929 + color: 25; 255; 0 + coloring: Label + line width: 0.05000000074505806 + only edge: true + show coords: false + Enabled: true + Global Options: + Background Color: 48; 48; 48 + Default Light: true + Fixed Frame: odom + Frame Rate: 30 + Name: root + Tools: + - Class: rviz/Interact + Hide Inactive Objects: true + - Class: rviz/MoveCamera + - Class: rviz/Select + - Class: rviz/FocusCamera + - Class: rviz/Measure + - Class: rviz/SetInitialPose + Theta std deviation: 0.2617993950843811 + Topic: /initialpose + X std deviation: 0.5 + Y std deviation: 0.5 + - Class: rviz/SetGoal + Topic: /move_base_simple/goal + - Class: rviz/PublishPoint + Single click: true + Topic: /clicked_point + Value: true + Views: + Current: + Class: rviz/Orbit + Distance: 8.482810974121094 + Enable Stereo Rendering: + Stereo Eye Separation: 0.05999999865889549 + Stereo Focal Distance: 1 + Swap Stereo Eyes: false + Value: false + Focal Point: + X: 0 + Y: 0 + Z: 0 + Focal Shape Fixed Size: true + Focal Shape Size: 0.05000000074505806 + Invert Z Axis: false + Name: Current View + Near Clip Distance: 0.009999999776482582 + Pitch: 0.3449980318546295 + Target Frame: base_link + Value: Orbit (rviz) + Yaw: 4.94684362411499 + Saved: ~ +Window Geometry: + Displays: + collapsed: true + Height: 1025 + Hide Left Dock: true + Hide Right Dock: true + QMainWindow State: 000000ff00000000fd00000004000000000000015700000363fc0200000012fb0000001200530065006c0065006300740069006f006e00000001e10000009b0000005c00fffffffb0000001e0054006f006f006c002000500072006f007000650072007400690065007302000001ed000001df00000185000000a3fb000000120056006900650077007300200054006f006f02000001df000002110000018500000122fb000000200054006f006f006c002000500072006f0070006500720074006900650073003203000002880000011d000002210000017afb000000100044006900730070006c006100790073000000003d00000363000000c900fffffffb0000002000730065006c0065006300740069006f006e00200062007500660066006500720200000138000000aa0000023a00000294fb00000014005700690064006500530074006500720065006f02000000e6000000d2000003ee0000030bfb0000000c004b0069006e0065006300740200000186000001060000030c00000261fb000000280020002d0020005400720061006a006500630074006f0072007900200053006c00690064006500720000000000ffffffff0000000000000000fb0000001c004d006f00740069006f006e0050006c0061006e006e0069006e006700000002300000018e0000000000000000fb00000044004d006f00740069006f006e0050006c0061006e006e0069006e00670020002d0020005400720061006a006500630074006f0072007900200053006c00690064006500720000000000ffffffff0000000000000000fb0000000a0049006d00610067006501000001f2000001ae0000000000000000fb0000000a0049006d00610067006501000002f4000000ca0000000000000000fb0000000a0049006d006100670065000000013f000000660000000000000000fb0000000a0049006d0061006700650000000164000000940000000000000000fb0000000a0049006d00610067006500000001a3000000e80000000000000000fc00000227000001970000000000fffffffa000000000100000002fb0000000a0049006d0061006700650000000000ffffffff0000000000000000fb0000000a0049006d0061006700650000000000000002ad0000000000000000fb0000000a0049006d00610067006503000004f5000002820000028800000157000000010000010f00000363fc0200000003fb0000001e0054006f006f006c002000500072006f00700065007200740069006500730100000041000000780000000000000000fb0000000a00560069006500770073000000003d00000363000000a400fffffffb0000001200530065006c0065006300740069006f006e010000025a000000b200000000000000000000000200000490000000a9fc0100000001fb0000000a00560069006500770073030000004e00000080000002e10000019700000003000007800000003efc0100000002fb0000000800540069006d0065010000000000000780000002eb00fffffffb0000000800540069006d00650100000000000004500000000000000000000007800000036300000004000000040000000800000008fc0000000100000002000000010000000a0054006f006f006c00730100000000ffffffff0000000000000000 + Selection: + collapsed: false + Time: + collapsed: false + Tool Properties: + collapsed: false + Views: + collapsed: true + Width: 1920 + X: 0 + Y: 27 diff --git a/jsk_spot_robot/jsk_spot_startup/config/twist_mux.yaml b/jsk_spot_robot/jsk_spot_startup/config/twist_mux.yaml new file mode 100644 index 0000000000..f5df2a4c8c --- /dev/null +++ b/jsk_spot_robot/jsk_spot_startup/config/twist_mux.yaml @@ -0,0 +1,13 @@ +topics: +# +# priority 9: high <--> 1: low +# +- name : bt_joy + topic : bluetooth_teleop/cmd_vel + timeout : 0.5 + priority: 9 +- name : head_joy + topic : joy_head/cmd_vel + timeout : 0.5 + priority: 7 +locks: [] diff --git a/jsk_spot_robot/jsk_spot_startup/launch/include/driver.launch b/jsk_spot_robot/jsk_spot_startup/launch/include/driver.launch new file mode 100644 index 0000000000..26e4953b77 --- /dev/null +++ b/jsk_spot_robot/jsk_spot_startup/launch/include/driver.launch @@ -0,0 +1,195 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/jsk_spot_robot/jsk_spot_startup/launch/include/interaction.launch b/jsk_spot_robot/jsk_spot_startup/launch/include/interaction.launch new file mode 100644 index 0000000000..d4f6ae4788 --- /dev/null +++ b/jsk_spot_robot/jsk_spot_startup/launch/include/interaction.launch @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/jsk_spot_robot/jsk_spot_startup/launch/include/lifelog.launch b/jsk_spot_robot/jsk_spot_startup/launch/include/lifelog.launch new file mode 100644 index 0000000000..3e6195e067 --- /dev/null +++ b/jsk_spot_robot/jsk_spot_startup/launch/include/lifelog.launch @@ -0,0 +1,224 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + approximate_sync: true + topics: + - /publish_trigger_mongodb_event + - /spot/camera/hand_color/image/compressed + - /spot/camera/frontleft/image/compressed + - /spot/camera/frontright/image/compressed + - /spot/camera/left/image/compressed + - /spot/camera/right/image/compressed + - /spot/camera/back/image/compressed + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + topics: + - /spot/ncb_provider/back_fisheye_image/class + - /spot/ncb_provider/frontleft_fisheye_image/class + - /spot/ncb_provider/frontright_fisheye_image/class + - /spot/ncb_provider/hand_color_image/class + - /spot/ncb_provider/left_fisheye_image/class + - /spot/ncb_provider/right_fisheye_image/class + + + + + + + topics: + - /spot/ncb_provider/bbox_array + + + + + + topics: + - /ublox/fix + - /ublox/fix_velocity + + + + + periodic: false + threshold: 0.01 + + + + white_list: + type: + - control_msgs/FollowJointTrajectoryActionFeedback + - control_msgs/FollowJointTrajectoryActionGoal + - control_msgs/FollowJointTrajectoryActionResult + - pr2_common_action_msgs/TuckArmsActionFeedback + - pr2_common_action_msgs/TuckArmsActionGoal + - pr2_common_action_msgs/TuckArmsActionResult + - pr2_controllers_msgs/PointHeadActionFeedback + - pr2_controllers_msgs/PointHeadActionGoal + - pr2_controllers_msgs/PointHeadActionResult + - pr2_controllers_msgs/Pr2GripperCommandActionFeedback + - pr2_controllers_msgs/Pr2GripperCommandActionGoal + - pr2_controllers_msgs/Pr2GripperCommandActionResult + - sound_play/SoundRequestActionResult + - sound_play/SoundRequestActionGoal + - spot_msgs/PickObjectInImageActionGoal + - spot_msgs/PickObjectInImageActionResult + + + + topics: + - /sound_play/goal + - /robotsound/goal + - /robotsound_jp/goal + - /sound_play + - /robotsound + - /robotsound_jp + + + + topics: + - /faces_name + - /aws_auto_checkin_app/output/class + - /aws_detect_faces/attributes + + + diff --git a/jsk_spot_robot/jsk_spot_startup/launch/include/lifelog_rgb_image.launch b/jsk_spot_robot/jsk_spot_startup/launch/include/lifelog_rgb_image.launch new file mode 100644 index 0000000000..6f0f1e4a28 --- /dev/null +++ b/jsk_spot_robot/jsk_spot_startup/launch/include/lifelog_rgb_image.launch @@ -0,0 +1,19 @@ + + + + + + + + + + + + enable_monitor: false + vital_check: false + + + diff --git a/jsk_spot_robot/jsk_spot_startup/launch/include/panorama_detection.launch b/jsk_spot_robot/jsk_spot_startup/launch/include/panorama_detection.launch new file mode 100644 index 0000000000..0def9ef903 --- /dev/null +++ b/jsk_spot_robot/jsk_spot_startup/launch/include/panorama_detection.launch @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + + + + + score_thresh: 0.6 + model_file: $(arg model_file) + label_file: $(arg label_file) + image_transport: raw + device_id: $(arg device_id) + n_split: $(arg n_split) + overlap: $(arg overlap) + enable_visualization: false + + + + + + + + + + + + frame_fixed: "odom" + dimensions_labels: + person: [0.5, 0.5, 1.5] + car: [4.0, 4.0, 2.0] + truck: [4.0, 4.0, 3.0] + + + diff --git a/jsk_spot_robot/jsk_spot_startup/launch/include/perception.launch b/jsk_spot_robot/jsk_spot_startup/launch/include/perception.launch new file mode 100644 index 0000000000..1772e298c1 --- /dev/null +++ b/jsk_spot_robot/jsk_spot_startup/launch/include/perception.launch @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + + + image_sources: $(arg image_sources) + ml_service: $(arg ml_service) + ml_models: $(arg ml_models) + ml_confidence: $(arg ml_confidence) + use_gui: $(arg use_gui) + + + + + + + + + use_window: $(arg use_gui) + aws_credentials_path: $(arg aws_credential_config) + always_publish: false + attributes: ALL + + + + + + + + use_window: $(arg use_gui) + env_path: $(arg aws_credential_config) + aws_credentials_path: $(arg aws_credential_config) + always_publish: false + approximate_sync: true + queue_size: 1 + image_transport: compressed + + + + + + + + + font_path: $(find jsk_recognition_utils)/font_data/NotoSansJP-Regular.otf + use_classification_result: true + label_size: 16 + approximate_sync: true + queue_size: 30 + image_transport: compressed + + + + diff --git a/jsk_spot_robot/jsk_spot_startup/launch/include/peripheral.launch b/jsk_spot_robot/jsk_spot_startup/launch/include/peripheral.launch new file mode 100644 index 0000000000..8a4bcc949f --- /dev/null +++ b/jsk_spot_robot/jsk_spot_startup/launch/include/peripheral.launch @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/jsk_spot_robot/jsk_spot_startup/launch/jsk_spot_bringup.launch b/jsk_spot_robot/jsk_spot_startup/launch/jsk_spot_bringup.launch new file mode 100644 index 0000000000..494c2c6dac --- /dev/null +++ b/jsk_spot_robot/jsk_spot_startup/launch/jsk_spot_bringup.launch @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/jsk_spot_robot/jsk_spot_startup/launch/rosbag_play.launch b/jsk_spot_robot/jsk_spot_startup/launch/rosbag_play.launch new file mode 100644 index 0000000000..d3e8eee15a --- /dev/null +++ b/jsk_spot_robot/jsk_spot_startup/launch/rosbag_play.launch @@ -0,0 +1,140 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/jsk_spot_robot/jsk_spot_startup/launch/rosbag_record.launch b/jsk_spot_robot/jsk_spot_startup/launch/rosbag_record.launch new file mode 100644 index 0000000000..7fe113e663 --- /dev/null +++ b/jsk_spot_robot/jsk_spot_startup/launch/rosbag_record.launch @@ -0,0 +1,179 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/jsk_spot_robot/jsk_spot_startup/launch/rviz.launch b/jsk_spot_robot/jsk_spot_startup/launch/rviz.launch new file mode 100644 index 0000000000..2d9fce1f16 --- /dev/null +++ b/jsk_spot_robot/jsk_spot_startup/launch/rviz.launch @@ -0,0 +1,11 @@ + + + + + diff --git a/jsk_spot_robot/jsk_spot_startup/launch/sample_pick_object_in_image.launch b/jsk_spot_robot/jsk_spot_startup/launch/sample_pick_object_in_image.launch new file mode 100644 index 0000000000..103827e8e4 --- /dev/null +++ b/jsk_spot_robot/jsk_spot_startup/launch/sample_pick_object_in_image.launch @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/jsk_spot_robot/jsk_spot_startup/node_scripts/battery_notifier.py b/jsk_spot_robot/jsk_spot_startup/node_scripts/battery_notifier.py new file mode 100755 index 0000000000..e35b6ceae2 --- /dev/null +++ b/jsk_spot_robot/jsk_spot_startup/node_scripts/battery_notifier.py @@ -0,0 +1,133 @@ +#!/usr/bin/env python +# -*- encoding: utf-8 -*- + +import rospy +from std_msgs.msg import Bool +from std_srvs.srv import Trigger, TriggerRequest +from spot_msgs.msg import BatteryStateArray +from sensor_msgs.msg import BatteryState + +from sound_play.libsoundplay import SoundClient + + +class SpotBatteryNotifier(object): + + def __init__(self): + + self._battery_spot = None + self._battery_temperature = 0 + self._is_connected = False + self.last_warn_bat_temp_time = rospy.get_time() + + self._sub_spot = rospy.Subscriber( + '/spot/status/battery_states', + BatteryStateArray, + self._cb_spot) + self._sub_connected = rospy.Subscriber( + '/spot/status/cable_connected', + Bool, + self._cb_connected) + + sound_client = SoundClient( + blocking=False, + sound_action='/robotsound_jp', + sound_topic='/robotsound_jp' + ) + + threshold_warning_battery_temperature =\ + float(rospy.get_param('~threshold_warning_battery_temperature', 45)) + + threshold_warning_spot = float( + rospy.get_param('~threshold_warning_spot', 20)) + + threshold_return_spot = float( + rospy.get_param('~threshold_return_spot', 15)) + + threshold_estop_spot = float( + rospy.get_param('~threshold_estop_spot', 5)) + + notification_duration = float( + rospy.get_param('~notification_duration', 300)) + + rate = rospy.Rate(0.1) + last_notified = rospy.Time.now() + while not rospy.is_shutdown(): + + rate.sleep() + + if not self._is_connected: + + if (rospy.Time.now() - last_notified).to_sec() > notification_duration: + sound_client.say('バッテリー残量、{}、パーセントです。'.format( + int(self._battery_spot)), volume=0.3) + last_notified = rospy.Time.now() + + if (self._battery_spot is not None and self._battery_spot < threshold_estop_spot): + rospy.logerr('Battery is low. Estop.') + sound_client.say('バッテリー残量が少ないため、動作を停止します') + rospy.ServiceProxy('/spot/estop/gentle', Trigger)(TriggerRequest()) + rospy.ServiceProxy('/spot/estop/hard', Trigger)(TriggerRequest()) + + elif (self._battery_spot is not None and self._battery_spot < threshold_return_spot): + rospy.logerr('Battery is low. Returning to home.') + sound_client.say('バッテリー残量が少ないため、ドックに戻ります') + self._call_go_back_home() + + elif (self._battery_spot is not None and self._battery_spot < threshold_warning_spot): + rospy.logwarn('Battery is low. Spot: {}'.format(self._battery_spot)) + sound_client.say('バッテリー残量が少ないです。本体が、{}、パーセントです。'.format( + int(self._battery_spot))) + + if self._battery_temperature > threshold_warning_battery_temperature\ + and (rospy.get_time() - self.last_warn_bat_temp_time) > 180: + rospy.logerr('Battery temperature is high. Battery temp:{:.2f} > threshold:{}' + .format(self._battery_temperature, threshold_warning_battery_temperature)) + sound_client.say('バッテリー温度が高いです。') + self.last_warn_bat_temp_time = rospy.get_time() + + def _cb_spot(self, msg): + + self._battery_spot = msg.battery_states[0].charge_percentage + self._battery_temperature = max(msg.battery_states[0].temperatures) + + def _cb_connected(self, msg): + + self._is_connected = msg.data + + def _call_estop_gentle(self): + + try: + trigger = rospy.ServiceProxy('/spot/estop/gentle', Trigger) + ret = trigger() + rospy.loginfo('Call %s and received %s(%s)'%(trigger.resolved_name, ret.sucess, ret.message)) + except rospy.ServiceException as e: + rospy.logerr('Service call failed: %s'%e) + + def _call_estop_hard(self): + + try: + trigger = rospy.ServiceProxy('/spot/estop/hard', Trigger) + ret = trigger() + rospy.loginfo('Call %s and received %s(%s)'%(trigger.resolved_name, ret.sucess, ret.message)) + except rospy.ServiceException as e: + rospy.logerr('Service call failed: %s'%e) + + def _call_go_back_home(self): + + try: + trigger = rospy.ServiceProxy('/spot/dock_fixed_id', Trigger) + ret = trigger() + rospy.loginfo('Call %s and received %s(%s)'%(trigger.resolved_name, ret.sucess, ret.message)) + except rospy.ServiceException as e: + rospy.logerr('Service call failed: %s'%e) + + +def main(): + + rospy.init_node('battery_notifier') + battery_notifier = SpotBatteryNotifier() + rospy.spin() + + +if __name__ == '__main__': + main() diff --git a/jsk_spot_robot/jsk_spot_startup/node_scripts/battery_visualizer.py b/jsk_spot_robot/jsk_spot_startup/node_scripts/battery_visualizer.py new file mode 100755 index 0000000000..95be4670c5 --- /dev/null +++ b/jsk_spot_robot/jsk_spot_startup/node_scripts/battery_visualizer.py @@ -0,0 +1,33 @@ +#!/usr/bin/env python + +import rospy +from spot_msgs.msg import BatteryStateArray +from std_msgs.msg import Float32 + + +class SpotBatteryVisualizer(object): + + def __init__(self): + + rospy.init_node('spot_battery_visualizer') + + self._sub = rospy.Subscriber( + '/spot/status/battery_states', BatteryStateArray, self._cb) + self._pub = rospy.Publisher( + '/spot/status/battery_percentage', Float32, queue_size=1) + + def _cb(self, msg): + + pub_msg = Float32() + pub_msg.data = msg.battery_states[0].charge_percentage + self._pub.publish(pub_msg) + + +def main(): + + battery_visualizer = SpotBatteryVisualizer() + rospy.spin() + + +if __name__ == '__main__': + main() diff --git a/jsk_spot_robot/jsk_spot_startup/node_scripts/cable_connection_detector.py b/jsk_spot_robot/jsk_spot_startup/node_scripts/cable_connection_detector.py new file mode 100755 index 0000000000..606c5abf36 --- /dev/null +++ b/jsk_spot_robot/jsk_spot_startup/node_scripts/cable_connection_detector.py @@ -0,0 +1,53 @@ +#!/usr/bin/env python + +import rospy +import spot_msgs.msg +import sensor_msgs.msg +from std_msgs.msg import Bool + + +class CalbeConnectionDetector: + + def __init__(self): + + self.pub = rospy.Publisher( + '/spot/status/cable_connected', Bool, queue_size=1) + + self.msg_battery_spot = None + self.msg_battery_laptop = None + + self.sub_battery_spot = rospy.Subscriber( + '/spot/status/power_state', + spot_msgs.msg.PowerState, + self.callback_battery_spot) + + rate = rospy.Rate(1) + while not rospy.is_shutdown(): + rate.sleep() + if self.msg_battery_spot == None and self.msg_battery_laptop == None: + continue + self.pub.publish(Bool(self.check_connection())) + + def check_connection(self): + + if self.msg_battery_spot is not None and\ + self.msg_battery_spot.shore_power_state == spot_msgs.msg.PowerState.STATE_ON_SHORE_POWER: + return True + else: + return False + + def callback_battery_spot(self, msg): + + self.msg_battery_spot = msg + + + +def main(): + + rospy.init_node('cable_connection_detector') + node = CalbeConnectionDetector() + rospy.spin() + + +if __name__ == '__main__': + main() diff --git a/jsk_spot_robot/jsk_spot_startup/node_scripts/call_spot_dock_with_id.py b/jsk_spot_robot/jsk_spot_startup/node_scripts/call_spot_dock_with_id.py new file mode 100755 index 0000000000..c21e1fec7e --- /dev/null +++ b/jsk_spot_robot/jsk_spot_startup/node_scripts/call_spot_dock_with_id.py @@ -0,0 +1,31 @@ +#!/usr/bin/env python +# -*- encoding: utf-8 -*- + +import rospy +from std_srvs.srv import Trigger, TriggerResponse +from spot_msgs.srv import Dock + +class SpotDockWithID(object): + + def __init__(self): + + self._dock_id = int(rospy.get_param('~dock_id', 1)) + + self._server = rospy.Service('/spot/dock_fixed_id', Trigger, self._cb) + self._client = rospy.ServiceProxy('/spot/dock', Dock) + + def _cb(self, req): + + resp = self._client(self._dock_id) + + rospy.loginfo("Call /spot/dock(dock_id={}) returns {}".format(self._dock_id, resp)) + return TriggerResponse(success=resp.success, message=resp.message) + +def main(): + rospy.init_node('spot_dock_with_id') + end_effector_to_joy = SpotDockWithID() + rospy.spin() + + +if __name__ == '__main__': + main() diff --git a/jsk_spot_robot/jsk_spot_startup/node_scripts/end_effector_to_joy.py b/jsk_spot_robot/jsk_spot_startup/node_scripts/end_effector_to_joy.py new file mode 100755 index 0000000000..e62715641d --- /dev/null +++ b/jsk_spot_robot/jsk_spot_startup/node_scripts/end_effector_to_joy.py @@ -0,0 +1,86 @@ +#!/usr/bin/env python +# -*- encoding: utf-8 -*- + +import rospy +import tf2_ros +from spot_msgs.msg import ManipulatorState +from sensor_msgs.msg import Joy +from std_srvs.srv import SetBool, SetBoolResponse + +# python3 does noth have cmp... +def cmp(a, b): + return (a > b) - (a < b) + +class EndEffectorToJoy(object): + + def __init__(self): + self._tf_buffer = tf2_ros.Buffer() + self._listener = tf2_ros.TransformListener(self._tf_buffer) + self._pub = rospy.Publisher( + '/joy_head/joy_raw', + Joy) + self._sub = None + self._enabled = rospy.Service('~set_enabled', SetBool, self._set_enabled) + + self._deadzone_x = float(rospy.get_param('~deadzone_x', 0.01)) + self._deadzone_y = float(rospy.get_param('~deadzone_y', 0.01)) + self._maxrange_y = float(rospy.get_param('~maxrange_x', 0.2)) + self._maxrange_x = float(rospy.get_param('~maxrange_y', 0.2)) + self._offset_x = float(rospy.get_param('~offset_x', 0.0)) + self._offset_y = float(rospy.get_param('~offset_y', 0.0)) + + def _set_enabled(self, req): + if req.data: + self._center_x = float(rospy.get_param('~center_x', 0.576)) + self._center_y = float(rospy.get_param('~center_0', 0)) + rospy.logwarn("Enabled end_effector_to_joy with center ({}, {})".format(self._center_x, self._center_y)) + self._sub = rospy.Subscriber('/spot/status/manipulator_state', ManipulatorState, self._cb) + else: + rospy.logwarn("Disabled end_effector_to_joy with center") + if self._sub: + self._sub.unregister() + self._sub = None + return SetBoolResponse(True, 'Success') + + def _cb(self, msg): + joy = Joy() + joy.header.frame_id = rospy.get_name() + joy.header.stamp = rospy.Time.now() + x = msg.estimated_end_effector_force_in_hand.x + y = msg.estimated_end_effector_force_in_hand.y + z = msg.estimated_end_effector_force_in_hand.z + try: + trans = self._tf_buffer.lookup_transform('body', 'hand', rospy.Time(0)) + except (tf2_ros.LookupException, tf2_ros.ConnectivityException, tf2_ros.ExtrapolationException) as e: + rospy.logerr(e) + return None + x = trans.transform.translation.x - self._center_x + y = trans.transform.translation.y - self._center_y + rospy.loginfo_throttle(10, "end_effector_froce_in_hand = ({:4.1f}, {:4.1f}, {:4.1f}), stow_state = {}".format(x, y, z, msg.stow_state)) + if msg.stow_state == ManipulatorState.STOWSTATE_STOWED: + x = 0 + y = 0 + else: + sign_x = cmp(x, 0) + sign_y = cmp(y, 0) + x = abs(x) + self._offset_x + y = abs(y) + self._offset_y + if x > self._maxrange_x: x = self._maxrange_x + if y > self._maxrange_y: y = self._maxrange_y + x = 0 if x < self._deadzone_x else x - self._deadzone_x + y = 0 if y < self._deadzone_y else y - self._deadzone_y + x = sign_x * x / (self._maxrange_x - self._deadzone_x) + y = sign_y * y / (self._maxrange_y - self._deadzone_y) + joy.axes = [x, y, 0, 0, 0, 0] + rospy.loginfo_throttle(10, " ({:4.1f}, {:4.1f}, {:4.1f})".format(x, y, 0)) + joy.buttons = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] # dummy button data + self._pub.publish(joy) + +def main(): + rospy.init_node('end_effector_to_joy') + end_effector_to_joy = EndEffectorToJoy() + rospy.spin() + + +if __name__ == '__main__': + main() diff --git a/jsk_spot_robot/jsk_spot_startup/node_scripts/laptop_battery_visualizer.py b/jsk_spot_robot/jsk_spot_startup/node_scripts/laptop_battery_visualizer.py new file mode 100755 index 0000000000..2969614532 --- /dev/null +++ b/jsk_spot_robot/jsk_spot_startup/node_scripts/laptop_battery_visualizer.py @@ -0,0 +1,32 @@ +#!/usr/bin/env python + +import rospy +from sensor_msgs.msg import BatteryState +from std_msgs.msg import Float32 + + +class LaptopBatteryVisualizer(object): + + def __init__(self): + + rospy.init_node('spot_battery_visualizer') + + self._sub = rospy.Subscriber('/laptop_charge', BatteryState, self._cb) + self._pub = rospy.Publisher( + '/spot/status/laptop_battery_percentage', Float32, queue_size=1) + + def _cb(self, msg): + + pub_msg = Float32() + pub_msg.data = msg.percentage + self._pub.publish(pub_msg) + + +def main(): + + battery_visualizer = LaptopBatteryVisualizer() + rospy.spin() + + +if __name__ == '__main__': + main() diff --git a/jsk_spot_robot/jsk_spot_startup/node_scripts/sample_pick_object_in_image.l b/jsk_spot_robot/jsk_spot_startup/node_scripts/sample_pick_object_in_image.l new file mode 100755 index 0000000000..9aec1452cf --- /dev/null +++ b/jsk_spot_robot/jsk_spot_startup/node_scripts/sample_pick_object_in_image.l @@ -0,0 +1,58 @@ +#!/usr/bin/env roseus + +(ros::load-ros-manifest "roseus") +(ros::load-ros-manifest "geometry_msgs") +(ros::load-ros-manifest "spot_msgs") + +(ros::roseus "sample_pick_object_in_image") +(setq *c* (instance ros::simple-action-client :init + "/spot/pick_object_in_image" spot_msgs::PickObjectInImageAction)) +(send *c* :wait-for-server) + +;; call-trigger-service reqrueis roseus>1.7.5 +;; https://github.com/jsk-ros-pkg/jsk_roseus/pull/705 +;; +(defun ros::call-trigger-service (srvname &key (wait nil) (timeout -1) (persistent nil)) + "Call std_srv/Trigger service. Use (setq r (call-trigger-service \"/test\" t)) (and r (send r :success)) to check if it succeed. If r is nil, it fail to find service." + (let (r) + (when (ros::wait-for-service srvname (if wait timeout 0)) + (setq r (ros::service-call srvname (instance std_srvs::TriggerRequest :init) persistent)) + (ros::ros-debug "Call \"~A\" returns \"~A\"" srvname (send r :message)) + (unless (send r :success) (ros::ros-warn "Call \"~A\" fails, it returns \"~A\"" srvname (send r :message))) + r))) + +(ros::subscribe "screenpoint" geometry_msgs::PointStamped + #'(lambda (msg) + (let ((goal (instance spot_msgs::PickObjectInImageActionGoal :init))) + (send goal :goal :image_source (ros::get-param "~image_source")) ;; required ex) "hand_color_image" + (send goal :goal :center (instance geometry_msgs::Point :init :x (send msg :point :x) :y (send msg :point :y))) + (send goal :goal :max_duration (instance ros::duration :init 15)) + (send *c* :send-goal goal)))) + +(do-until-key + (ros::rate 3) + (while (memq (send (send (*c* . ros::comm-state) :latest-goal-status) :status) ;; bugs in actionlib.l?? + (list actionlib_msgs::GoalStatus::*pending* + actionlib_msgs::GoalStatus::*preempted* + actionlib_msgs::GoalStatus::*succeeded* + actionlib_msgs::GoalStatus::*aborted*)) + (ros::ros-warn "waiting to select target object in image_view ...") + (ros::sleep) + (ros::spin-once)) + (ros::ros-warn ";; wait-for-result") + (send *c* :wait-for-result) + (if (send (send *c* :get-result) :success) + ;; if robot grab object, unstow then open gripper + (progn + (ros::call-trigger-service "/spot/unstow_arm") + (ros::call-trigger-service "/spot/gripper_open")) + (progn + ;; if robot failed to grab object, just open gripper + (ros::call-trigger-service "/spot/gripper_open"))) + (ros::call-trigger-service "/spot/stow_arm") + ) + + + + + diff --git a/jsk_spot_robot/jsk_spot_startup/node_scripts/start-spot.l b/jsk_spot_robot/jsk_spot_startup/node_scripts/start-spot.l new file mode 100755 index 0000000000..204ccfaef5 --- /dev/null +++ b/jsk_spot_robot/jsk_spot_startup/node_scripts/start-spot.l @@ -0,0 +1,23 @@ +#!/usr/bin/env roseus + +(load "package://spoteus/spot-interface.l") +(unless (boundp '*spot*) (spot) (send *spot* :reset-pose)) +(unless (ros::ok) (ros::roseus "spot_eus_interface")) +(unless (boundp '*ri*) (setq *ri* (instance spot-interface :init))) +(send *ri* :claim) + +(setq ip "unknown") +(setq host (unix::gethostbyname "spotcore.jsk.imi.i.u-tokyo.ac.jp")) +(if (listp host) + (setq ip (format nil "~A . ~A . ~A . ~A" (elt (car host) 0) (elt (car host) 1) (elt (car host) 2) (elt (car host) 3)))) + +(setq info-txt (format nil "Hello, my name is ~A, my internet address is ~A, my owner is ~A and my dock id is ~A" + (ros::get-param "/robot/name") + ip + (unix::getenv "USER") + (round (ros::get-param "/spot/spot_ros/dock_id")))) +(ros::ros-info info-txt) +(send *ri* :speak info-txt :wait t) + + +(ros::exit) diff --git a/jsk_spot_robot/jsk_spot_startup/node_scripts/static_tf_republisher.py b/jsk_spot_robot/jsk_spot_startup/node_scripts/static_tf_republisher.py new file mode 100755 index 0000000000..ff5363d6c0 --- /dev/null +++ b/jsk_spot_robot/jsk_spot_startup/node_scripts/static_tf_republisher.py @@ -0,0 +1,42 @@ +#!/usr/bin/env python + +import sys +import argparse + +import rospy +import rosbag +import tf2_ros + +from geometry_msgs.msg import TransformStamped +from tf2_msgs.msg import TFMessage + + +def main(): + + rospy.init_node('static_tf_republisher') + + topicname = '/tf_static' + + myargv = rospy.myargv(argv=sys.argv) + if len(myargv) > 1: + bagfilename = myargv[1] + else: + bagfilename = rospy.get_param("~file") + + broadcaster = tf2_ros.StaticTransformBroadcaster() + + list_messages = [] + transforms = [] + with rosbag.Bag(bagfilename, 'r') as inputbag: + for topic, msg, t in inputbag.read_messages('/tf_static'): + transforms.extend(msg.transforms) + + rospy.loginfo( + 'republish /tf_static with {} TransformStamped messages'.format(len(transforms))) + + broadcaster.sendTransform(transforms) + rospy.spin() + + +if __name__ == '__main__': + main() diff --git a/jsk_spot_robot/jsk_spot_startup/package.xml b/jsk_spot_robot/jsk_spot_startup/package.xml new file mode 100644 index 0000000000..c72d0da068 --- /dev/null +++ b/jsk_spot_robot/jsk_spot_startup/package.xml @@ -0,0 +1,60 @@ + + + jsk_spot_startup + 1.1.0 + The jsk_spot_startup package + + Kei Okada + Koki Shinjo + Koki Shinjo + + BSD + + catkin + + roslaunch + roslint + + app_manager + app_scheduler + aques_talk + audio_capture + cmd_vel_smoother + compressed_image_transport + coral_usb + image_transport + interactive_marker_twist_server + jsk_perception + jsk_robot_startup + jsk_tools + julius_ros + laptop_battery_monitor + robot_state_publisher + ros_speech_recognition + rosbag + roseus + roseus_smach + roslaunch + rosserial_python + rviz + rwt_app_chooser + sound_play + spot_description + spot_driver + spoteus + topic_tools + twist_mux + voice_text + xacro + xterm + + jsk_spot_behavior_manager + spot_basic_behaviors + esp_now_ros + + rostest + + + + + diff --git a/jsk_spot_robot/jsk_spot_startup/scripts/deploy-scripts-and-services.sh b/jsk_spot_robot/jsk_spot_startup/scripts/deploy-scripts-and-services.sh new file mode 100755 index 0000000000..0d702f4cac --- /dev/null +++ b/jsk_spot_robot/jsk_spot_startup/scripts/deploy-scripts-and-services.sh @@ -0,0 +1,15 @@ +#!/bin/bash + +PACKAGE_PATH=$(rospack find jsk_spot_startup) +SERVICE_SOURCE_PATH=$PACKAGE_PATH/services +SERVICE_DESTINATION_PATH=/etc/systemd/system + +# install systemd unit files +cd $SERVICE_SOURCE_PATH +for service_file in $(ls ./*); +do + sudo cp $service_file $SERVICE_DESTINATION_PATH/$service_file + sudo chmod 644 $SERVICE_DESTINATION_PATH/$service_file + sudo chown root:root $SERVICE_DESTINATION_PATH/$service_file +done +sudo systemctl daemon-reload diff --git a/jsk_spot_robot/jsk_spot_startup/scripts/install_udev_rules.sh b/jsk_spot_robot/jsk_spot_startup/scripts/install_udev_rules.sh new file mode 100755 index 0000000000..bab4be1279 --- /dev/null +++ b/jsk_spot_robot/jsk_spot_startup/scripts/install_udev_rules.sh @@ -0,0 +1,4 @@ +#!/bin/bash + +sudo cp $(rospack find jsk_spot_startup)/udev_rules/* /etc/udev/rules.d/ +sudo udevadm control --reload-rules diff --git a/jsk_spot_robot/jsk_spot_startup/scripts/update-network-connection.sh b/jsk_spot_robot/jsk_spot_startup/scripts/update-network-connection.sh new file mode 100755 index 0000000000..c5edb7f505 --- /dev/null +++ b/jsk_spot_robot/jsk_spot_startup/scripts/update-network-connection.sh @@ -0,0 +1,157 @@ +#!/bin/bash + +IF_ETH=enxa0cec875af37 +IF_WIFI=wlxc006c31b1a80 +IF_LTE=enxf8b7975c750a +WIFI_PROFILE=sanshiro +CURRENT_CONNECTION= + +function existIF() { + interface_name=$1 + for DEV in `find /sys/devices -name net | grep -v virtual` + do + if [ `ls $DEV/` = $interface_name ]; then + echo 0 + return 0 + fi + done + echo 1 + return 1 +} + +function restartWIFI() { + sudo nmcli connection down $WIFI_PROFILE + #sudo ip l set $IF_WIFI down + #sudo ip l set $IF_WIFI up + sudo nmcli connection up $WIFI_PROFILE +} + +function updateRouteToETH() { + sudo ifmetric $IF_ETH 100 + sudo ifmetric $IF_WIFI 101 + sudo ifmetric $IF_LTE 101 +} + +function updateRouteToWIFI() { + sudo ifmetric $IF_WIFI 100 + sudo ifmetric $IF_ETH 102 + sudo ifmetric $IF_LTE 101 +} + +function updateRouteToLTE() { + sudo ifmetric $IF_LTE 100 + sudo ifmetric $IF_WIFI 101 + sudo ifmetric $IF_ETH 102 +} + +# get default route interface name which has most priority +function get_default_route() { + ip route | grep default | awk '{print $5}' | head -n 1 +} + +function updateConnectionToETH() { + if [ $(existIF $IF_ETH) = 0 ]; then + ping -c 1 -W 1 1.1.1.1 -I $IF_ETH > /dev/null 2>&1 + if [ $? = 0 ]; then + echo "Ethernet is online. switched to ethernet." + CURRENT_CONNECTION=$IF_ETH + updateRouteToETH + else + echo "Ethernet is offline.: $?" + fi + else + echo "No ethernet device is found." + fi +} + +function updateConnectionToWiFi() { + if [ $(existIF $IF_WIFI) = 0 ]; then + ping -c 1 -W 1 1.1.1.1 -I $IF_WIFI > /dev/null 2>&1 + if [ $? = 0 ]; then + echo "Wifi is online. switched to wifi." + CURRENT_CONNECTION=$IF_WIFI + updateRouteToWIFI + else + echo "Wifi is offline.: $?" + fi + else + echo "No wifi device is found." + fi +} + +function updateConnectionToLTE() { + if [ $(existIF $IF_LTE) = 0 ]; then + echo "Connection type switched to lte" + CURRENT_CONNECTION=$IF_LTE + updateRouteToLTE + else + echo "No lte device is found" + fi + + echo "No network device found." +} + +function updateConnection() { + echo "Connection updating." + + # Check default route is current connection + default_route=$(get_default_route) + if [ $default_route = $CURRENT_CONNECTION ]; then + echo "Current connection $CURRENT_CONNECTION is default route. No need to update." + else + echo "Current connection $CURRENT_CONNECTION is not default route. Trying to update." + if [ $CURRENT_CONNECTION = $IF_ETH ]; then + updateConnectionToETH + elif [ $CURRENT_CONNECTION = $IF_WIFI ]; then + updateConnectionToWiFi + elif [ $CURRENT_CONNECTION = $IF_LTE ]; then + updateConnectionToLTE + else + echo "Unknown current connection: $CURRENT_CONNECTION" + fi + fi + + # Reconnect WIFI + if [ $(existIF $IF_WIFI) = 0 ]; then + ping -c 1 -W 1 1.1.1.1 -I $IF_WIFI > /dev/null 2>&1 + if [ $? = 0 ]; then + if [ $CURRENT_CONNECTION = $IF_WIFI ]; then + echo "wifi connected" + else + echo "wifi connected. But current connection $CURRENT_CONNECTION is not wifi $IF_WIFI. So update" + updateConnectionToWiFi + fi + else + echo "wifi not connected. trying to connect $WIFI_PROFILE" + restartWIFI + updateConnectionToWiFi + fi + else + echo "wifi device not found. skipped wifi reconnection." + fi + + # Check internet connection + ping -c 1 -W 1 1.1.1.1 > /dev/null 2>&1 + if [ $? = 0 ]; then + echo "Network connected" + return 0 + else + echo "Network disconnected. Trying to reconnect." + fi + + # First, try ethernet to connect with Ethernet if available + updateConnectionToETH + + # Second, try wifi to connect with Wi-Fi if available + updateConnectionToWiFi + + # connect with LTE if available + updateConnectionToLTE +} + +CURRENT_CONNECTION=$IF_ETH +while : +do + sleep 5 + updateConnection +done diff --git a/jsk_spot_robot/jsk_spot_startup/services/jsk-spot-utils-network.service b/jsk_spot_robot/jsk_spot_startup/services/jsk-spot-utils-network.service new file mode 100644 index 0000000000..8137664641 --- /dev/null +++ b/jsk_spot_robot/jsk_spot_startup/services/jsk-spot-utils-network.service @@ -0,0 +1,12 @@ +[Unit] +Description=JSK Network Service + +[Service] +Type=simple +ExecStart=/bin/bash -c ". /opt/ros/melodic/setup.bash && . /home/sktometometo/ros/ws_main_extend/devel/setup.bash && rosrun jsk_spot_startup update-network-connection.sh" +ExecStop=/bin/kill -WINCH ${MAINPID} +KillMode=control-group +KillSignal=SIGTERM + +[Install] +WantedBy=multi-user.target diff --git a/jsk_spot_robot/jsk_spot_startup/udev_rules/75-spot-m5stack.rules b/jsk_spot_robot/jsk_spot_startup/udev_rules/75-spot-m5stack.rules new file mode 100644 index 0000000000..1c5a8c97f6 --- /dev/null +++ b/jsk_spot_robot/jsk_spot_startup/udev_rules/75-spot-m5stack.rules @@ -0,0 +1,3 @@ +SUBSYSTEM=="tty", ATTRS{idVendor}=="1a86", ATTRS{idProduct}=="55d4", SYMLINK+="elevator_device", GROUP="dialout" +SUBSYSTEM=="tty", ATTRS{idVendor}=="303a", ATTRS{idProduct}=="1001", SYMLINK+="esp_now_ros_interface", GROUP="dialout" + diff --git a/jsk_spot_robot/jsk_spot_startup/udev_rules/89-spot-eye-devices.rules b/jsk_spot_robot/jsk_spot_startup/udev_rules/89-spot-eye-devices.rules new file mode 100644 index 0000000000..fd1615fc24 --- /dev/null +++ b/jsk_spot_robot/jsk_spot_startup/udev_rules/89-spot-eye-devices.rules @@ -0,0 +1,4 @@ +# M5Stamp +SUBSYSTEM=="tty", ATTRS{idVendor}=="1a86", ATTRS{idProduct}=="55d4", ATTRS{serial}=="5319011411", SYMLINK+="eye_left", GROUP="dialout" +SUBSYSTEM=="tty", ATTRS{idVendor}=="1a86", ATTRS{idProduct}=="55d4", ATTRS{serial}=="537A013618", SYMLINK+="eye_right", GROUP="dialout" + diff --git a/jsk_spot_robot/jsk_spot_user.rosinstall b/jsk_spot_robot/jsk_spot_user.rosinstall new file mode 100644 index 0000000000..1fb7336910 --- /dev/null +++ b/jsk_spot_robot/jsk_spot_user.rosinstall @@ -0,0 +1,16 @@ +# spot-ros is required for spot +## use https://github.com/cst0/spot_ros/pull/4 for Arm and gripper +- git: + local-name: spot-ros + uri: https://github.com/k-okada/spot_ros-arm.git + version: arm +## to support custom limb name, we need https://github.com/jsk-ros-pkg/jsk_model_tools/pull/249 +- git: + local-name: jsk_model_tools + uri: https://github.com/k-okada/jsk_model_tools.git + version: custom_limb +# wait for https://github.com/jsk-ros-pkg/jsk_pr2eus/pull/443 (add volume key arg for robot-interface :speak) to be released +- git: + local-name: jsk_pr2eus + uri: https://github.com/jsk-ros-pkg/jsk_pr2eus.git + version: master diff --git a/jsk_spot_robot/requirements.txt b/jsk_spot_robot/requirements.txt new file mode 100644 index 0000000000..6306e82a90 --- /dev/null +++ b/jsk_spot_robot/requirements.txt @@ -0,0 +1,6 @@ +protobuf == 3.19.4 # protobuf > 3.20 requres Python3.7 (https://github.com/protocolbuffers/protobuf/pull/9480) +bosdyn-client == 3.2.0 +bosdyn-mission == 3.2.0 +bosdyn-api == 3.2.0 +bosdyn-core == 3.2.0 +bosdyn-choreography-client == 3.2.0 diff --git a/jsk_spot_robot/spot_autowalk_data/CMakeLists.txt b/jsk_spot_robot/spot_autowalk_data/CMakeLists.txt new file mode 100644 index 0000000000..7dea41d2c2 --- /dev/null +++ b/jsk_spot_robot/spot_autowalk_data/CMakeLists.txt @@ -0,0 +1,24 @@ +cmake_minimum_required(VERSION 2.8.3) +project(spot_autowalk_data) + +find_package(catkin REQUIRED COMPONENTS + catkin_virtualenv +) + +catkin_generate_virtualenv( + PYTHON_INTERPRETER python3 + USE_SYSTEM_PACKAGES FALSE + CHECK_VENV FALSE +) + +add_custom_target(${PROJECT_NAME}_download_autowalk_data ALL COMMAND python$ENV{ROS_PYTHON_VERSION} ${PROJECT_SOURCE_DIR}/scripts/download_autowalk_data.py) + +catkin_package() + +install(DIRECTORY autowalk + DESTINATION ${CATKIN_PACKAGE_SHARE_DESTINATION} +) + +catkin_install_python(PROGRAMS scripts/view_map.py + DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION} +) diff --git a/jsk_spot_robot/spot_autowalk_data/README.md b/jsk_spot_robot/spot_autowalk_data/README.md new file mode 100644 index 0000000000..31552a9930 --- /dev/null +++ b/jsk_spot_robot/spot_autowalk_data/README.md @@ -0,0 +1,116 @@ +# spot_autowalk_data + +This package includes autowalk data downloaded from public Googledrive folder with jsk_data. + +## scripts + +### download_autowalk_data.py + +this scripts download autowalk data from google drive. this script will be run when catkin build. + +### view_map.py + +this script visualize autowalk data and each waypoint id + +#### Usage + +when you want to visualize autowalk data without waypoint id, just run + +``` +rosrun spot_autowalk_data view_map.py +``` + +when you want to visualize autowalk data with waypoint id, run the script with `--draw-id` option + +``` +rosrun spot_autowalk_data view_map.py --draw-id +``` + +## autowalk data in this packages + +### eng2_2FElevator_to_2FEntrance.walk + +![eng2_2FElevator_to_2FEntrance](https://user-images.githubusercontent.com/9410362/124133634-fbe92700-dabc-11eb-90d5-84b434729233.png) + +### eng2_2FElevator_to_MainGate + +![eng2_2FElevator_to_MainGate](https://user-images.githubusercontent.com/9410362/124133994-55e9ec80-dabd-11eb-8ba1-1bd118146244.png) + +### eng2_2Felevator_to_eng8_2FElevator + +![eng2_2FElevator_to_eng8_2FElevator](https://user-images.githubusercontent.com/9410362/124134033-600beb00-dabd-11eb-823c-483e75318f8b.png) + +### eng2_73B2_to_2FElevator + +![eng2_73B2_to_7FElevator](https://user-images.githubusercontent.com/9410362/135750882-3d72e538-316c-40a4-b209-cffd06a774a4.png) + +### eng2_73B2_to_81C1 + +![eng2_73B2_to_81C1](https://user-images.githubusercontent.com/9410362/124134118-7b76f600-dabd-11eb-829b-c36f105d7051.png) + +### eng2_elevator_7FElevator_to_2FElevator + +![eng2_elevator_7FElevator_to_2FElevator](https://user-images.githubusercontent.com/9410362/124134162-86318b00-dabd-11eb-86e3-092fcc5e8719.png) + +### eng_TelephoneBox_to_HongoMainGate + +![eng_TelephoneBox_to_HongoMainGate](https://user-images.githubusercontent.com/9410362/124134207-90ec2000-dabd-11eb-988b-62ffbf98ca67.png) + +### eng2_elevator_3FElevator_to_2FElevator.walk + +![eng2_elevator_3FElevator_to_2FElevator walk](https://user-images.githubusercontent.com/9410362/124587250-6077fd80-de92-11eb-8014-2852dfa7f60a.png) + +### eng2_3FElevator_to_Mech_Office.walk + +![eng2_3FElevator_to_Mech_Office walk](https://user-images.githubusercontent.com/9410362/124587299-6f5eb000-de92-11eb-9427-0ed88e6ca414.png) + +### eng2_2FElevator_to_MainEntrance.walk + +![eng2_2FElevator_to_MainEntrance](https://user-images.githubusercontent.com/9410362/129509474-c3429db1-6a6f-4076-bbbb-c3fb27bc3167.png) + +### eng2_MainEntrance_to_HongoMainGate.walk + +![eng2_MainEntrance_to_HongoMainGate](https://user-images.githubusercontent.com/9410362/129509500-1bde2818-66c7-4770-a87d-88c4f9d65ae3.png) + +### eng2_MainEntrance_to_HongoMainGate_Daylight.walk + +![eng2_MainEntrance_to_HongoMainGate_Daylight](https://user-images.githubusercontent.com/9410362/129997430-620ee1dc-6659-4028-acd7-f80b7539364a.png) + +### eng2_2FElevator_to_subway.walk + +![eng2_2FElevator_to_subway](https://user-images.githubusercontent.com/9410362/132933793-63c627bc-e210-4922-9fa2-4c1538b6f45e.png) + +### eng2_subway_tablenavigation.walk + +![eng2_subway_tablenavigation](https://user-images.githubusercontent.com/9410362/132933800-6f68566e-8471-4ac4-a030-94414f4a1b83.png) + +### eng2_subway_tablenavigation_02.walk + +![eng2_subway_tablenavigation_02](https://user-images.githubusercontent.com/9410362/132933805-f3c10dc3-01da-429c-96b8-74225401d703.png) + +### eng2_73b2kitchen.walk + +![eng2_73b2kitchen.walk](https://user-images.githubusercontent.com/67531577/136342909-7979a058-538e-4f76-a705-0186439c478a.png) + +### eng2_73b2trashcans.walk + +![eng2_73b2trashcans.walk](https://user-images.githubusercontent.com/67531577/136343017-74229050-1b36-41e3-bba1-fcad53e92df7.png) + +## How to add autowalk data in this package + +1. record a new autowalk data +2. archive and compress an autowalk data directory (e.g. `autowalk.walk`) to `autowalk.walk.tar.gz` by `tar -zcvf autowalk.walk.tar.gz autowalk.walk` +3. upload `autowalk.walk.tar.gz` to [jsk_data public google drive folger](https://drive.google.com/drive/folders/1PPOZDoWbJnxM7XkPMPP3iKQqrji56Vgq?usp=sharing) +4. add new entry to download_autowalk_data.py like + +``` + download_data( + pkg_name=PKG, + path='autowalk/autowalk.walk.tar.gz', + url='https://drive.google.com/uc?id=', + md5='', + extract=True + ) +``` + +5. update README.md diff --git a/jsk_spot_robot/spot_autowalk_data/autowalk/.gitignore b/jsk_spot_robot/spot_autowalk_data/autowalk/.gitignore new file mode 100644 index 0000000000..72e8ffc0db --- /dev/null +++ b/jsk_spot_robot/spot_autowalk_data/autowalk/.gitignore @@ -0,0 +1 @@ +* diff --git a/jsk_spot_robot/spot_autowalk_data/package.xml b/jsk_spot_robot/spot_autowalk_data/package.xml new file mode 100644 index 0000000000..a77a1f0987 --- /dev/null +++ b/jsk_spot_robot/spot_autowalk_data/package.xml @@ -0,0 +1,21 @@ + + + spot_autowalk_data + 1.1.0 + The spot_autowalk_data package + + Kei Okada + Koki Shinjo + Koki Shinjo + + BSD + + catkin + jsk_data + catkin_virtualenv + python-gdown-pip + + + requirements.txt + + diff --git a/jsk_spot_robot/spot_autowalk_data/requirements.txt b/jsk_spot_robot/spot_autowalk_data/requirements.txt new file mode 100644 index 0000000000..934b434976 --- /dev/null +++ b/jsk_spot_robot/spot_autowalk_data/requirements.txt @@ -0,0 +1,4 @@ +bosdyn-client +bosdyn-mission +bosdyn-choreography-client +vtk diff --git a/jsk_spot_robot/spot_autowalk_data/scripts/download_autowalk_data.py b/jsk_spot_robot/spot_autowalk_data/scripts/download_autowalk_data.py new file mode 100755 index 0000000000..78a5b07b06 --- /dev/null +++ b/jsk_spot_robot/spot_autowalk_data/scripts/download_autowalk_data.py @@ -0,0 +1,306 @@ +#!/usr/bin/env python + +from jsk_data import download_data + + +def main(): + PKG = 'spot_autowalk_data' + + download_data( + pkg_name=PKG, + path='autowalk/eng2_73B2_to_81C1.walk.tar.gz', + url='https://drive.google.com/uc?id=1IDTCP7n4LCowizW3mFQvTOQtE4qOH9tx', + md5='65f09629c0ac2aa21df6f179c9875bd0', + extract=True + ) + + download_data( + pkg_name=PKG, + path='autowalk/eng2_73B2_to_7FElevator.walk.tar.gz', + url='https://drive.google.com/uc?id=1O8o6voq2v8WenfaUYcmpSU-IwJSsXW5_', + md5='b75ff21caf475a044e7ea197ffe9e378', + extract=True + ) + + download_data( + pkg_name=PKG, + path='autowalk/eng2_7FElevator_to_2FElevator.walk.tar.gz', + url='https://drive.google.com/uc?id=12MOg5okckmQlYiM6flkdeMYxqjn9-C9l', + md5='67ae3210cbfb55791fff6494f84abb3b', + extract=True + ) + + download_data( + pkg_name=PKG, + path='autowalk/eng2_elevator_7FElevator_to_2FElevator.walk.tar.gz', + url='https://drive.google.com/uc?id=1iyx0y1dPu4HUPMNepR_VaZd_WEaC5Lku', + md5='915916d084abd54c2c17f0738a726da3', + extract=True + ) + + download_data( + pkg_name=PKG, + path='autowalk/eng2_2FElevator_to_2FEntrance.walk.tar.gz', + url='https://drive.google.com/uc?id=1cYUn_qnRslWuH0ZMEBN6ovqmdcUB0MzY', + md5='78c6e1e8e5967b216c9f53e38893750e', + extract=True + ) + + download_data( + pkg_name=PKG, + path='autowalk/eng2_2FElevator_to_MainGate.walk.tar.gz', + url='https://drive.google.com/uc?id=1CIuStpjxIA188MLxUsfUjI-47Ev36Wiv', + md5='47d669bcb1394b97c95e5d77f78da3e5', + extract=True + ) + + download_data( + pkg_name=PKG, + path='autowalk/eng2_2FElevator_to_eng8_2FElevator.walk.tar.gz', + url='https://drive.google.com/uc?id=1oyU1ufqy9gryPXw8YZ45Ff7AR8K825dT', + md5='ac9a67567104df2ddd78b8c53fa61ba2', + extract=True + ) + + download_data( + pkg_name=PKG, + path='autowalk/eng_TelephoneBox_to_HongoMainGate.walk.tar.gz', + url='https://drive.google.com/uc?id=120WC6SE4C_9XIvy0j3HEraljIigjcJ5U', + md5='6f89cd74bea3934171e0ae720747da24', + extract=True + ) + + download_data( + pkg_name=PKG, + path='autowalk/eng2_3FElevator_to_Mech_Office.walk.tar.gz', + url='https://drive.google.com/uc?id=10pWP1qnbr5TCfQVUeOHnDacdwQ3X3Awh', + md5='52564afd6471c9551c8f77ac031a366d', + extract=True + ) + + download_data( + pkg_name=PKG, + path='autowalk/eng2_elevator_3FElevator_to_2FElevator.walk.tar.gz', + url='https://drive.google.com/uc?id=1OFB38ISYjTtu9A87QtfYyVLhtcGnpu2a', + md5='e8ed341b22c5eb66373d29b580bede05', + extract=True + ) + + download_data( + pkg_name=PKG, + path='autowalk/eng2_2FElevator_to_MainEntrance.walk.tar.gz', + url='https://drive.google.com/uc?id=1e5hCLtfOlDYWhrQoefYGxz2AnjaSfs_f', + md5='e79f25769b702c203d4f9229c81a1f89', + extract=True + ) + + download_data( + pkg_name=PKG, + path='autowalk/eng2_MainEntrance_to_HongoMainGate.walk.tar.gz', + url='https://drive.google.com/uc?id=13mG7iFMkFWN04S7BeR2rPwvpL-0cTgko', + md5='e0260203d5a3fa4958014b4149c3749a', + extract=True + ) + + download_data( + pkg_name=PKG, + path='autowalk/eng2_MainEntrance_to_HongoMainGate_Daylight.walk.tar.gz', + url='https://drive.google.com/uc?id=1EHpKaMBa8rnRZRyK8KwTK3727cxNqph4', + md5='43cff60ed9dc444d3d7de0e4ef304d0f', + extract=True + ) + + download_data( + pkg_name=PKG, + path='autowalk/73b2_inside.walk.tar.gz', + url='https://drive.google.com/uc?id=14BPDOJBNS2v5EoUyIZ9gDwm4VFXyj-FD', + md5='5b45fcd7271f42f32414602145b77bd8', + extract=True + ) + + download_data( + pkg_name=PKG, + path='autowalk/eng2_1FElevator_to_1FGarbageCollectionAreaSouth.walk.tar.gz', + url='https://drive.google.com/uc?id=1eZbBLwzl9nVpSAebtux5nbN1B4UfGEoh', + md5='01ef288775d6c21a634a17fa427e4900', + extract=True + ) + + download_data( + pkg_name=PKG, + path='autowalk/eng2_1FElevator_to_1FGarbageCollectionAreaEast.walk.tar.gz', + url='https://drive.google.com/uc?id=1_zySu-e94wzHLaLYTbQuUd9A0Sb8x9NQ', + md5='fc9fb3161b745ff5f7b62be8977f7e56', + extract=True + ) + + download_data( + pkg_name=PKG, + path='autowalk/eng2_elevator_7FElevator_to_1FElevator.walk.tar.gz', + url='https://drive.google.com/uc?id=1kLSNCn2lhkcdOFBaYSVHUFzklhVz_jL8', + md5='fa5a26677d9136ef4671d7a32ca59097', + extract=True + ) + + download_data( + pkg_name=PKG, + path='autowalk/eng2_1FElevator_to_1FGarbageCollectionAreaSouth_via213.walk.tar.gz', + url='https://drive.google.com/uc?id=1oSFjkwG0EcVXapMVgLlc_i1VtcWXwEwE', + md5='72ba498fe56700bee31c8ce166e77008', + extract=True + ) + + download_data( + pkg_name=PKG, + path='autowalk/eng2_2FElevator_to_subway.walk.tar.gz', + url='https://drive.google.com/uc?id=1uRZA0JnFyenVcZDtXhQGzqGAIcO9lwop', + md5='ef40ee8677ef68b4beee4e516a11d1f1', + extract=True + ) + + download_data( + pkg_name=PKG, + path='autowalk/eng2_subway_tablenavigation.walk.tar.gz', + url='https://drive.google.com/uc?id=1Lye5ZfFLgmU-G8TdevSiHG72kN04XnFt', + md5='e65a06fbb7b2037659bf657ebb4e5d36', + extract=True + ) + + download_data( + pkg_name=PKG, + path='autowalk/eng2_subway_tablenavigation_02.walk.tar.gz', + url='https://drive.google.com/uc?id=1GgIra90yX21IMYYL8VgrY9YJpGevA1ek', + md5='08eca6ac0e1310236f32f229370aa161', + extract=True + ) + + download_data( + pkg_name=PKG, + path='autowalk/eng2_1FElevator_to_GarbageCollectionArea_inside_clockwise.walk.tar.gz', + url='https://drive.google.com/uc?id=1Ur6pZZKxZQsP_3zsbikWFkOWOB1KsKlb', + md5='da542fc9b4376378dc2a644454175e06', + extract=True + ) + + download_data( + pkg_name=PKG, + path='autowalk/eng2_GarbageCollectionArea_inside_clockwise.walk.tar.gz', + url='https://drive.google.com/uc?id=1ipkw696vWe_DeO87ZcoQmTgFUE7adpL7', + md5='71d5555f4f6772a9022768fe5dabf813', + extract=True + ) + + download_data( + pkg_name=PKG, + path='autowalk/eng2_GarbageCollectionArea_inside_counterclockwise.walk.tar.gz', + url='https://drive.google.com/uc?id=1pIrpc_q9GVCtH4YO2-E2rXSEf1yTfP4U', + md5='d0f279bc94b194600bccf3a1c5d98c35', + extract=True + ) + + download_data( + pkg_name=PKG, + path='autowalk/eng2_GarbageCollectionArea_to_1FElevator.walk.tar.gz', + url='https://drive.google.com/uc?id=1rUCVbhbSmUd4OEA6W35quKs-6CJIJW7b', + md5='2fdfc99d640bc9c796c36e3127078fcf', + extract=True + ) + + download_data( + pkg_name=PKG, + path='autowalk/eng2_73b2kitchen.walk.tar.gz', + url='https://drive.google.com/uc?id=1JrDRIQpGpB6sqofIyk9eR03KsE2EJ5Ev', + md5='8666d1c8b21ae7ffb88978ed0b1bfb33', + extract=True + ) + + download_data( + pkg_name=PKG, + path='autowalk/eng2_73b2trashcans.walk.tar.gz', + url='https://drive.google.com/uc?id=1w19wmLsQHMM9ErjK4mN-zstWkvrHITLO', + md5='264c2e561a0c128a26b490059e10e30c', + extract=True + ) + + download_data( + pkg_name=PKG, + path='autowalk/eng2_MechOffice_inside.walk.tar.gz', + url='https://drive.google.com/uc?id=1dSbU0tADrxwGLEBywMt312NXUgFHtsCy', + md5='5460bc55c34824ce75722881a4e0b77d', + extract=True + ) + + download_data( + pkg_name=PKG, + path='autowalk/eng2_MechOffice_to_MainEntrance.walk.tar.gz', + url='https://drive.google.com/uc?id=1aepZnkxV8SLo9a8DdCEU9KnNoyjG7sZF', + md5='4aba2398e8eae87509820180b2e89cb0', + extract=True + ) + + download_data( + pkg_name=PKG, + path='autowalk/eng2_MechOffice_to_3felevator_subwayside.walk.tar.gz', + url='https://drive.google.com/uc?id=1twc6Ui5z7nqqZHMHPeynNAyE19PKyihN', + md5='6d720c0c6f5692a205b386c9248d69f6', + extract=True + ) + + download_data( + pkg_name=PKG, + path='autowalk/eng2_1fentrance_to_1FElevator.walk.tar.gz', + url='https://drive.google.com/uc?id=1Q7gX8Cg58dUtV0he7R8fxYciinYnAmd9', + md5='b82831bd317014367af5f28614368bd9', + extract=True + ) + + download_data( + pkg_name=PKG, + path='autowalk/eng2_2fentrance_to_1fentrance.walk.tar.gz', + url='https://drive.google.com/uc?id=1eKdUYOyWEO2lz07uUlpayb6StFC_hCco', + md5='0b7c8e960825014a3d3b4637265e966c', + extract=True + ) + + download_data( + pkg_name=PKG, + path='autowalk/eng2_73B2_to_83A2.walk.tar.gz', + url='https://drive.google.com/uc?id=1GwOD53C7I8dpi3YcrrMcHlHbN3k_Auj1', + md5='aa063402ab604b012ae7c7a4123fb5ac', + extract=True + ) + + download_data( + pkg_name=PKG, + path='autowalk/eng2_7FElevator_to_73B2_trash.walk.tar.gz', + url='https://drive.google.com/uc?id=1gmkQa5oTSIJAKST8SBLev_nui_XN40dE', + md5='d8b0711270fb2ae67159d28d50bb5231', + extract=True + ) + + download_data( + pkg_name=PKG, + path='autowalk/eng2_7f_around.walk.tar.gz', + url='https://drive.google.com/uc?id=1lKELjOiUU02RnrK2V7AWos8GkJqylEKu', + md5='daae9c4c7555780b148baad7906b6710', + extract=True + ) + + download_data( + pkg_name=PKG, + path='autowalk/eng2_outside.walk.tar.gz', + url='https://drive.google.com/uc?id=1E9QJJYz7Puk72KL2lFRTKfPtRiCHZYYw', + md5='f44de8d70da1bd1a512b5e1002335c81', + extract=True + ) + + download_data( + pkg_name=PKG, + path='autowalk/eng2_8F.walk.tar.gz', + url='https://drive.google.com/uc?id=1LPluVprB4FCZ8L7ePpeQM8qFzijHQ3Ic', + md5='1032a79f9ac5201caddcefcb867637a5', + extract=True + ) + +if __name__ == '__main__': + main() diff --git a/jsk_spot_robot/spot_autowalk_data/scripts/view_map.py b/jsk_spot_robot/spot_autowalk_data/scripts/view_map.py new file mode 100644 index 0000000000..16891ea280 --- /dev/null +++ b/jsk_spot_robot/spot_autowalk_data/scripts/view_map.py @@ -0,0 +1,405 @@ +#!/usr/bin/env python3 +# Copyright (c) 2021 Boston Dynamics, Inc. All rights reserved. +# +# Downloading, reproducing, distributing or otherwise using the SDK Software +# is subject to the terms and conditions of the Boston Dynamics Software +# Development Kit License (20191101-BDSDK-SL). + +import argparse + +from vtk.util import numpy_support +import google.protobuf.timestamp_pb2 +import math +import numpy as np +import numpy.linalg +import os +import sys +import time +import vtk + +from bosdyn.api.graph_nav import map_pb2 +from bosdyn.api import geometry_pb2 +from bosdyn.client.frame_helpers import * +from bosdyn.client.math_helpers import * +""" +This example shows how to load and view a graph nav map. + +""" + + +def numpy_to_poly_data(pts): + """ + Converts numpy array data into vtk poly data. + :param pts: the numpy array to convert (3 x N). + :return: a vtkPolyData. + """ + pd = vtk.vtkPolyData() + pd.SetPoints(vtk.vtkPoints()) + # Makes a deep copy + pd.GetPoints().SetData(numpy_support.numpy_to_vtk(pts.copy())) + + f = vtk.vtkVertexGlyphFilter() + f.SetInputData(pd) + f.Update() + pd = f.GetOutput() + + return pd + + +def mat_to_vtk(mat): + """ + Converts a 4x4 homogenous transform into a vtk transform object. + :param mat: A 4x4 homogenous transform (numpy array). + :return: A VTK transform object representing the transform. + """ + t = vtk.vtkTransform() + t.SetMatrix(mat.flatten()) + return t + + +def vtk_to_mat(transform): + """ + Converts a VTK transform object to 4x4 homogenous numpy matrix. + :param transform: an object of type vtkTransform + : return: a numpy array with a 4x4 matrix representation of the transform. + """ + tf_matrix = transform.GetMatrix() + out = np.array(np.eye(4)) + for r in range(4): + for c in range(4): + out[r, c] = tf_matrix.GetElement(r, c) + return out + + +def api_to_vtk_se3_pose(se3_pose): + """ + Convert a bosdyn SDK SE3Pose into a VTK pose. + :param se3_pose: the bosdyn SDK SE3 Pose. + :return: A VTK pose representing the bosdyn SDK SE3 Pose. + """ + return mat_to_vtk(se3_pose.to_matrix()) + + +def create_fiducial_object(world_object, waypoint, renderer): + """ + Creates a VTK object representing a fiducial. + :param world_object: A WorldObject representing a fiducial. + :param waypoint: The waypoint the AprilTag is associated with. + :param renderer: The VTK renderer + :return: a tuple of (vtkActor, 4x4 homogenous transform) representing the vtk actor for the fiducial, and its + transform w.r.t the waypoint. + """ + fiducial_object = world_object.apriltag_properties + odom_tform_fiducial_filtered = get_a_tform_b( + world_object.transforms_snapshot, ODOM_FRAME_NAME, + world_object.apriltag_properties.frame_name_fiducial_filtered) + waypoint_tform_odom = SE3Pose.from_obj(waypoint.waypoint_tform_ko) + waypoint_tform_fiducial_filtered = api_to_vtk_se3_pose( + waypoint_tform_odom * odom_tform_fiducial_filtered) + plane_source = vtk.vtkPlaneSource() + plane_source.SetCenter(0.0, 0.0, 0.0) + plane_source.SetNormal(0.0, 0.0, 1.0) + plane_source.Update() + plane = plane_source.GetOutput() + mapper = vtk.vtkPolyDataMapper() + mapper.SetInputData(plane) + + actor = vtk.vtkActor() + actor.SetMapper(mapper) + actor.GetProperty().SetColor(0.5, 0.7, 0.9) + actor.SetScale(fiducial_object.dimensions.x, fiducial_object.dimensions.y, 1.0) + renderer.AddActor(actor) + return actor, waypoint_tform_fiducial_filtered + + +def create_point_cloud_object(waypoints, snapshots, waypoint_id): + """ + Create a VTK object representing the point cloud in a snapshot. Note that in graph_nav, "point cloud" refers to the + feature cloud of a waypoint -- that is, a collection of visual features observed by all five cameras at a particular + point in time. The visual features are associated with points that are rigidly attached to a waypoint. + :param waypoints: dict of waypoint ID to waypoint. + :param snapshots: dict of waypoint snapshot ID to waypoint snapshot. + :param waypoint_id: the waypoint ID of the waypoint whose point cloud we want to render. + :return: a vtkActor containing the point cloud data. + """ + wp = waypoints[waypoint_id] + snapshot = snapshots[wp.snapshot_id] + cloud = snapshot.point_cloud + odom_tform_cloud = get_a_tform_b(cloud.source.transforms_snapshot, ODOM_FRAME_NAME, + cloud.source.frame_name_sensor) + waypoint_tform_odom = SE3Pose.from_obj(wp.waypoint_tform_ko) + waypoint_tform_cloud = api_to_vtk_se3_pose(waypoint_tform_odom * odom_tform_cloud) + + point_cloud_data = np.frombuffer(cloud.data, dtype=np.float32).reshape(int(cloud.num_points), 3) + poly_data = numpy_to_poly_data(point_cloud_data) + arr = vtk.vtkFloatArray() + for i in range(cloud.num_points): + arr.InsertNextValue(point_cloud_data[i, 2]) + arr.SetName("z_coord") + poly_data.GetPointData().AddArray(arr) + poly_data.GetPointData().SetActiveScalars("z_coord") + actor = vtk.vtkActor() + mapper = vtk.vtkPolyDataMapper() + mapper.SetInputData(poly_data) + mapper.ScalarVisibilityOn() + actor.SetMapper(mapper) + actor.GetProperty().SetPointSize(2) + actor.SetUserTransform(waypoint_tform_cloud) + return actor + + +def create_waypoint_object(renderer, waypoints, snapshots, waypoint_id): + """ + Creates a VTK object representing a waypoint and its point cloud. + :param renderer: The VTK renderer. + :param waypoints: dict of waypoint ID to waypoint. + :param snapshots: dict of snapshot ID to snapshot. + :param waypoint_id: the waypoint id of the waypoint object we wish to create. + :return: A vtkAssembly representing the waypoint (an axis) and its point cloud. + """ + assembly = vtk.vtkAssembly() + actor = vtk.vtkAxesActor() + actor.SetXAxisLabelText("") + actor.SetYAxisLabelText("") + actor.SetZAxisLabelText("") + actor.SetTotalLength(0.2, 0.2, 0.2) + point_cloud_actor = create_point_cloud_object(waypoints, snapshots, waypoint_id) + assembly.AddPart(actor) + assembly.AddPart(point_cloud_actor) + renderer.AddActor(assembly) + return assembly + + +def make_line(pt_A, pt_B, renderer): + """ + Creates a VTK object which is a white line between two points. + :param pt_A: starting point of the line. + :param pt_B: ending point of the line. + :param renderer: the VTK renderer. + :return: A VTK object that is a while line between pt_A and pt_B. + """ + line_source = vtk.vtkLineSource() + line_source.SetPoint1(pt_A[0], pt_A[1], pt_A[2]) + line_source.SetPoint2(pt_B[0], pt_B[1], pt_B[2]) + mapper = vtk.vtkPolyDataMapper() + mapper.SetInputConnection(line_source.GetOutputPort()) + + actor = vtk.vtkActor() + actor.SetMapper(mapper) + actor.GetProperty().SetLineWidth(2) + actor.GetProperty().SetColor(0.7, 0.7, 0.7) + renderer.AddActor(actor) + return actor + + +def make_text(name, pt, renderer): + """ + Creates white text on a black background at a particular point. + :param name: The text to display. + :param pt: The point in the world where the text will be displayed. + :param renderer: The VTK renderer + :return: the vtkActor representing the text. + """ + actor = vtk.vtkTextActor() + actor.SetInput(name) + prop = actor.GetTextProperty() + prop.SetBackgroundColor(0.0, 0.0, 0.0) + prop.SetBackgroundOpacity(0.5) + prop.SetFontSize(16) + coord = actor.GetPositionCoordinate() + coord.SetCoordinateSystemToWorld() + coord.SetValue((pt[0], pt[1], pt[2])) + + renderer.AddActor(actor) + return actor + + +def create_edge_object(curr_wp_tform_to_wp, world_tform_curr_wp, renderer): + # Concatenate the edge transform. + world_tform_to_wp = np.dot(world_tform_curr_wp, curr_wp_tform_to_wp) + # Make a line between the current waypoint and the neighbor. + make_line(world_tform_curr_wp[:3, 3], world_tform_to_wp[:3, 3], renderer) + return world_tform_to_wp + + +def load_map(path): + """ + Load a map from the given file path. + :param path: Path to the root directory of the map. + :return: the graph, waypoints, waypoint snapshots and edge snapshots. + """ + with open(os.path.join(path, "graph"), "rb") as graph_file: + # Load the graph file and deserialize it. The graph file is a protobuf containing only the waypoints and the + # edges between them. + data = graph_file.read() + current_graph = map_pb2.Graph() + current_graph.ParseFromString(data) + + # Set up maps from waypoint ID to waypoints, edges, snapshots, etc. + current_waypoints = {} + current_waypoint_snapshots = {} + current_edge_snapshots = {} + + # For each waypoint, load any snapshot associated with it. + for waypoint in current_graph.waypoints: + current_waypoints[waypoint.id] = waypoint + + # Load the snapshot. Note that snapshots contain all of the raw data in a waypoint and may be large. + file_name = os.path.join(path, "waypoint_snapshots", waypoint.snapshot_id) + if not os.path.exists(file_name): + continue + with open(file_name, "rb") as snapshot_file: + waypoint_snapshot = map_pb2.WaypointSnapshot() + waypoint_snapshot.ParseFromString(snapshot_file.read()) + current_waypoint_snapshots[waypoint_snapshot.id] = waypoint_snapshot + # Similarly, edges have snapshot data. + for edge in current_graph.edges: + + if not edge.snapshot_id: + continue # Rare case that snapshot id is empty + + file_name = os.path.join(path, "edge_snapshots", edge.snapshot_id) + if not os.path.exists(file_name): + continue + with open(file_name, "rb") as snapshot_file: + edge_snapshot = map_pb2.EdgeSnapshot() + edge_snapshot.ParseFromString(snapshot_file.read()) + current_edge_snapshots[edge_snapshot.id] = edge_snapshot + print("Loaded graph with {} waypoints and {} edges".format( + len(current_graph.waypoints), len(current_graph.edges))) + return (current_graph, current_waypoints, current_waypoint_snapshots, + current_edge_snapshots) + + +def create_graph_objects(current_graph, current_waypoint_snapshots, current_waypoints, renderer, draw_id=True): + """ + Creates all the VTK objects associated with the graph. + :param current_graph: the graph to use. + :param current_waypoint_snapshots: dict from snapshot id to snapshot. + :param current_waypoints: dict from waypoint id to waypoint. + :param renderer: The VTK renderer + :return: the average position in world space of all the waypoints. + """ + waypoint_objects = {} + # Create VTK objects associated with each waypoint. + for waypoint in current_graph.waypoints: + waypoint_objects[waypoint.id] = create_waypoint_object(renderer, current_waypoints, + current_waypoint_snapshots, + waypoint.id) + # Now, perform a breadth first search of the graph starting from an arbitrary waypoint. Graph nav graphs + # have no global reference frame. The only thing we can say about waypoints is that they have relative + # transformations to their neighbors via edges. So the goal is to get the whole graph into a global reference + # frame centered on some waypoint as the origin. + queue = [] + queue.append((current_graph.waypoints[0], np.eye(4))) + visited = {} + # Get the camera in the ballpark of the right position by centering it on the average position of a waypoint. + avg_pos = np.array([0.0, 0.0, 0.0]) + + # Breadth first search. + while len(queue) > 0: + # Visit a waypoint. + curr_element = queue[0] + queue.pop(0) + curr_waypoint = curr_element[0] + visited[curr_waypoint.id] = True + + # We now know the global pose of this waypoint, so set the pose. + waypoint_objects[curr_waypoint.id].SetUserTransform(mat_to_vtk(curr_element[1])) + world_tform_current_waypoint = curr_element[1] + # Add text to the waypoint. + print('{}: {}'.format(curr_waypoint.annotations.name, curr_waypoint.id)) + if draw_id: + make_text( + '{}: {}'.format(curr_waypoint.annotations.name, curr_waypoint.id), + world_tform_current_waypoint[:3, 3], + renderer) + + # For each fiducial in the waypoint's snapshot, add an object at the world pose of that fiducial. + if (curr_waypoint.snapshot_id in current_waypoint_snapshots): + snapshot = current_waypoint_snapshots[curr_waypoint.snapshot_id] + for fiducial in snapshot.objects: + if fiducial.HasField("apriltag_properties"): + (fiducial_object, + curr_wp_tform_fiducial) = create_fiducial_object(fiducial, curr_waypoint, + renderer) + world_tform_fiducial = np.dot(world_tform_current_waypoint, + vtk_to_mat(curr_wp_tform_fiducial)) + fiducial_object.SetUserTransform(mat_to_vtk(world_tform_fiducial)) + make_text( + 'fid: {}'.format(fiducial.apriltag_properties.tag_id), world_tform_fiducial[:3, 3], + renderer) + + # Now, for each edge, walk along the edge and concatenate the transform to the neighbor. + for edge in current_graph.edges: + # If the edge is directed away from us... + if edge.id.from_waypoint == curr_waypoint.id and edge.id.to_waypoint not in visited: + current_waypoint_tform_to_waypoint = SE3Pose.from_obj( + edge.from_tform_to).to_matrix() + world_tform_to_wp = create_edge_object(current_waypoint_tform_to_waypoint, + world_tform_current_waypoint, renderer) + # Add the neighbor to the queue. + queue.append((current_waypoints[edge.id.to_waypoint], world_tform_to_wp)) + avg_pos += world_tform_to_wp[:3, 3] + # If the edge is directed toward us... + elif edge.id.to_waypoint == curr_waypoint.id and edge.id.from_waypoint not in visited: + current_waypoint_tform_from_waypoint = (SE3Pose.from_obj( + edge.from_tform_to).inverse()).to_matrix() + world_tform_from_wp = create_edge_object(current_waypoint_tform_from_waypoint, + world_tform_current_waypoint, renderer) + # Add the neighbor to the queue. + queue.append((current_waypoints[edge.id.from_waypoint], world_tform_from_wp)) + avg_pos += world_tform_from_wp[:3, 3] + return avg_pos + + +def main( path, draw_id ): + # Load the map from the given file. + (current_graph, current_waypoints, current_waypoint_snapshots, + current_edge_snapshots) = load_map(path) + + # Create the renderer. + renderer = vtk.vtkRenderer() + renderer.SetBackground(0.05, 0.1, 0.15) + + avg_pos = create_graph_objects(current_graph, current_waypoint_snapshots, current_waypoints, + renderer, draw_id) + + # Compute the average waypoint position to place the camera appropriately. + avg_pos /= len(current_waypoints) + camera_pos = avg_pos + np.array([-1, 0, 5]) + + camera = renderer.GetActiveCamera() + camera.SetViewUp(0, 0, 1) + camera.SetPosition(camera_pos[0], camera_pos[1], camera_pos[2]) + + # Create the VTK renderer and interactor. + renderWindow = vtk.vtkRenderWindow() + renderWindow.SetWindowName(path) + renderWindow.AddRenderer(renderer) + renderWindowInteractor = vtk.vtkRenderWindowInteractor() + renderWindowInteractor.SetRenderWindow(renderWindow) + renderWindow.SetSize(1280, 720) + style = vtk.vtkInteractorStyleTerrain() + renderWindowInteractor.SetInteractorStyle(style) + renderer.ResetCamera() + + # Start rendering. + renderWindow.Render() + renderWindow.Start() + renderWindowInteractor.Start() + + +if __name__ == '__main__': + + parser = argparse.ArgumentParser() + + parser.add_argument('map_directory') + parser.add_argument('--draw-id', help='Draw ID of each waypoints', action='store_true') + + args = parser.parse_args() + + if args.draw_id: + main( args.map_directory, True ) + else: + main( args.map_directory, False ) diff --git a/jsk_spot_robot/spot_ros_client/.vscode/c_cpp_properties.json b/jsk_spot_robot/spot_ros_client/.vscode/c_cpp_properties.json new file mode 100644 index 0000000000..2efcb04f38 --- /dev/null +++ b/jsk_spot_robot/spot_ros_client/.vscode/c_cpp_properties.json @@ -0,0 +1,30 @@ +{ + "configurations": [ + { + "browse": { + "databaseFilename": "${default}", + "limitSymbolsToIncludedHeaders": false + }, + "includePath": [ + "/home/sktometometo/ros/ws_jsk_spot/devel/include/**", + "/opt/ros/noetic/include/**", + "/home/sktometometo/ros/ws_jsk_spot/src/jsk-ros-pkg/jsk_robot/jsk_robot_common/jsk_robot_startup/include/**", + "/opt/ros/noetic/share/julius/include/**", + "/home/sktometometo/ros/ws_jsk_spot/src/jsk-ros-pkg/jsk_robot/jsk_pr2_robot/pr2_base_trajectory_action/include/**", + "/home/sktometometo/ros/ws_jsk_spot/src/jsk-ros-pkg/jsk_robot/jsk_robot_common/speak_and_wait_recovery/include/**", + "/home/sktometometo/ros/ws_jsk_spot/src/ublox/ublox_gps/include/**", + "/home/sktometometo/ros/ws_jsk_spot/src/ublox/ublox_msg_filters/include/**", + "/home/sktometometo/ros/ws_jsk_spot/src/ublox/ublox_msgs/include/**", + "/home/sktometometo/ros/ws_jsk_spot/src/ublox/ublox_serialization/include/**", + "/home/sktometometo/ros/ws_jsk_spot/src/jsk-ros-pkg/jsk_robot/jsk_robot_common/update_move_base_parameter_recovery/include/**", + "/usr/include/**" + ], + "name": "ROS", + "intelliSenseMode": "gcc-x64", + "compilerPath": "/usr/bin/gcc", + "cStandard": "gnu11", + "cppStandard": "c++14" + } + ], + "version": 4 +} \ No newline at end of file diff --git a/jsk_spot_robot/spot_ros_client/.vscode/settings.json b/jsk_spot_robot/spot_ros_client/.vscode/settings.json new file mode 100644 index 0000000000..97568205e8 --- /dev/null +++ b/jsk_spot_robot/spot_ros_client/.vscode/settings.json @@ -0,0 +1,10 @@ +{ + "python.autoComplete.extraPaths": [ + "/home/sktometometo/ros/ws_jsk_spot/devel/lib/python3/dist-packages", + "/opt/ros/noetic/lib/python3/dist-packages" + ], + "python.analysis.extraPaths": [ + "/home/sktometometo/ros/ws_jsk_spot/devel/lib/python3/dist-packages", + "/opt/ros/noetic/lib/python3/dist-packages" + ] +} diff --git a/jsk_spot_robot/spot_ros_client/CMakeLists.txt b/jsk_spot_robot/spot_ros_client/CMakeLists.txt new file mode 100644 index 0000000000..4c7961bfb2 --- /dev/null +++ b/jsk_spot_robot/spot_ros_client/CMakeLists.txt @@ -0,0 +1,12 @@ +cmake_minimum_required(VERSION 2.8.3) +project(spot_ros_client) + +find_package(catkin REQUIRED) + +catkin_python_setup() + +catkin_package() + +catkin_install_python(PROGRAMS scripts/demo.py + DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION} +) diff --git a/jsk_spot_robot/spot_ros_client/README.md b/jsk_spot_robot/spot_ros_client/README.md new file mode 100644 index 0000000000..f6918bd87c --- /dev/null +++ b/jsk_spot_robot/spot_ros_client/README.md @@ -0,0 +1,9 @@ +# spot_ros_client + +This package provide a python client library for spot_ros. + +## Demo + +```python +rosrun spot_ros_client demo.py +``` diff --git a/jsk_spot_robot/spot_ros_client/package.xml b/jsk_spot_robot/spot_ros_client/package.xml new file mode 100644 index 0000000000..a0e71c116f --- /dev/null +++ b/jsk_spot_robot/spot_ros_client/package.xml @@ -0,0 +1,31 @@ + + + spot_ros_client + 1.1.0 + The spot_ros_client package + + Koki Shinjo + + Koki Shinjo + + BSD + + catkin + + rospy + + actionlib + spot_msgs + std_srvs + geometry_msgs + jsk_spot_behavior_msgs + + actionlib + spot_msgs + std_srvs + geometry_msgs + jsk_spot_behavior_msgs + + + + diff --git a/jsk_spot_robot/spot_ros_client/scripts/demo.py b/jsk_spot_robot/spot_ros_client/scripts/demo.py new file mode 100755 index 0000000000..955588af34 --- /dev/null +++ b/jsk_spot_robot/spot_ros_client/scripts/demo.py @@ -0,0 +1,99 @@ +#!/usr/bin/env python + +import rospy +import math +from spot_ros_client.libspotros import SpotRosClient +from geometry_msgs.msg import Quaternion # for body pose + +rospy.init_node('spot_ros_client_demo') +client = SpotRosClient() + +# claim +rospy.loginfo('claim') +client.claim() +rospy.sleep(2) + +# power on +rospy.loginfo('power on') +client.power_on() +rospy.sleep(2) + +# stand +rospy.loginfo('stand') +client.stand() +rospy.sleep(2) + +# sit +rospy.loginfo('sit') +client.sit() +rospy.sleep(2) + +# stand +rospy.loginfo('stand') +client.stand() +rospy.sleep(2) + +# send velocity commands +rospy.loginfo('sending cmd_vel') +rate = rospy.Rate(10) +start_time = rospy.Time.now() +while not rospy.is_shutdown() and rospy.Time.now() < start_time + rospy.Duration(10): + rate.sleep() + client.pub_cmd_vel(0, 0, 0.5) + +# change body pose +quaternion_rotated = Quaternion(x=0,y=0,z=math.sin(0.2),w=math.cos(0.2)) +quaternion_equal = Quaternion(x=0,y=0,z=0,w=1) + +rospy.loginfo('rotating body pose') +client.pub_body_pose(0,quaternion_rotated) +client.stand() +rospy.sleep(5) + +client.pub_body_pose(0,quaternion_equal) +client.stand() +rospy.sleep(5) + +rospy.loginfo('changing body height') +client.pub_body_pose(0.2,quaternion_equal) +client.stand() +rospy.sleep(5) + +client.pub_body_pose(0,quaternion_equal) +client.stand() +rospy.sleep(5) + +# switch stair mode +rospy.loginfo('switching stair mode') +client.stair_mode(True) +rospy.sleep(5) +client.stair_mode(False) +rospy.sleep(5) + +# send a trajectory command +rospy.loginfo('sending trajectory command') +client.trajectory(1.0,1.0,1.57,5,blocking=True) +rospy.sleep(5) + +# use graphnav navigation +## assuming spot is at initial place of example.walk +rospy.loginfo('sending navigate to command') +import rospkg +upload_filepath = rospkg.RosPack().get_path('spot_autowalk_data')+'/autowalk/eng2_73b2_to_81c1_night.walk' +rospy.loginfo('upload map') +client.upload_graph(upload_filepath) +rospy.sleep(1) +rospy.loginfo('localization') +client.set_localization_fiducial() +rospy.sleep(1) +rospy.loginfo('list graph') +waypoint_ids = client.list_graph() +rospy.sleep(1) +rospy.loginfo('send command') +client.navigate_to(waypoint_ids[-1],blocking=False) +rospy.sleep(1) +rospy.loginfo('wait for result') +client.wait_navigate_to_result(rospy.Duration(30)) +rospy.sleep(1) +rospy.loginfo('get a result') +result = client.get_navigate_to_result() diff --git a/jsk_spot_robot/spot_ros_client/setup.py b/jsk_spot_robot/spot_ros_client/setup.py new file mode 100644 index 0000000000..fa9d4a812b --- /dev/null +++ b/jsk_spot_robot/spot_ros_client/setup.py @@ -0,0 +1,9 @@ +from setuptools import setup +from catkin_pkg.python_setup import generate_distutils_setup + +d = generate_distutils_setup( + packages=['spot_ros_client'], + package_dir={'': 'src'} + ) + +setup(**d) diff --git a/jsk_spot_robot/spot_ros_client/src/spot_ros_client/__init__.py b/jsk_spot_robot/spot_ros_client/src/spot_ros_client/__init__.py new file mode 100644 index 0000000000..a45c5f4930 --- /dev/null +++ b/jsk_spot_robot/spot_ros_client/src/spot_ros_client/__init__.py @@ -0,0 +1 @@ +import spot_ros_client.libspotros as libspotros diff --git a/jsk_spot_robot/spot_ros_client/src/spot_ros_client/libspotros.py b/jsk_spot_robot/spot_ros_client/src/spot_ros_client/libspotros.py new file mode 100644 index 0000000000..86279a1661 --- /dev/null +++ b/jsk_spot_robot/spot_ros_client/src/spot_ros_client/libspotros.py @@ -0,0 +1,582 @@ +import PyKDL +import rospy +import actionlib +import tf2_ros +import tf2_geometry_msgs + +import math + +# msg +from std_msgs.msg import Bool +from std_msgs.msg import Float32 +from std_msgs.msg import String +from spot_msgs.msg import Feedback +from spot_msgs.msg import PowerState +from geometry_msgs.msg import Pose +from geometry_msgs.msg import PoseArray +from geometry_msgs.msg import PoseStamped +from geometry_msgs.msg import Quaternion +from geometry_msgs.msg import Twist +# services +from std_srvs.srv import Trigger +from std_srvs.srv import TriggerRequest +from std_srvs.srv import SetBool +from std_srvs.srv import SetBoolRequest +from spot_msgs.srv import ListGraph +from spot_msgs.srv import ListGraphRequest +from spot_msgs.srv import SetLocalizationFiducial +from spot_msgs.srv import SetLocalizationFiducialRequest +from spot_msgs.srv import SetLocalizationWaypoint +from spot_msgs.srv import SetLocalizationWaypointRequest +from spot_msgs.srv import SetLocomotion +from spot_msgs.srv import SetLocomotionRequest +from spot_msgs.srv import UploadGraph +from spot_msgs.srv import UploadGraphRequest +from spot_msgs.srv import Dock +from spot_msgs.srv import DockRequest +from jsk_spot_behavior_msgs.srv import ResetCurrentNode, ResetCurrentNodeRequest +# actions +from jsk_spot_behavior_msgs.msg import NavigationAction +from jsk_spot_behavior_msgs.msg import NavigationGoal +from spot_msgs.msg import NavigateToAction +from spot_msgs.msg import NavigateToGoal +from spot_msgs.msg import TrajectoryAction +from spot_msgs.msg import TrajectoryGoal + + +def calc_distance_to_pose(pose): + if isinstance(pose, Pose): + return math.sqrt(pose.position.x ** 2 + pose.position.y ** 2 + pose.position.z ** 2) + elif isinstance(pose, PoseStamped): + pose = pose.pose + return math.sqrt(pose.position.x ** 2 + pose.position.y ** 2 + pose.position.z ** 2) + else: + raise TypeError( + 'pose must be geometry_msgs.msg.Pose or geometry_msgs.msg.PoseStamped') + + +def convert_msg_point_to_kdl_vector(point): + return PyKDL.Vector(point.x, point.y, point.z) + + +def get_nearest_person_pose(topicname='/spot_recognition_person_tracker/people_pose_array'): + try: + msg = rospy.wait_for_message( + topicname, + PoseArray, + timeout=rospy.Duration(5)) + except rospy.ROSException as e: + rospy.logwarn('Timeout exceede: {}'.format(e)) + return None + + if len(msg.poses) == 0: + rospy.logwarn('No person visible') + return None + + distance = calc_distance_to_pose(msg.poses[0]) + target_pose = msg.poses[0] + for pose in msg.poses: + if calc_distance_to_pose(pose) < distance: + distance = calc_distance_to_pose(pose) + target_pose = pose + + pose_stamped = PoseStamped() + pose_stamped.header = msg.header + pose_stamped.pose = target_pose + + return pose_stamped + + +def get_diff_for_person(pose): + + if isinstance(pose, Pose): + x = pose.position.x + y = pose.position.y + z = pose.position.z + elif isinstance(pose, PoseStamped): + x = pose.pose.position.x + y = pose.pose.position.y + z = pose.pose.position.z + else: + raise TypeError( + 'pose must be geometry_msgs.msg.Pose or geometry_msgs.msg.PoseStamped') + + yaw = math.atan2(y, x) + try: + pitch = math.acos(z / math.sqrt(x**2 + y**2)) + except ValueError: + pitch = 0 + return pitch, yaw + + +class SpotRosClient: + + def __init__(self, + topicname_cmd_vel='/spot/cmd_vel', + topicname_body_pose='/spot/body_pose', + topicname_cable_connected='/spot/status/cable_connected', + topicname_current_node_id='/spot_behavior_manager_server/current_node_id', + topicname_laptop_percentage='/spot/status/laptop_battery_percentage', + topicname_battery_percentage='/spot/status/battery_percentage', + topicname_status_feedback='/spot/status/feedback', + topicname_status_power_state='/spot/status/power_state', + servicename_claim='/spot/claim', + servicename_release='/spot/release', + servicename_stop='/spot/stop', + servicename_self_right='/spot/self_right', + servicename_sit='/spot/sit', + servicename_stand='/spot/stand', + servicename_power_on='/spot/power_on', + servicename_power_off='/spot/power_off', + servicename_estop_hard='/spot/estop/hard', + servicename_estop_gentle='/spot/estop/gentle', + servicename_stair_mode='/spot/stair_mode', + servicename_locomotion_mode='/spot/locomotion_mode', + servicename_upload_graph='/spot/upload_graph', + servicename_list_graph='/spot/list_graph', + servicename_set_localization_fiducial='/spot/set_localization_fiducial', + servicename_set_localization_waypoint='/spot/set_localization_waypoint', + servicename_dock='/spot/dock', + servicename_undock='/spot/undock', + servicename_reset_current_node='/spot_behavior_manager_server/reset_current_node_id', + actionname_navigate_to='/spot/navigate_to', + actionname_trajectory='/spot/trajectory', + actionname_execute_behaviors='/spot_behavior_manager_server/execute_behaviors', + duration_timeout=0.05): + + self.topicname_cable_connected = topicname_cable_connected + self.topicname_current_node_id = topicname_current_node_id + self.topicname_laptop_percentage = topicname_laptop_percentage + self.topicname_battery_percentage = topicname_battery_percentage + self.topicname_status_feedback = topicname_status_feedback + self.topicname_status_power_state = topicname_status_power_state + + # Publishers + self._pub_cmd_vel = rospy.Publisher( + topicname_cmd_vel, + Twist, + queue_size=1 + ) + self._pub_body_pose = rospy.Publisher( + topicname_body_pose, + Pose, + queue_size=1 + ) + rospy.loginfo("Publisher initialization done.") + + # wait for services + rospy.loginfo("Waiting all service available...") + try: + rospy.wait_for_service(servicename_claim, rospy.Duration(5)) + rospy.wait_for_service(servicename_release, rospy.Duration(5)) + rospy.wait_for_service(servicename_stop, rospy.Duration(5)) + rospy.wait_for_service(servicename_self_right, rospy.Duration(5)) + rospy.wait_for_service(servicename_sit, rospy.Duration(5)) + rospy.wait_for_service(servicename_stand, rospy.Duration(5)) + rospy.wait_for_service(servicename_power_on, rospy.Duration(5)) + rospy.wait_for_service(servicename_power_off, rospy.Duration(5)) + rospy.wait_for_service(servicename_estop_hard, rospy.Duration(5)) + rospy.wait_for_service(servicename_estop_gentle, rospy.Duration(5)) + rospy.wait_for_service(servicename_stair_mode, rospy.Duration(5)) + rospy.wait_for_service( + servicename_locomotion_mode, rospy.Duration(5)) + rospy.wait_for_service(servicename_upload_graph, rospy.Duration(5)) + rospy.wait_for_service(servicename_list_graph, rospy.Duration(5)) + rospy.wait_for_service( + servicename_set_localization_fiducial, rospy.Duration(5)) + rospy.wait_for_service( + servicename_set_localization_waypoint, rospy.Duration(5)) + rospy.wait_for_service(servicename_dock, rospy.Duration(5)) + rospy.wait_for_service(servicename_undock, rospy.Duration(5)) + rospy.wait_for_service(servicename_reset_current_node, rospy.Duration(5)) + except rospy.ROSException as e: + rospy.logerr('Service unavaliable: {}'.format(e)) + + # Service Clients + self._srv_client_claim = rospy.ServiceProxy( + servicename_claim, + Trigger + ) + self._srv_client_release = rospy.ServiceProxy( + servicename_release, + Trigger + ) + self._srv_client_stop = rospy.ServiceProxy( + servicename_stop, + Trigger + ) + self._srv_client_self_right = rospy.ServiceProxy( + servicename_self_right, + Trigger + ) + self._srv_client_sit = rospy.ServiceProxy( + servicename_sit, + Trigger + ) + self._srv_client_stand = rospy.ServiceProxy( + servicename_stand, + Trigger + ) + self._srv_client_power_on = rospy.ServiceProxy( + servicename_power_on, + Trigger + ) + self._srv_client_power_off = rospy.ServiceProxy( + servicename_power_off, + Trigger + ) + self._srv_client_estop_hard = rospy.ServiceProxy( + servicename_estop_hard, + Trigger + ) + self._srv_client_estop_gentle = rospy.ServiceProxy( + servicename_estop_gentle, + Trigger + ) + self._srv_client_stair_mode = rospy.ServiceProxy( + servicename_stair_mode, + SetBool + ) + self._srv_client_locomotion_mode = rospy.ServiceProxy( + servicename_locomotion_mode, + SetLocomotion + ) + self._srv_client_upload_graph = rospy.ServiceProxy( + servicename_upload_graph, + UploadGraph + ) + self._srv_client_list_graph = rospy.ServiceProxy( + servicename_list_graph, + ListGraph + ) + self._srv_client_set_localization_fiducial = rospy.ServiceProxy( + servicename_set_localization_fiducial, + SetLocalizationFiducial + ) + self._srv_client_set_localization_waypoint = rospy.ServiceProxy( + servicename_set_localization_waypoint, + SetLocalizationWaypoint + ) + self._srv_client_dock = rospy.ServiceProxy( + servicename_dock, + Dock + ) + self._srv_client_undock = rospy.ServiceProxy( + servicename_undock, + Trigger + ) + self._srv_client_reset_current_node = rospy.ServiceProxy( + servicename_reset_current_node, + ResetCurrentNode + ) + + rospy.loginfo("Service initialization done.") + + # Action Clients + self._actionclient_navigate_to = actionlib.SimpleActionClient( + actionname_navigate_to, + NavigateToAction + ) + self._actionclient_trajectory = actionlib.SimpleActionClient( + actionname_trajectory, + TrajectoryAction + ) + self._actionclient_execute_behaviors = actionlib.SimpleActionClient( + actionname_execute_behaviors, + NavigationAction + ) + + rospy.loginfo("Waiting actions available") + # wait for action + try: + self._actionclient_navigate_to.wait_for_server(rospy.Duration(5)) + self._actionclient_trajectory.wait_for_server(rospy.Duration(5)) + self._actionclient_execute_behaviors.wait_for_server( + rospy.Duration(5)) + except rospy.ROSException as e: + rospy.logerr('Action unavaliable: {}'.format(e)) + + # + self.tf_buffer = tf2_ros.Buffer() + self.tf_listener = tf2_ros.TransformListener(self.tf_buffer) + + rospy.loginfo("Action initialization done.") + + rospy.loginfo("Initialization done") + + def get_laptop_percepntage(self): + try: + msg = rospy.wait_for_message( + self.topicname_laptop_percentage, Float32, timeout=rospy.Duration(5)) + return msg.data + except rospy.ROSException as e: + rospy.logwarn('{}'.format(e)) + return None + + def get_battery_percepntage(self): + try: + msg = rospy.wait_for_message( + self.topicname_battery_percentage, Float32, timeout=rospy.Duration(5)) + return msg.data + except rospy.ROSException as e: + rospy.logwarn('{}'.format(e)) + return None + + def get_robot_pose(self): + try: + frame_odom_to_base = tf2_geometry_msgs.transform_to_kdl( + self.tf_buffer.lookup_transform( + 'odom', + 'base_link', + rospy.Time() + ) + ) + except (tf2_ros.LookupException, tf2_ros.ConnectivityException, tf2_ros.ExtrapolationException) as e: + return None + return frame_odom_to_base + + def go_pose(self, target_frame, timeout = None): + self.trajectory( + target_frame.p[0], + target_frame.p[1], + target_frame.M.GetRPY()[2], + duration=timeout, + blocking=True + ) + + def is_connected(self): + try: + msg = rospy.wait_for_message( + self.topicname_cable_connected, Bool, timeout=rospy.Duration(5)) + return msg.data + except rospy.ROSException as e: + rospy.logwarn('{}'.format(e)) + return None + + def is_sitting(self): + try: + msg = rospy.wait_for_message( + self.topicname_status_feedback, Feedback, timeout=rospy.Duration(5)) + return msg.sitting + except rospy.ROSException as e: + rospy.logwarn('{}'.format(e)) + return None + + def is_standing(self): + try: + msg = rospy.wait_for_message( + self.topicname_status_feedback, Feedback, timeout=rospy.Duration(5)) + return msg.standing + except rospy.ROSException as e: + rospy.logwarn('{}'.format(e)) + return None + + def is_powered_on(self): + try: + msg = rospy.wait_for_message( + self.topicname_status_power_state, PowerState, timeout=rospy.Duration(5)) + if msg.motor_power_state == PowerState.STATE_ON: + return True + else: + return False + except rospy.ROSException as e: + rospy.logwarn('{}'.format(e)) + return None + + def pub_cmd_vel(self, vx, vy, vtheta): + msg = Twist() + msg.linear.x = vx + msg.linear.y = vy + msg.angular.z = vtheta + self._pub_cmd_vel.publish(msg) + + def pub_body_pose(self, height, orientation): + msg = Pose() + msg.position.z = height + msg.orientation = orientation + self._pub_body_pose.publish(msg) + + def claim(self): + res = self._srv_client_claim(TriggerRequest()) + return res.success, res.message + + def release(self): + res = self._srv_client_release(TriggerRequest()) + return res.success, res.message + + def stop(self): + res = self._srv_client_stop(TriggerRequest()) + return res.success, res.message + + def self_right(self): + res = self._srv_client_self_right(TriggerRequest()) + return res.success, res.message + + def sit(self): + res = self._srv_client_sit(TriggerRequest()) + return res.success, res.message + + def stand(self): + res = self._srv_client_stand(TriggerRequest()) + return res.success, res.message + + def power_on(self): + res = self._srv_client_power_on(TriggerRequest()) + return res.success, res.message + + def power_off(self): + res = self._srv_client_power_off(TriggerRequest()) + return res.success, res.message + + def estop_hard(self): + res = self._srv_client_estop_hard(TriggerRequest()) + return res.success, res.message + + def estop_gentle(self): + res = self._srv_client_estop_gentle(TriggerRequest()) + return res.success, res.message + + def stair_mode(self, stair_mode): + res = self._srv_client_stair_mode(SetBoolRequest(data=stair_mode)) + return res.success, res.message + + def locomotion_mode(self, locomotion_mode): + res = self._srv_client_locomotion_mode( + SetLocomotionRequest(locomotion_mode=locomotion_mode)) + return res.success, res.message + + def upload_graph(self, upload_filepath): + res = self._srv_client_upload_graph( + UploadGraphRequest(upload_filepath=upload_filepath)) + return res.success, res.message + + def list_graph(self): + res = self._srv_client_list_graph(ListGraphRequest()) + return res.waypoint_ids + + def set_localization_fiducial(self): + res = self._srv_client_set_localization_fiducial( + SetLocalizationFiducialRequest()) + return res.success, res.message + + def set_localization_waypoint(self, waypoint_id): + res = self._srv_client_set_localization_waypoint( + SetLocalizationWaypointRequest(waypoint_id=waypoint_id)) + return res.success, res.message + + def dock(self, dock_id): + req = DockRequest() + req.dock_id = dock_id + self.pub_body_pose(0, Quaternion(0, 0, 0, 1.0)) + self.sit() + self.stand() + res = self._srv_client_dock(req) + return res.success, res.message + + def undock(self): + self.power_on() + res = self._srv_client_undock(TriggerRequest()) + if not self.is_powered_on(): + self.power_on() + if not self.is_standing(): + self.stand() + return res.success, res.message + + def auto_dock(self, dock_id, home_node_id='eng2_73B2_dock'): + res = self.execute_behaviors(home_node_id) + if not res.success: + return res.success, res.message + success, message = self.dock(dock_id) + return success, message + + def auto_undock(self): + self.sit() + self.power_off() + self.release() + self.claim() + self.power_on() + if self.is_connected(): + self.undock() + else: + self.stand() + + def navigate_to(self, + id_navigate_to, + velocity_limit=(1.0, 1.0, 1.0), + blocking=False): + goal = NavigateToGoal() + goal.id_navigate_to = id_navigate_to + goal.velocity_limit.linear.x = float(velocity_limit[0]) + goal.velocity_limit.linear.y = float(velocity_limit[1]) + goal.velocity_limit.angular.z = float(velocity_limit[2]) + self._actionclient_navigate_to.send_goal(goal) + if blocking: + self._actionclient_navigate_to.wait_for_result() + return self._actionclient_navigate_to.get_result() + + def wait_for_navigate_to_result(self, duration=rospy.Duration(0)): + return self._actionclient_navigate_to.wait_for_result(duration) + + def get_navigate_to_result(self): + return self._actionclient_navigate_to.get_result() + + def cancel_navigate_to(self): + self._actionclient_navigate_to.cancel_all_goals() + + def get_current_node(self): + try: + msg = rospy.wait_for_message( + self.topicname_current_node_id, String, timeout=rospy.Duration(5)) + return msg.data + except rospy.ROSException as e: + rospy.logwarn('{}'.format(e)) + return None + + def execute_behaviors(self, target_node_id, blocking=True): + goal = NavigationGoal() + goal.target_node_id = target_node_id + self._actionclient_execute_behaviors.send_goal(goal) + if blocking: + self._actionclient_execute_behaviors.wait_for_result() + return self._actionclient_execute_behaviors.get_result() + + def wait_execute_behaviors_result(self, duration=None): + if duration is None: + return self._actionclient_execute_behaviors.wait_for_result() + else: + return self._actionclient_execute_behaviors.wait_for_result(duration=duration) + + def get_execute_behaviors_result(self): + return self._actionclient_execute_behaviors.get_result() + + def reset_current_node(self, node): + req = ResetCurrentNodeRequest() + req.current_node_id = node + res = self._srv_client_reset_current_node(req) + return res.success + + def list_nodes(self): + return list(rospy.get_param("/spot_behavior_manager_server/map/nodes", {}).keys()) + + # \brief call trajectory service + # \param x x value of the target position [m] + # \param x y value of the target position [m] + # \param theta theta value of the target position [rad] + # \param duration duration of trajectory command [secs] + def trajectory(self, x, y, theta, duration=None, blocking=False): + rotation = PyKDL.Rotation.RotZ(theta) + goal = TrajectoryGoal() + goal.target_pose.header.frame_id = 'body' + goal.target_pose.pose.position.x = x + goal.target_pose.pose.position.y = y + goal.target_pose.pose.orientation.x = rotation.GetQuaternion()[0] + goal.target_pose.pose.orientation.y = rotation.GetQuaternion()[1] + goal.target_pose.pose.orientation.z = rotation.GetQuaternion()[2] + goal.target_pose.pose.orientation.w = rotation.GetQuaternion()[3] + if duration is None: + goal.duration.data = rospy.Duration(math.sqrt(x**2 + y**2)/0.5) + else: + goal.duration.data = rospy.Duration(duration) + self._actionclient_trajectory.send_goal(goal) + if blocking: + self._actionclient_trajectory.wait_for_result() diff --git a/jsk_spot_robot/spoteus/.gitignore b/jsk_spot_robot/spoteus/.gitignore new file mode 100644 index 0000000000..a1fe640a20 --- /dev/null +++ b/jsk_spot_robot/spoteus/.gitignore @@ -0,0 +1,3 @@ +spot.l +spot.urdf +spot.dae diff --git a/jsk_spot_robot/spoteus/CMakeLists.txt b/jsk_spot_robot/spoteus/CMakeLists.txt new file mode 100644 index 0000000000..2d49a15d63 --- /dev/null +++ b/jsk_spot_robot/spoteus/CMakeLists.txt @@ -0,0 +1,58 @@ +cmake_minimum_required(VERSION 2.8.3) +project(spoteus) + +## Find catkin macros and libraries +## if COMPONENTS list like find_package(catkin REQUIRED COMPONENTS xyz) +## is used, also find other catkin packages +find_package(catkin REQUIRED COMPONENTS + collada_urdf + roseus + euscollada +) +find_package(spot_description) # Just in case when description is not released. Avoid compile failing + +catkin_package() + +if(NOT spot_description_FOUND) + message(WARNING "spot_description is not found, so skip generating spot.l") + message(WARNING "Install spot_description from https://github.com/clearpathrobotics/spot_ros") + return() +endif() + + +########### +## Build ## +########### +if(EXISTS ${spot_description_SOURCE_PREFIX}/urdf) + set(_spot_urdf ${spot_description_SOURCE_PREFIX}/urdf) +else() + set(_spot_urdf ${spot_description_PREFIX}/share/spot_description/urdf) +endif() +set(_urdf_to_collada ${collada_urdf_PREFIX}/lib/collada_urdf/urdf_to_collada) +set(_collada2eus ${euscollada_PREFIX}/lib/euscollada/collada2eus) + +message("spot_urdf: ${_spot_urdf}") +message("urdf_to_collada: ${_urdf_to_collada}") +message("collada2eus: ${_collada2eus}") + +add_custom_command(OUTPUT ${PROJECT_SOURCE_DIR}/spot.urdf + COMMAND xacro ${_spot_urdf}/spot.urdf.xacro > ${PROJECT_SOURCE_DIR}/spot.urdf + DEPENDS ${_spot_urdf}/spot.urdf.xacro) + +add_custom_command(OUTPUT ${PROJECT_SOURCE_DIR}/spot.l + COMMAND echo "${_collada2eus} spot.urdf spot.l" + COMMAND ${_collada2eus} ${PROJECT_SOURCE_DIR}/spot.urdf ${PROJECT_SOURCE_DIR}/spot.yaml ${PROJECT_SOURCE_DIR}/spot.l + DEPENDS ${PROJECT_SOURCE_DIR}/spot.urdf ${PROJECT_SOURCE_DIR}/spot.yaml ${_collada2eus}) +add_custom_target(compile_spot ALL DEPENDS ${PROJECT_SOURCE_DIR}/spot.l) + + +install(DIRECTORY euslisp test + DESTINATION ${CATKIN_PACKAGE_SHARE_DESTINATION} + USE_SOURCE_PERMISSIONS) + +install(FILES spot.l spot-interface.l spot-util.l spot.yaml DESTINATION ${CATKIN_PACKAGE_SHARE_DESTINATION}) + +if(CATKIN_ENABLE_TESTING) + find_package(catkin REQUIRED COMPONENTS rostest) + add_rostest(test/test-spot.test) +endif() diff --git a/jsk_spot_robot/spoteus/demo/sample_basics.l b/jsk_spot_robot/spoteus/demo/sample_basics.l new file mode 100755 index 0000000000..fcd8910854 --- /dev/null +++ b/jsk_spot_robot/spoteus/demo/sample_basics.l @@ -0,0 +1,80 @@ +#!/usr/bin/env roseus + +(load "package://spoteus/spot-interface.l") + +(spot-init nil) ;; do not create-viewer + +;; power on +(send *ri* :claim) +(send *ri* :power-on) + +(send *ri* :stand) +(ros::duration-sleep 1) + +;; states +(format t "(send *ri* :state :metrics) : ~A~%" (send *ri* :state :metrics)) +(format t "(send *ri* :state :leases) : ~A~%" (send *ri* :state :leases)) +(format t "(send *ri* :state :estop) : ~A~%" (send *ri* :state :estop)) +(format t "(send *ri* :state :wifi) : ~A~%" (send *ri* :state :wifi)) +(format t "(send *ri* :state :power-state) : ~A~%" (send *ri* :state :power-state)) +(format t "(send *ri* :state :battery-faults) : ~A~%" (send *ri* :state :battery-faults)) +(format t "(send *ri* :state :system-faults) : ~A~%" (send *ri* :state :system-faults)) +(format t "(send *ri* :state :feedback) : ~A~%" (send *ri* :state :feedback)) + +;; basic services +(format t "Testing Basic Functionality~%") +(send *ri* :sit) +(ros::duration-sleep 3) + +(send *ri* :power-off) +(ros::duration-sleep 3) + +(send *ri* :release) +(ros::duration-sleep 3) + +(send *ri* :claim) +(ros::duration-sleep 3) + +(send *ri* :power-on) +(ros::duration-sleep 3) + +(send *ri* :stand) +(ros::duration-sleep 5) + +;; cmd-vel +(format t "Testing cmd-vel~%") +(setq end-time (ros::time+ (ros::time 10) (ros::time-now))) +(while (ros::time< (ros::time-now) end-time) + (send *ri* :go-velocity 0 0 0.8) + ) + +(setq end-time (ros::time+ (ros::time 10) (ros::time-now))) +(while (ros::time< (ros::time-now) end-time) + (send *ri* :go-velocity 0 0 -0.8) + ) + +;; body-pose +(format t "Testing body-pose~%") +(send *ri* :body-pose '(0 0.2 0)) +(ros::duration-sleep 1) +(send *ri* :body-pose '(0 -0.2 0)) +(ros::duration-sleep 1) +(send *ri* :body-pose '(0 0 0.2)) +(ros::duration-sleep 1) +(send *ri* :body-pose '(0 0 -0.2)) +(ros::duration-sleep 1) +(send *ri* :body-pose '(0.1 0 0)) +(ros::duration-sleep 1) +(send *ri* :body-pose '(-0.1 0 0)) +(ros::duration-sleep 1) +(send *ri* :body-pose '(0 0 0)) +(ros::duration-sleep 1) + +;; go pos +(format t "Testing go pos") +(send *ri* :go-pos 1 0) +(send *ri* :go-pos 0 0 90) +(send *ri* :go-pos 0 1 -90) + +;; +(exit 1) diff --git a/jsk_spot_robot/spoteus/demo/sample_go_to_spot.l b/jsk_spot_robot/spoteus/demo/sample_go_to_spot.l new file mode 100755 index 0000000000..e623800d64 --- /dev/null +++ b/jsk_spot_robot/spoteus/demo/sample_go_to_spot.l @@ -0,0 +1,19 @@ +#!/usr/bin/env roseus + +(load "package://spoteus/spot-interface.l") + +(spot-init) +(send *ri* :stand) +;; if dock is used +;; (setq dock-id (ros::get-param "~dock_id" 520)) +;; (send *ri* :undock) + +;;(send *ri* :go-to-spot "eng2_7FElevator") +(send *ri* :go-to-spot "eng2_2FElevator") +(send *ri* :speak-jp "こんにちは" :wait t) +(send *ri* :speak-en "hello" :wait t) +(send *ri* :go-to-spot "eng2_73B2") + +(send *ri* :sit) +(send *ri* :stand) +;; (send *ri* :dock dock-id) diff --git a/jsk_spot_robot/spoteus/demo/sample_navigate_to.l b/jsk_spot_robot/spoteus/demo/sample_navigate_to.l new file mode 100755 index 0000000000..81a9b58e1e --- /dev/null +++ b/jsk_spot_robot/spoteus/demo/sample_navigate_to.l @@ -0,0 +1,38 @@ +#!/usr/bin/env roseus + +;; +;; This script is for demonstration of GraphNav interface with euslisp. +;; By default, it is assumed that Spot is at the entrance of 73B2 and headed to the AR marker on the door. +;; + +(load "package://spoteus/spot-interface.l") + +(spot-init nil) ;; do not create-viewer + +(setq *path* (ros::get-param "~path" (format nil "~A/autowalk/eng2_73B2_to_81C1.walk" (ros::rospack-find "spot_autowalk_data")))) +(setq *init-waypoint* (floor (ros::get-param "~init_waypoint" 0))) +(setq *upload* (ros::get-param "~upload" t)) + +;; Upload graphnav files to the robot. +(if *upload* (send *ri* :upload-path *path*)) + +;; Localize the robot in the map +(ros::ros-info "initialize position with waypoint of ~A" *init-waypoint*) +(send *ri* :initial-localization-waypoint *init-waypoint*) +;; you can also use following command to initialize localization if you start from 73B2 +;; (send *ri* :initial-localization-fiducial) +;; the difference is, :initial-localization-waypoint can initialize with any waypoint, meaning you can start from arbitary wapoint, e.g., -1 -> 0, 1 -> -1 + +(ros::ros-info "ready go to 81C1?") +(if (y-or-n-p) t (exit)) + +;; go to 81C1 +(send *ri* :navigate-to -1) + +(ros::ros-info "ready go back to 73B2?") +(if (y-or-n-p) t (exit)) + +;; go back to 73B2 +(send *ri* :navigate-to 0) + +(send *ri* :sit) diff --git a/jsk_spot_robot/spoteus/demo/sample_visualization.l b/jsk_spot_robot/spoteus/demo/sample_visualization.l new file mode 100755 index 0000000000..1d5e539adc --- /dev/null +++ b/jsk_spot_robot/spoteus/demo/sample_visualization.l @@ -0,0 +1,23 @@ +#!/usr/bin/env roseus + +;; +;; This script is for demonstration of visualization of spot's angle vector and global pose with irtviewer +;; + +(load "package://spoteus/spot-interface.l") + +(spot-init t) ;; create-viewer +(send *irtviewer* :draw-floor t) + +;; if not power-on, power on +;; if not stand, stand + +(do-until-key + ;; update body posture + (send *spot* :angle-vector (send *ri* :state :angle-vector)) + ;; + (send *spot* :move-to (send *ri* :state :worldcoords) :world) + (send *irtviewer* :look1 (send *spot* :worldpos)) + (send *irtviewer* :draw-objects) + (x::window-main-one) + ) diff --git a/jsk_spot_robot/spoteus/package.xml b/jsk_spot_robot/spoteus/package.xml new file mode 100644 index 0000000000..258253ca59 --- /dev/null +++ b/jsk_spot_robot/spoteus/package.xml @@ -0,0 +1,29 @@ + + + spoteus + 1.1.0 + The spoteus package + + Kei Okada + Yoshiki Obinata + BSD + Kei Okada + Yoshiki Obinata + + catkin + + collada_urdf + euscollada + rostest + roseus + pr2eus + spot_description + + pr2eus + roseus + spot_msgs + std_srvs + + + + diff --git a/jsk_spot_robot/spoteus/spot-interface.l b/jsk_spot_robot/spoteus/spot-interface.l new file mode 100644 index 0000000000..f57df8cb74 --- /dev/null +++ b/jsk_spot_robot/spoteus/spot-interface.l @@ -0,0 +1,578 @@ +(ros::roseus "spot") + +(require "package://spoteus/spot-utils.l") +(require "package://pr2eus/robot-interface.l") + +(ros::roseus-add-srvs "std_srvs") +(ros::roseus-add-msgs "spot_msgs") +(ros::roseus-add-srvs "spot_msgs") +(ros::roseus-add-srvs "control_msgs") + +(defun call-trigger-service (srvname &key (wait nil)) + "Call std_srv/Trigger service" + (let (r) + (when (send *ri* :simulation-modep) + (ros::ros-warn "call-trigger-service (~A)" srvname) + (return-from call-trigger-service t)) + (if wait (ros::wait-for-service srvname)) + (setq r (ros::service-call srvname (instance std_srvs::TriggerRequest :init))) + (ros::ros-debug "Call \"~A\" returns \"~A\"" srvname (send r :message)) + (send r :success))) + +(defun call-set-bool-service (srvname data &key (wait nil)) + "Call std_srv/Trigger service" + (let (r) + (when (send *ri* :simulation-modep) + (ros::ros-warn "call-set-bool-service (~A) ~A" srvname data) + (return-from call-set-bool-service t)) + (if wait (ros::wait-for-service srvname)) + (setq r (ros::service-call srvname (instance std_srvs::SetBoolRequest :init :data data))) + (ros::ros-info "Call \"~A\" returns \"~A\"" srvname (send r :message)) + (send r :success))) + +(defun call-set-locomotion-service (srvname locomotion_hint &key (wait nil)) + "Call spot_msgs/SetLocomotion service" + (let (r) + (when (send *ri* :simulation-modep) + (ros::ros-warn "call-set-locomotion-service (~A) ~A" srvname locomotion_hint) + (return-from call-set-locomotion-service t)) + (if wait (ros::wait-for-service srvname)) + (setq r (ros::service-call srvname (instance spot_msgs::SetLocomotionRequest :init :locomotion_mode locomotion_hint))) + (ros::ros-info "Call \"~A\" returns \"~A\"" srvname (send r :message)) + (send r :success))) + +(defun call-dock-service (srvname dock_id &key (wait nil)) + "Call spot_msgs/Dock service" + (let (r) + (when (send *ri* :simulation-modep) + (ros::ros-warn "call-dock-service (~A) ~A" srvname dock_id) + (return-from call-dock-service t)) + (if wait (ros::wait-for-service srvname)) + (setq r (ros::service-call srvname (instance spot_msgs::DockRequest :init :dock_id dock_id))) + (ros::ros-info "Call \"~A\" returns \"~A\"" srvname (send r :message)) + (send r :success))) + +(defclass spot-interface + :super robot-move-base-interface + :slots (trajectory-cmd-action pick-object-in-image-client pick-object-in-image-feedback-msg) + ) + +(defmethod spot-interface + (:init + (&rest args &key (trajectory-cmd-action-name "/spot/trajectory") &allow-other-keys) + (prog1 + (send-super* :init :robot spot-robot :base-frame-id "base_link" :odom-topic "/odom_combined" :base-controller-action-name nil args) + ;; check if spot_ros/driver.launch started + (unless (or (send self :simulation-modep) (ros::wait-for-service "/spot/claim" 5)) + (ros::ros-error "could not communicate with robot, may be forget to roslaunch spot_driver driver.launch, or did not power on the robot")) + ;; http://www.clearpathrobotics.com/assets/guides/melodic/spot-ros/ros_usage.html#view-the-robot + ;; spot_msgs version 0.0.0 + (ros::subscribe "/spot/status/metrics" spot_msgs::Metrics #'send self :spot-status-metrics-callback :groupname groupname) + (ros::subscribe "/spot/status/leases" spot_msgs::LeaseArray #'send self :spot-status-leases-callback :groupname groupname) + (ros::subscribe "/spot/status/feet" spot_msgs::FootStateArray #'send self :spot-status-feet-callback :groupname groupname) + (ros::subscribe "/spot/status/estop" spot_msgs::EStopStateArray #'send self :spot-status-estop-callback :groupname groupname) + (ros::subscribe "/spot/status/wifi" spot_msgs::WiFiState #'send self :spot-status-wifi-callback :groupname groupname) + (ros::subscribe "/spot/status/manipulator_state" spot_msgs::ManipulatorState #'send self :spot-status-manipulator-state-callback :groupname groupname) + (ros::subscribe "/spot/status/power_state" spot_msgs::PowerState #'send self :spot-status-power-state-callback :groupname groupname) + (ros::subscribe "/spot/status/battery_states" spot_msgs::BatteryStateArray #'send self :spot-status-battery-states-callback :groupname groupname) + (ros::subscribe "/spot/status/behavior_faults" spot_msgs::BehaviorFaultState #'send self :spot-status-behavior-faults-callback :groupname groupname) + (ros::subscribe "/spot/status/system_faults" spot_msgs::SystemFaultState #'send self :spot-status-system-faults-callback :groupname groupname) + (ros::subscribe "/spot/status/feedback" spot_msgs::Feedback #'send self :spot-feedback-callback :groupname groupname) + (setq trajectory-cmd-action (instance ros::simple-action-client :init + trajectory-cmd-action-name spot_msgs::TrajectoryAction + :groupname groupname)) + (setq pick-object-in-image-client (instance ros::simple-action-client :init + "/spot/pick_object_in_image" spot_msgs::PickObjectInImageAction)) + )) + ;; (:default-controller () + ;; (append + ;; (send self :arm-controller) + ;; (send self :dummy-controller) + ;; )) + (:default-controller () (send self :arm-controller)) + (:arm-controller () + (list + (list + (cons :controller-action "spot/arm_controller/follow_joint_trajectory") + (cons :controller-state "spot/arm_controller/follow_joint_trajectory/state") + (cons :action-type control_msgs::FollowJointTrajectoryAction) + (cons :joint-names (send-all (send robot :arm :joint-list) :name)) + ))) + (:set-impedance-param + (&key (linear-stiffness #f(500 500 500)) + (rotational-stiffness #f(60 60 60)) + (linear-damping #f(2.5 2.5 2.5)) + (rotational-damping #f(1.0 1.0 1.0))) + "X is up, Z is forward" + (let (m r) + (labels + ((v2v (v) (instance geometry_msgs::Vector3 :init :x (elt v 0) :y (elt v 1) :z (elt v 2)))) + (setq m (instance spot_msgs::SetArmImpedanceParamsRequest :init + :linear_stiffness (v2v linear-stiffness) + :rotational_stiffness (v2v rotational-stiffness) + :linear_damping (v2v linear-damping) + :rotational_damping (v2v rotational-damping))) + (setq r (ros::service-call "/spot/arm_impedance_parameters" m)) + (send r :success)))) + (:unstow-arm () (call-trigger-service "/spot/unstow_arm")) + (:stow-arm () (call-trigger-service "/spot/stow_arm")) + (:gripper-open () (call-trigger-service "/spot/gripper_open")) + (:gripper-close () (call-trigger-service "/spot/gripper_close")) + (:start-grasp + () + (let (p s) + (send self :gripper-close) + (setq p (send *ri* :state :manipulator-state-gripper-open-percentage) + s (send *ri* :state :manipulator-state-is-gripper-holding-item)) + (ros::ros-info "gripper-open-percentage ~A" p) + (ros::ros-info "manipulator-state-is-gripper-holding-item ~A" s) + s)) + (:stop-grasp + () + (let (p s) + (send self :gripper-open) + (setq p (send *ri* :state :manipulator-state-gripper-open-percentage) + s (send *ri* :state :manipulator-state-is-gripper-holding-item)) + (ros::ros-info "gripper-open-percentage ~A" p) + (ros::ros-info "manipulator-state-is-gripper-holding-item ~A" s) + s)) + (:pick-object-in-image + (image-source x y &optional (max-duration 15)) + (let ((goal (instance spot_msgs::PickObjectInImageActionGoal :init))) + (print (list image-source x y max-duration)) + (send goal :goal :image_source image-source) + (send goal :goal :center (instance geometry_msgs::Point :init :x x :y y)) + (send goal :goal :max_duration (instance ros::duration :init max-duration)) + (send pick-object-in-image-client :send-goal goal :feedback-cb `(lambda (msg) (send ,self :pick-object-in-image-feedback-cb msg))) + )) + (:pick-object-in-image-feedback-cb + (msg) + (setq pick-object-in-image-feedback-msg msg)) + (:pick-object-in-image-feedback-msg + (&rest args) + (forward-message-to pick-object-in-image-feedback-msg args)) + (:pick-object-in-image-wait-for-result + () + (send pick-object-in-image-client :wait-for-result) + (send pick-object-in-image-client :get-result)) + (:pick-object-in-image-get-state + () + (send pick-object-in-image-client :get-state)) + ;; (:dummy-controller () + ;; (list + ;; (list + ;; (cons :controller-action "spot/dummy_controller/follow_joint_trajectory") + ;; (cons :controller-state "spot/dummy_controller/follow_joint_trajectory/state") + ;; (cons :action-type control_msgs::FollowJointTrajectoryAction) + ;; (cons :joint-names (send-all (set-difference (send robot :joint-list) (send robot :arm :joint-list)) :name)) + ;; ))) + (:spot-status-metrics-callback + (msg) + (send self :set-robot-state1 :metrics-distance (send msg :distance)) + (send self :set-robot-state1 :metrics-gaint-cycles (send msg :gait_cycles)) + (send self :set-robot-state1 :metrics-time-moving (send (send msg :time_moving) :to-sec)) + (send self :set-robot-state1 :metrics-electric-power (send (send msg :electric_power) :to-sec))) + (:spot-status-leases-callback + (msg) + (dolist (resource (send msg :resources)) + (let ((r (string-upcase (send resource :resource)))) + (send self :set-robot-state1 (intern (format nil "LEASES-~A-RESOURCE" r) *keyword-package*) + (send resource :lease :resource)) + (send self :set-robot-state1 (intern (format nil "LEASES-~A-EPOCH" r) *keyword-package*) + (send resource :lease :epoch)) + (send self :set-robot-state1 (intern (format nil "LEASES-~A-SEQUENCE" r) *keyword-package*) + (send resource :lease :sequence)) + (send self :set-robot-state1 (intern (format nil "LEASES-~A-CLIENT-NAME" r) *keyword-package*) + (send resource :lease_owner :client_name)) + (send self :set-robot-state1 (intern (format nil "LEASES-~A-USER-NAME" r) *keyword-package*) + (send resource :lease_owner :user_name))))) + (:spot-status-feet-callback + (msg) + (send self :set-robot-state1 :feet + (mapcar #'(lambda (state) + (list (cons :foot-position-rt-body (ros::tf-point->pos (send state :foot_position_rt_body))) + (cons :contact (case (send state :contact) (0 'unknown) (1 'made) (2 'lost))))) (send msg :states)))) + (:spot-status-estop-callback + (msg) + (dolist (state (send msg :estop_states)) + (let ((s (string-upcase (substitute #\- #\_ (send state :name))))) + (send self :set-robot-state1 (intern (format nil "ESTOP-~A-NAME" s) *keyword-package*) + (send state :name)) + (send self :set-robot-state1 (intern (format nil "ESTOP-~A-TYPE" s) *keyword-package*) + (case (send state :type) (0 'unknown) (1 'hardware) (2 'software))) + (send self :set-robot-state1 (intern (format nil "ESTOP-~A-STATE" s) *keyword-package*) + (case (send state :state) (0 'unknown) (1 'estopped) (2 'not-estopped))) + (send self :set-robot-state1 (intern (format nil "ESTOP-~A-STATE-DESCRIPTION" s) *keyword-package*) + (send state :state_description))))) + (:spot-status-wifi-callback + (msg) + (send self :set-robot-state1 :wifi-current-mode + (case (send msg :current_mode) + (0 'unknown) (1 'access-point) (2 'client))) + (send self :set-robot-state1 :wifi-essid (send msg :essid))) + (:spot-status-manipulator-state-callback + (msg) + (send self :set-robot-state1 :manipulator-state-gripper-open-percentage (send msg :gripper_open_percentage)) + (send self :set-robot-state1 :manipulator-state-is-gripper-holding-item (send msg :is_gripper_holding_item)) + (send self :set-robot-state1 :manipulator-state-estimated-end-effector-force-in-hand + (let ((m (send msg :estimated_end_effector_force_in_hand))) + (float-vector (send m :x) (send m :y) (send m :z)))) + (send self :set-robot-state1 :manipulator-state-stow-state + (case (send msg :stow_state) (0 'unknown) (1 'stowed) (2 'deployed))) + (send self :set-robot-state1 :manipulator-state-velocity-of-hand-in-vision + (let ((m (send msg :velocity_of_hand_in_vision))) + (float-vector (send m :linear :x) (send m :linear :y) (send m :linear :z) + (send m :angular :x) (send m :angular :y) (send m :angular :z)))) + (send self :set-robot-state1 :manipulator-state-velocity-of-hand-in-odom + (let ((m (send msg :velocity_of_hand_in_odom))) + (float-vector (send m :linear :x) (send m :linear :y) (send m :linear :z) + (send m :angular :x) (send m :angular :y) (send m :angular :z)))) + (send self :set-robot-state1 :manipulator-carry-state + (case (send msg :carry_state) (0 'unknown) (1 'not-carriable) (2 'carriable) (3 'carriable-and-stowable))) + ) + (:spot-status-power-state-callback + (msg) + (send self :set-robot-state1 :power-state-motor-power-state + (case (send msg :motor_power_state) + (0 'unknown) (1 'off) (2 'on) (3 'powering-on) (4 'powering-off) (5 'error))) + (send self :set-robot-state1 :power-state-shore-power-state + (case (send msg :shore_power_state) + (0 'unknown-shore-power) (1 'on-shore-power) (2 'off-shore-power))) + (send self :set-robot-state1 :power-state-locomotion-charge-percentage (send msg :locomotion_charge_percentage)) + (send self :set-robot-state1 :power-state-locomotion-estimated-runtime (send (send msg :locomotion_estimated_runtime) :to-sec))) + (:spot-status-battery-states-callback + (msg) + (dolist (state (send msg :battery_states)) + (let ((i 1)) + (send self :set-robot-state1 (intern (format nil "BATTERY-STATES-~A-IDENTIFIER" i) *keyword-package*) + (send state :identifier)) + (send self :set-robot-state1 (intern (format nil "BATTERY-STATES-~A-CHARGE-PERCENTAGE" i) *keyword-package*) + (send state :charge_percentage)) + (send self :set-robot-state1 (intern (format nil "BATTERY-STATES-~A-ESTIMATED-RUNTIME" i) *keyword-package*) + (send (send state :estimated_runtime) :to-sec)) + (send self :set-robot-state1 (intern (format nil "BATTERY-STATES-~A-CURRENT" i) *keyword-package*) + (send state :current)) + (send self :set-robot-state1 (intern (format nil "BATTERY-STATES-~A-VOLTAGE" i) *keyword-package*) + (send state :voltage)) + (send self :set-robot-state1 (intern (format nil "BATTERY-STATES-~A-TEMPERATURES" i) *keyword-package*) + (send state :temperatures)) + (send self :set-robot-state1 (intern (format nil "BATTERY-STATES-~A-STATUS" i) *keyword-package*) + (case (send state :status) (0 'unknown) (1 'missing) (2 'charging) (3 'discharging) (4 'booting))) + (setq i (+ i 1)) + ))) + (:spot-status-behavior-faults-callback + (msg) + (send self :set-robot-state1 :behavior-faults + (mapcar #'(lambda (fault) + (list (cons :behavior-fault-id (send fault :behavior_fault_id)) + (cons :cause (case (send fault :cause) (0 'unknown) (1 'fall) (2 'hardware))) + (cons :status (case (send fault :status) (0 'unknown) (1 'clearable) (2 'unclearable))))) + (send msg :faults)))) + (:spot-status-system-faults-callback + (msg) + (dolist (fault (send msg :faults)) + (let ((s (string-upcase (substitute #\- #\_ (send fault :name))))) + (send self :set-robot-state1 (intern (format nil "SYSTEM-FAULTS-~A-NAME" s) *keyword-package*) + (send fault :NAME)) + (send self :set-robot-state1 (intern (format nil "SYSTEM-FAULTS-~A-DURATION" s) *keyword-package*) + (send (send fault :duration) :to-sec)) + (send self :set-robot-state1 (intern (format nil "SYSTEM-FAULTS-~A-CODE" s) *keyword-package*) + (send fault :code)) + (send self :set-robot-state1 (intern (format nil "SYSTEM-FAULTS-~A-UID" s) *keyword-package*) + (send fault :uid)) + (send self :set-robot-state1 (intern (format nil "SYSTEM-FAULTS-~A-ERROR_MESSAGE" s) *keyword-package*) + (send fault :error_message)) + (send self :set-robot-state1 (intern (format nil "SYSTEM-FAULTS-~A-ATTRIBUTES" s) *keyword-package*) + (send fault :attributes)) + (send self :set-robot-state1 (intern (format nil "SYSTEM-FAULTS-~A-SEVERITY" s) *keyword-package*) + (case (send fault :severity) (0 'unknown) (1 'info) (2 'warn) (3 'critical)))))) + (:spot-feedback-callback + (msg) + (send self :set-robot-state1 :feedback-standing (send msg :standing)) + (send self :set-robot-state1 :feedback-sitting (send msg :sitting)) + (send self :set-robot-state1 :feedback-moving (send msg :moving)) + (send self :set-robot-state1 :feedback-serial-number (send msg :serial_number)) + (send self :set-robot-state1 :feedback-species (send msg :species)) + (send self :set-robot-state1 :feedback-version (send msg :version)) + (send self :set-robot-state1 :feedback-nickname (send msg :nickname)) + (send self :set-robot-state1 :feedback-computer-serial-number(send msg :computer_serial_number))) + (:state + (&rest args) + "use :metrics, :leases :feet, :estop, :wifi, :power-state, :battery-states, :behvior-faults, :system-fault, :feedback to get spot status, you can also acess to the specific data by concatenating these method name + key value, for example :metrics-time-moving" + (prog1 + (send-message* self robot-interface :state args) + (flet ((gen-status + (key) + (mapcan #'(lambda (x) (if (substringp (string key) (string (car x))) (list (cons (intern (subseq (string (car x)) (1+ (length (string key)))) *keyword-package*) (cdr x))))) robot-state))) + (case (car args) + (:metrics (return-from :state (gen-status :metrics))) + (:leases (return-from :state (gen-status :leases))) + ;; (:feet (return-from :state (gen-status :feet))) + (:estop (return-from :state (gen-status :estop))) + (:wifi (return-from :state (gen-status :wifi))) + (:manipulator-state (return-from :state (gen-status :manipulator-state))) + (:power-state (return-from :state (gen-status :power-state))) + (:battery-states (return-from :state (gen-status :battery-states))) + ;; (:behavior-faults (return-from :state (gen-status :behavior-faults))) + (:system-faults (return-from :state (gen-status :system-faults))) + (:feedback (return-from :state (gen-status :feedback))) + (:angle-vector + (return-from :state (send robot :angle-vector))) + (:worldcoords + (unless joint-action-enable + (return-from :state (send self :worldcoords))) + (return-from :state (send *tfl* :lookup-transform "vision" base-frame-id (ros::time 0)))))))) + ;; + (:estop-gentle () (call-trigger-service "/spot/estop/gentle")) + (:estop-hard () (call-trigger-service "/spot/estop/hard")) + (:claim () + "Claim the robot control" + (let ((client (send self :state :leases-body-client-name))) + (if (or (null client) (string= client "")) + (call-trigger-service "/spot/claim") + (ros::ros-warn "robot is already claimed by ~A" client)))) + (:release () "Relase the robot control" (call-trigger-service "/spot/release")) + (:power-on () + "Power on the robot" + (if (send *ri* :simulation-modep) + (send self :set-robot-state1 :power-state-motor-power-state 'on) + (call-trigger-service "/spot/power_on"))) + (:power-off + () + "Power off the robot" + (if (send *ri* :simulation-modep) + (send self :set-robot-state1 :power-state-motor-power-state 'off) + (call-trigger-service "/spot/power_off"))) + (:self-right () (call-trigger-service "/spot/self_right")) + (:stand () "Stand the robot up" (call-trigger-service "/spot/stand")) + (:sit () "Sit the robot down" (call-trigger-service "/spot/sit")) + (:stop () "Stop the robot in place with minimal motion" (call-trigger-service "/spot/stop")) + (:set-locomotion-mode (locomotion-hint) "Set locomotion mode" (call-set-locomotion-service "/spot/locomotion_mode" locomotion-hint)) + (:set-stair-mode (is-stair-mode) "Set stair mode" (call-set-bool-service "/spot/stair_mode" is-stair-mode)) + ;; + (:dock (dock-id) "Dock the robot" (call-dock-service "/spot/dock" dock-id)) + (:undock + () + "Undock the robot" + (send self :power-on) + (call-trigger-service "/spot/undock")) + ;; + (:send-cmd-vel-raw + (x y d &key (topic-name "/spot/cmd_vel")) + (when (send self :simulation-modep) + (return-from :send-cmd-vel-raw t)) + (unless (ros::get-topic-publisher topic-name) + (ros::advertise topic-name geometry_msgs::Twist 1) + (unix:sleep 1)) + (let ((msg (instance geometry_msgs::Twist :init))) + (send msg :linear :x x) + (send msg :linear :y y) + (send msg :angular :z d) + (ros::publish topic-name msg))) + (:go-velocity + (x y d ;; [m/sec] [m/sec] [rad/sec] + &optional (msec 1000) ;; msec is total animation time [msec] + &key (stop t) (wait t)) + "control the robot velocity x([m/sec]) y([m/sec]) d([rad/sec]) msec([msec]). msec is the time to travel." + (unless wait + (ros::ros-error ":go-velocity without wait is unsupported") + (return-from :go-velocity nil)) + (ros::rate 100) + (let ((start-time (ros::time-now))) + (while (and (ros::ok) + (< (* 1000.0 (send (ros::time- (ros::time-now) start-time) :to-sec)) msec)) + (send self :spin-once) + (send self :send-cmd-vel-raw x y d) + (ros::sleep))) + (when stop + (send self :send-cmd-vel-raw 0 0 0)) + (ros::rate 10) + t) + (:go-pos + (x y &optional (d 0) &key (timeout 10) (wait t)) ;; [m] [m] [degree] + "move robot toward (x, y, d) (units are m, m and degrees respectively)." + ;; + (setq trajectory-cmd-goal-msg (instance spot_msgs::TrajectoryGoal :init)) + (send trajectory-cmd-goal-msg :target_pose :header :stamp (ros::time-now)) + (send trajectory-cmd-goal-msg :target_pose :header :frame_id "body") + (send trajectory-cmd-goal-msg :target_pose :pose :position :x x) + (send trajectory-cmd-goal-msg :target_pose :pose :position :y y) + (send trajectory-cmd-goal-msg :target_pose :pose :orientation :z (sin (/ (deg2rad d) 2))) + (send trajectory-cmd-goal-msg :target_pose :pose :orientation :w (cos (/ (deg2rad d) 2))) + (send trajectory-cmd-goal-msg :duration :data (ros::time timeout)) + ;; + (send trajectory-cmd-action :send-goal trajectory-cmd-goal-msg) + (if wait + (send trajectory-cmd-action :wait-for-result) + ) + ) + (:go-pos-wait + (x y &optional (d 0) (timeout 10)) ;; [m] [m] [degree] + "move robot toward (x, y, d) (units are m, m and degrees respectively). and wait" + (send self :go-pos x y d :timeout timeout :wait t) + ) + (:go-pos-no-wait + (x y &optional (d 0) (timeout 10)) ;; [m] [m] [degree] + "move robot toward (x, y, d) (units are m, m and degrees respectively)." + (send self :go-pos x y d :timeout timeout :wait nil) + ) + (:body-pose + ;; pose expected to get 3 elements float-vector (r p y), or eus coords. + (pose &key (topic-name "/spot/body_pose")) + (when (send self :simulation-modep) + (return-from :body-pose t)) + (unless (ros::get-topic-publisher topic-name) + (ros::advertise topic-name geometry_msgs::Pose 1) + (unix:sleep 1)) + (if (or (vectorp pose) (listp pose)) + (progn + (ros::ros-debug "Got r p y float-vector or list as args.") + (let ((pose-msg (instance geometry_msgs::Pose :init)) + (quaternion-msg (send self :create-quaternion-msg-from-rpy (elt pose 0) (elt pose 1) (elt pose 2)))) + (send pose-msg :orientation quaternion-msg) + (ros::publish topic-name pose-msg))) + (progn + (ros::ros-debug "Got coords variable as args.") + (let ((pose-msg (ros::coords->tf-pose pose))) + (ros::publish topic-name pose-msg)))) + (send self :stand)) + (:find-waypoint-position-from-id + (id ids) + (let (ret) + (setq ret (position id ids :test #'string=)) + ret)) + (:find-waypoint-id-from-position + (index ids) + "return waypint id from position, if you set -1 to index, it returns last waypoint" + (let (ret) + (if (< index 0) + (setq index (+ (length ids) index))) + (if (< index (length ids)) + (setq ret (elt ids index))) + ret)) + (:set-localization-fiducial + () + "initial the localization for autowalk based on the fiducial marker" + (let (r) + (setq r (ros::service-call "/spot/set_localization_fiducial" + (instance spot_msgs::SetLocalizationFiducialRequest :init))) + (ros::ros-info "Call \"/spot/set_localization_fiducial\" returns \"~A\"" (send r :message)) + t)) + (:set-localization-waypoint + (init-waypoint) + "initial the localization for waypoint id in the graph" + (let (r) + (if (numberp init-waypoint) + (prog1 + (setq ids (send self :list-graph)) + (setq init-waypoint (send self :find-waypoint-id-from-position init-waypoint ids)))) + (setq r (ros::service-call "/spot/set_localization_waypoint" + (instance spot_msgs::SetLocalizationWaypointRequest :init :waypoint_id init-waypoint))) + (ros::ros-info "Call \"/spot/set_localization_waypoint\" returns \"~A\"" (send r :message)) + t)) + (:start-recording + () + "Start AutoWalk recording" + (call-trigger-service "/spot/start-recording")) + (:stop-recording + () + "Stop AutoWalk recording" + (call-trigger-service "/spot/stop-recording")) + (:upload-graph + (path &key (initial-localization-fiducial t) (wait nil)) + "upload graph for autowalk" + (let (r + (srvname "/spot/upload_graph")) + (if wait (ros::wait-for-service srvname)) + (setq r (ros::service-call srvname + (instance spot_msgs::UploadGraphRequest :init :upload_filepath path))) + (ros::ros-info "Call \"~A\" returns .. \"~A\"" srvname (send r :message)) + (if initial-localization-fiducial (send self :initial-localization-fiducial)) + (send r :success))) + (:download-graph + (path &key (wait nil)) + "Download AutoWalk graph from robot" + (let (r) + (if wait (ros::wait-for-service srvname)) + (setq r (ros::service-call srvname + (instance spot_msgs::DownloadGraphRequest :init :download_filepath path))) + (ros::ros-info "Call \"~A\" returns... \"~A\"" srvname (send r :message)) + (send r :success) + )) + (:list-graph + () + "list up the waypoint (a string type hash id) list in the uploaded graph" + (let (r) + (setq r (ros::service-call "/spot/list_graph" + (instance spot_msgs::ListGraphRequest :init))) + (ros::ros-info "Call \"/spot/list_graph\" returns ..") + (dolist (id (send r :waypoint_ids)) + (ros::ros-info " \"~A\"" id)) + (send r :waypoint_ids))) + (:clear-graph + () + "Clear the AutoWalk graph in the robot." + (let () + (call-trigger-service "/spot/clear_graph") + )) + (:navigate-to + (navigate-to &key (initial-localization-waypoint nil)) + (let (ids c goal ret) + (setq ids (send self :list-graph)) + (if (numberp navigate-to) + (setq navigate-to (send self :find-waypoint-id-from-position navigate-to ids))) + (if (numberp initial-localization-waypoint) + (setq initial-localization-waypoint (send self :find-waypoint-id-from-position initial-localization-waypoint ids))) + (setq c (instance ros::simple-action-client :init + "/spot/navigate_to" spot_msgs::NavigateToAction)) + (send c :wait-for-server) + (ros::ros-info "initial waypoit ~3D/~3D ~A" + (if initial-localization-waypoint + (position initial-localization-waypoint ids :test #'string=) + -1) + (length ids) + initial-localization-waypoint) + (ros::ros-info " goal waypoit ~3D/~3D ~A" + (position navigate-to ids :test #'string=) (length ids) navigate-to) + (setq goal (instance spot_msgs::NavigateToActionGoal :init)) + (send goal :goal :id_navigate_to navigate-to) + (send c :send-goal goal :feedback-cb #'(lambda (msg) (let ((id (send msg :feedback :waypoint_id))) (ros::ros-info "~A/~A ~A" (position id ids :test #'string=) (length ids) id)))) + (setq ret (send c :wait-for-result)) + (ros::ros-info "~A ~A" ret (send (send c :get-result) :message)) + ret)) + ;; These are the methods to calculate rpy -> quaternion. + ;; You can use ros::create-quaternion-from-rpy after https://github.com/jsk-ros-pkg/jsk_roseus/pull/662 was merged and released. + (:create-quaternion-from-rpy + (roll pitch yaw) ;; return [w x y z] + (let ((sin-roll (sin (* roll 0.5))) (cos-roll (cos (* roll 0.5))) + (sin-pitch (sin (* pitch 0.5))) (cos-pitch (cos (* pitch 0.5))) + (sin-yaw (sin (* yaw 0.5))) (cos-yaw (cos (* yaw 0.5)))) + (float-vector (+ (* cos-roll cos-pitch cos-yaw) (* sin-roll sin-pitch sin-yaw)) + (- (* sin-roll cos-pitch cos-yaw) (* cos-roll sin-pitch sin-yaw)) + (+ (* cos-roll sin-pitch cos-yaw) (* sin-roll cos-pitch sin-yaw)) + (- (* cos-roll cos-pitch sin-yaw) (* sin-roll sin-pitch cos-yaw))))) + (:create-quaternion-msg-from-rpy + (roll pitch yaw) + (let* ((q (send self :create-quaternion-from-rpy roll pitch yaw)) + (qx (elt q 1)) (qy (elt q 2)) (qz (elt q 3)) (qw (elt q 0))) + (instance geometry_msgs::quaternion :init :x qx :y qy :z qz :w qw))) + ) + +(defun spot-init (&optional (create-viewer)) + (unless (boundp '*spot*) (spot) (send *spot* :reset-pose)) + (unless (ros::ok) (ros::roseus "spot_eus_interface")) + (unless (boundp '*ri*) (setq *ri* (instance spot-interface :init))) + + (ros::spin-once) + (send *ri* :spin-once) + (send *ri* :claim) + (while (member (send *ri* :state :power-state-motor-power-state) (list 'off nil)) + (unix::sleep 1) + (ros::ros-info "powering on...") + (send *ri* :power-on)) + ;; + (unless (every #'(lambda (x) (eq x 'made)) (mapcar #'(lambda (x) (cdr (assoc :contact x))) (send *ri* :state :feet))) + (ros::ros-info "run (send *ri* :stand) to stand the robot")) + + (when create-viewer (objects (list *spot*))) + ) diff --git a/jsk_spot_robot/spoteus/spot-utils.l b/jsk_spot_robot/spoteus/spot-utils.l new file mode 100644 index 0000000000..86e7b92b1b --- /dev/null +++ b/jsk_spot_robot/spoteus/spot-utils.l @@ -0,0 +1,143 @@ +(require :spot "package://spoteus/spot.l") + +(unless (assoc :init-orig (send spot-robot :methods)) + (rplaca (assoc :init (send spot-robot :methods)) :init-orig)) + +(defmethod spot-robot + (:init + (&rest args) ;; fix colors + (dolist (b (list :_make_instance_body_geom0 + :_make_instance_front_left_upper_leg_geom0 + :_make_instance_front_left_lower_leg_geom0 + :_make_instance_front_right_upper_leg_geom0 + :_make_instance_front_right_lower_leg_geom0 + :_make_instance_rear_left_upper_leg_geom0 + :_make_instance_rear_left_lower_leg_geom0 + :_make_instance_rear_right_upper_leg_geom0 + :_make_instance_rear_right_lower_leg_geom0)) + (rplacd (assoc b (send (class self) :methods)) + (cdr (subst '(list :diffuse #f(1.00 0.84 0.32 0)) + '(list :diffuse (float-vector 1.000000 1.000000 1.000000 1.000000)) + (assoc b (send (class self) :methods))))) + ) + (dolist (b (list :_make_instance_arm0.link_sh0_geom0 + :_make_instance_arm0.link_sh1_geom0 + :_make_instance_arm0.link_hr0_geom0 +; :_make_instance_arm0.link_el0_geom0 + :_make_instance_arm0.link_el1_geom0 + :_make_instance_arm0.link_wr0_geom0 + :_make_instance_arm0.link_wr1_geom0 + :_make_instance_arm0.link_fngr_geom0 + )) + (rplacd (assoc b (send (class self) :methods)) + (cdr (subst '(list :diffuse #f(1.00 0.84 0.32 0)) + '(list :diffuse (float-vector 0.200000 0.200000 0.200000 1.000000)) + (assoc b (send (class self) :methods))))) + ) + (dolist (b (list :_make_instance_front_left_hip_geom0 + :_make_instance_front_right_hip_geom0 + :_make_instance_rear_left_hip_geom0 + :_make_instance_rear_right_hip_geom0)) + (rplacd (assoc b (send (class self) :methods)) + (cdr (subst '(list :diffuse #f(0.1 0.1 0.1 0)) + '(list :diffuse (float-vector 1.000000 1.000000 1.000000 1.000000)) + (assoc b (send (class self) :methods))))) + ) + (prog1 + (send* self :init-orig args) + ;; add head-end-coords (camera coords) + (let ((head-end-coords (make-cascoords :coords (send arm-end-coords :copy-worldcoords)))) + (send head-end-coords :rotate pi/2 :y) + (send (send arm-end-coords :parent) :assoc head-end-coords) + (send self :put :head-end-coords head-end-coords)))) + ;; add head method so that (send *spot* :head :look-at) works + (:head + (&rest args) + (unless args (setq args (list nil))) + (case (car args) + (:end-coords + (user::forward-message-to (send self :get :head-end-coords) (cdr args))) + (:look-at + (let ((target-coords + (orient-coords-to-axis ;; orient target-coords to look-at direction + (make-coords :pos (cadr args)) + (v- (cadr args) (send self :head :end-coords :worldpos)))) + (head-coords (send self :head :end-coords :copy-worldcoords))) + ;; align target-coords x (image-y) direction to head direction + (send target-coords :rotate + (acos (v. (send target-coords :axis :x) + (send head-coords :axis :x))) :z) + (send* self :head :inverse-kinematics target-coords + :move-target (send self :head :end-coords) + :rotation-axis t + :translation-axis nil + :target-coords target-coords ;; for debug-view + (cddr args)))) + ;;(send* self :inverse-kinematics-loop-for-look-at :head (cdr args))) + (t + (send* self :limb :arm args)))) + (:legs ;; support legs for all limbs + (&rest args) + (case (car args) + (:crotch-r + (forward-message-to front_left_hip_x_jt (cdr args)) + (forward-message-to front_right_hip_x_jt (cdr args)) + (forward-message-to rear_left_hip_x_jt (cdr args)) + (forward-message-to rear_right_hip_x_jt (cdr args))) + (:crotch-p + (forward-message-to front_left_hip_y_jt (cdr args)) + (forward-message-to front_right_hip_y_jt (cdr args)) + (forward-message-to rear_left_hip_y_jt (cdr args)) + (forward-message-to rear_right_hip_y_jt (cdr args))) + (:knee-p + (forward-message-to front_left_knee_jt (cdr args)) + (forward-message-to front_right_knee_jt (cdr args)) + (forward-message-to rear_left_knee_jt (cdr args)) + (forward-message-to rear_right_knee_jt (cdr args))))) + (:body-inverse-kinematics + (target-coords + &rest args + &key (move-target) (link-list) + (min (float-vector -500 -500 -500 -200 -200 -100)) + (max (float-vector 500 500 500 200 200 100)) + (target-centroid-pos) + (rotation-axis) + (root-link-virtual-joint-weight #f(0.0 0.0 0.1 0.1 0.5 0.5)) + &allow-other-keys) + "The purpose of this function is to use :fullbody-inverse-kinematics by only specifying body target-coords. +Example: +(send *spot* :body-inverse-kinematics (make-coords :pos #f(0 0 50))) +" + (let ((body-coords (make-cascoords :coords (send (send self :coords) :copy-worldcoords)))) + (send body_lk :assoc body-coords) + (setq target-coords + (list target-coords + (send self :larm :end-coords :copy-worldcoords) + (send self :rarm :end-coords :copy-worldcoords) + (send self :lleg :end-coords :copy-worldcoords) + (send self :rleg :end-coords :copy-worldcoords))) + (unless move-target + (setq move-target + (list body-coords + (send self :larm :end-coords) + (send self :rarm :end-coords) + (send self :lleg :end-coords) + (send self :rleg :end-coords)))) + (unless link-list + (setq link-list + (mapcar #'(lambda (limb) (send self :link-list (send limb :parent))) move-target))) + (unless rotation-axis + (setq rotation-axis (list t nil nil nil nil))) + (prog1 + (send-super* + :fullbody-inverse-kinematics target-coords + :move-target move-target + :link-list link-list + :min min + :max max + :target-centroid-pos target-centroid-pos + :rotation-axis rotation-axis + :root-link-virtual-joint-weight root-link-virtual-joint-weight + args) + (send body_lk :dissoc body-coords)))) + ) diff --git a/jsk_spot_robot/spoteus/spot.yaml b/jsk_spot_robot/spoteus/spot.yaml new file mode 100644 index 0000000000..1157a69b53 --- /dev/null +++ b/jsk_spot_robot/spoteus/spot.yaml @@ -0,0 +1,54 @@ +## +## - collada_joint_name : euslisp_joint_name (start with :) +## + +lfleg: + - front_left_hip_x : lfleg-crotch-r + - front_left_hip_y : lfleg-crotch-p + - front_left_knee : lfleg-knee-p +rfleg: + - front_right_hip_x : rfleg-crotch-r + - front_right_hip_y : rfleg-crotch-p + - front_right_knee : rfleg-knee-p +lrleg: + - rear_left_hip_x : lrleg-crotch-r + - rear_left_hip_y : lrleg-crotch-p + - rear_left_knee : lrleg-knee-p +rrleg: + - rear_right_hip_x : rrleg-crotch-r + - rear_right_hip_y : rrleg-crotch-p + - rear_right_knee : rrleg-knee-p +arm: + - arm0.sh0 : arm-shoulder-y + - arm0.sh1 : arm-shoulder-p + - arm0.el0 : arm-elbow-p + - arm0.el1 : arm-elbow-r + - arm0.wr0 : arm-wrist-p + - arm0.wr1 : arm-wrist-r +hand: + - arm0.f1x : hand-gripper-p + + +angle-vector: + reset-pose : [0.0, 45.0, -90.0, 0.0, 45.0, -90.0, 0.0, 45.0, -90.0, 0.0, 45.0, -90.0, 0.0, -170.0, 160.0, 0.0, 10.0, 0.0, 0] + +lfleg-end-coords: + parent : front_left_lower_leg + translate : [0, 0, -0.38] + rotate : [0, 1, 0, 0] +rfleg-end-coords: + parent : front_right_lower_leg + translate : [0, 0, -0.38] + rotate : [0, 1, 0, 0] +lrleg-end-coords: + parent : rear_left_lower_leg + translate : [0, 0, -0.38] + rotate : [0, 1, 0, 0] +rrleg-end-coords: + parent : rear_right_lower_leg + translate : [0, 0, -0.38] + rotate : [0, 1, 0, 0] +arm-end-coords: + parent: arm0.link_wr1 + translate : [0.175, 0, -0.03] + rotate : [1, 0, 0, 0] diff --git a/jsk_spot_robot/spoteus/test/test-spot.l b/jsk_spot_robot/spoteus/test/test-spot.l new file mode 100755 index 0000000000..1d378779a4 --- /dev/null +++ b/jsk_spot_robot/spoteus/test/test-spot.l @@ -0,0 +1,37 @@ +#!/usr/bin/env roseus +(require :unittest "lib/llib/unittest.l") +(require "package://spoteus/spot-utils.l") +(require "package://spoteus/spot-interface.l") + +(init-unit-test) + +(deftest test-pose + (let (robot) + (setq robot (instance spot-robot :init)) + ;; move robot arm to #f(600 0 600) relative to body + (send robot :arm :inverse-kinematics (send (make-coords :pos #f(600 0 600)) :transform robot)) + )) + +(defun look-at (robot) + (let ((targets (list (make-cube 100 100 100 :pos #f(2000 0 0)) + (make-cube 100 100 100 :pos #f(2000 1000 2000)) + (make-cube 100 100 100 :pos #f(2000 -1000 1000)) + (make-cube 100 100 100 :pos #f(0 2000 2000)) + (make-cube 100 100 100 :pos #f(0 -2000 1000))))) + (objects (append (list robot) targets)) + (dolist (target targets) + (send robot :head :look-at (send target :worldpos) :debug-view :no-mesage) + (send *irtviewer* :draw-objects)))) + +(deftest test-spot-look-at + (let (robot) + (setq robot (instance spot-robot :init)) + (look-at robot) + (send robot :arm :inverse-kinematics (send (make-coords :pos #f(600 0 600)) :transform robot)) + (look-at robot))) + +(deftest test-spot-init + (spot-init)) + +(run-all-tests) +(exit) diff --git a/jsk_spot_robot/spoteus/test/test-spot.test b/jsk_spot_robot/spoteus/test/test-spot.test new file mode 100644 index 0000000000..b2e8fb2164 --- /dev/null +++ b/jsk_spot_robot/spoteus/test/test-spot.test @@ -0,0 +1,3 @@ + + +