Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Split MoveToMouth Into two Actions #94

Merged
merged 9 commits into from
Nov 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
class FaceDetectionNode(Node):
def __init__(
self,
face_detection_interval=150,
face_detection_interval=90,
num_images_with_face=60,
open_mouth_interval=90,
num_images_with_open_mouth=30,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#!/usr/bin/env python3
from ada_feeding_msgs.action import MoveTo
from feeding_web_app_ros2_test.MoveToDummy import MoveToDummy
import rclpy
from rclpy.executors import MultiThreadedExecutor


def main(args=None):
rclpy.init(args=args)

move_from_mouth_to_staging_configuration = MoveToDummy(
"MoveFromMouthToStagingConfiguration", MoveTo
)

# Use a MultiThreadedExecutor to enable processing goals concurrently
executor = MultiThreadedExecutor()

rclpy.spin(move_from_mouth_to_staging_configuration, executor=executor)


if __name__ == "__main__":
main()
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
from ada_feeding_msgs.action import MoveTo
from ada_feeding_msgs.action import MoveToMouth
from feeding_web_app_ros2_test.MoveToDummy import MoveToDummy
import rclpy
from rclpy.executors import MultiThreadedExecutor
Expand All @@ -8,7 +8,7 @@
def main(args=None):
rclpy.init(args=args)

move_to_mouth = MoveToDummy("MoveToMouth", MoveTo)
move_to_mouth = MoveToDummy("MoveToMouth", MoveToMouth)

# Use a MultiThreadedExecutor to enable processing goals concurrently
executor = MultiThreadedExecutor()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#!/usr/bin/env python3
from ada_feeding_msgs.action import MoveTo
from feeding_web_app_ros2_test.MoveToDummy import MoveToDummy
import rclpy
from rclpy.executors import MultiThreadedExecutor


def main(args=None):
rclpy.init(args=args)

move_to_staging_configuration = MoveToDummy("MoveToStagingConfiguration", MoveTo)

# Use a MultiThreadedExecutor to enable processing goals concurrently
executor = MultiThreadedExecutor()

rclpy.spin(move_to_staging_configuration, executor=executor)


if __name__ == "__main__":
main()
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,12 @@
<node pkg="feeding_web_app_ros2_test" exec="AcquireFood" name="AcquireFood"/>
<!-- Motion: The MoveToRestingPosition action -->
<node pkg="feeding_web_app_ros2_test" exec="MoveToRestingPosition" name="MoveToRestingPosition"/>
<!-- Motion: The MoveToStagingConfiguration action -->
<node pkg="feeding_web_app_ros2_test" exec="MoveToStagingConfiguration" name="MoveToStagingConfiguration"/>
<!-- Motion: The MoveToMouth action -->
<node pkg="feeding_web_app_ros2_test" exec="MoveToMouth" name="MoveToMouth"/>
<!-- Motion: The MoveFromMouthToStagingConfiguration action -->
<node pkg="feeding_web_app_ros2_test" exec="MoveFromMouthToStagingConfiguration" name="MoveFromMouthToStagingConfiguration"/>
<!-- Motion: The MoveFromMouthToAbovePlate action -->
<node pkg="feeding_web_app_ros2_test" exec="MoveFromMouthToAbovePlate" name="MoveFromMouthToAbovePlate"/>
<!-- Motion: The MoveFromMouthToRestingPosition action -->
Expand Down
2 changes: 2 additions & 0 deletions feeding_web_app_ros2_test/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,9 @@
"FaceDetection = feeding_web_app_ros2_test.FaceDetection:main",
"MoveAbovePlate = feeding_web_app_ros2_test.MoveAbovePlate:main",
"MoveToRestingPosition = feeding_web_app_ros2_test.MoveToRestingPosition:main",
"MoveToStagingConfiguration = feeding_web_app_ros2_test.MoveToStagingConfiguration:main",
"MoveToMouth = feeding_web_app_ros2_test.MoveToMouth:main",
"MoveFromMouthToStagingConfiguration = feeding_web_app_ros2_test.MoveFromMouthToStagingConfiguration:main",
"MoveFromMouthToAbovePlate = feeding_web_app_ros2_test.MoveFromMouthToAbovePlate:main",
"MoveFromMouthToRestingPosition = feeding_web_app_ros2_test.MoveFromMouthToRestingPosition:main",
"MoveToStowLocation = feeding_web_app_ros2_test.MoveToStowLocation:main",
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
35 changes: 34 additions & 1 deletion feedingwebapp/src/Pages/Constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,18 @@ MOVING_STATE_ICON_DICT[MEAL_STATE.R_MovingAbovePlate] = '/robot_state_imgs/move_
MOVING_STATE_ICON_DICT[MEAL_STATE.R_MovingFromMouthToAbovePlate] = '/robot_state_imgs/move_above_plate_position.svg'
MOVING_STATE_ICON_DICT[MEAL_STATE.R_MovingToRestingPosition] = '/robot_state_imgs/move_to_resting_position.svg'
MOVING_STATE_ICON_DICT[MEAL_STATE.R_MovingFromMouthToRestingPosition] = '/robot_state_imgs/move_to_resting_position.svg'
MOVING_STATE_ICON_DICT[MEAL_STATE.R_MovingToStagingConfiguration] = '/robot_state_imgs/move_to_staging_configuration.svg'
MOVING_STATE_ICON_DICT[MEAL_STATE.R_MovingFromMouthToStagingConfiguration] = '/robot_state_imgs/move_to_staging_configuration.svg'
MOVING_STATE_ICON_DICT[MEAL_STATE.R_MovingToMouth] = '/robot_state_imgs/move_to_mouth_position.svg'
MOVING_STATE_ICON_DICT[MEAL_STATE.R_StowingArm] = '/robot_state_imgs/stowing_arm_position.svg'
export { MOVING_STATE_ICON_DICT }

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

// States from which, if they fail, it is NOT okay for the user to retry the
// same action.
let NON_RETRYABLE_STATES = new Set()
NON_RETRYABLE_STATES.add(MEAL_STATE.R_BiteAcquisition)
export { NON_RETRYABLE_STATES }

/**
* For states that call ROS actions, this dictionary contains
* the action name and the message type
Expand Down Expand Up @@ -76,16 +88,37 @@ ROS_ACTIONS_NAMES[MEAL_STATE.R_MovingFromMouthToRestingPosition] = {
actionName: 'MoveFromMouthToRestingPosition',
messageType: 'ada_feeding_msgs/action/MoveTo'
}
ROS_ACTIONS_NAMES[MEAL_STATE.R_MovingToStagingConfiguration] = {
actionName: 'MoveToStagingConfiguration',
messageType: 'ada_feeding_msgs/action/MoveTo'
}
ROS_ACTIONS_NAMES[MEAL_STATE.R_MovingFromMouthToStagingConfiguration] = {
actionName: 'MoveFromMouthToStagingConfiguration',
messageType: 'ada_feeding_msgs/action/MoveTo'
}
ROS_ACTIONS_NAMES[MEAL_STATE.R_MovingToMouth] = {
actionName: 'MoveToMouth',
messageType: 'ada_feeding_msgs/action/MoveTo'
messageType: 'ada_feeding_msgs/action/MoveToMouth'
}
ROS_ACTIONS_NAMES[MEAL_STATE.R_StowingArm] = {
actionName: 'MoveToStowLocation',
messageType: 'ada_feeding_msgs/action/MoveTo'
}
export { ROS_ACTIONS_NAMES }

/**
* For states that call ROS services, this dictionary contains
* the service name and the message type
*/
let ROS_SERVICE_NAMES = {}
ROS_SERVICE_NAMES[MEAL_STATE.R_DetectingFace] = {
serviceName: 'toggle_face_detection',
messageType: 'std_srvs/srv/SetBool'
}
export { ROS_SERVICE_NAMES }
export const CLEAR_OCTOMAP_SERVICE_NAME = 'clear_octomap'
export const CLEAR_OCTOMAP_SERVICE_TYPE = 'std_srvs/srv/Empty'

/**
* The meaning of the status that motion actions return in their results.
* These should match the action definition(s).
Expand Down
33 changes: 29 additions & 4 deletions feedingwebapp/src/Pages/GlobalState.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,15 @@ export const APP_PAGE = {
* position.
* - U_BiteAcquisitionCheck: Waiting for the user to specify whether the
* bite acquisition was succesful or not.
* - R_MovingToStagingConfiguration: Waiting for the robot to move to the
* staging configuration.
* - R_DetectingFace: Waiting for the robot to detect a face.
* - R_MovingToMouth: Waiting for the robot to finish moving to the user's
* mouth.
* - R_MovingFromMouthToStagingConfiguration: Waiting for the robot to move
* from the user's mouth to the staging configuration. This is a separate
* action from R_MovingToStagingConfiguration to allow us to customize the
* departure from the mouth (e.g., a slower speed).
* - R_MovingFromMouthToAbovePlate: Waiting for the robot to move from the
* user's mouth to above the plate. This is a separate action from
* R_MovingAbovePlate to allow us to customize the departure from the mouth
Expand All @@ -57,7 +64,10 @@ export const MEAL_STATE = {
R_BiteAcquisition: 'R_BiteAcquisition',
R_MovingToRestingPosition: 'R_MovingToRestingPosition',
U_BiteAcquisitionCheck: 'U_BiteAcquisitionCheck',
R_MovingToStagingConfiguration: 'R_MovingToStagingConfiguration',
R_DetectingFace: 'R_DetectingFace',
R_MovingToMouth: 'R_MovingToMouth',
R_MovingFromMouthToStagingConfiguration: 'R_MovingFromMouthToStagingConfiguration',
R_MovingFromMouthToAbovePlate: 'R_MovingFromMouthToAbovePlate',
R_MovingFromMouthToRestingPosition: 'R_MovingFromMouthToRestingPosition',
U_BiteDone: 'U_BiteDone',
Expand Down Expand Up @@ -100,12 +110,19 @@ export const useGlobalState = create(
mealStateTransitionTime: Date.now(),
// The current app page
appPage: APP_PAGE.Home,
// The most recent food item that the user selected in "bite selection"
desiredFoodItem: null,
// The goal for the bite acquisition action, including the most recent
// food item that the user selected in "bite selection"
biteAcquisitionActionGoal: null,
// The goal for the move to mouth action, including the most recent
// message received from the face detection node where a
// face was detected and within the distance bounds of the camera.
moveToMouthActionGoal: null,
// Whether or not the currently-executing robot motion was paused by the user
paused: false,
// Flag to indicate robot motion trough teleoperation interface
teleopIsMoving: false,
// Flag to indicate whether to auto-continue after face detection
faceDetectionAutoContinue: false,
// Settings values
stagingPosition: SETTINGS.stagingPosition[0],
biteInitiation: SETTINGS.biteInitiation[0],
Expand All @@ -121,9 +138,13 @@ export const useGlobalState = create(
set(() => ({
appPage: appPage
})),
setDesiredFoodItem: (desiredFoodItem) =>
setBiteAcquisitionActionGoal: (biteAcquisitionActionGoal) =>
set(() => ({
desiredFoodItem: desiredFoodItem
biteAcquisitionActionGoal: biteAcquisitionActionGoal
})),
setMoveToMouthActionGoal: (moveToMouthActionGoal) =>
set(() => ({
moveToMouthActionGoal: moveToMouthActionGoal
})),
setPaused: (paused) =>
set(() => ({
Expand All @@ -133,6 +154,10 @@ export const useGlobalState = create(
set(() => ({
teleopIsMoving: teleopIsMoving
})),
setFaceDetectionAutoContinue: (faceDetectionAutoContinue) =>
set(() => ({
faceDetectionAutoContinue: faceDetectionAutoContinue
})),
setStagingPosition: (stagingPosition) =>
set(() => ({
stagingPosition: stagingPosition
Expand Down
21 changes: 11 additions & 10 deletions feedingwebapp/src/Pages/Header/Header.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { useMediaQuery } from 'react-responsive'
// Component
import PropTypes from 'prop-types'
// Toast generates a temporary pop-up with a timeout.
import { ToastContainer, toast } from 'react-toastify'
import { ToastContainer /* , toast */ } from 'react-toastify'
import 'react-toastify/dist/ReactToastify.css'
// ROS imports
import { useROS } from '../../ros/ros_helpers'
Expand Down Expand Up @@ -65,13 +65,13 @@ const Header = (props) => {
* started, take the user to the settings menu. Else, ask them to complete
* or terminate the meal because modifying settings.
*/
const settingsClicked = useCallback(() => {
if (mealState === MEAL_STATE.U_PreMeal || mealState === MEAL_STATE.U_PostMeal) {
setAppPage(APP_PAGE.Settings)
} else {
toast('Please complete or terminate the feeding process to access Settings.')
}
}, [mealState, setAppPage])
// const settingsClicked = useCallback(() => {
// if (mealState === MEAL_STATE.U_PreMeal || mealState === MEAL_STATE.U_PostMeal) {
// setAppPage(APP_PAGE.Settings)
// } else {
// toast('Please complete or terminate the feeding process to access Settings.')
// }
// }, [mealState, setAppPage])

// Render the component. The NavBar will stay fixed even as we vertically scroll.
return (
Expand Down Expand Up @@ -109,13 +109,14 @@ const Header = (props) => {
>
Home
</Nav.Link>
<Nav.Link
{/* TODO: Reinstate the settings menu when we implement settings! */}
{/* <Nav.Link
onClick={settingsClicked}
className='text-dark bg-info rounded mx-1 btn-lg btn-huge p-2'
style={{ fontSize: textFontSize }}
>
Settings
</Nav.Link>
</Nav.Link> */}
</Nav>
{NON_MOVING_STATES.has(mealState) || paused || (mealState === MEAL_STATE.U_PlateLocator && teleopIsMoving === false) ? (
<Nav>
Expand Down
57 changes: 52 additions & 5 deletions feedingwebapp/src/Pages/Home/Home.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { useGlobalState, MEAL_STATE } from '../GlobalState'
import BiteAcquisitionCheck from './MealStates/BiteAcquisitionCheck'
import BiteDone from './MealStates/BiteDone'
import BiteSelection from './MealStates/BiteSelection'
import DetectingFace from './MealStates/DetectingFace'
import PlateLocator from './MealStates/PlateLocator'
import PostMeal from './MealStates/PostMeal'
import PreMeal from './MealStates/PreMeal'
Expand All @@ -28,6 +29,8 @@ function Home(props) {
// Get the relevant values from global state
const mealState = useGlobalState((state) => state.mealState)
const mealStateTransitionTime = useGlobalState((state) => state.mealStateTransitionTime)
const setBiteAcquisitionActionGoal = useGlobalState((state) => state.setBiteAcquisitionActionGoal)
const setMoveToMouthActionGoal = useGlobalState((state) => state.setMoveToMouthActionGoal)
const setMealState = useGlobalState((state) => state.setMealState)
const setPaused = useGlobalState((state) => state.setPaused)

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

// Get the relevant global variables
const desiredFoodItem = useGlobalState((state) => state.desiredFoodItem)
const biteAcquisitionActionGoal = useGlobalState((state) => state.biteAcquisitionActionGoal)
const moveToMouthActionGoal = useGlobalState((state) => state.moveToMouthActionGoal)

/**
* All action inputs are constant. Note that we must be cautious if making
* them non-constant, because the robot will re-execute an action every time
* the action input changes (even on re-renders).
*/
const moveAbovePlateActionInput = useMemo(() => ({}), [])
const biteAcquisitionActionInput = useMemo(() => desiredFoodItem, [desiredFoodItem])
const biteAcquisitionActionInput = useMemo(() => biteAcquisitionActionGoal, [biteAcquisitionActionGoal])
const moveToRestingPositionActionInput = useMemo(() => ({}), [])
const moveToMouthActionInput = useMemo(() => ({}), [])
const moveToStagingConfigurationActionInput = useMemo(() => ({}), [])
const moveToMouthActionInput = useMemo(() => moveToMouthActionGoal, [moveToMouthActionGoal])
const moveToStowPositionActionInput = useMemo(() => ({}), [])

/**
Expand Down Expand Up @@ -127,6 +134,27 @@ function Home(props) {
case MEAL_STATE.U_BiteAcquisitionCheck: {
return <BiteAcquisitionCheck debug={props.debug} />
}
case MEAL_STATE.R_MovingToStagingConfiguration: {
/**
* We recreate currentMealState due to a race condition where sometimes
* the app is performing a re-rendering and *then* the state is updated.
*/
let currentMealState = MEAL_STATE.R_MovingToStagingConfiguration
let nextMealState = MEAL_STATE.R_DetectingFace
let waitingText = 'Waiting to move in front of you...'
return (
<RobotMotion
debug={props.debug}
mealState={currentMealState}
nextMealState={nextMealState}
actionInput={moveToStagingConfigurationActionInput}
waitingText={waitingText}
/>
)
}
case MEAL_STATE.R_DetectingFace: {
return <DetectingFace debug={props.debug} webVideoServerURL={props.webVideoServerURL} />
}
case MEAL_STATE.R_MovingToMouth: {
/**
* We recreate currentMealState due to a race condition where sometimes
Expand All @@ -145,6 +173,24 @@ function Home(props) {
/>
)
}
case MEAL_STATE.R_MovingFromMouthToStagingConfiguration: {
/**
* We recreate currentMealState due to a race condition where sometimes
* the app is performing a re-rendering and *then* the state is updated.
*/
let currentMealState = MEAL_STATE.R_MovingFromMouthToStagingConfiguration
let nextMealState = MEAL_STATE.R_DetectingFace
let waitingText = 'Waiting to move from your mouth to in front of you...'
return (
<RobotMotion
debug={props.debug}
mealState={currentMealState}
nextMealState={nextMealState}
actionInput={moveToStagingConfigurationActionInput}
waitingText={waitingText}
/>
)
}
case MEAL_STATE.R_MovingFromMouthToAbovePlate: {
/**
* We recreate currentMealState due to a race condition where sometimes
Expand Down Expand Up @@ -213,7 +259,8 @@ function Home(props) {
moveAbovePlateActionInput,
moveToMouthActionInput,
moveToRestingPositionActionInput,
moveToStowPositionActionInput
moveToStowPositionActionInput,
moveToStagingConfigurationActionInput
])

// Render the component
Expand Down
Loading