diff --git a/feedingwebapp/src/Pages/GlobalState.jsx b/feedingwebapp/src/Pages/GlobalState.jsx index 8398b344..3958a126 100644 --- a/feedingwebapp/src/Pages/GlobalState.jsx +++ b/feedingwebapp/src/Pages/GlobalState.jsx @@ -82,7 +82,8 @@ export { NON_MOVING_STATES } export const SETTINGS_STATE = { MAIN: 'MAIN', BITE_TRANSFER: 'BITE_TRANSFER', - ABOVE_PLATE: 'ABOVE_PLATE' + ABOVE_PLATE: 'ABOVE_PLATE', + RESTING_CONFIGURATION: 'RESTING_CONFIGURATION' } // The name of the default parameter namespace diff --git a/feedingwebapp/src/Pages/Header/TeleopSubcomponent.jsx b/feedingwebapp/src/Pages/Header/TeleopSubcomponent.jsx index 02c83eee..132f9ef9 100644 --- a/feedingwebapp/src/Pages/Header/TeleopSubcomponent.jsx +++ b/feedingwebapp/src/Pages/Header/TeleopSubcomponent.jsx @@ -152,14 +152,14 @@ const TeleopSubcomponent = (props) => { * When the component is unmounted, stop servo. */ useEffect(() => { - let stopServoSuccessCallback = props.stopServoSuccessCallback + let unmountCallback = props.unmountCallback return () => { console.log('Unmounting teleop subcomponent.') destroyActionClient(startCartesianControllerAction) destroyActionClient(startJointControllerAction) - stopServoSuccessCallback.current() + unmountCallback.current() } - }, [startCartesianControllerAction, startJointControllerAction, props.stopServoSuccessCallback]) + }, [startCartesianControllerAction, startJointControllerAction, props.unmountCallback]) /** * Callback function to publish constant cartesian cartesian velocity commands. @@ -650,12 +650,12 @@ const TeleopSubcomponent = (props) => { } TeleopSubcomponent.propTypes = { // A reference to a function to be called if StopServo is succesfully run. - stopServoSuccessCallback: PropTypes.object, + unmountCallback: PropTypes.object, // A function to be called when one of the teleop buttons are released teleopButtonOnReleaseCallback: PropTypes.func } TeleopSubcomponent.defaultProps = { - stopServoSuccessCallback: { current: () => {} }, + unmountCallback: { current: () => {} }, teleopButtonOnReleaseCallback: () => {} } export default TeleopSubcomponent diff --git a/feedingwebapp/src/Pages/Settings/AbovePlate.jsx b/feedingwebapp/src/Pages/Settings/CustomizeConfiguration.jsx similarity index 74% rename from feedingwebapp/src/Pages/Settings/AbovePlate.jsx rename to feedingwebapp/src/Pages/Settings/CustomizeConfiguration.jsx index be405172..514e2567 100644 --- a/feedingwebapp/src/Pages/Settings/AbovePlate.jsx +++ b/feedingwebapp/src/Pages/Settings/CustomizeConfiguration.jsx @@ -8,7 +8,6 @@ import { View } from 'react-native' // Local imports import { useROS, createROSService, createROSServiceRequest } from '../../ros/ros_helpers' import { - ABOVE_PLATE_PARAM_JOINTS, CAMERA_FEED_TOPIC, getRobotMotionText, GET_JOINT_STATE_SERVICE_NAME, @@ -23,10 +22,16 @@ import SettingsPageParent from './SettingsPageParent' import VideoFeed from '../Home/VideoFeed' /** - * The AbovePlate component allows users to configure the "above plate" configuration - * the robot moves to before bite selection. + * The CustomizeConfiguration component allows users to configure the one of the + * fixed configurations the robot uses. In its current form, the node can take in + * multiple parameters, but will set them all to the same joint state positions. */ -const AbovePlate = (props) => { +const CustomizeConfiguration = (props) => { + // Check the props + if (Object.values(MEAL_STATE).indexOf(props.startingMealState) === -1) { + throw new Error('Invalid starting meal state ' + props.startingMealState) + } + // Get relevant global state variables const setSettingsState = useGlobalState((state) => state.setSettingsState) const globalMealState = useGlobalState((state) => state.mealState) @@ -35,18 +40,17 @@ const AbovePlate = (props) => { // Create relevant local state variables // Configure the parameters for SettingsPageParent - const paramNames = useMemo(() => [ABOVE_PLATE_PARAM_JOINTS], []) - const [currentAbovePlateParam, setCurrentAbovePlateParam] = useState([null]) + const [currentConfigurationParams, setCurrentConfigurationParams] = useState(props.paramNames.map(() => null)) const [localCurrAndNextMealState, setLocalCurrAndNextMealState] = useState( globalMealState === MEAL_STATE.U_BiteDone || biteTransferPageAtFace - ? [MEAL_STATE.R_MovingFromMouth, MEAL_STATE.R_MovingAbovePlate] - : [MEAL_STATE.R_MovingAbovePlate, null] + ? [MEAL_STATE.R_MovingFromMouth, props.startingMealState] + : [props.startingMealState, null] ) const actionInput = useMemo(() => ({}), []) const doneButtonIsClicked = useRef(false) const [zoomLevel, setZoomLevel] = useState(1.0) const [mountTeleopSubcomponent, setMountTeleopSubcomponent] = useState(false) - const stopServoSuccessCallback = useRef(() => {}) + const unmountTeleopSubcomponentCallback = useRef(() => {}) // Flag to check if the current orientation is portrait const isPortrait = useMediaQuery({ query: '(orientation: portrait)' }) @@ -62,8 +66,8 @@ const AbovePlate = (props) => { (newLocalCurrMealState, newLocalNextMealState = null) => { console.log('setLocalCurrMealStateWrapper evaluated') let oldLocalCurrMealState = localCurrAndNextMealState[0] - // Only mount the teleop subcomponent if the robot finished moving above the plate - if (newLocalCurrMealState === null && oldLocalCurrMealState === MEAL_STATE.R_MovingAbovePlate) { + // Only mount the teleop subcomponent if the robot finished the prereq motion for this page + if (newLocalCurrMealState === null && oldLocalCurrMealState === props.startingMealState) { setMountTeleopSubcomponent(true) } // Start in a moving state, not a paused state @@ -77,18 +81,24 @@ const AbovePlate = (props) => { setLocalCurrAndNextMealState([newLocalCurrMealState, newLocalNextMealState]) } }, - [localCurrAndNextMealState, setLocalCurrAndNextMealState, setMountTeleopSubcomponent, doneButtonIsClicked, setPaused, setSettingsState] + [ + doneButtonIsClicked, + localCurrAndNextMealState, + props.startingMealState, + setLocalCurrAndNextMealState, + setMountTeleopSubcomponent, + setPaused, + setSettingsState + ] ) // Get the function that sets the local curr meal state and next meal state variables. // Note this does not execute it. It is used to pass the function to other components. const getSetLocalCurrMealStateWrapper = useCallback( (newLocalCurrMealState, newLocalNextMealState = null) => { - console.log('getSetLocalCurrMealStateWrapper evaluated') let retval = () => { - console.log('getSetLocalCurrMealStateWrapper retval evaluated') setLocalCurrMealStateWrapper(newLocalCurrMealState, newLocalNextMealState) - stopServoSuccessCallback.current = () => {} + unmountTeleopSubcomponentCallback.current = () => {} } // If the teleop subcomponent was mounted, unmount it and let the stopServo // success callback handle the rest. However, sometimes the success message @@ -101,7 +111,7 @@ const AbovePlate = (props) => { } return retval }, - [mountTeleopSubcomponent, setLocalCurrMealStateWrapper, setMountTeleopSubcomponent, stopServoSuccessCallback] + [mountTeleopSubcomponent, setLocalCurrMealStateWrapper, setMountTeleopSubcomponent, unmountTeleopSubcomponentCallback] ) // Store the props for the RobotMotion call. @@ -149,17 +159,17 @@ const AbovePlate = (props) => { }) service.callService(request, (response) => { console.log('Got joint state response', response) - setCurrentAbovePlateParam([response.joint_state.position]) + setCurrentConfigurationParams(props.paramNames.map(() => response.joint_state.position)) }) - }, [getJointStateService, setCurrentAbovePlateParam]) + }, [getJointStateService, props.paramNames, setCurrentConfigurationParams]) // Callback to move the robot to another configuration const moveToButtonClicked = useCallback( (nextMealState) => { doneButtonIsClicked.current = false - stopServoSuccessCallback.current = getSetLocalCurrMealStateWrapper(nextMealState) + unmountTeleopSubcomponentCallback.current = getSetLocalCurrMealStateWrapper(nextMealState) }, - [getSetLocalCurrMealStateWrapper, doneButtonIsClicked, stopServoSuccessCallback] + [getSetLocalCurrMealStateWrapper, doneButtonIsClicked, unmountTeleopSubcomponentCallback] ) // Callback to return to the main settings page @@ -191,11 +201,11 @@ const AbovePlate = (props) => { localCurrMealState = MEAL_STATE.R_MovingAbovePlate break } - stopServoSuccessCallback.current = getSetLocalCurrMealStateWrapper(localCurrMealState, localNextMealState) - }, [getSetLocalCurrMealStateWrapper, globalMealState, doneButtonIsClicked, stopServoSuccessCallback]) + unmountTeleopSubcomponentCallback.current = getSetLocalCurrMealStateWrapper(localCurrMealState, localNextMealState) + }, [getSetLocalCurrMealStateWrapper, globalMealState, doneButtonIsClicked, unmountTeleopSubcomponentCallback]) // Callback to render the main contents of the page - const renderAbovePlateSettings = useCallback(() => { + const renderSettings = useCallback(() => { return ( { <> ) : (

