diff --git a/.github/workflows/apk.yaml b/.github/workflows/apk.yaml
index ea3e5768..9127ee7c 100644
--- a/.github/workflows/apk.yaml
+++ b/.github/workflows/apk.yaml
@@ -1,5 +1,3 @@
-# Check for lint error and auto correct them
-
name: Compile APK
on: ['push', 'pull_request', 'workflow_dispatch']
diff --git a/.github/workflows/ci_pr.yml b/.github/workflows/ci_pr.yml
index d768e72b..6a1f767b 100644
--- a/.github/workflows/ci_pr.yml
+++ b/.github/workflows/ci_pr.yml
@@ -7,27 +7,6 @@ on:
jobs:
- build-xenial:
-
- runs-on: ubuntu-20.04
-
- steps:
-
- - name: Checkout Astrobee
- uses: actions/checkout@v3
- with:
- repository: nasa/astrobee
- path: astrobee/
-
- - name: Checkout ISAAC
- uses: actions/checkout@v3
- with:
- submodules: recursive
- path: isaac/
-
- - name: Build code for isaac:astrobee Ubuntu 16
- run: isaac/scripts/docker/build.sh --remote --xenial --astrobee-source-path astrobee/
-
build-focal:
runs-on: ubuntu-20.04
diff --git a/.github/workflows/ci_push.yml b/.github/workflows/ci_push.yml
index c3fa9ed5..f5a49a6a 100644
--- a/.github/workflows/ci_push.yml
+++ b/.github/workflows/ci_push.yml
@@ -8,65 +8,6 @@ on:
jobs:
- build-xenial:
-
- runs-on: ubuntu-20.04
-
- steps:
-
- - name: Checkout Astrobee
- uses: actions/checkout@v3
- with:
- repository: nasa/astrobee
- path: astrobee/
-
- - name: Checkout ISAAC
- uses: actions/checkout@v3
- with:
- submodules: recursive
- path: isaac/
-
- - name: Build code for isaac:astrobee Ubuntu 16
- run: docker build isaac -f isaac/scripts/docker/isaac_astrobee.Dockerfile
- --build-arg UBUNTU_VERSION=16.04
- --build-arg ROS_VERSION=kinetic
- --build-arg PYTHON=''
- --build-arg REMOTE=ghcr.io/nasa
- -t ghcr.io/${{ github.repository_owner }}/isaac:latest-astrobee-ubuntu16.04
-
- - name: Build code for isaac:latest Ubuntu 16
- run: docker build isaac -f isaac/scripts/docker/isaac.Dockerfile
- --build-arg UBUNTU_VERSION=16.04
- --build-arg ROS_VERSION=kinetic
- --build-arg PYTHON=''
- --build-arg REMOTE=ghcr.io/nasa
- -t ghcr.io/${{ github.repository_owner }}/isaac:latest-ubuntu16.04
-
- - name: Build messages dockers for Ubuntu 16 (astrobee)
- run: docker build astrobee -f isaac/scripts/docker/astrobee_msgs.Dockerfile
- --build-arg UBUNTU_VERSION=16.04
- --build-arg ROS_VERSION=kinetic
- --build-arg PYTHON=''
- -t ghcr.io/${{ github.repository_owner }}/isaac:astrobee-msgs-ubuntu16.04
-
- - name: Build messages dockers for Ubuntu 16 (isaac)
- run: docker build isaac -f isaac/scripts/docker/isaac_msgs.Dockerfile
- --build-arg UBUNTU_VERSION=16.04
- --build-arg ROS_VERSION=kinetic
- --build-arg PYTHON=''
- --build-arg REMOTE=ghcr.io/nasa
- -t ghcr.io/${{ github.repository_owner }}/isaac:msgs-ubuntu16.04
-
- - name: Log in to registry
- run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.repository_owner }} --password-stdin
-
- - name: Push Docker image
- run: |
- if [ "${{ github.repository_owner }}" = "nasa" ]; then docker push ghcr.io/${{ github.repository_owner }}/isaac:latest-astrobee-ubuntu16.04; fi;
- if [ "${{ github.repository_owner }}" = "nasa" ]; then docker push ghcr.io/${{ github.repository_owner }}/isaac:latest-ubuntu16.04; fi;
- if [ "${{ github.repository_owner }}" = "nasa" ]; then docker push ghcr.io/${{ github.repository_owner }}/isaac:astrobee-msgs-ubuntu16.04; fi;
- if [ "${{ github.repository_owner }}" = "nasa" ]; then docker push ghcr.io/${{ github.repository_owner }}/isaac:msgs-ubuntu16.04; fi;
-
build-focal:
runs-on: ubuntu-20.04
diff --git a/.github/workflows/ci_release.yml b/.github/workflows/ci_release.yml
index c7b48cc9..bcaaa749 100644
--- a/.github/workflows/ci_release.yml
+++ b/.github/workflows/ci_release.yml
@@ -8,68 +8,6 @@ on:
jobs:
- build-xenial:
-
- runs-on: ubuntu-20.04
-
- steps:
- - uses: actions/checkout@v3
-
- - name: Checkout Astrobee
- uses: actions/checkout@v3
- with:
- repository: nasa/astrobee
- path: astrobee/
-
- - name: Checkout ISAAC
- uses: actions/checkout@v3
- with:
- submodules: recursive
- path: isaac/
-
- - name: Build code for isaac:astrobee Ubuntu 16
- run: docker build isaac -f isaac/scripts/docker/isaac_astrobee.Dockerfile
- --build-arg UBUNTU_VERSION=16.04
- --build-arg ROS_VERSION=kinetic
- --build-arg PYTHON=''
- --build-arg REMOTE=ghcr.io/nasa
- -t isaac/isaac:latest-astrobee-ubuntu16.04
-
- - name: Build code for isaac:latest Ubuntu 16
- run: docker build isaac -f isaac/scripts/docker/isaac.Dockerfile
- --build-arg UBUNTU_VERSION=16.04
- --build-arg ROS_VERSION=kinetic
- --build-arg PYTHON=''
- --build-arg REMOTE=isaac
- -t isaac/isaac:latest-ubuntu16.04
-
- - name: Build messages dockers for Ubuntu 16 (astrobee)
- run: docker build astrobee -f isaac/scripts/docker/astrobee_msgs.Dockerfile
- --build-arg UBUNTU_VERSION=16.04
- --build-arg ROS_VERSION=kinetic
- --build-arg PYTHON=''
- -t isaac/isaac:astrobee-msgs-ubuntu16.04
-
- - name: Build messages dockers for Ubuntu 16 (isaac)
- run: docker build isaac -f isaac/scripts/docker/isaac_msgs.Dockerfile
- --build-arg UBUNTU_VERSION=16.04
- --build-arg ROS_VERSION=kinetic
- --build-arg PYTHON=''
- --build-arg REMOTE=isaac
- -t isaac/isaac:msgs-ubuntu16.04
-
- - name: Log in to registry
- run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.repository_owner }} --password-stdin
-
- - name: Push Docker image
- run: |
- cd isaac
- export VERSION=`grep -w -m 1 "Release" RELEASE.md | awk '{print $3}'`
- docker tag isaac/isaac:latest-astrobee-ubuntu16.04 ghcr.io/${{ github.repository_owner }}/isaac:v${VERSION}-astrobee-ubuntu16.04
- if [ "${{ github.repository_owner }}" = "nasa" ]; then docker push ghcr.io/${{ github.repository_owner }}/isaac:v${VERSION}-astrobee-ubuntu16.04; fi;
- docker tag isaac/isaac:latest-ubuntu16.04 ghcr.io/${{ github.repository_owner }}/isaac:v${VERSION}-ubuntu16.04
- if [ "${{ github.repository_owner }}" = "nasa" ]; then docker push ghcr.io/${{ github.repository_owner }}/isaac:v${VERSION}-ubuntu16.04; fi;
-
build-focal:
runs-on: ubuntu-20.04
diff --git a/.gitmodules b/.gitmodules
index b94bfb0e..6d14a890 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -1,3 +1,11 @@
[submodule "isaac_msgs"]
path = communications/isaac_msgs
url = https://github.com/nasa/isaac_msgs.git
+[submodule "astrobee/survey_manager/survey_planner/src/ros2_planning_system"]
+ path = astrobee/survey_manager/survey_planner/src/ros2_planning_system
+ url = https://github.com/bckempa/ros2_planning_system
+ branch = noetic-devel
+[submodule "astrobee/survey_manager/survey_planner/src/ros1_lifecycle"]
+ path = astrobee/survey_manager/survey_planner/src/ros1_lifecycle
+ url = https://github.com/bckempa/ros1_lifecycle
+ branch = noetic-devel
diff --git a/README.md b/README.md
index 3383cae3..b0287805 100644
--- a/README.md
+++ b/README.md
@@ -33,6 +33,7 @@ the [Astrobee robot](https://github.com/nasa/astrobee). This repository includes
- [Dense mapping](https://nasa.github.io/isaac/html/geometric_streaming_mapper.html) to create a textured 3D map
- [Volumetric mapping](https://nasa.github.io/isaac/html/volumetric_mapper.html) to map volumetric signals, such as WiFi.
- [Image analysis](https://nasa.github.io/isaac/html/ano.html) module to train a neural network to detect anomalies
+- [Survey Manager](https://nasa.github.io/isaac/html/survey.html) semi-autonomous planning and execution of imaging tasks.
You may also be interested in the separate repository for the [ISAAC User Interface](https://github.com/nasa/isaac_user_interface),
which enables monitoring of multiple robots through a web browser.
diff --git a/astrobee/readme.md b/astrobee/readme.md
index 89e60ac6..3f2f50d3 100644
--- a/astrobee/readme.md
+++ b/astrobee/readme.md
@@ -9,4 +9,6 @@ The Astrobeemodule contains the ISAAC additions to the Astrobee FSW
\subpage sim : Gazebo plugins for simulation including acoustic and heat camera, RFID reader and emmiter, as well as WiFi reader and emmiters.
-\subpage gs_action_helper : Guest science action helper used to communicate with the ground through DDS messages
\ No newline at end of file
+\subpage gs_action_helper : Guest science action helper used to communicate with the ground through DDS messages
+
+\subpage survey : Semi-autonomous survey tasking of Astrobees.
diff --git a/astrobee/survey_manager/readme.md b/astrobee/survey_manager/readme.md
new file mode 100644
index 00000000..75b544a4
--- /dev/null
+++ b/astrobee/survey_manager/readme.md
@@ -0,0 +1,5 @@
+\page survey Survey Manager
+
+The ISAAC Survey Manager provides semi-autonomous planning and execution of panorama and stereophotography tasks to reduce operator loading and improve activity utilization.
+
+\subpage survey_planner : Planning and scheduling of queued survey actions.
diff --git a/astrobee/survey_manager/survey_planner/behavior_trees/collecting_panoramas.xml b/astrobee/survey_manager/survey_planner/behavior_trees/collecting_panoramas.xml
new file mode 100644
index 00000000..d8cb181f
--- /dev/null
+++ b/astrobee/survey_manager/survey_planner/behavior_trees/collecting_panoramas.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/astrobee/survey_manager/survey_planner/behavior_trees/move_to.xml b/astrobee/survey_manager/survey_planner/behavior_trees/move_to.xml
new file mode 100644
index 00000000..a708b178
--- /dev/null
+++ b/astrobee/survey_manager/survey_planner/behavior_trees/move_to.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/astrobee/survey_manager/survey_planner/behavior_trees/move_to_inspect.xml b/astrobee/survey_manager/survey_planner/behavior_trees/move_to_inspect.xml
new file mode 100644
index 00000000..b40db393
--- /dev/null
+++ b/astrobee/survey_manager/survey_planner/behavior_trees/move_to_inspect.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
diff --git a/astrobee/survey_manager/survey_planner/data/jem_survey_dynamic.yaml b/astrobee/survey_manager/survey_planner/data/jem_survey_dynamic.yaml
new file mode 100644
index 00000000..680641ff
--- /dev/null
+++ b/astrobee/survey_manager/survey_planner/data/jem_survey_dynamic.yaml
@@ -0,0 +1,54 @@
+# Copyright (c) 2023, United States Government, as represented by the
+# Administrator of the National Aeronautics and Space Administration.
+#
+# All rights reserved.
+#
+# The "ISAAC - Integrated System for Autonomous and Adaptive Caretaking
+# platform" software is licensed under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with the
+# License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+# Example dynamic configuration info used when generating a PDDL problem. For now, this is goal
+# conditions and initial state. A likely conops is that the initial version of this file for a
+# specific activity would be hand-generated, but it might later be automatically regenerated by the
+# survey manager when a replan is needed (remove completed/failed goals, add retry goals, update
+# initial state to match actual current state, etc.) See also jem_survey_static.yaml.
+
+goals:
+
+- {type: panorama, robot: bumble, order: 0, location: jem_bay4, run: 1}
+- {type: panorama, robot: bumble, order: 1, location: jem_bay3, run: 1}
+- {type: panorama, robot: bumble, order: 2, location: jem_bay2, run: 1}
+- {type: panorama, robot: bumble, order: 3, location: jem_bay1, run: 1}
+- {type: stereo, robot: bumble, order: 4, trajectory: jem_bay1_to_bay3, run: 1}
+
+# We want Bumble to return to its berth at the end of the run, but adding this goal causes POPF to
+# get confused and greatly increase the total run time. For some reason, it doesn't notice it can
+# use the same plan as without this goal and then add some motion actions at the end to achieve this
+# goal. Instead, it falls back to only undocking one robot at a time, which slows things down by
+# about 2x.
+# - {type: robot_at, robot: bumble, location: berth1}
+
+- {type: panorama, robot: honey, order: 0, location: jem_bay7, run: 1}
+- {type: panorama, robot: honey, order: 1, location: jem_bay6, run: 1}
+- {type: panorama, robot: honey, order: 2, location: jem_bay5, run: 1}
+
+# This is another objective we want to include that for some reason causes POPF to fail to generate
+# a plan (hang indefinitely). No obvious reason why it should cause a problem.
+# - {type: stereo, robot: honey, order: 3, trajectory: jem_bay4_to_bay7, run: 1}
+
+- {type: robot_at, robot: honey, location: berth2}
+
+init:
+ bumble:
+ location: berth1
+ honey:
+ location: berth2
diff --git a/astrobee/survey_manager/survey_planner/data/jem_survey_static.yaml b/astrobee/survey_manager/survey_planner/data/jem_survey_static.yaml
new file mode 100644
index 00000000..db617126
--- /dev/null
+++ b/astrobee/survey_manager/survey_planner/data/jem_survey_static.yaml
@@ -0,0 +1,59 @@
+# Copyright (c) 2023, United States Government, as represented by the
+# Administrator of the National Aeronautics and Space Administration.
+#
+# All rights reserved.
+#
+# The "ISAAC - Integrated System for Autonomous and Adaptive Caretaking
+# platform" software is licensed under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with the
+# License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+# Static configuration info used when generating a PDDL problem and also when executing actions in a
+# PDDL plan. This info should be static in the sense that it nominally doesn't change during an ISS
+# activity, so the survey manager doesn't have to modify it. However, an edge case is that an
+# operator might want to manually edit something in here (like add a new symbolic location or nudge
+# the position of a named bay away from an obstacle) and restart the survey manager. On the other
+# hand, info that is *expected* to change as part of the survey manager conops belongs in
+# jem_survey_dynamic.yaml.
+
+# Useful reference for positions and stereo survey trajectories:
+# https://babelfish.arc.nasa.gov/confluence/display/FFOPS/ISAAC+Phase+1X+Activity+9+Ground+Procedure
+
+bays:
+ # 3D coordinates for symbolic bays in ISS Analysis Coordinate System used by Astrobee
+ jem_bay1: [11.0, -4.0, 4.8]
+ jem_bay2: [11.0, -5.0, 4.8]
+ jem_bay3: [11.0, -6.0, 4.8]
+ jem_bay4: [11.0, -7.0, 4.8]
+ jem_bay5: [11.0, -8.0, 4.8]
+ jem_bay6: [11.0, -9.0, 4.8]
+ jem_bay7: [11.0, -9.7, 4.8]
+
+bogus_bays: [jem_bay0, jem_bay8]
+berths: [berth1, berth2]
+robots: [bumble, honey]
+num_orders: 10
+
+stereo:
+ # Meta-data about stereo survey options
+ jem_bay1_to_bay3:
+ # fplan: Name of external fplan specification of trajectory in astrobee_ops/gds/plans/ISAAC/
+ fplan: "jem_stereo_mapping_bay1_to_bay3.fplan"
+ # base_location: Where trajectory starts and ends for planning purposes (rough location, not exact)
+ base_location: jem_bay1
+ # bound_location: The other end of the interval covered by the trajectory, for planner collision
+ # check purposes. (Note a trajectory may fly a bit into a bay that it doesn't claim to cover.
+ # The two surveys that cover the module purposefully overlap.)
+ bound_location: jem_bay4
+ jem_bay4_to_bay7:
+ fplan: "jem_stereo_mapping_bay4_to_bay7.fplan"
+ base_location: jem_bay7
+ bound_location: jem_bay4
diff --git a/astrobee/survey_manager/survey_planner/data/sample_output_plan.txt b/astrobee/survey_manager/survey_planner/data/sample_output_plan.txt
new file mode 100644
index 00000000..6a119031
--- /dev/null
+++ b/astrobee/survey_manager/survey_planner/data/sample_output_plan.txt
@@ -0,0 +1,31 @@
+$ time popf domain_survey.pddl problem_jem_survey.pddl
+Constructing lookup tables: [10%] [20%] [30%] [40%] [50%] [60%] [70%] [80%] [90%] [100%]
+Post filtering unreachable actions: [10%] [20%] [30%] [40%] [50%] [60%] [70%] [80%] [90%] [100%]
+3% of the ground temporal actions in this problem are compression-safe
+b (25.000 | 30.000)b (24.000 | 50.001)b (23.000 | 70.002)b (22.000 | 90.003)b (21.000 | 990.004)b (20.000 | 990.004)b (19.000 | 1010.005)b (18.000 | 1910.006)b (17.000 | 1910.006)b (16.000 | 1930.007)b (15.000 | 2830.008)b (14.000 | 2830.008)b (13.000 | 2850.009)b (12.000 | 3750.010)b (11.000 | 3750.010)b (10.000 | 4350.011)b (9.000 | 4350.011)b (8.000 | 4350.011)b (7.000 | 4350.011)b (6.000 | 4350.011)b (5.000 | 4350.011)b (4.000 | 5270.013)b (3.000 | 5270.013)b (2.000 | 5290.014)b (1.000 | 5310.015);;;; Solution Found
+; Time 0.56
+0.000: (undock bumble berth1 jem_bay7 jem_bay8 jem_bay6) [30.000]
+30.001: (move bumble jem_bay7 jem_bay6 jem_bay5) [20.000]
+50.002: (move bumble jem_bay6 jem_bay5 jem_bay4) [20.000]
+70.003: (move bumble jem_bay5 jem_bay4 jem_bay3) [20.000]
+70.003: (undock honey berth2 jem_bay7 jem_bay8 jem_bay6) [30.000]
+90.004: (panorama bumble o0 jem_bay4 run1) [900.000]
+100.004: (panorama honey o0 jem_bay7 run1) [900.000]
+990.005: (move bumble jem_bay4 jem_bay3 jem_bay2) [20.000]
+1000.005: (move honey jem_bay7 jem_bay6 jem_bay5) [20.000]
+1010.006: (panorama bumble o1 jem_bay3 run1) [900.000]
+1020.006: (panorama honey o1 jem_bay6 run1) [900.000]
+1910.007: (move bumble jem_bay3 jem_bay2 jem_bay1) [20.000]
+1930.008: (panorama bumble o2 jem_bay2 run1) [900.000]
+2830.009: (move bumble jem_bay2 jem_bay1 jem_bay0) [20.000]
+2850.010: (panorama bumble o3 jem_bay1 run1) [900.000]
+3750.011: (stereo bumble o4 jem_bay1 jem_bay4 jem_bay5 jem_bay3 run1) [600.000]
+4350.012: (move honey jem_bay6 jem_bay5 jem_bay4) [20.000]
+4370.013: (panorama honey o2 jem_bay5 run1) [900.000]
+5270.014: (move honey jem_bay5 jem_bay6 jem_bay7) [20.000]
+5290.015: (move honey jem_bay6 jem_bay7 jem_bay8) [20.000]
+5310.016: (dock honey jem_bay7 berth2) [30.000]
+
+real 0m0.585s
+user 0m0.553s
+sys 0m0.032s
diff --git a/astrobee/survey_manager/survey_planner/data/sample_output_plan.yaml b/astrobee/survey_manager/survey_planner/data/sample_output_plan.yaml
new file mode 100644
index 00000000..d17b7e1c
--- /dev/null
+++ b/astrobee/survey_manager/survey_planner/data/sample_output_plan.yaml
@@ -0,0 +1,200 @@
+- start_time_seconds: '0.000'
+ action:
+ type: undock
+ robot: bumble
+ duration_seconds: '30.000'
+- start_time_seconds: '30.001'
+ action:
+ type: move
+ robot: bumble
+ to_name: jem_bay6
+ to_pos:
+ - 11.0
+ - -9.0
+ - 4.8
+ duration_seconds: '20.000'
+- start_time_seconds: '50.002'
+ action:
+ type: move
+ robot: bumble
+ to_name: jem_bay5
+ to_pos:
+ - 11.0
+ - -8.0
+ - 4.8
+ duration_seconds: '20.000'
+- start_time_seconds: '70.003'
+ action:
+ type: move
+ robot: bumble
+ to_name: jem_bay4
+ to_pos:
+ - 11.0
+ - -7.0
+ - 4.8
+ duration_seconds: '20.000'
+- start_time_seconds: '70.003'
+ action:
+ type: undock
+ robot: honey
+ duration_seconds: '30.000'
+- start_time_seconds: '90.004'
+ action:
+ type: panorama
+ robot: bumble
+ location_name: jem_bay4
+ location_pos:
+ - 11.0
+ - -7.0
+ - 4.8
+ run: 1
+ duration_seconds: '900.000'
+- start_time_seconds: '100.004'
+ action:
+ type: panorama
+ robot: honey
+ location_name: jem_bay7
+ location_pos:
+ - 11.0
+ - -9.7
+ - 4.8
+ run: 1
+ duration_seconds: '900.000'
+- start_time_seconds: '990.005'
+ action:
+ type: move
+ robot: bumble
+ to_name: jem_bay3
+ to_pos:
+ - 11.0
+ - -6.0
+ - 4.8
+ duration_seconds: '20.000'
+- start_time_seconds: '1000.005'
+ action:
+ type: move
+ robot: honey
+ to_name: jem_bay6
+ to_pos:
+ - 11.0
+ - -9.0
+ - 4.8
+ duration_seconds: '20.000'
+- start_time_seconds: '1010.006'
+ action:
+ type: panorama
+ robot: bumble
+ location_name: jem_bay3
+ location_pos:
+ - 11.0
+ - -6.0
+ - 4.8
+ run: 1
+ duration_seconds: '900.000'
+- start_time_seconds: '1020.006'
+ action:
+ type: panorama
+ robot: honey
+ location_name: jem_bay6
+ location_pos:
+ - 11.0
+ - -9.0
+ - 4.8
+ run: 1
+ duration_seconds: '900.000'
+- start_time_seconds: '1910.007'
+ action:
+ type: move
+ robot: bumble
+ to_name: jem_bay2
+ to_pos:
+ - 11.0
+ - -5.0
+ - 4.8
+ duration_seconds: '20.000'
+- start_time_seconds: '1930.008'
+ action:
+ type: panorama
+ robot: bumble
+ location_name: jem_bay2
+ location_pos:
+ - 11.0
+ - -5.0
+ - 4.8
+ run: 1
+ duration_seconds: '900.000'
+- start_time_seconds: '2830.009'
+ action:
+ type: move
+ robot: bumble
+ to_name: jem_bay1
+ to_pos:
+ - 11.0
+ - -4.0
+ - 4.8
+ duration_seconds: '20.000'
+- start_time_seconds: '2850.010'
+ action:
+ type: panorama
+ robot: bumble
+ location_name: jem_bay1
+ location_pos:
+ - 11.0
+ - -4.0
+ - 4.8
+ run: 1
+ duration_seconds: '900.000'
+- start_time_seconds: '3750.011'
+ action:
+ type: stereo
+ robot: bumble
+ fplan: jem_stereo_mapping_bay1_to_bay3.fplan
+ run: 1
+ duration_seconds: '600.000'
+- start_time_seconds: '4350.012'
+ action:
+ type: move
+ robot: honey
+ to_name: jem_bay5
+ to_pos:
+ - 11.0
+ - -8.0
+ - 4.8
+ duration_seconds: '20.000'
+- start_time_seconds: '4370.013'
+ action:
+ type: panorama
+ robot: honey
+ location_name: jem_bay5
+ location_pos:
+ - 11.0
+ - -8.0
+ - 4.8
+ run: 1
+ duration_seconds: '900.000'
+- start_time_seconds: '5270.014'
+ action:
+ type: move
+ robot: honey
+ to_name: jem_bay6
+ to_pos:
+ - 11.0
+ - -9.0
+ - 4.8
+ duration_seconds: '20.000'
+- start_time_seconds: '5290.015'
+ action:
+ type: move
+ robot: honey
+ to_name: jem_bay7
+ to_pos:
+ - 11.0
+ - -9.7
+ - 4.8
+ duration_seconds: '20.000'
+- start_time_seconds: '5310.016'
+ action:
+ type: dock
+ robot: honey
+ berth: berth2
+ duration_seconds: '30.000'
diff --git a/astrobee/survey_manager/survey_planner/pddl/domain_survey.pddl b/astrobee/survey_manager/survey_planner/pddl/domain_survey.pddl
new file mode 100644
index 00000000..e31999fd
--- /dev/null
+++ b/astrobee/survey_manager/survey_planner/pddl/domain_survey.pddl
@@ -0,0 +1,331 @@
+(define (domain survey-manager)
+ (:requirements
+ :strips
+ :typing
+ :durative-actions
+ :fluents
+ )
+
+ (:types
+ location
+ robot
+ order
+ run-number
+ )
+
+ (:predicates
+ ;; === Static predicates ===
+ ;; move-connected: Indicates a robot can travel from ?from to ?to using a single
+ ;; move action. Note that these are interpreted as directed links, so for the usual case
+ ;; that you can travel in either direction, you must assert the predicate both ways.
+ ;; Neither location can be a berth (use the dock-connected predicate for that). However,
+ ;; both bays 6 and 7 should be move-connected to both berth approach points.
+ (move-connected ?from ?to - location)
+
+ ;; location-real: Indicates a location is a real place the robot can fly to. We've added
+ ;; bogus locations bay0 and bay8 to the problem instance to satisfy the implicit assumption
+ ;; of the collision checking that every bay has two neighbors. We assert location-real for
+ ;; the other locations and use it as a precondition on moves so the planner can't
+ ;; accidentally fly to a bogus location.
+ (location-real ?location - location)
+
+ ;; dock-connected: Indicates a robot can move from ?approach to ?berth using a dock
+ ;; action, or from ?berth to ?approach using an undock action.
+ (dock-connected ?approach ?berth - location)
+
+ ;; robots-different: Indicates a != b. Needs to be expressed as a positive predicate
+ ;; so it can be used as a precondition. Must be asserted in both directions.
+ (robots-different ?a ?b - robot)
+
+ ;; locations-different: Indicates a != b. Needs to be expressed as a positive predicate
+ ;; so it can be used as a precondition. Must be asserted in both directions.
+ (locations-different ?a ?b - location)
+
+ ;; === Dynamic predicates ===
+ ;; robot-available: Since a robot can only perform one action at a time in our domain, each
+ ;; action grabs this mutex. In the initial state, both robots should be available.
+ (robot-available ?robot - robot)
+
+ ;; robot-at: Indicates the robot's current position (usually not set during execution of
+ ;; motion actions). In the initial state, both robots should have robot-at set for their
+ ;; initial locations.
+ (robot-at ?robot - robot ?location - location)
+
+ ;; location-available: Indicates that no robot has reserved the location. When stationary,
+ ;; each robot reserves its current location. During a move, it reserves both its ?from
+ ;; location and its ?to location. Collision avoidance checks prevent robots from reserving
+ ;; the same location or reserving adjacent bays while flying. Note: It might be more natural
+ ;; to express this as a location-reserved predicate with the opposite boolean sense, but we
+ ;; need it to be this way so we can use it as a precondition without negating it. In the
+ ;; initial state, we must mark location-available for all locations, real or bogus, except
+ ;; the initial robot locations.
+ (location-available ?location - location)
+
+ ;; need-stereo: If you add a completed-stereo goal, you must also add a need-stereo
+ ;; predicate with identical parameters to the initial state. This is part of a hack that
+ ;; greatly improves planner performance. The need-stereo predicate has been made part of the
+ ;; preconditions of the stereo action, and one of its effects is to clear the
+ ;; predicate. Therefore, the planner won't waste time trying to execute stereo actions that
+ ;; the user didn't explicitly request. Without this hack, the planner run time blows up.
+ (need-stereo ?robot - robot ?order - order ?base ?bound - location ?run-number - run-number)
+
+ ;; === Goal predicates ===
+ ;; completed-panorama: The goal to add if you want the plan to include collecting a
+ ;; panorama. For now, goals specify ?robot and ?order parameters that constrain
+ ;; multi-robot task allocation and task ordering. The ?run-number is used to indicate
+ ;; retries and is meaningless to the planner but helpful for post-run analysis.
+ (completed-panorama
+ ?robot - robot
+ ?order - order
+ ?location - location
+ ?run-number - run-number
+ )
+
+ ;; completed-stereo: The goal to add if you want the plan to include collecting a stereo
+ ;; survey. For now, goals specify ?robot and ?order parameters that constrain multi-robot
+ ;; task allocation and task ordering. The ?run-number is used to indicate retries and is
+ ;; meaningless to the planner but helpful for post-run analysis. The current model for
+ ;; stereo surveys assumes the robot starts and ends the survey at the same location called
+ ;; ?base (these locations only need to be same to the effective precision modeled in the
+ ;; planner, "less than a bay apart"). The ?bound argument indicates the other end of the
+ ;; interval covered by the survey and is used for collision checking. It's assumed that
+ ;; ?base and ?bound are not adjacent locations. If future stereo surveys violate these
+ ;; assumptions the model will need to be revisited.
+ (completed-stereo
+ ?robot - robot
+ ?order - order
+ ?base ?bound - location
+ ?run-number - run-number
+ )
+ )
+
+ (:functions
+ ;; === Static numeric fluents ===
+ ;; order-identity: An identity operator that maps from a symbolic order like o0 to its
+ ;; corresponding numeric value 0.
+ (order-identity ?order - order)
+
+ ;; === Dynamic numeric fluents ===
+ ;; robot-order: Indicates the order of the last action executed by ?robot. Later actions
+ ;; must not have a lower ?order (this only applies to the panorama and stereo actions that
+ ;; take an ?order parameter). In the initial state, each robot must have order -1.
+ (robot-order ?robot - robot)
+ )
+
+ (:durative-action dock
+ :parameters (?robot - robot ?from ?to - location) ;; from bay7 to berth1 or berth2
+ :duration (= ?duration 30)
+ :condition
+ (and
+ ;; Check robot mutex
+ (at start (robot-available ?robot))
+
+ ;; Check parameters make sense
+ (at start (robot-at ?robot ?from))
+ (at start (dock-connected ?from ?to))
+
+ ;; Check collision avoidance
+ (at start (location-available ?to))
+ ; Don't need to check berth neighbors
+ )
+ :effect
+ (and
+ ;; Grab and release robot mutex
+ (at start (not (robot-available ?robot)))
+ (at end (robot-available ?robot))
+
+ ;; Grab and release reserved locations
+ (at start (not (location-available ?to)))
+ (at end (location-available ?from))
+
+ ;; Update robot location
+ (at start (not (robot-at ?robot ?from)))
+ (at end (robot-at ?robot ?to))
+ )
+ )
+
+ (:durative-action undock
+ :parameters (
+ ?robot - robot
+ ?from ?to - location ;; from berth1 or berth2 to bay7
+ ?check1 ?check2 - location ;; neighbors of ?to to check for collision avoidance
+ )
+ :duration (= ?duration 30)
+ :condition
+ (and
+ ;; Check robot mutex
+ (at start (robot-available ?robot))
+
+ ;; Check parameters make sense
+ (at start (robot-at ?robot ?from))
+ (at start (dock-connected ?to ?from))
+ (at start (location-real ?to))
+
+ ;(at start (robot-can-undock-now ?robot))
+
+ ;; Check collision avoidance
+ (at start (location-available ?to))
+ (at start (locations-different ?check1 ?check2))
+ (at start (move-connected ?check1 ?to))
+ (at start (move-connected ?check2 ?to))
+ (at start (location-available ?check1))
+ (at start (location-available ?check2))
+ )
+ :effect
+ (and
+ ;; Grab and release robot mutex
+ (at start (not (robot-available ?robot)))
+ (at end (robot-available ?robot))
+
+ ;; Grab and release reserved locations
+ (at start (not (location-available ?to)))
+ (at end (location-available ?from))
+
+ ;; Update robot location
+ (at start (not (robot-at ?robot ?from)))
+ (at end (robot-at ?robot ?to))
+ )
+ )
+
+ (:durative-action move
+ :parameters (
+ ?robot - robot
+ ?from ?to - location
+ ?check - location ;; neighbor of ?to to check for collision avoidance
+ )
+ :duration (= ?duration 20)
+ :condition
+ (and
+ ;; Check robot mutex
+ (at start (robot-available ?robot))
+
+ ;; Check parameters make sense
+ (at start (robot-at ?robot ?from))
+ (at start (move-connected ?from ?to))
+ (at start (location-real ?to))
+
+ ;; Check collision avoidance
+ (at start (location-available ?to))
+ ;; In general when flying to ?to we collision check whether ?to or either of its
+ ;; neighbors are reserved (by the other robot). In this case, one of the neighbors
+ ;; of ?to is ?from, which can't be reserved by the other robot since this robot
+ ;; previously reserved it. Therefore, we only need to check if the other neighbor
+ ;; (?check) is reserved.
+ (at start (locations-different ?check ?from))
+ (at start (move-connected ?check ?to))
+ (at start (location-available ?check))
+ )
+ :effect
+ (and
+ ;; Grab and release robot mutex
+ (at start (not (robot-available ?robot)))
+ (at end (robot-available ?robot))
+
+ ;; Grab and release reserved locations
+ (at start (not (location-available ?to)))
+ (at end (location-available ?from))
+
+ ;; Update robot location
+ (at start (not (robot-at ?robot ?from)))
+ (at end (robot-at ?robot ?to))
+ )
+ )
+
+ (:durative-action panorama
+ :parameters
+ (
+ ?robot - robot
+ ?order - order
+ ?location - location
+ ?run-number - run-number
+ )
+ ;; ~13 minutes, per https://babelfish.arc.nasa.gov/confluence/display/FFOPS/ISAAC+Phase+1X+Activity+9+Ground+Procedure
+ :duration (= ?duration 780)
+ :condition
+ (and
+ ;; Check robot mutex
+ (at start (robot-available ?robot))
+
+ ;; Check order
+ (at start (< (robot-order ?robot) (order-identity ?order)))
+
+ ;; Check parameters make sense
+ (at start (robot-at ?robot ?location))
+ )
+ :effect
+ (and
+ ;; Grab and release robot mutex
+ (at start (not (robot-available ?robot)))
+ (at end (robot-available ?robot))
+
+ ;; Update order
+ (at end (assign (robot-order ?robot) (order-identity ?order)))
+
+ ;; Mark success
+ (at end (completed-panorama ?robot ?order ?location ?run-number))
+ )
+ )
+
+ (:durative-action stereo
+ :parameters
+ (
+ ?robot - robot
+ ?order - order
+ ;; ?base: The bay where we start and also stop. ?bound: The other end of the survey
+ ?base ?bound - location
+ ;; ?check1 and ?check2: Planner-selected neighbors of ?bound for collision check
+ ?check1 ?check2 - location
+ ?run-number - run-number
+ )
+ :duration (= ?duration 600) ;; 10 minutes
+ :condition
+ (and
+ ;; Check robot mutex
+ (at start (robot-available ?robot))
+
+ ;; Check order
+ (at start (< (robot-order ?robot) (order-identity ?order)))
+
+ ;; Check parameters make sense
+ (at start (robot-at ?robot ?base))
+ (at start (location-real ?bound))
+
+ ;; Check for need-stereo so the planner only tries this action when the user
+ ;; explicitly requests it.
+ (at start (need-stereo ?robot ?order ?base ?bound ?run-number))
+
+ ;; Check collision avoidance
+ (at start (location-available ?bound))
+ (at start (locations-different ?check1 ?check2))
+ (at start (move-connected ?check1 ?bound))
+ (at start (move-connected ?check2 ?bound))
+ (at start (location-available ?check1))
+ (at start (location-available ?check2))
+ )
+ :effect
+ (and
+ ;; Grab and release robot mutex
+ (at start (not (robot-available ?robot)))
+ (at end (robot-available ?robot))
+
+ ;; Update order
+ (at end (assign (robot-order ?robot) (order-identity ?order)))
+
+ ;; Grab and release reserved locations
+ (at start (not (location-available ?bound)))
+ (at end (location-available ?bound))
+
+ ;; Update robot location - technically correct but not really needed
+ ;(at start (not (robot-at ?robot ?base)))
+ ;(at end (robot-at ?robot ?base))
+
+ ;; Clear need-stereo so the planner won't try to use the stereo action
+ ;; again after the user request is satisfied.
+ (at end (not (need-stereo ?robot ?order ?base ?bound ?run-number)))
+
+ ;; Mark success
+ (at end (completed-stereo ?robot ?order ?base ?bound ?run-number))
+ )
+ )
+
+)
diff --git a/astrobee/survey_manager/survey_planner/pddl/jem_survey_template.pddl b/astrobee/survey_manager/survey_planner/pddl/jem_survey_template.pddl
new file mode 100644
index 00000000..313ca28f
--- /dev/null
+++ b/astrobee/survey_manager/survey_planner/pddl/jem_survey_template.pddl
@@ -0,0 +1,57 @@
+{{ header }}
+(define (problem jem-survey)
+ (:domain survey-manager)
+ (:metric minimize (total-time))
+ (:objects
+ {{ bays }} {{ berths }} - location
+ {{ robots }} - robot
+ {{ orders }} - order
+ run1 run2 run3 run4 run5 - run-number
+ )
+
+ (:goal
+ (and
+ {{ goals }}
+ )
+ )
+
+ (:init
+ ;; === Static predicates ===
+ {{ move_connected_predicates }}
+
+ {{ location_real_predicates }}
+
+ {{ dock_connected_predicates }}
+
+ {{ robots_different_predicates }}
+
+ {{ locations_different_predicates }}
+
+ ;; === Dynamic predicates ===
+ {{ robot_available_predicates }}
+
+ {{ robot_at_predicates }}
+
+ {{ location_available_predicates }}
+
+ ;; need-stereo predicates must be asserted with identical parameters to the
+ ;; stereo-completed goals. See the need-stereo docs for more.
+ {{ need_stereo_predicates }}
+
+ ;; === Static numeric fluents ===
+ {{ order_identity_fluents }}
+
+ ;; === Dynamic numeric fluents ===
+ {{ robot_order_fluents }}
+ ) ;; end :init
+) ;; end problem
+
+;; Include raw high-level config in problem in case an (ISAAC-custom) planner prefers to use it.
+
+;; BEGIN CONFIG_DYNAMIC
+{{ config_dynamic }}
+;; END CONFIG_DYNAMIC
+
+;; BEGIN CONFIG_STATIC
+{{ config_static }}
+;; END CONFIG_STATIC
diff --git a/astrobee/survey_manager/survey_planner/pddl/problem_jem_survey.pddl b/astrobee/survey_manager/survey_planner/pddl/problem_jem_survey.pddl
new file mode 100644
index 00000000..301f8c05
--- /dev/null
+++ b/astrobee/survey_manager/survey_planner/pddl/problem_jem_survey.pddl
@@ -0,0 +1,296 @@
+;; Auto-generated by problem_generator.py. Do not edit!
+;; Problem template: jem_survey_template.pddl
+;; Dynamic config: jem_survey_dynamic.yaml
+;; Static config: jem_survey_static.yaml
+
+(define (problem jem-survey)
+ (:domain survey-manager)
+ (:metric minimize (total-time))
+ (:objects
+ jem_bay0 jem_bay1 jem_bay2 jem_bay3 jem_bay4 jem_bay5 jem_bay6 jem_bay7 jem_bay8 berth1 berth2 - location
+ bumble honey - robot
+ o0 o1 o2 o3 o4 o5 o6 o7 o8 o9 - order
+ run1 run2 run3 run4 run5 - run-number
+ )
+
+ (:goal
+ (and
+ (completed-panorama bumble o0 jem_bay4 run1)
+ (completed-panorama bumble o1 jem_bay3 run1)
+ (completed-panorama bumble o2 jem_bay2 run1)
+ (completed-panorama bumble o3 jem_bay1 run1)
+ (completed-stereo bumble o4 jem_bay1 jem_bay4 run1)
+ (completed-panorama honey o0 jem_bay7 run1)
+ (completed-panorama honey o1 jem_bay6 run1)
+ (completed-panorama honey o2 jem_bay5 run1)
+ (robot-at honey berth2)
+ )
+ )
+
+ (:init
+ ;; === Static predicates ===
+ (move-connected jem_bay0 jem_bay1)
+ (move-connected jem_bay1 jem_bay0)
+ (move-connected jem_bay1 jem_bay2)
+ (move-connected jem_bay2 jem_bay1)
+ (move-connected jem_bay2 jem_bay3)
+ (move-connected jem_bay3 jem_bay2)
+ (move-connected jem_bay3 jem_bay4)
+ (move-connected jem_bay4 jem_bay3)
+ (move-connected jem_bay4 jem_bay5)
+ (move-connected jem_bay5 jem_bay4)
+ (move-connected jem_bay5 jem_bay6)
+ (move-connected jem_bay6 jem_bay5)
+ (move-connected jem_bay6 jem_bay7)
+ (move-connected jem_bay7 jem_bay6)
+ (move-connected jem_bay7 jem_bay8)
+ (move-connected jem_bay8 jem_bay7)
+
+ (location-real jem_bay1)
+ (location-real jem_bay2)
+ (location-real jem_bay3)
+ (location-real jem_bay4)
+ (location-real jem_bay5)
+ (location-real jem_bay6)
+ (location-real jem_bay7)
+
+ (dock-connected jem_bay7 berth1)
+ (dock-connected jem_bay7 berth2)
+
+ (robots-different bumble honey)
+ (robots-different honey bumble)
+
+ (locations-different jem_bay0 jem_bay1)
+ (locations-different jem_bay0 jem_bay2)
+ (locations-different jem_bay0 jem_bay3)
+ (locations-different jem_bay0 jem_bay4)
+ (locations-different jem_bay0 jem_bay5)
+ (locations-different jem_bay0 jem_bay6)
+ (locations-different jem_bay0 jem_bay7)
+ (locations-different jem_bay0 jem_bay8)
+ (locations-different jem_bay1 jem_bay0)
+ (locations-different jem_bay1 jem_bay2)
+ (locations-different jem_bay1 jem_bay3)
+ (locations-different jem_bay1 jem_bay4)
+ (locations-different jem_bay1 jem_bay5)
+ (locations-different jem_bay1 jem_bay6)
+ (locations-different jem_bay1 jem_bay7)
+ (locations-different jem_bay1 jem_bay8)
+ (locations-different jem_bay2 jem_bay0)
+ (locations-different jem_bay2 jem_bay1)
+ (locations-different jem_bay2 jem_bay3)
+ (locations-different jem_bay2 jem_bay4)
+ (locations-different jem_bay2 jem_bay5)
+ (locations-different jem_bay2 jem_bay6)
+ (locations-different jem_bay2 jem_bay7)
+ (locations-different jem_bay2 jem_bay8)
+ (locations-different jem_bay3 jem_bay0)
+ (locations-different jem_bay3 jem_bay1)
+ (locations-different jem_bay3 jem_bay2)
+ (locations-different jem_bay3 jem_bay4)
+ (locations-different jem_bay3 jem_bay5)
+ (locations-different jem_bay3 jem_bay6)
+ (locations-different jem_bay3 jem_bay7)
+ (locations-different jem_bay3 jem_bay8)
+ (locations-different jem_bay4 jem_bay0)
+ (locations-different jem_bay4 jem_bay1)
+ (locations-different jem_bay4 jem_bay2)
+ (locations-different jem_bay4 jem_bay3)
+ (locations-different jem_bay4 jem_bay5)
+ (locations-different jem_bay4 jem_bay6)
+ (locations-different jem_bay4 jem_bay7)
+ (locations-different jem_bay4 jem_bay8)
+ (locations-different jem_bay5 jem_bay0)
+ (locations-different jem_bay5 jem_bay1)
+ (locations-different jem_bay5 jem_bay2)
+ (locations-different jem_bay5 jem_bay3)
+ (locations-different jem_bay5 jem_bay4)
+ (locations-different jem_bay5 jem_bay6)
+ (locations-different jem_bay5 jem_bay7)
+ (locations-different jem_bay5 jem_bay8)
+ (locations-different jem_bay6 jem_bay0)
+ (locations-different jem_bay6 jem_bay1)
+ (locations-different jem_bay6 jem_bay2)
+ (locations-different jem_bay6 jem_bay3)
+ (locations-different jem_bay6 jem_bay4)
+ (locations-different jem_bay6 jem_bay5)
+ (locations-different jem_bay6 jem_bay7)
+ (locations-different jem_bay6 jem_bay8)
+ (locations-different jem_bay7 jem_bay0)
+ (locations-different jem_bay7 jem_bay1)
+ (locations-different jem_bay7 jem_bay2)
+ (locations-different jem_bay7 jem_bay3)
+ (locations-different jem_bay7 jem_bay4)
+ (locations-different jem_bay7 jem_bay5)
+ (locations-different jem_bay7 jem_bay6)
+ (locations-different jem_bay7 jem_bay8)
+ (locations-different jem_bay8 jem_bay0)
+ (locations-different jem_bay8 jem_bay1)
+ (locations-different jem_bay8 jem_bay2)
+ (locations-different jem_bay8 jem_bay3)
+ (locations-different jem_bay8 jem_bay4)
+ (locations-different jem_bay8 jem_bay5)
+ (locations-different jem_bay8 jem_bay6)
+ (locations-different jem_bay8 jem_bay7)
+
+ ;; === Dynamic predicates ===
+ (robot-available bumble)
+ (robot-available honey)
+
+ (robot-at bumble berth1)
+ (robot-at honey berth2)
+
+ (location-available jem_bay0)
+ (location-available jem_bay1)
+ (location-available jem_bay2)
+ (location-available jem_bay3)
+ (location-available jem_bay4)
+ (location-available jem_bay5)
+ (location-available jem_bay6)
+ (location-available jem_bay7)
+ (location-available jem_bay8)
+
+ ;; need-stereo predicates must be asserted with identical parameters to the
+ ;; stereo-completed goals. See the need-stereo docs for more.
+ (need-stereo bumble o4 jem_bay1 jem_bay4 run1)
+
+ ;; === Static numeric fluents ===
+ (= (order-identity o0) 0)
+ (= (order-identity o1) 1)
+ (= (order-identity o2) 2)
+ (= (order-identity o3) 3)
+ (= (order-identity o4) 4)
+ (= (order-identity o5) 5)
+ (= (order-identity o6) 6)
+ (= (order-identity o7) 7)
+ (= (order-identity o8) 8)
+ (= (order-identity o9) 9)
+
+ ;; === Dynamic numeric fluents ===
+ (= (robot-order bumble) -1)
+ (= (robot-order honey) -1)
+ ) ;; end :init
+) ;; end problem
+
+;; Include raw high-level config in problem in case an (ISAAC-custom) planner prefers to use it.
+
+;; BEGIN CONFIG_DYNAMIC
+;; # Copyright (c) 2023, United States Government, as represented by the
+;; # Administrator of the National Aeronautics and Space Administration.
+;; #
+;; # All rights reserved.
+;; #
+;; # The "ISAAC - Integrated System for Autonomous and Adaptive Caretaking
+;; # platform" software is licensed under the Apache License, Version 2.0
+;; # (the "License"); you may not use this file except in compliance with the
+;; # License. You may obtain a copy of the License at
+;; #
+;; # http://www.apache.org/licenses/LICENSE-2.0
+;; #
+;; # Unless required by applicable law or agreed to in writing, software
+;; # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+;; # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+;; # License for the specific language governing permissions and limitations
+;; # under the License.
+;;
+;; # Example dynamic configuration info used when generating a PDDL problem. For now, this is goal
+;; # conditions and initial state. A likely conops is that the initial version of this file for a
+;; # specific activity would be hand-generated, but it might later be automatically regenerated by the
+;; # survey manager when a replan is needed (remove completed/failed goals, add retry goals, update
+;; # initial state to match actual current state, etc.) See also jem_survey_static.yaml.
+;;
+;; goals:
+;;
+;; - {type: panorama, robot: bumble, order: 0, location: jem_bay4, run: 1}
+;; - {type: panorama, robot: bumble, order: 1, location: jem_bay3, run: 1}
+;; - {type: panorama, robot: bumble, order: 2, location: jem_bay2, run: 1}
+;; - {type: panorama, robot: bumble, order: 3, location: jem_bay1, run: 1}
+;; - {type: stereo, robot: bumble, order: 4, trajectory: jem_bay1_to_bay3, run: 1}
+;;
+;; # We want Bumble to return to its berth at the end of the run, but adding this goal causes POPF to
+;; # get confused and greatly increase the total run time. For some reason, it doesn't notice it can
+;; # use the same plan as without this goal and then add some motion actions at the end to achieve this
+;; # goal. Instead, it falls back to only undocking one robot at a time, which slows things down by
+;; # about 2x.
+;; # - {type: robot_at, robot: bumble, location: berth1}
+;;
+;; - {type: panorama, robot: honey, order: 0, location: jem_bay7, run: 1}
+;; - {type: panorama, robot: honey, order: 1, location: jem_bay6, run: 1}
+;; - {type: panorama, robot: honey, order: 2, location: jem_bay5, run: 1}
+;;
+;; # This is another objective we want to include that for some reason causes POPF to fail to generate
+;; # a plan (hang indefinitely). No obvious reason why it should cause a problem.
+;; # - {type: stereo, robot: honey, order: 3, trajectory: jem_bay4_to_bay7, run: 1}
+;;
+;; - {type: robot_at, robot: honey, location: berth2}
+;;
+;; init:
+;; bumble:
+;; location: berth1
+;; honey:
+;; location: berth2
+;;
+;; END CONFIG_DYNAMIC
+
+;; BEGIN CONFIG_STATIC
+;; # Copyright (c) 2023, United States Government, as represented by the
+;; # Administrator of the National Aeronautics and Space Administration.
+;; #
+;; # All rights reserved.
+;; #
+;; # The "ISAAC - Integrated System for Autonomous and Adaptive Caretaking
+;; # platform" software is licensed under the Apache License, Version 2.0
+;; # (the "License"); you may not use this file except in compliance with the
+;; # License. You may obtain a copy of the License at
+;; #
+;; # http://www.apache.org/licenses/LICENSE-2.0
+;; #
+;; # Unless required by applicable law or agreed to in writing, software
+;; # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+;; # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+;; # License for the specific language governing permissions and limitations
+;; # under the License.
+;;
+;; # Static configuration info used when generating a PDDL problem and also when executing actions in a
+;; # PDDL plan. This info should be static in the sense that it nominally doesn't change during an ISS
+;; # activity, so the survey manager doesn't have to modify it. However, an edge case is that an
+;; # operator might want to manually edit something in here (like add a new symbolic location or nudge
+;; # the position of a named bay away from an obstacle) and restart the survey manager. On the other
+;; # hand, info that is *expected* to change as part of the survey manager conops belongs in
+;; # jem_survey_dynamic.yaml.
+;;
+;; # Useful reference for positions and stereo survey trajectories:
+;; # https://babelfish.arc.nasa.gov/confluence/display/FFOPS/ISAAC+Phase+1X+Activity+9+Ground+Procedure
+;;
+;; bays:
+;; # 3D coordinates for symbolic bays in ISS Analysis Coordinate System used by Astrobee
+;; jem_bay1: [11.0, -4.0, 4.8]
+;; jem_bay2: [11.0, -5.0, 4.8]
+;; jem_bay3: [11.0, -6.0, 4.8]
+;; jem_bay4: [11.0, -7.0, 4.8]
+;; jem_bay5: [11.0, -8.0, 4.8]
+;; jem_bay6: [11.0, -9.0, 4.8]
+;; jem_bay7: [11.0, -9.7, 4.8]
+;;
+;; bogus_bays: [jem_bay0, jem_bay8]
+;; berths: [berth1, berth2]
+;; robots: [bumble, honey]
+;; num_orders: 10
+;;
+;; stereo:
+;; # Meta-data about stereo survey options
+;; jem_bay1_to_bay3:
+;; # fplan: Name of external fplan specification of trajectory in astrobee_ops/gds/plans/ISAAC/
+;; fplan: "jem_stereo_mapping_bay1_to_bay3.fplan"
+;; # base_location: Where trajectory starts and ends for planning purposes (rough location, not exact)
+;; base_location: jem_bay1
+;; # bound_location: The other end of the interval covered by the trajectory, for planner collision
+;; # check purposes. (Note a trajectory may fly a bit into a bay that it doesn't claim to cover.
+;; # The two surveys that cover the module purposefully overlap.)
+;; bound_location: jem_bay4
+;; jem_bay4_to_bay7:
+;; fplan: "jem_stereo_mapping_bay4_to_bay7.fplan"
+;; base_location: jem_bay7
+;; bound_location: jem_bay4
+;;
+;; END CONFIG_STATIC
diff --git a/astrobee/survey_manager/survey_planner/readme.md b/astrobee/survey_manager/survey_planner/readme.md
new file mode 100644
index 00000000..6fd77ac8
--- /dev/null
+++ b/astrobee/survey_manager/survey_planner/readme.md
@@ -0,0 +1,5 @@
+\page survey_planner Survey Planner
+
+Planning and scheduling of queued survey actions using Playsys2 for PDDL solutions and behavior trees for execution.
+
+Based on [preliminary work](https://github.com/traclabs/astrobee_task_planning_ws) by [Ana](https://github.com/ana-GT) at [Traclabs](https://traclabs.com).
diff --git a/astrobee/survey_manager/survey_planner/src/ros1_lifecycle b/astrobee/survey_manager/survey_planner/src/ros1_lifecycle
new file mode 160000
index 00000000..15a50352
--- /dev/null
+++ b/astrobee/survey_manager/survey_planner/src/ros1_lifecycle
@@ -0,0 +1 @@
+Subproject commit 15a50352f9d522c7815ecedaa2b77b6f99ecdb31
diff --git a/astrobee/survey_manager/survey_planner/src/ros2_planning_system b/astrobee/survey_manager/survey_planner/src/ros2_planning_system
new file mode 160000
index 00000000..3a988a5b
--- /dev/null
+++ b/astrobee/survey_manager/survey_planner/src/ros2_planning_system
@@ -0,0 +1 @@
+Subproject commit 3a988a5bc14a1700a8f276e2438c47ac80e7bf8c
diff --git a/astrobee/survey_manager/survey_planner/tools/plan_interpreter.py b/astrobee/survey_manager/survey_planner/tools/plan_interpreter.py
new file mode 100755
index 00000000..ce98e096
--- /dev/null
+++ b/astrobee/survey_manager/survey_planner/tools/plan_interpreter.py
@@ -0,0 +1,251 @@
+#!/usr/bin/env python3
+
+# Copyright (c) 2023, United States Government, as represented by the
+# Administrator of the National Aeronautics and Space Administration.
+#
+# All rights reserved.
+#
+# The "ISAAC - Integrated System for Autonomous and Adaptive Caretaking
+# platform" software is licensed under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with the
+# License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+"""
+Provides an example of how to interpret the actions in a survey domain plan, dropping the irrelevant
+arguments, mapping the positional arguments back to the names used in the dynamic config, and
+looking up extra info that's not needed by the planner but is important for execution (e.g.,
+coordinates of named locations, fplan filenames of stereo surveys).
+
+For testing, you can call this from the command line on a complete sample output plan.
+
+For use during execution, if you are receiving one action at a time from an executor, you may
+prefer to import this module and call the yaml_action_from_pddl() function directly.
+"""
+
+import argparse
+import pathlib
+import re
+import sys
+from typing import Any, Dict, List
+
+import yaml
+
+ACTION_TYPE_OPTIONS = ("dock", "undock", "move", "panorama", "stereo")
+
+
+# Type alias
+YamlMapping = Dict[str, Any]
+FloatStr = str # String representation of a floating-point value
+
+# POPF plan output weirdly mixes its random debugging output with the actual plan actions that it
+# outputs in a standard format. (Maybe there's some way to suppress the debug info?) Anyway, this
+# regex works nicely to ignore the debug info and parse the fields of the plan actions we care
+# about. It might need fine-tuning if we use other planners.
+PLAN_ACTION_REGEX = re.compile(
+ r"^(?P\d+(\.\d*)?): (?P\(.*?\))\s+\[(?P\d+(\.\d*)?)\]\s*$"
+)
+
+
+def load_yaml(yaml_path: pathlib.Path) -> YamlMapping:
+ """
+ Return the YAML parse result for the file at `yaml_path`.
+ """
+ with yaml_path.open(encoding="utf-8") as yaml_stream:
+ return yaml.safe_load(yaml_stream)
+
+
+class PlanAction:
+ """
+ Class representing one entry in the output plan sequence of a PDDL planner.
+ """
+
+ def __init__(
+ self, start_time_seconds: FloatStr, action: str, duration_seconds: FloatStr
+ ):
+ self.start_time_seconds = start_time_seconds
+ self.action = action
+ self.duration_seconds = duration_seconds
+
+ def __repr__(self):
+ return f"{self.start_time_seconds}: {self.action} [{self.duration_seconds}]"
+
+
+def yaml_action_from_pddl(action: str, static_config: YamlMapping) -> YamlMapping:
+ """
+ Return a YamlMapping representation of `action`. This is the only place
+ we really need domain-specific logic.
+ """
+ action_args = action[1:-1].split()
+ action_type = action_args[0]
+ assert (
+ action_type in ACTION_TYPE_OPTIONS
+ ), f"Expected action type in {ACTION_TYPE_OPTIONS}, got {action_type}"
+
+ if action_type == "dock":
+ robot, _from_bay, to_berth = action_args[1:]
+ # Can discard from_bay
+ return {"type": "dock", "robot": robot, "berth": to_berth}
+
+ if action_type == "undock":
+ robot, _from_berth, _to_bay, _check1, _check2 = action_args[1:]
+ # Can discard from_berth, to_bay, check1, check2
+ return {"type": "undock", "robot": robot}
+
+ if action_type == "move":
+ robot, _from_bay, to_bay, _check_bay = action_args[1:]
+ # Can discard from_bay, check_bay. Look up coordinates for to_bay.
+ return {
+ "type": "move",
+ "robot": robot,
+ "to_name": to_bay,
+ "to_pos": static_config["bays"][to_bay],
+ }
+
+ if action_type == "panorama":
+ robot, _order, location, run_name = action_args[1:]
+ run_number = int(run_name[-1])
+ # Can discard order. Look up coordinates for location.
+ return {
+ "type": "panorama",
+ "robot": robot,
+ "location_name": location,
+ "location_pos": static_config["bays"][location],
+ "run": run_number,
+ }
+
+ if action_type == "stereo":
+ robot, _order, base, bound, _check1, _check2, run_name = action_args[1:]
+ run_number = int(run_name[-1])
+ # Use base and bound to look up trajectory.
+ traj_matches = [
+ traj
+ for traj in static_config["stereo"].values()
+ if traj["base_location"] == base and traj["bound_location"] == bound
+ ]
+ assert (
+ len(traj_matches) == 1
+ ), f"Expected exactly 1 matching stereo trajectory with base {base} and bound {bound}, got {len(traj_matches)}"
+ fplan = traj_matches[0]["fplan"]
+ # Can discard order, base, bound, check1, check2.
+ return {"type": "stereo", "robot": robot, "fplan": fplan, "run": run_number}
+
+ assert False, "Never reach this point."
+ return {} # Make pylint happy
+
+
+def yaml_plan_action_from_pddl(
+ plan_action: PlanAction, static_config: YamlMapping
+) -> YamlMapping:
+ """
+ Return a YamlMapping representation of `plan_action`.
+ """
+ return {
+ "start_time_seconds": plan_action.start_time_seconds,
+ "action": yaml_action_from_pddl(plan_action.action, static_config),
+ "duration_seconds": plan_action.duration_seconds,
+ }
+
+
+def parse_plan(plan_path: pathlib.Path) -> List[PlanAction]:
+ """
+ Return a list of PlanActions read from the PDDL plan at `plan_path`.
+ """
+ actions = []
+ with plan_path.open(encoding="utf-8") as plan_stream:
+ for plan_line in plan_stream:
+ match = PLAN_ACTION_REGEX.search(plan_line)
+ if match:
+ start_time_seconds = match.group("start_time_seconds")
+ action = match.group("action")
+ duration_seconds = match.group("duration_seconds")
+
+ # Raise an exception if these values are not the string representation of a
+ # floating-point value. However, we are storing the original string representation
+ # rather than storing the float to avoid any weird issues with the values getting
+ # mangled due to floating-point precision and formatting.
+ float(start_time_seconds)
+ float(duration_seconds)
+
+ actions.append(PlanAction(start_time_seconds, action, duration_seconds))
+ return actions
+
+
+class NoAliasDumper(
+ yaml.SafeDumper
+): # pylint: disable=too-many-ancestors # It only has 1, so (?)
+ """
+ Configures yaml dump output to avoid using confusing aliases.
+ """
+
+ def ignore_aliases(self, data):
+ return True
+
+
+def plan_interpreter(
+ config_static_path: pathlib.Path, plan_path: pathlib.Path, output_path: pathlib.Path
+) -> None:
+ """
+ The main function that interprets an entire plan file.
+
+ However, if you are receiving one action at a time from an executor, you may prefer to import
+ this module and call yaml_action_from_pddl() directly.
+ """
+ config_static = load_yaml(config_static_path)
+ pddl_actions = parse_plan(plan_path)
+ yaml_actions = [
+ yaml_plan_action_from_pddl(plan_action, config_static)
+ for plan_action in pddl_actions
+ ]
+ with output_path.open("w", encoding="utf-8") as output_stream:
+ yaml.dump(yaml_actions, output_stream, Dumper=NoAliasDumper, sort_keys=False)
+ print(f"Wrote to {output_path}", file=sys.stderr)
+
+
+class CustomFormatter(
+ argparse.ArgumentDefaultsHelpFormatter, argparse.RawDescriptionHelpFormatter
+):
+ pass
+
+
+def main():
+ parser = argparse.ArgumentParser(
+ description=__doc__, formatter_class=CustomFormatter
+ )
+ parser.add_argument(
+ "--config-static",
+ help="Path to input static problem config YAML (module geometry, available stereo surveys, etc.)",
+ type=pathlib.Path,
+ default="jem_survey_static.yaml",
+ )
+ parser.add_argument(
+ "--plan",
+ help="Path to input plan generated by PDDL planner (parser currently tuned for POPF idiosyncrasies)",
+ type=pathlib.Path,
+ default="sample_output_plan.txt",
+ )
+ parser.add_argument(
+ "-o",
+ "--output",
+ help="Path for output converted to YAML format",
+ type=pathlib.Path,
+ default="sample_output_plan.yaml",
+ )
+ args = parser.parse_args()
+
+ plan_interpreter(
+ config_static_path=args.config_static,
+ plan_path=args.plan,
+ output_path=args.output,
+ )
+
+
+if __name__ == "__main__":
+ main()
diff --git a/astrobee/survey_manager/survey_planner/tools/problem_generator.py b/astrobee/survey_manager/survey_planner/tools/problem_generator.py
new file mode 100755
index 00000000..7b5b2b31
--- /dev/null
+++ b/astrobee/survey_manager/survey_planner/tools/problem_generator.py
@@ -0,0 +1,307 @@
+#!/usr/bin/env python3
+
+# Copyright (c) 2023, United States Government, as represented by the
+# Administrator of the National Aeronautics and Space Administration.
+#
+# All rights reserved.
+#
+# The "ISAAC - Integrated System for Autonomous and Adaptive Caretaking
+# platform" software is licensed under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with the
+# License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+"""
+Generates a PDDL problem specification for the survey domain in domain_survey.pddl. Takes as input
+a PDDL problem template and higher-level static and dynamic configuration specified in YAML.
+
+The generator takes care of error-prone repetitive tasks like asserting predicates for which
+locations are different, which robots are different, asserting a need-stereo predicate for every
+completed-stereo goal, making the initial location-available predicates agree with the initial robot
+locations, etc.
+"""
+
+import argparse
+import itertools
+import pathlib
+import re
+import sys
+from typing import Any, Dict, Iterable, Sequence, T, Tuple
+
+import yaml
+
+GOAL_TYPE_OPTIONS = ("panorama", "stereo", "robot_at")
+
+
+# Type alias
+YamlMapping = Dict[str, Any]
+
+# Replace the text "{{ foo }}" in the template with the value of the foo parameter
+TEMPLATE_SUBST_REGEX = re.compile(r"{{\s*([\w]+)\s*}}")
+
+
+def load_yaml(yaml_path: pathlib.Path) -> YamlMapping:
+ """
+ Return the YAML parse result for the file at `yaml_path`.
+ """
+ with yaml_path.open(encoding="utf-8") as yaml_stream:
+ return yaml.safe_load(yaml_stream)
+
+
+def pddl_goal_from_yaml(goal: YamlMapping, config_static: YamlMapping) -> str:
+ """
+ Convert a YAML goal with named fields from the dynamic config into a PDDL goal predicate.
+ """
+ goal_type = goal["type"]
+ assert (
+ goal_type in GOAL_TYPE_OPTIONS
+ ), f"Expected goal type in {GOAL_TYPE_OPTIONS}, got {goal_type}"
+
+ if goal_type == "panorama":
+ robot = goal["robot"]
+ order = goal["order"]
+ location = goal["location"]
+ run = goal["run"]
+ return f"(completed-panorama {robot} o{order} {location} run{run})"
+
+ if goal_type == "stereo":
+ robot = goal["robot"]
+ order = goal["order"]
+ trajectory = goal["trajectory"]
+ run = goal["run"]
+ traj_info = config_static["stereo"][trajectory]
+ base = traj_info["base_location"]
+ bound = traj_info["bound_location"]
+ return f"(completed-stereo {robot} o{order} {base} {bound} run{run})"
+
+ if goal_type == "robot_at":
+ robot = goal["robot"]
+ location = goal["location"]
+ return f"(robot-at {robot} {location})"
+
+ assert False, "Never reach this point"
+ return {} # Make pylint happy
+
+
+def indent_lines(lines: Sequence[str], indent: int) -> str:
+ """
+ Return `lines` joined with carriage returns, prepending `indent` spaces to each line after the
+ first.
+ """
+ sep = "\n" + (" " * indent)
+ return sep.join(lines)
+
+
+def pairwise(elts: Iterable[T]) -> Iterable[Tuple[T, T]]:
+ """
+ Returns consecutive pairs drawn from `elts`. Back-port itertools.pairwise() to earlier Python
+ versions.
+ """
+ a, b = itertools.tee(elts)
+ next(b, None)
+ return zip(a, b)
+
+
+def both_ways(elts: Iterable[Tuple[T, T]]) -> Iterable[Tuple[T, T]]:
+ """
+ Given `elts` an iterable of 2-tuples, return a new iterable that includes each 2-tuple twice,
+ once in its original order and once reversed.
+ """
+ for a, b in elts:
+ yield a, b
+ yield b, a
+
+
+def distinct_pairs(elts: Iterable[T]) -> Iterable[Tuple[T, T]]:
+ """
+ Return distinct pairs of items drawn from `elts`.
+ """
+ for a, b in itertools.product(elts, repeat=2):
+ if a != b:
+ yield a, b
+
+
+class TemplateFiller:
+ """
+ Class for substituting parameters into a template.
+ """
+
+ def __init__(self, params):
+ self.params = params
+
+ def __call__(self, match: re.match) -> str:
+ param = match.group(1)
+ if param not in self.params:
+ raise KeyError(
+ "Expected template param in {self.params.keys()}, got {{{{ {param} }}}}"
+ )
+ return self.params[param]
+
+
+def comment_for_pddl(text: str) -> str:
+ """
+ Return the result of commenting `text` using PDDL (Lisp-like) comment syntax.
+ """
+ return ";; " + text.replace("\n", "\n;; ")
+
+
+def problem_generator(
+ problem_template_path: pathlib.Path,
+ config_dynamic_path: pathlib.Path,
+ config_static_path: pathlib.Path,
+ output_path: pathlib.Path,
+) -> None:
+ """
+ The main function that generates the problem.
+ """
+ problem_template = problem_template_path.read_text()
+ config_dynamic = load_yaml(config_dynamic_path)
+ config_static = load_yaml(config_static_path)
+ params = {}
+
+ params["header"] = (
+ ";; Auto-generated by problem_generator.py. Do not edit!\n"
+ f";; Problem template: {problem_template_path}\n"
+ f";; Dynamic config: {config_dynamic_path}\n"
+ f";; Static config: {config_static_path}\n"
+ )
+
+ bays = list(config_static["bays"].keys())
+ bogus_bays = config_static["bogus_bays"]
+ all_bays = sorted(bays + bogus_bays)
+ params["bays"] = " ".join(all_bays)
+
+ berths = config_static["berths"]
+ params["berths"] = " ".join(berths)
+
+ robots = config_static["robots"]
+ params["robots"] = " ".join(robots)
+
+ num_orders = config_static["num_orders"]
+ params["orders"] = " ".join([f"o{i}" for i in range(num_orders)])
+
+ yaml_goals = config_dynamic["goals"]
+ pddl_goals = [pddl_goal_from_yaml(goal, config_static) for goal in yaml_goals]
+ params["goals"] = indent_lines(pddl_goals, 12)
+
+ move_connected_lines = [
+ f"(move-connected {a} {b})" for a, b in both_ways(pairwise(all_bays))
+ ]
+ params["move_connected_predicates"] = indent_lines(move_connected_lines, 8)
+
+ location_real_lines = [f"(location-real {bay})" for bay in bays]
+ params["location_real_predicates"] = indent_lines(location_real_lines, 8)
+
+ candidates = (("jem_bay7", "berth1"), ("jem_bay7", "berth2"))
+ dock_connected_lines = [
+ f"(dock-connected {bay} {berth})"
+ for bay, berth in candidates
+ if bay in bays and berth in berths
+ ]
+ params["dock_connected_predicates"] = indent_lines(dock_connected_lines, 8)
+
+ robots_different_lines = [
+ f"(robots-different {a} {b})" for a, b in distinct_pairs(robots)
+ ]
+ params["robots_different_predicates"] = indent_lines(robots_different_lines, 8)
+
+ locations_different_lines = [
+ f"(locations-different {a} {b})" for a, b in distinct_pairs(all_bays)
+ ]
+ params["locations_different_predicates"] = indent_lines(
+ locations_different_lines, 8
+ )
+
+ robot_available_lines = [f"(robot-available {robot})" for robot in robots]
+ params["robot_available_predicates"] = indent_lines(robot_available_lines, 8)
+
+ init = config_dynamic["init"]
+ robot_at_lines = [
+ f"(robot-at {robot} {init[robot]['location']})" for robot in robots
+ ]
+ params["robot_at_predicates"] = indent_lines(robot_at_lines, 8)
+
+ all_locations = all_bays + berths
+ occupied_locations = [init[robot]["location"] for robot in robots]
+ available_locations = sorted(set(all_locations).difference(occupied_locations))
+ location_available_lines = [
+ f"(location-available {location})" for location in available_locations
+ ]
+ params["location_available_predicates"] = indent_lines(location_available_lines, 8)
+
+ need_stereo_lines = [
+ goal.replace("completed-stereo", "need-stereo")
+ for goal in pddl_goals
+ if "completed-stereo" in goal
+ ]
+ params["need_stereo_predicates"] = indent_lines(need_stereo_lines, 8)
+
+ order_identity_lines = [f"(= (order-identity o{i}) {i})" for i in range(num_orders)]
+ params["order_identity_fluents"] = indent_lines(order_identity_lines, 8)
+
+ robot_order_lines = [f"(= (robot-order {robot}) -1)" for robot in robots]
+ params["robot_order_fluents"] = indent_lines(robot_order_lines, 8)
+
+ params["config_dynamic"] = comment_for_pddl(config_dynamic_path.read_text())
+ params["config_static"] = comment_for_pddl(config_static_path.read_text())
+
+ # print(yaml.safe_dump(params, indent=4, sort_keys=False))
+ filled_template = TEMPLATE_SUBST_REGEX.sub(TemplateFiller(params), problem_template)
+ output_path.write_text(filled_template)
+ print(f"Wrote to {output_path}", file=sys.stderr)
+
+
+class CustomFormatter(
+ argparse.ArgumentDefaultsHelpFormatter, argparse.RawDescriptionHelpFormatter
+):
+ pass
+
+
+def main():
+ parser = argparse.ArgumentParser(
+ description=__doc__, formatter_class=CustomFormatter
+ )
+ parser.add_argument(
+ "--problem-template",
+ help="Path to input PDDL problem template",
+ type=pathlib.Path,
+ default="jem_survey_template.pddl",
+ )
+ parser.add_argument(
+ "--config-dynamic",
+ help="Path to input dynamic problem config YAML (goals, initial state)",
+ type=pathlib.Path,
+ default="jem_survey_dynamic.yaml",
+ )
+ parser.add_argument(
+ "--config-static",
+ help="Path to input static problem config YAML (module geometry, available stereo surveys, etc.)",
+ type=pathlib.Path,
+ default="jem_survey_static.yaml",
+ )
+ parser.add_argument(
+ "-o",
+ "--output",
+ help="Path for output PDDL problem",
+ type=pathlib.Path,
+ default="problem_jem_survey.pddl",
+ )
+ args = parser.parse_args()
+
+ problem_generator(
+ problem_template_path=args.problem_template,
+ config_dynamic_path=args.config_dynamic,
+ config_static_path=args.config_static,
+ output_path=args.output,
+ )
+
+
+if __name__ == "__main__":
+ main()
diff --git a/isaac/Subsystems.md b/isaac/Subsystems.md
index bff1f708..ddd1e7a8 100644
--- a/isaac/Subsystems.md
+++ b/isaac/Subsystems.md
@@ -13,4 +13,6 @@ The ISAAC Repo contains the following Modules:
\subpage astrobee : High-level actions that support complex maneuvers.
+\subpage survey : Semi-autonomous survey tasking of Astrobees.
+
\subpage shared : Shared tools common to the entire ISAAC repo.
diff --git a/scripts/docker/astrobee_msgs.Dockerfile b/scripts/docker/astrobee_msgs.Dockerfile
index 1ac4e76c..1a8e4716 100644
--- a/scripts/docker/astrobee_msgs.Dockerfile
+++ b/scripts/docker/astrobee_msgs.Dockerfile
@@ -19,11 +19,11 @@
# This will set up an Astrobee docker container using the non-NASA install instructions.
# You must set the docker context to be the repository root directory
-ARG UBUNTU_VERSION=16.04
+ARG UBUNTU_VERSION=20.04
FROM ubuntu:${UBUNTU_VERSION}
-ARG ROS_VERSION=kinetic
-ARG PYTHON=""
+ARG ROS_VERSION=noetic
+ARG PYTHON=3
# try to suppress certain warnings during apt-get calls
ARG DEBIAN_FRONTEND=noninteractive
diff --git a/scripts/docker/isaac.Dockerfile b/scripts/docker/isaac.Dockerfile
index 18fb3cd3..4ee67c98 100644
--- a/scripts/docker/isaac.Dockerfile
+++ b/scripts/docker/isaac.Dockerfile
@@ -19,12 +19,12 @@
# This will set up an Astrobee docker container using the non-NASA install instructions.
# You must set the docker context to be the repository root directory
-ARG UBUNTU_VERSION=16.04
+ARG UBUNTU_VERSION=20.04
ARG REMOTE=isaac
FROM ${REMOTE}/isaac:latest-astrobee-ubuntu${UBUNTU_VERSION}
-ARG ROS_VERSION=kinetic
-ARG PYTHON=""
+ARG ROS_VERSION=noetic
+ARG PYTHON=3
# suppress detached head warnings later
RUN git config --global advice.detachedHead false
diff --git a/scripts/docker/isaac_astrobee.Dockerfile b/scripts/docker/isaac_astrobee.Dockerfile
index 269c85ca..12afb06d 100644
--- a/scripts/docker/isaac_astrobee.Dockerfile
+++ b/scripts/docker/isaac_astrobee.Dockerfile
@@ -19,13 +19,13 @@
# This will set up an Astrobee docker container using the non-NASA install instructions.
# You must set the docker context to be the repository root directory
-ARG UBUNTU_VERSION=16.04
+ARG UBUNTU_VERSION=20.04
ARG REMOTE=astrobee
FROM ${REMOTE}/astrobee:latest-ubuntu${UBUNTU_VERSION}
# Already inherited from astrobee:base-latest-ubuntu...
-ARG ROS_VERSION=kinetic
-ARG PYTHON=""
+ARG ROS_VERSION=noetic
+ARG PYTHON=3
RUN apt-get update && apt-get install -y \
libmnl-dev \
diff --git a/scripts/docker/isaac_msgs.Dockerfile b/scripts/docker/isaac_msgs.Dockerfile
index 27190252..280c5ae8 100644
--- a/scripts/docker/isaac_msgs.Dockerfile
+++ b/scripts/docker/isaac_msgs.Dockerfile
@@ -19,12 +19,12 @@
# This will set up an Astrobee docker container using the non-NASA install instructions.
# You must set the docker context to be the repository root directory
-ARG UBUNTU_VERSION=16.04
+ARG UBUNTU_VERSION=20.04
ARG REMOTE=isaac
FROM ${REMOTE}/isaac:astrobee-msgs-ubuntu${UBUNTU_VERSION}
-ARG ROS_VERSION=kinetic
-ARG PYTHON=""
+ARG ROS_VERSION=noetic
+ARG PYTHON=3
# Copy over the isaac_msgs
COPY communications/isaac_msgs /src/msgs/src/communications/
diff --git a/scripts/setup/packages_focal.lst b/scripts/setup/packages_focal.lst
index 87954901..e1c70e58 100644
--- a/scripts/setup/packages_focal.lst
+++ b/scripts/setup/packages_focal.lst
@@ -2,7 +2,9 @@ doxygen
python3-catkin-tools
python3-osrf-pycommon
python3-rosdep
+libreadline-dev
ros-noetic-eigen-conversions
ros-noetic-pcl-ros
+ros-noetic-behaviortree-cpp
libmnl-dev
libproj-dev