Skip to content

Commit c774e43

Browse files
authored
Split MoveToMouth Into two Actions (#94)
* [WIP] Mostly implemented, a few lingering bugs * Fixed bugs, tested in sim * Fixed back/resume calls * Lowered dummy FaceDetection time * Added MoveFromMouthToStagingConfiguration * Add the option to auto-continue or not from face detection * Reduce distance for valid mouths to 1.25m * Clear Octomap on Retry * Show detected food once the SegmentFood action has been called
1 parent ef0f61c commit c774e43

16 files changed

+800
-47
lines changed

feeding_web_app_ros2_test/feeding_web_app_ros2_test/FaceDetection.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
class FaceDetectionNode(Node):
1515
def __init__(
1616
self,
17-
face_detection_interval=150,
17+
face_detection_interval=90,
1818
num_images_with_face=60,
1919
open_mouth_interval=90,
2020
num_images_with_open_mouth=30,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
#!/usr/bin/env python3
2+
from ada_feeding_msgs.action import MoveTo
3+
from feeding_web_app_ros2_test.MoveToDummy import MoveToDummy
4+
import rclpy
5+
from rclpy.executors import MultiThreadedExecutor
6+
7+
8+
def main(args=None):
9+
rclpy.init(args=args)
10+
11+
move_from_mouth_to_staging_configuration = MoveToDummy(
12+
"MoveFromMouthToStagingConfiguration", MoveTo
13+
)
14+
15+
# Use a MultiThreadedExecutor to enable processing goals concurrently
16+
executor = MultiThreadedExecutor()
17+
18+
rclpy.spin(move_from_mouth_to_staging_configuration, executor=executor)
19+
20+
21+
if __name__ == "__main__":
22+
main()

feeding_web_app_ros2_test/feeding_web_app_ros2_test/MoveToMouth.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
#!/usr/bin/env python3
2-
from ada_feeding_msgs.action import MoveTo
2+
from ada_feeding_msgs.action import MoveToMouth
33
from feeding_web_app_ros2_test.MoveToDummy import MoveToDummy
44
import rclpy
55
from rclpy.executors import MultiThreadedExecutor
@@ -8,7 +8,7 @@
88
def main(args=None):
99
rclpy.init(args=args)
1010

11-
move_to_mouth = MoveToDummy("MoveToMouth", MoveTo)
11+
move_to_mouth = MoveToDummy("MoveToMouth", MoveToMouth)
1212

1313
# Use a MultiThreadedExecutor to enable processing goals concurrently
1414
executor = MultiThreadedExecutor()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
#!/usr/bin/env python3
2+
from ada_feeding_msgs.action import MoveTo
3+
from feeding_web_app_ros2_test.MoveToDummy import MoveToDummy
4+
import rclpy
5+
from rclpy.executors import MultiThreadedExecutor
6+
7+
8+
def main(args=None):
9+
rclpy.init(args=args)
10+
11+
move_to_staging_configuration = MoveToDummy("MoveToStagingConfiguration", MoveTo)
12+
13+
# Use a MultiThreadedExecutor to enable processing goals concurrently
14+
executor = MultiThreadedExecutor()
15+
16+
rclpy.spin(move_to_staging_configuration, executor=executor)
17+
18+
19+
if __name__ == "__main__":
20+
main()

feeding_web_app_ros2_test/launch/feeding_web_app_dummy_nodes_launch.xml

+4
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,12 @@
4646
<node pkg="feeding_web_app_ros2_test" exec="AcquireFood" name="AcquireFood"/>
4747
<!-- Motion: The MoveToRestingPosition action -->
4848
<node pkg="feeding_web_app_ros2_test" exec="MoveToRestingPosition" name="MoveToRestingPosition"/>
49+
<!-- Motion: The MoveToStagingConfiguration action -->
50+
<node pkg="feeding_web_app_ros2_test" exec="MoveToStagingConfiguration" name="MoveToStagingConfiguration"/>
4951
<!-- Motion: The MoveToMouth action -->
5052
<node pkg="feeding_web_app_ros2_test" exec="MoveToMouth" name="MoveToMouth"/>
53+
<!-- Motion: The MoveFromMouthToStagingConfiguration action -->
54+
<node pkg="feeding_web_app_ros2_test" exec="MoveFromMouthToStagingConfiguration" name="MoveFromMouthToStagingConfiguration"/>
5155
<!-- Motion: The MoveFromMouthToAbovePlate action -->
5256
<node pkg="feeding_web_app_ros2_test" exec="MoveFromMouthToAbovePlate" name="MoveFromMouthToAbovePlate"/>
5357
<!-- Motion: The MoveFromMouthToRestingPosition action -->

feeding_web_app_ros2_test/setup.py

+2
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,9 @@
3838
"FaceDetection = feeding_web_app_ros2_test.FaceDetection:main",
3939
"MoveAbovePlate = feeding_web_app_ros2_test.MoveAbovePlate:main",
4040
"MoveToRestingPosition = feeding_web_app_ros2_test.MoveToRestingPosition:main",
41+
"MoveToStagingConfiguration = feeding_web_app_ros2_test.MoveToStagingConfiguration:main",
4142
"MoveToMouth = feeding_web_app_ros2_test.MoveToMouth:main",
43+
"MoveFromMouthToStagingConfiguration = feeding_web_app_ros2_test.MoveFromMouthToStagingConfiguration:main",
4244
"MoveFromMouthToAbovePlate = feeding_web_app_ros2_test.MoveFromMouthToAbovePlate:main",
4345
"MoveFromMouthToRestingPosition = feeding_web_app_ros2_test.MoveFromMouthToRestingPosition:main",
4446
"MoveToStowLocation = feeding_web_app_ros2_test.MoveToStowLocation:main",

feedingwebapp/public/robot_state_imgs/move_to_staging_configuration.svg

+272
Loading

feedingwebapp/src/Pages/Constants.js

+34-1
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,18 @@ MOVING_STATE_ICON_DICT[MEAL_STATE.R_MovingAbovePlate] = '/robot_state_imgs/move_
2626
MOVING_STATE_ICON_DICT[MEAL_STATE.R_MovingFromMouthToAbovePlate] = '/robot_state_imgs/move_above_plate_position.svg'
2727
MOVING_STATE_ICON_DICT[MEAL_STATE.R_MovingToRestingPosition] = '/robot_state_imgs/move_to_resting_position.svg'
2828
MOVING_STATE_ICON_DICT[MEAL_STATE.R_MovingFromMouthToRestingPosition] = '/robot_state_imgs/move_to_resting_position.svg'
29+
MOVING_STATE_ICON_DICT[MEAL_STATE.R_MovingToStagingConfiguration] = '/robot_state_imgs/move_to_staging_configuration.svg'
30+
MOVING_STATE_ICON_DICT[MEAL_STATE.R_MovingFromMouthToStagingConfiguration] = '/robot_state_imgs/move_to_staging_configuration.svg'
2931
MOVING_STATE_ICON_DICT[MEAL_STATE.R_MovingToMouth] = '/robot_state_imgs/move_to_mouth_position.svg'
3032
MOVING_STATE_ICON_DICT[MEAL_STATE.R_StowingArm] = '/robot_state_imgs/stowing_arm_position.svg'
3133
export { MOVING_STATE_ICON_DICT }
3234

3335
/**
3436
* A set containing the states where the robot does not move.
37+
*
38+
* NOTE: Although in R_DetectingFace the robot does not technically move,
39+
* the app might transition out of that state into a robot motion state without
40+
* user intervention, so it is not included in this set.
3541
*/
3642
let NON_MOVING_STATES = new Set()
3743
NON_MOVING_STATES.add(MEAL_STATE.U_PreMeal)
@@ -47,6 +53,12 @@ export const FACE_DETECTION_TOPIC = '/face_detection'
4753
export const FACE_DETECTION_TOPIC_MSG = 'ada_feeding_msgs/FaceDetection'
4854
export const FACE_DETECTION_IMG_TOPIC = '/face_detection_img'
4955

56+
// States from which, if they fail, it is NOT okay for the user to retry the
57+
// same action.
58+
let NON_RETRYABLE_STATES = new Set()
59+
NON_RETRYABLE_STATES.add(MEAL_STATE.R_BiteAcquisition)
60+
export { NON_RETRYABLE_STATES }
61+
5062
/**
5163
* For states that call ROS actions, this dictionary contains
5264
* the action name and the message type
@@ -76,16 +88,37 @@ ROS_ACTIONS_NAMES[MEAL_STATE.R_MovingFromMouthToRestingPosition] = {
7688
actionName: 'MoveFromMouthToRestingPosition',
7789
messageType: 'ada_feeding_msgs/action/MoveTo'
7890
}
91+
ROS_ACTIONS_NAMES[MEAL_STATE.R_MovingToStagingConfiguration] = {
92+
actionName: 'MoveToStagingConfiguration',
93+
messageType: 'ada_feeding_msgs/action/MoveTo'
94+
}
95+
ROS_ACTIONS_NAMES[MEAL_STATE.R_MovingFromMouthToStagingConfiguration] = {
96+
actionName: 'MoveFromMouthToStagingConfiguration',
97+
messageType: 'ada_feeding_msgs/action/MoveTo'
98+
}
7999
ROS_ACTIONS_NAMES[MEAL_STATE.R_MovingToMouth] = {
80100
actionName: 'MoveToMouth',
81-
messageType: 'ada_feeding_msgs/action/MoveTo'
101+
messageType: 'ada_feeding_msgs/action/MoveToMouth'
82102
}
83103
ROS_ACTIONS_NAMES[MEAL_STATE.R_StowingArm] = {
84104
actionName: 'MoveToStowLocation',
85105
messageType: 'ada_feeding_msgs/action/MoveTo'
86106
}
87107
export { ROS_ACTIONS_NAMES }
88108

109+
/**
110+
* For states that call ROS services, this dictionary contains
111+
* the service name and the message type
112+
*/
113+
let ROS_SERVICE_NAMES = {}
114+
ROS_SERVICE_NAMES[MEAL_STATE.R_DetectingFace] = {
115+
serviceName: 'toggle_face_detection',
116+
messageType: 'std_srvs/srv/SetBool'
117+
}
118+
export { ROS_SERVICE_NAMES }
119+
export const CLEAR_OCTOMAP_SERVICE_NAME = 'clear_octomap'
120+
export const CLEAR_OCTOMAP_SERVICE_TYPE = 'std_srvs/srv/Empty'
121+
89122
/**
90123
* The meaning of the status that motion actions return in their results.
91124
* These should match the action definition(s).

feedingwebapp/src/Pages/GlobalState.jsx

+29-4
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,15 @@ export const APP_PAGE = {
3434
* position.
3535
* - U_BiteAcquisitionCheck: Waiting for the user to specify whether the
3636
* bite acquisition was succesful or not.
37+
* - R_MovingToStagingConfiguration: Waiting for the robot to move to the
38+
* staging configuration.
39+
* - R_DetectingFace: Waiting for the robot to detect a face.
3740
* - R_MovingToMouth: Waiting for the robot to finish moving to the user's
3841
* mouth.
42+
* - R_MovingFromMouthToStagingConfiguration: Waiting for the robot to move
43+
* from the user's mouth to the staging configuration. This is a separate
44+
* action from R_MovingToStagingConfiguration to allow us to customize the
45+
* departure from the mouth (e.g., a slower speed).
3946
* - R_MovingFromMouthToAbovePlate: Waiting for the robot to move from the
4047
* user's mouth to above the plate. This is a separate action from
4148
* R_MovingAbovePlate to allow us to customize the departure from the mouth
@@ -57,7 +64,10 @@ export const MEAL_STATE = {
5764
R_BiteAcquisition: 'R_BiteAcquisition',
5865
R_MovingToRestingPosition: 'R_MovingToRestingPosition',
5966
U_BiteAcquisitionCheck: 'U_BiteAcquisitionCheck',
67+
R_MovingToStagingConfiguration: 'R_MovingToStagingConfiguration',
68+
R_DetectingFace: 'R_DetectingFace',
6069
R_MovingToMouth: 'R_MovingToMouth',
70+
R_MovingFromMouthToStagingConfiguration: 'R_MovingFromMouthToStagingConfiguration',
6171
R_MovingFromMouthToAbovePlate: 'R_MovingFromMouthToAbovePlate',
6272
R_MovingFromMouthToRestingPosition: 'R_MovingFromMouthToRestingPosition',
6373
U_BiteDone: 'U_BiteDone',
@@ -100,12 +110,19 @@ export const useGlobalState = create(
100110
mealStateTransitionTime: Date.now(),
101111
// The current app page
102112
appPage: APP_PAGE.Home,
103-
// The most recent food item that the user selected in "bite selection"
104-
desiredFoodItem: null,
113+
// The goal for the bite acquisition action, including the most recent
114+
// food item that the user selected in "bite selection"
115+
biteAcquisitionActionGoal: null,
116+
// The goal for the move to mouth action, including the most recent
117+
// message received from the face detection node where a
118+
// face was detected and within the distance bounds of the camera.
119+
moveToMouthActionGoal: null,
105120
// Whether or not the currently-executing robot motion was paused by the user
106121
paused: false,
107122
// Flag to indicate robot motion trough teleoperation interface
108123
teleopIsMoving: false,
124+
// Flag to indicate whether to auto-continue after face detection
125+
faceDetectionAutoContinue: false,
109126
// Settings values
110127
stagingPosition: SETTINGS.stagingPosition[0],
111128
biteInitiation: SETTINGS.biteInitiation[0],
@@ -121,9 +138,13 @@ export const useGlobalState = create(
121138
set(() => ({
122139
appPage: appPage
123140
})),
124-
setDesiredFoodItem: (desiredFoodItem) =>
141+
setBiteAcquisitionActionGoal: (biteAcquisitionActionGoal) =>
125142
set(() => ({
126-
desiredFoodItem: desiredFoodItem
143+
biteAcquisitionActionGoal: biteAcquisitionActionGoal
144+
})),
145+
setMoveToMouthActionGoal: (moveToMouthActionGoal) =>
146+
set(() => ({
147+
moveToMouthActionGoal: moveToMouthActionGoal
127148
})),
128149
setPaused: (paused) =>
129150
set(() => ({
@@ -133,6 +154,10 @@ export const useGlobalState = create(
133154
set(() => ({
134155
teleopIsMoving: teleopIsMoving
135156
})),
157+
setFaceDetectionAutoContinue: (faceDetectionAutoContinue) =>
158+
set(() => ({
159+
faceDetectionAutoContinue: faceDetectionAutoContinue
160+
})),
136161
setStagingPosition: (stagingPosition) =>
137162
set(() => ({
138163
stagingPosition: stagingPosition

feedingwebapp/src/Pages/Header/Header.jsx

+11-10
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { useMediaQuery } from 'react-responsive'
88
// Component
99
import PropTypes from 'prop-types'
1010
// Toast generates a temporary pop-up with a timeout.
11-
import { ToastContainer, toast } from 'react-toastify'
11+
import { ToastContainer /* , toast */ } from 'react-toastify'
1212
import 'react-toastify/dist/ReactToastify.css'
1313
// ROS imports
1414
import { useROS } from '../../ros/ros_helpers'
@@ -65,13 +65,13 @@ const Header = (props) => {
6565
* started, take the user to the settings menu. Else, ask them to complete
6666
* or terminate the meal because modifying settings.
6767
*/
68-
const settingsClicked = useCallback(() => {
69-
if (mealState === MEAL_STATE.U_PreMeal || mealState === MEAL_STATE.U_PostMeal) {
70-
setAppPage(APP_PAGE.Settings)
71-
} else {
72-
toast('Please complete or terminate the feeding process to access Settings.')
73-
}
74-
}, [mealState, setAppPage])
68+
// const settingsClicked = useCallback(() => {
69+
// if (mealState === MEAL_STATE.U_PreMeal || mealState === MEAL_STATE.U_PostMeal) {
70+
// setAppPage(APP_PAGE.Settings)
71+
// } else {
72+
// toast('Please complete or terminate the feeding process to access Settings.')
73+
// }
74+
// }, [mealState, setAppPage])
7575

7676
// Render the component. The NavBar will stay fixed even as we vertically scroll.
7777
return (
@@ -109,13 +109,14 @@ const Header = (props) => {
109109
>
110110
Home
111111
</Nav.Link>
112-
<Nav.Link
112+
{/* TODO: Reinstate the settings menu when we implement settings! */}
113+
{/* <Nav.Link
113114
onClick={settingsClicked}
114115
className='text-dark bg-info rounded mx-1 btn-lg btn-huge p-2'
115116
style={{ fontSize: textFontSize }}
116117
>
117118
Settings
118-
</Nav.Link>
119+
</Nav.Link> */}
119120
</Nav>
120121
{NON_MOVING_STATES.has(mealState) || paused || (mealState === MEAL_STATE.U_PlateLocator && teleopIsMoving === false) ? (
121122
<Nav>

feedingwebapp/src/Pages/Home/Home.jsx

+52-5
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { useGlobalState, MEAL_STATE } from '../GlobalState'
99
import BiteAcquisitionCheck from './MealStates/BiteAcquisitionCheck'
1010
import BiteDone from './MealStates/BiteDone'
1111
import BiteSelection from './MealStates/BiteSelection'
12+
import DetectingFace from './MealStates/DetectingFace'
1213
import PlateLocator from './MealStates/PlateLocator'
1314
import PostMeal from './MealStates/PostMeal'
1415
import PreMeal from './MealStates/PreMeal'
@@ -28,6 +29,8 @@ function Home(props) {
2829
// Get the relevant values from global state
2930
const mealState = useGlobalState((state) => state.mealState)
3031
const mealStateTransitionTime = useGlobalState((state) => state.mealStateTransitionTime)
32+
const setBiteAcquisitionActionGoal = useGlobalState((state) => state.setBiteAcquisitionActionGoal)
33+
const setMoveToMouthActionGoal = useGlobalState((state) => state.setMoveToMouthActionGoal)
3134
const setMealState = useGlobalState((state) => state.setMealState)
3235
const setPaused = useGlobalState((state) => state.setPaused)
3336

@@ -40,23 +43,27 @@ function Home(props) {
4043
useEffect(() => {
4144
if (Date.now() - mealStateTransitionTime >= TIME_TO_RESET_MS) {
4245
console.log('Reverting to PreMeal due to too much elapsed time in one state.')
46+
setBiteAcquisitionActionGoal(null)
47+
setMoveToMouthActionGoal(null)
4348
setMealState(MEAL_STATE.U_PreMeal)
4449
setPaused(false)
4550
}
46-
}, [mealStateTransitionTime, setMealState, setPaused])
51+
}, [mealStateTransitionTime, setMealState, setPaused, setMoveToMouthActionGoal, setBiteAcquisitionActionGoal])
4752

4853
// Get the relevant global variables
49-
const desiredFoodItem = useGlobalState((state) => state.desiredFoodItem)
54+
const biteAcquisitionActionGoal = useGlobalState((state) => state.biteAcquisitionActionGoal)
55+
const moveToMouthActionGoal = useGlobalState((state) => state.moveToMouthActionGoal)
5056

5157
/**
5258
* All action inputs are constant. Note that we must be cautious if making
5359
* them non-constant, because the robot will re-execute an action every time
5460
* the action input changes (even on re-renders).
5561
*/
5662
const moveAbovePlateActionInput = useMemo(() => ({}), [])
57-
const biteAcquisitionActionInput = useMemo(() => desiredFoodItem, [desiredFoodItem])
63+
const biteAcquisitionActionInput = useMemo(() => biteAcquisitionActionGoal, [biteAcquisitionActionGoal])
5864
const moveToRestingPositionActionInput = useMemo(() => ({}), [])
59-
const moveToMouthActionInput = useMemo(() => ({}), [])
65+
const moveToStagingConfigurationActionInput = useMemo(() => ({}), [])
66+
const moveToMouthActionInput = useMemo(() => moveToMouthActionGoal, [moveToMouthActionGoal])
6067
const moveToStowPositionActionInput = useMemo(() => ({}), [])
6168

6269
/**
@@ -127,6 +134,27 @@ function Home(props) {
127134
case MEAL_STATE.U_BiteAcquisitionCheck: {
128135
return <BiteAcquisitionCheck debug={props.debug} />
129136
}
137+
case MEAL_STATE.R_MovingToStagingConfiguration: {
138+
/**
139+
* We recreate currentMealState due to a race condition where sometimes
140+
* the app is performing a re-rendering and *then* the state is updated.
141+
*/
142+
let currentMealState = MEAL_STATE.R_MovingToStagingConfiguration
143+
let nextMealState = MEAL_STATE.R_DetectingFace
144+
let waitingText = 'Waiting to move in front of you...'
145+
return (
146+
<RobotMotion
147+
debug={props.debug}
148+
mealState={currentMealState}
149+
nextMealState={nextMealState}
150+
actionInput={moveToStagingConfigurationActionInput}
151+
waitingText={waitingText}
152+
/>
153+
)
154+
}
155+
case MEAL_STATE.R_DetectingFace: {
156+
return <DetectingFace debug={props.debug} webVideoServerURL={props.webVideoServerURL} />
157+
}
130158
case MEAL_STATE.R_MovingToMouth: {
131159
/**
132160
* We recreate currentMealState due to a race condition where sometimes
@@ -145,6 +173,24 @@ function Home(props) {
145173
/>
146174
)
147175
}
176+
case MEAL_STATE.R_MovingFromMouthToStagingConfiguration: {
177+
/**
178+
* We recreate currentMealState due to a race condition where sometimes
179+
* the app is performing a re-rendering and *then* the state is updated.
180+
*/
181+
let currentMealState = MEAL_STATE.R_MovingFromMouthToStagingConfiguration
182+
let nextMealState = MEAL_STATE.R_DetectingFace
183+
let waitingText = 'Waiting to move from your mouth to in front of you...'
184+
return (
185+
<RobotMotion
186+
debug={props.debug}
187+
mealState={currentMealState}
188+
nextMealState={nextMealState}
189+
actionInput={moveToStagingConfigurationActionInput}
190+
waitingText={waitingText}
191+
/>
192+
)
193+
}
148194
case MEAL_STATE.R_MovingFromMouthToAbovePlate: {
149195
/**
150196
* We recreate currentMealState due to a race condition where sometimes
@@ -213,7 +259,8 @@ function Home(props) {
213259
moveAbovePlateActionInput,
214260
moveToMouthActionInput,
215261
moveToRestingPositionActionInput,
216-
moveToStowPositionActionInput
262+
moveToStowPositionActionInput,
263+
moveToStagingConfigurationActionInput
217264
])
218265

219266
// Render the component

0 commit comments

Comments
 (0)