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

Allow users to customize the distance to mouth during bite transfer #113

Merged
merged 12 commits into from
Jan 16, 2024
Merged
1,874 changes: 1,828 additions & 46 deletions feedingwebapp/package-lock.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions feedingwebapp/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"version": "0.1.0",
"private": true,
"dependencies": {
"@fluentui/react-components": "^9.44.1",
"@mapbox/node-pre-gyp": "^1.0.11",
"@testing-library/jest-dom": "^5.16.4",
"@testing-library/react": "^13.2.0",
Expand Down
20 changes: 7 additions & 13 deletions feedingwebapp/src/Pages/Constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,9 @@ export const TIME_TO_RESET_MS = 3600000 // 1 hour in milliseconds
*/
let MOVING_STATE_ICON_DICT = {}
MOVING_STATE_ICON_DICT[MEAL_STATE.R_MovingAbovePlate] = '/robot_state_imgs/move_above_plate_position.svg'
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_MovingFromMouth] = '/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 }
Expand Down Expand Up @@ -69,10 +67,6 @@ ROS_ACTIONS_NAMES[MEAL_STATE.R_MovingAbovePlate] = {
actionName: 'MoveAbovePlate',
messageType: 'ada_feeding_msgs/action/MoveTo'
}
ROS_ACTIONS_NAMES[MEAL_STATE.R_MovingFromMouthToAbovePlate] = {
actionName: 'MoveFromMouthToAbovePlate',
messageType: 'ada_feeding_msgs/action/MoveTo'
}
ROS_ACTIONS_NAMES[MEAL_STATE.U_BiteSelection] = {
actionName: 'SegmentFromPoint',
messageType: 'ada_feeding_msgs/action/SegmentFromPoint'
Expand All @@ -85,16 +79,12 @@ ROS_ACTIONS_NAMES[MEAL_STATE.R_MovingToRestingPosition] = {
actionName: 'MoveToRestingPosition',
messageType: 'ada_feeding_msgs/action/MoveTo'
}
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',
ROS_ACTIONS_NAMES[MEAL_STATE.R_MovingFromMouth] = {
actionName: 'MoveFromMouth',
messageType: 'ada_feeding_msgs/action/MoveTo'
}
ROS_ACTIONS_NAMES[MEAL_STATE.R_MovingToMouth] = {
Expand All @@ -119,6 +109,10 @@ ROS_SERVICE_NAMES[MEAL_STATE.R_DetectingFace] = {
export { ROS_SERVICE_NAMES }
export const CLEAR_OCTOMAP_SERVICE_NAME = 'clear_octomap'
export const CLEAR_OCTOMAP_SERVICE_TYPE = 'std_srvs/srv/Empty'
export const GET_PARAMETERS_SERVICE_NAME = 'ada_feeding_action_servers/get_parameters'
export const GET_PARAMETERS_SERVICE_TYPE = 'rcl_interfaces/srv/GetParameters'
export const SET_PARAMETERS_SERVICE_NAME = 'ada_feeding_action_servers/set_parameters'
export const SET_PARAMETERS_SERVICE_TYPE = 'rcl_interfaces/srv/SetParameters'

/**
* The meaning of the status that motion actions return in their results.
Expand Down
47 changes: 24 additions & 23 deletions feedingwebapp/src/Pages/Footer/Footer.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,13 @@ import { useMediaQuery } from 'react-responsive'
import PropTypes from 'prop-types'
// Local imports
import { MOVING_STATE_ICON_DICT } from '../Constants'
import { useGlobalState } from '../GlobalState'

/**
* The Footer shows a pause button. When users click it, the app tells the robot
* to immediately pause and displays a back button that allows them to return to
* previous state and a resume button that allows them to resume current state.
*
* @param {string} mealState - the current meal state
* @param {bool} paused - whether the robot is currently paused
* @param {function} pauseCallback - callback function for when the pause button
* is clicked
Expand All @@ -27,14 +27,12 @@ import { useGlobalState } from '../GlobalState'
* button is clicked. If null, don't render the resume button.
*/
const Footer = (props) => {
// Get the current meal state
const mealState = useGlobalState((state) => state.mealState)
// Flag to check if the current orientation is portrait
const isPortrait = useMediaQuery({ query: '(orientation: portrait)' })
// Icons for the footer buttons
let pauseIcon = '/robot_state_imgs/pause_button_icon.svg'
let backIcon = props.backMealState ? MOVING_STATE_ICON_DICT[props.backMealState] : ''
let resumeIcon = MOVING_STATE_ICON_DICT[mealState]
let resumeIcon = MOVING_STATE_ICON_DICT[props.mealState]
// Sizes (width, height, fontsize) of footer buttons
let pauseButtonWidth = '98vw'
let backResumeButtonWidth = '47vw'
Expand Down Expand Up @@ -95,7 +93,7 @@ const Footer = (props) => {
(config) => {
return (
<>
<Row className='justify-content-center'>
<Row className='justify-content-center' style={{ width: '100%' }}>
<Button
variant={config.variant}
disabled={config.disabled}
Expand Down Expand Up @@ -163,34 +161,37 @@ const Footer = (props) => {

// Render the component
return (
<View>
<View style={{ wdith: '100%' }}>
<MDBFooter bgColor='dark' className='text-center text-lg-left' style={{ width: '100vw' }}>
<div className='text-center' style={{ backgroundColor: 'rgba(0, 0, 0, 0.2)', paddingBottom: '5px', paddingTop: '5px' }}>
{props.paused ? (
<View style={{ flex: 1, flexDirection: 'row', justifyContent: 'center', alignItems: 'center', width: '100%' }}>
<View
style={{ flex: 5, flexDirection: 'row', justifyContent: 'center', alignItems: 'center', width: '100%', height: '100%' }}
>
{props.backCallback ? renderFooterButton(buttonConfig.back) : <></>}
</View>
<View
style={{ flex: 5, flexDirection: 'row', justifyContent: 'center', alignItems: 'center', width: '100%', height: '100%' }}
>
{props.resumeCallback ? renderFooterButton(buttonConfig.resume) : <></>}
</View>
</View>
) : (
renderFooterButton(buttonConfig.pause)
)}
<View style={{ flex: 1, flexDirection: 'row', justifyContent: 'center', alignItems: 'center', width: '100%' }}>
{props.paused ? (
<>
<View
style={{ flex: 1, flexDirection: 'row', justifyContent: 'center', alignItems: 'center', width: '100%', height: '100%' }}
>
{props.backCallback ? renderFooterButton(buttonConfig.back) : <></>}
</View>
<View
style={{ flex: 1, flexDirection: 'row', justifyContent: 'center', alignItems: 'center', width: '100%', height: '100%' }}
>
{props.resumeCallback ? renderFooterButton(buttonConfig.resume) : <></>}
</View>
</>
) : (
renderFooterButton(buttonConfig.pause)
)}
</View>
</div>
</MDBFooter>
</View>
)
}
Footer.propTypes = {
mealState: PropTypes.string.isRequired,
paused: PropTypes.bool.isRequired,
pauseCallback: PropTypes.func.isRequired,
// If any of the below three are null, the Footer won't render that button
// If any of the below two are null, the Footer won't render that button
resumeCallback: PropTypes.func,
backCallback: PropTypes.func,
backMealState: PropTypes.string
Expand Down
108 changes: 69 additions & 39 deletions feedingwebapp/src/Pages/GlobalState.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,18 +39,9 @@ export const APP_PAGE = {
* - 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
* - R_MovingFromMouth: 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
* (e.g., a slower speed).
* - R_MovingFromMouthToRestingPosition: Waiting for the robot to move from
* the user's mouth to resting position. This is a separate action from
* R_MovingToRestingPosition to allow us to customize the departure from
* the mouth (e.g., a slower speed).
* action from R_MovingToStagingConfiguration since it is cartesian.
* - U_BiteDone: Waiting for the user to indicate that they are done eating
* the bite.
* - R_StowingArm: Waiting for the robot to stow the arm.
Expand All @@ -67,14 +58,23 @@ export const MEAL_STATE = {
R_MovingToStagingConfiguration: 'R_MovingToStagingConfiguration',
R_DetectingFace: 'R_DetectingFace',
R_MovingToMouth: 'R_MovingToMouth',
R_MovingFromMouthToStagingConfiguration: 'R_MovingFromMouthToStagingConfiguration',
R_MovingFromMouthToAbovePlate: 'R_MovingFromMouthToAbovePlate',
R_MovingFromMouthToRestingPosition: 'R_MovingFromMouthToRestingPosition',
R_MovingFromMouth: 'R_MovingFromMouth',
U_BiteDone: 'U_BiteDone',
R_StowingArm: 'R_StowingArm',
U_PostMeal: 'U_PostMeal'
}

/**
* SETTINGS_STATE controls which settings page to display.
* - MAIN: The main page, with options to navigate to the other pages.
* - BITE_TRANSFER: The bite transfer page, where the user can configure
* parameters for bite transfer.
*/
export const SETTINGS_STATE = {
MAIN: 'MAIN',
BITE_TRANSFER: 'BITE_TRANSFER'
}

/**
* The parameters that users can set (keys) and a list of human-readable values
* they can take on.
Expand All @@ -90,11 +90,11 @@ export const MEAL_STATE = {
* TODO (amaln): When we connect this to ROS, each of these settings types and
* value options will have to have corresponding rosparam names and value options.
*/
export const SETTINGS = {
stagingPosition: ['In Front of Me', 'On My Right Side'],
biteInitiation: ['Open Mouth', 'Say "I am Ready"', 'Press Button'],
biteSelection: ['Name of Food', 'Click on Food']
}
// export const SETTINGS = {
// stagingPosition: ['In Front of Me', 'On My Right Side'],
// biteInitiation: ['Open Mouth', 'Say "I am Ready"', 'Press Button'],
// biteSelection: ['Name of Food', 'Click on Food']
// }

/**
* useGlobalState is a hook to store and manipulate web app state that we want
Expand All @@ -104,12 +104,14 @@ export const SETTINGS = {
export const useGlobalState = create(
persist(
(set) => ({
// The current app page
appPage: APP_PAGE.Home,
// The app's current meal state
mealState: MEAL_STATE.U_PreMeal,
// The timestamp when the robot transitioned to its current meal state
mealStateTransitionTime: Date.now(),
// The current app page
appPage: APP_PAGE.Home,
// The currently displayed settings page
settingsState: SETTINGS_STATE.MAIN,
// The goal for the bite acquisition action, including the most recent
// food item that the user selected in "bite selection"
biteAcquisitionActionGoal: null,
Expand All @@ -123,20 +125,44 @@ export const useGlobalState = create(
teleopIsMoving: false,
// Flag to indicate whether to auto-continue after face detection
faceDetectionAutoContinue: false,
// Whether the settings bite transfer page is currently at the user's face
// or not. This is in the off-chance that the mealState is not at the user's
// face, the settings page is, and the user refreshes -- the page should
// call MoveFromMouthToStaging instead of just MoveToStaging.
biteTransferPageAtFace: false,
// The button the user most recently clicked on the BiteDone page. In practice,
// this is the state we transition to after R_MovingFromMouth. In practice,
// it is either R_MovingAbovePlate, R_MovingToRestingPosition, or R_DetectingFace.
mostRecentBiteDoneResponse: MEAL_STATE.R_DetectingFace,
// Settings values
stagingPosition: SETTINGS.stagingPosition[0],
biteInitiation: SETTINGS.biteInitiation[0],
biteSelection: SETTINGS.biteSelection[0],
// stagingPosition: SETTINGS.stagingPosition[0],
// biteInitiation: SETTINGS.biteInitiation[0],
// biteSelection: SETTINGS.biteSelection[0],

// Setters for global state
setMealState: (mealState) =>
setAppPage: (appPage) =>
set(() => ({
mealState: mealState,
mealStateTransitionTime: Date.now()
appPage: appPage,
settingsState: SETTINGS_STATE.MAIN,
// Sometimes the settings menu leaves the robot in a paused state.
// Thus, we reset it to an unpaused state.
paused: false
})),
setAppPage: (appPage) =>
setMealState: (mealState, mostRecentBiteDoneResponse = null) =>
set(() => {
let retval = {
mealState: mealState,
mealStateTransitionTime: Date.now(),
biteTransferPageAtFace: false // Reset this flag when the meal state changes
}
if (mostRecentBiteDoneResponse) {
retval.mostRecentBiteDoneResponse = mostRecentBiteDoneResponse
}
return retval
}),
setSettingsState: (settingsState) =>
set(() => ({
appPage: appPage
settingsState: settingsState
})),
setBiteAcquisitionActionGoal: (biteAcquisitionActionGoal) =>
set(() => ({
Expand All @@ -158,18 +184,22 @@ export const useGlobalState = create(
set(() => ({
faceDetectionAutoContinue: faceDetectionAutoContinue
})),
setStagingPosition: (stagingPosition) =>
set(() => ({
stagingPosition: stagingPosition
})),
setBiteInitiation: (biteInitiation) =>
set(() => ({
biteInitiation: biteInitiation
})),
setBiteSelection: (biteSelection) =>
setBiteTransferPageAtFace: (biteTransferPageAtFace) =>
set(() => ({
biteSelection: biteSelection
biteTransferPageAtFace: biteTransferPageAtFace
}))
// setStagingPosition: (stagingPosition) =>
// set(() => ({
// stagingPosition: stagingPosition
// })),
// setBiteInitiation: (biteInitiation) =>
// set(() => ({
// biteInitiation: biteInitiation
// })),
// setBiteSelection: (biteSelection) =>
// set(() => ({
// biteSelection: biteSelection
// }))
}),
{ name: 'ada_web_app_global_state' }
)
Expand Down
21 changes: 10 additions & 11 deletions feedingwebapp/src/Pages/Header/Header.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import Navbar from 'react-bootstrap/Navbar'
import Nav from 'react-bootstrap/Nav'
import { useMediaQuery } from 'react-responsive'
// 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 @@ -63,13 +63,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 (NON_MOVING_STATES.has(mealState)) {
setAppPage(APP_PAGE.Settings)
} else {
toast('Wait for robot motion to complete before accessing Settings.')
}
}, [mealState, setAppPage])

// Render the component. The NavBar will stay fixed even as we vertically scroll.
return (
Expand Down Expand Up @@ -107,14 +107,13 @@ const Header = (props) => {
>
Home
</Nav.Link>
{/* TODO: Reinstate the settings menu when we implement settings! */}
{/* <Nav.Link
<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
Loading