- To tune the “Above Plate” configuration, first “Move Above Plate.” + To tune the “{props.configurationName}” configuration, first “{props.buttonName}.”

)}
@@ -238,38 +248,24 @@ const AbovePlate = (props) => { height: '100%' }} > - - - - - - + {props.otherButtonConfigs.map(({ name, mealState }) => ( + + + + ))} @@ -295,10 +291,14 @@ const AbovePlate = (props) => { textFontSize, sizeSuffix, zoomLevel, + props.buttonName, + props.configurationName, + props.otherButtonConfigs, + props.startingMealState, props.webrtcURL, mountTeleopSubcomponent, moveToButtonClicked, - stopServoSuccessCallback, + unmountTeleopSubcomponentCallback, storeJointStatesAsLocalParam ]) @@ -311,6 +311,7 @@ const AbovePlate = (props) => { // Render the modal body, for calling robot code from within this settings page const renderModalBody = useCallback(() => { + console.log('renderModalBody', localCurrAndNextMealState, robotMotionProps) let localCurrMealState = localCurrAndNextMealState[0] switch (localCurrMealState) { case MEAL_STATE.R_MovingToStagingConfiguration: @@ -338,23 +339,38 @@ const AbovePlate = (props) => { return ( setLocalCurrMealStateWrapper(null)} modalChildren={renderModalBody()} - paramNames={paramNames} - localParamValues={currentAbovePlateParam} - setLocalParamValues={setCurrentAbovePlateParam} - resetToPresetSuccessCallback={() => moveToButtonClicked(MEAL_STATE.R_MovingAbovePlate)} + paramNames={props.paramNames} + localParamValues={currentConfigurationParams} + setLocalParamValues={setCurrentConfigurationParams} + resetToPresetSuccessCallback={() => moveToButtonClicked(props.startingMealState)} > - {renderAbovePlateSettings()} + {renderSettings()} ) } -AbovePlate.propTypes = { +CustomizeConfiguration.propTypes = { + // The meal state that must be executed before this page is rendered + startingMealState: PropTypes.string.isRequired, + // The names of the parameter this component should tune. + paramNames: PropTypes.arrayOf(PropTypes.string).isRequired, + // The name of the configuration this component tunes. + configurationName: PropTypes.string.isRequired, + // The name of the button that should be clicked to tune the configuration. + buttonName: PropTypes.string.isRequired, + // Other button configs + otherButtonConfigs: PropTypes.arrayOf( + PropTypes.shape({ + name: PropTypes.string.isRequired, + mealState: PropTypes.string.isRequired + }) + ).isRequired, // The URL of the webrtc signalling server webrtcURL: PropTypes.string.isRequired } -export default AbovePlate +export default CustomizeConfiguration diff --git a/feedingwebapp/src/Pages/Settings/Main.jsx b/feedingwebapp/src/Pages/Settings/Main.jsx index b0bd2bde..f0f696a6 100644 --- a/feedingwebapp/src/Pages/Settings/Main.jsx +++ b/feedingwebapp/src/Pages/Settings/Main.jsx @@ -156,6 +156,7 @@ const Main = () => { // Get icon image for move to mouth let moveToMouthConfigurationImage = MOVING_STATE_ICON_DICT[MEAL_STATE.R_MovingToMouth] let moveAbovePlateConfigurationImage = MOVING_STATE_ICON_DICT[MEAL_STATE.R_MovingAbovePlate] + let moveToRestingConfigurationImage = MOVING_STATE_ICON_DICT[MEAL_STATE.R_MovingToRestingPosition] // Configure the different options in the settings menu let settingsConfig = [ @@ -168,6 +169,11 @@ const Main = () => { title: 'Above Plate', icon: moveAbovePlateConfigurationImage, onClick: () => onClickSettingsPage(SETTINGS_STATE.ABOVE_PLATE) + }, + { + title: 'Resting Position', + icon: moveToRestingConfigurationImage, + onClick: () => onClickSettingsPage(SETTINGS_STATE.RESTING_CONFIGURATION) } ] diff --git a/feedingwebapp/src/Pages/Settings/Settings.jsx b/feedingwebapp/src/Pages/Settings/Settings.jsx index cc84b68c..840467cf 100644 --- a/feedingwebapp/src/Pages/Settings/Settings.jsx +++ b/feedingwebapp/src/Pages/Settings/Settings.jsx @@ -4,10 +4,11 @@ import PropTypes from 'prop-types' import { View } from 'react-native' // Local imports -import { useGlobalState, SETTINGS_STATE } from '../GlobalState' +import { useGlobalState, SETTINGS_STATE, MEAL_STATE } from '../GlobalState' import Main from './Main' -import AbovePlate from './AbovePlate' +import CustomizeConfiguration from './CustomizeConfiguration' import BiteTransfer from './BiteTransfer' +import { ABOVE_PLATE_PARAM_JOINTS, RESTING_PARAM_JOINTS_1, RESTING_PARAM_JOINTS_2 } from '../Constants' /** * The Settings components displays the appropriate settings page based on the @@ -25,7 +26,45 @@ const Settings = (props) => { case SETTINGS_STATE.BITE_TRANSFER: return case SETTINGS_STATE.ABOVE_PLATE: - return + return ( + + ) + case SETTINGS_STATE.RESTING_CONFIGURATION: + return ( + + ) default: console.log('Invalid settings state', settingsState) return