From 9b54c09b004596ee6d5aca6c23bee59af0772840 Mon Sep 17 00:00:00 2001 From: Amal Nanavati Date: Thu, 2 May 2024 20:59:01 -0700 Subject: [PATCH 01/10] Added staging configuration customization --- feedingwebapp/src/Pages/Constants.js | 5 +- feedingwebapp/src/Pages/GlobalState.jsx | 3 +- .../MealStates/DetectingFaceSubcomponent.jsx | 2 +- .../Pages/Settings/CustomizeConfiguration.jsx | 108 +++++++++++++++--- feedingwebapp/src/Pages/Settings/Main.jsx | 6 + feedingwebapp/src/Pages/Settings/Settings.jsx | 52 ++++++++- .../src/Pages/Settings/SettingsPageParent.jsx | 18 --- 7 files changed, 155 insertions(+), 39 deletions(-) diff --git a/feedingwebapp/src/Pages/Constants.js b/feedingwebapp/src/Pages/Constants.js index 8572eee6..6b5b397d 100644 --- a/feedingwebapp/src/Pages/Constants.js +++ b/feedingwebapp/src/Pages/Constants.js @@ -114,8 +114,8 @@ export const CLEAR_OCTOMAP_SERVICE_NAME = 'clear_octomap' export const CLEAR_OCTOMAP_SERVICE_TYPE = 'std_srvs/srv/Empty' export const ACQUISITION_REPORT_SERVICE_NAME = 'ada_feeding_action_select/action_report' export const ACQUISITION_REPORT_SERVICE_TYPE = 'ada_feeding_msgs/srv/AcquisitionReport' -export const GET_JOINT_STATE_SERVICE_NAME = 'get_joint_state' -export const GET_JOINT_STATE_SERVICE_TYPE = 'ada_feeding_msgs/srv/GetJointState' +export const GET_ROBOT_STATE_SERVICE_NAME = 'get_robot_state' +export const GET_ROBOT_STATE_SERVICE_TYPE = 'ada_feeding_msgs/srv/GetRobotState' 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' @@ -135,6 +135,7 @@ export const RESTING_PARAM_JOINTS_2 = 'MoveToRestingPosition.tree_kwargs.goal_co // Robot link names export const ROBOT_BASE_LINK = 'j2n6s200_link_base' +export const ROBOT_END_EFFECTOR = 'forkTip' export const ROBOT_JOINTS = [ 'j2n6s200_joint_1', 'j2n6s200_joint_2', diff --git a/feedingwebapp/src/Pages/GlobalState.jsx b/feedingwebapp/src/Pages/GlobalState.jsx index 3958a126..36c958d3 100644 --- a/feedingwebapp/src/Pages/GlobalState.jsx +++ b/feedingwebapp/src/Pages/GlobalState.jsx @@ -83,7 +83,8 @@ export const SETTINGS_STATE = { MAIN: 'MAIN', BITE_TRANSFER: 'BITE_TRANSFER', ABOVE_PLATE: 'ABOVE_PLATE', - RESTING_CONFIGURATION: 'RESTING_CONFIGURATION' + RESTING_CONFIGURATION: 'RESTING_CONFIGURATION', + STAGING_CONFIGURATION: 'STAGING_CONFIGURATION' } // The name of the default parameter namespace diff --git a/feedingwebapp/src/Pages/Home/MealStates/DetectingFaceSubcomponent.jsx b/feedingwebapp/src/Pages/Home/MealStates/DetectingFaceSubcomponent.jsx index fe832277..f0902ae7 100644 --- a/feedingwebapp/src/Pages/Home/MealStates/DetectingFaceSubcomponent.jsx +++ b/feedingwebapp/src/Pages/Home/MealStates/DetectingFaceSubcomponent.jsx @@ -70,7 +70,7 @@ const DetectingFaceSubcomponent = (props) => { }, [faceDetectionCallback]) /** - * Create the ROS Service. This is created in local state to avoid re-creating + * Create the ROS Service. This is created in as a ref to avoid re-creating * it upon every re-render. */ let { serviceName, messageType } = ROS_SERVICE_NAMES[MEAL_STATE.R_DetectingFace] diff --git a/feedingwebapp/src/Pages/Settings/CustomizeConfiguration.jsx b/feedingwebapp/src/Pages/Settings/CustomizeConfiguration.jsx index 514e2567..74d85e31 100644 --- a/feedingwebapp/src/Pages/Settings/CustomizeConfiguration.jsx +++ b/feedingwebapp/src/Pages/Settings/CustomizeConfiguration.jsx @@ -10,9 +10,12 @@ import { useROS, createROSService, createROSServiceRequest } from '../../ros/ros import { CAMERA_FEED_TOPIC, getRobotMotionText, - GET_JOINT_STATE_SERVICE_NAME, - GET_JOINT_STATE_SERVICE_TYPE, - ROBOT_JOINTS + GET_ROBOT_STATE_SERVICE_NAME, + GET_ROBOT_STATE_SERVICE_TYPE, + ROBOT_BASE_LINK, + ROBOT_END_EFFECTOR, + ROBOT_JOINTS, + ROS_SERVICE_NAMES } from '../Constants' import { useGlobalState, MEAL_STATE, SETTINGS_STATE } from '../GlobalState' import RobotMotion from '../Home/MealStates/RobotMotion' @@ -21,6 +24,38 @@ import TeleopSubcomponent from '../Header/TeleopSubcomponent' import SettingsPageParent from './SettingsPageParent' import VideoFeed from '../Home/VideoFeed' +/** + * This function extracts the joint positions from the robot state service's response + * and returns it. + */ +export function getJointPositionsFromRobotStateResponse(response) { + return response.joint_state.position +} + +/** + * The function extracts the end effector position and quaternion in the robot's + * base link frame, from the robot state service's response. + */ +export function getEndEffectorPositionFromRobotStateResponse(response) { + if (response.poses.length === 0) { + return [] + } + let pose = response.poses[0].pose + return [pose.position.x, pose.position.y, pose.position.z] +} + +/** + * This function extracts the end effector orientation in the robot's base link frame, + * from the robot state service's response. + */ +export function getEndEffectorOrientationFromRobotStateResponse(response) { + if (response.poses.length === 0) { + return [] + } + let pose = response.poses[0].pose + return [pose.orientation.x, pose.orientation.y, pose.orientation.z, pose.orientation.w] +} + /** * 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 @@ -139,29 +174,58 @@ const CustomizeConfiguration = (props) => { const ros = useRef(useROS().ros) /** - * Create the ROS Service Clients to get/set parameters. + * Create the ROS Service Clients to get/set parameters and to toggle face detection. */ - let getJointStateService = useRef(createROSService(ros.current, GET_JOINT_STATE_SERVICE_NAME, GET_JOINT_STATE_SERVICE_TYPE)) + let getRobotStateService = useRef(createROSService(ros.current, GET_ROBOT_STATE_SERVICE_NAME, GET_ROBOT_STATE_SERVICE_TYPE)) + let { serviceName, messageType } = ROS_SERVICE_NAMES[MEAL_STATE.R_DetectingFace] + let toggleFaceDetectionService = useRef(createROSService(ros.current, serviceName, messageType)) // Reset state the first time the page is rendered useEffect(() => { doneButtonIsClicked.current = false // Start in a moving state, not a paused state setPaused(false) - }, [doneButtonIsClicked, setPaused]) + // Toggle face detection is specified + let service = toggleFaceDetectionService.current + if (props.toggleFaceDetection) { + // Create a service request + let request = createROSServiceRequest({ data: true }) + // Call the service + service.callService(request, (response) => console.log('Got toggle face detection service response', response)) + } + + /** + * In practice, because the values passed in in the second argument of + * useEffect will not change on re-renders, this return statement will + * only be called when the component unmounts. + */ + return () => { + if (props.toggleFaceDetection) { + // Create a service request + let request = createROSServiceRequest({ data: false }) + // Call the service + service.callService(request, (response) => console.log('Got toggle face detection service response', response)) + } + } + }, [doneButtonIsClicked, props.toggleFaceDetection, setPaused, toggleFaceDetectionService]) // Get the current joint states and store them as the above plate param const storeJointStatesAsLocalParam = useCallback(() => { - console.log('storeJointStatesAsLocalParam called') - let service = getJointStateService.current - let request = createROSServiceRequest({ + let service = getRobotStateService.current + let request_object = { joint_names: ROBOT_JOINTS - }) + } + if (props.getEndEffectorPose) { + request_object.child_frames = [ROBOT_END_EFFECTOR] + request_object.parent_frames = [ROBOT_BASE_LINK] + } + let request = createROSServiceRequest(request_object) + console.log('storeJointStatesAsLocalParam called with request', request) service.callService(request, (response) => { console.log('Got joint state response', response) - setCurrentConfigurationParams(props.paramNames.map(() => response.joint_state.position)) + setCurrentConfigurationParams(props.paramNames.map((_, i) => props.getParamValues[i](response))) }) - }, [getJointStateService, props.paramNames, setCurrentConfigurationParams]) + }, [getRobotStateService, props.getEndEffectorPose, props.getParamValues, props.paramNames, setCurrentConfigurationParams]) // Callback to move the robot to another configuration const moveToButtonClicked = useCallback( @@ -218,7 +282,7 @@ const CustomizeConfiguration = (props) => { }} > - + @@ -258,7 +322,7 @@ const CustomizeConfiguration = (props) => { fontSize: (textFontSize * 0.5).toString() + sizeSuffix, width: '90%', height: '90%', - color: 'black' + padding: 0 }} onClick={() => moveToButtonClicked(mealState)} > @@ -275,7 +339,7 @@ const CustomizeConfiguration = (props) => { fontSize: (textFontSize * 0.5).toString() + sizeSuffix, width: '90%', height: '90%', - color: 'black' + padding: 0 }} onClick={() => moveToButtonClicked(props.startingMealState)} > @@ -295,6 +359,7 @@ const CustomizeConfiguration = (props) => { props.configurationName, props.otherButtonConfigs, props.startingMealState, + props.videoTopic, props.webrtcURL, mountTeleopSubcomponent, moveToButtonClicked, @@ -358,6 +423,10 @@ CustomizeConfiguration.propTypes = { startingMealState: PropTypes.string.isRequired, // The names of the parameter this component should tune. paramNames: PropTypes.arrayOf(PropTypes.string).isRequired, + // Whether to get the end effector pose from the robot state service + getEndEffectorPose: PropTypes.bool, + // Functions to get the param values from the robot state service's response + getParamValues: PropTypes.arrayOf(PropTypes.func).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. @@ -369,8 +438,17 @@ CustomizeConfiguration.propTypes = { mealState: PropTypes.string.isRequired }) ).isRequired, + // Whether to toggle face detection on/off when this component mounts/unmounts + toggleFaceDetection: PropTypes.bool, + // The video topic to display + videoTopic: PropTypes.string, // The URL of the webrtc signalling server webrtcURL: PropTypes.string.isRequired } +CustomizeConfiguration.defaultProps = { + getEndEffectorPose: false, + toggleFaceDetection: false, + videoTopic: CAMERA_FEED_TOPIC +} export default CustomizeConfiguration diff --git a/feedingwebapp/src/Pages/Settings/Main.jsx b/feedingwebapp/src/Pages/Settings/Main.jsx index f0f696a6..5df0b2b2 100644 --- a/feedingwebapp/src/Pages/Settings/Main.jsx +++ b/feedingwebapp/src/Pages/Settings/Main.jsx @@ -157,6 +157,7 @@ const Main = () => { 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] + let moveToStagingConfigurationImage = MOVING_STATE_ICON_DICT[MEAL_STATE.R_MovingToStagingConfiguration] // Configure the different options in the settings menu let settingsConfig = [ @@ -174,6 +175,11 @@ const Main = () => { title: 'Resting Position', icon: moveToRestingConfigurationImage, onClick: () => onClickSettingsPage(SETTINGS_STATE.RESTING_CONFIGURATION) + }, + { + title: 'Staging Position', + icon: moveToStagingConfigurationImage, + onClick: () => onClickSettingsPage(SETTINGS_STATE.STAGING_CONFIGURATION) } ] diff --git a/feedingwebapp/src/Pages/Settings/Settings.jsx b/feedingwebapp/src/Pages/Settings/Settings.jsx index 840467cf..fcdd96ac 100644 --- a/feedingwebapp/src/Pages/Settings/Settings.jsx +++ b/feedingwebapp/src/Pages/Settings/Settings.jsx @@ -6,9 +6,21 @@ import { View } from 'react-native' // Local imports import { useGlobalState, SETTINGS_STATE, MEAL_STATE } from '../GlobalState' import Main from './Main' -import CustomizeConfiguration from './CustomizeConfiguration' +import CustomizeConfiguration, { + getJointPositionsFromRobotStateResponse, + getEndEffectorPositionFromRobotStateResponse, + getEndEffectorOrientationFromRobotStateResponse +} from './CustomizeConfiguration' import BiteTransfer from './BiteTransfer' -import { ABOVE_PLATE_PARAM_JOINTS, RESTING_PARAM_JOINTS_1, RESTING_PARAM_JOINTS_2 } from '../Constants' +import { + ABOVE_PLATE_PARAM_JOINTS, + FACE_DETECTION_IMG_TOPIC, + RESTING_PARAM_JOINTS_1, + RESTING_PARAM_JOINTS_2, + STAGING_PARAM_JOINTS, + STAGING_PARAM_ORIENTATION, + STAGING_PARAM_POSITION +} from '../Constants' /** * The Settings components displays the appropriate settings page based on the @@ -30,6 +42,8 @@ const Settings = (props) => { { { webrtcURL={props.webrtcURL} /> ) + case SETTINGS_STATE.STAGING_CONFIGURATION: + return ( + + ) default: console.log('Invalid settings state', settingsState) return
diff --git a/feedingwebapp/src/Pages/Settings/SettingsPageParent.jsx b/feedingwebapp/src/Pages/Settings/SettingsPageParent.jsx index b5ee082a..459d8c33 100644 --- a/feedingwebapp/src/Pages/Settings/SettingsPageParent.jsx +++ b/feedingwebapp/src/Pages/Settings/SettingsPageParent.jsx @@ -244,24 +244,6 @@ const SettingsPageParent = (props) => { > Done - {/* - props.doneCallback()}> - Go To Menu Without Saving - - */} Date: Fri, 3 May 2024 22:15:45 -0700 Subject: [PATCH 02/10] [WIP] fixes from in-person testing --- feedingwebapp/src/Pages/GlobalState.jsx | 17 ++- .../Pages/Home/MealStates/DetectingFace.jsx | 22 +-- .../MealStates/DetectingFaceSubcomponent.jsx | 79 +++++----- feedingwebapp/src/Pages/Home/VideoFeed.jsx | 135 +++++++++++------ .../src/Pages/Settings/BiteTransfer.jsx | 30 ++-- .../Pages/Settings/CustomizeConfiguration.jsx | 137 +++++++++++------- feedingwebapp/src/Pages/Settings/Settings.jsx | 17 ++- .../src/Pages/Settings/SettingsPageParent.jsx | 23 ++- feedingwebapp/src/robot/VideoStream.jsx | 4 +- 9 files changed, 282 insertions(+), 182 deletions(-) diff --git a/feedingwebapp/src/Pages/GlobalState.jsx b/feedingwebapp/src/Pages/GlobalState.jsx index 36c958d3..4409ea4c 100644 --- a/feedingwebapp/src/Pages/GlobalState.jsx +++ b/feedingwebapp/src/Pages/GlobalState.jsx @@ -145,7 +145,7 @@ export const useGlobalState = create( // 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, + settingsPageAtFace: 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. @@ -169,7 +169,7 @@ export const useGlobalState = create( let retval = { mealState: mealState, mealStateTransitionTime: Date.now(), - biteTransferPageAtFace: false // Reset this flag when the meal state changes + settingsPageAtFace: false // Reset this flag when the meal state changes } // Only update the previous state if it is not a self-transition (to // account for cases where a MoveTo action result message is reveived twice) @@ -212,9 +212,12 @@ export const useGlobalState = create( lastMotionActionResponse: lastMotionActionResponse })), setMoveToMouthActionGoal: (moveToMouthActionGoal) => - set(() => ({ - moveToMouthActionGoal: moveToMouthActionGoal - })), + set(() => { + console.log('setMoveToMouthActionGoal called with', moveToMouthActionGoal) + return { + moveToMouthActionGoal: moveToMouthActionGoal + } + }), setPaused: (paused) => set(() => { let retval = { paused: paused } @@ -277,9 +280,9 @@ export const useGlobalState = create( set(() => ({ biteAcquisitionCheckAutoContinueProbThreshUpper: biteAcquisitionCheckAutoContinueProbThreshUpper })), - setBiteTransferPageAtFace: (biteTransferPageAtFace) => + setSettingsPageAtFace: (settingsPageAtFace) => set(() => ({ - biteTransferPageAtFace: biteTransferPageAtFace + settingsPageAtFace: settingsPageAtFace })), setBiteSelectionZoom: (biteSelectionZoom) => set(() => ({ diff --git a/feedingwebapp/src/Pages/Home/MealStates/DetectingFace.jsx b/feedingwebapp/src/Pages/Home/MealStates/DetectingFace.jsx index 2ee3d905..01267df3 100644 --- a/feedingwebapp/src/Pages/Home/MealStates/DetectingFace.jsx +++ b/feedingwebapp/src/Pages/Home/MealStates/DetectingFace.jsx @@ -26,7 +26,6 @@ const DetectingFace = (props) => { const prevMealState = useGlobalState((state) => state.prevMealState) const setInNonMovingState = useGlobalState((state) => state.setInNonMovingState) const setMealState = useGlobalState((state) => state.setMealState) - const setMoveToMouthActionGoal = useGlobalState((state) => state.setMoveToMouthActionGoal) const faceDetectionAutoContinue = useGlobalState((state) => state.faceDetectionAutoContinue) const setFaceDetectionAutoContinue = useGlobalState((state) => state.setFaceDetectionAutoContinue) // Get icon image for move to mouth @@ -105,20 +104,13 @@ const DetectingFace = (props) => { /** * Callback for when a face is detected within the correct range. */ - const faceDetectedCallback = useCallback( - (message) => { - console.log('Face detected callback') - setMouthDetected(true) - setMoveToMouthActionGoal({ - face_detection: message - }) - // If auto-continue is enabled, move to the mouth position - if (autoContinueIsEnabled()) { - moveToMouthCallback() - } - }, - [autoContinueIsEnabled, moveToMouthCallback, setMoveToMouthActionGoal] - ) + const faceDetectedCallback = useCallback(() => { + setMouthDetected(true) + // If auto-continue is enabled, move to the mouth position + if (autoContinueIsEnabled()) { + moveToMouthCallback() + } + }, [autoContinueIsEnabled, moveToMouthCallback]) /** Get the full page view * diff --git a/feedingwebapp/src/Pages/Home/MealStates/DetectingFaceSubcomponent.jsx b/feedingwebapp/src/Pages/Home/MealStates/DetectingFaceSubcomponent.jsx index f0902ae7..61ff7146 100644 --- a/feedingwebapp/src/Pages/Home/MealStates/DetectingFaceSubcomponent.jsx +++ b/feedingwebapp/src/Pages/Home/MealStates/DetectingFaceSubcomponent.jsx @@ -5,11 +5,16 @@ import { useMediaQuery } from 'react-responsive' import { View } from 'react-native' // Local Imports -import { useROS, createROSService, createROSServiceRequest, subscribeToROSTopic, unsubscribeFromROSTopic } from '../../../ros/ros_helpers' +import { + useROS, + /*createROSService, createROSServiceRequest, */ subscribeToROSTopic, + unsubscribeFromROSTopic +} from '../../../ros/ros_helpers' import '../Home.css' -import { MEAL_STATE } from '../../GlobalState' -import { FACE_DETECTION_IMG_TOPIC, FACE_DETECTION_TOPIC, FACE_DETECTION_TOPIC_MSG, ROS_SERVICE_NAMES } from '../../Constants' +// import { MEAL_STATE } from '../../GlobalState' +import { FACE_DETECTION_IMG_TOPIC, FACE_DETECTION_TOPIC, FACE_DETECTION_TOPIC_MSG /*, ROS_SERVICE_NAMES*/ } from '../../Constants' import VideoFeed from '../VideoFeed' +import { useGlobalState } from '../../GlobalState' /** * The DetectingFace component appears after the robot has moved to the staging @@ -17,6 +22,9 @@ import VideoFeed from '../VideoFeed' * moves on to `R_MovingToMouth` when a face is detected. */ const DetectingFaceSubcomponent = (props) => { + // Get the relevant global variables + const setMoveToMouthActionGoal = useGlobalState((state) => state.setMoveToMouthActionGoal) + // Keep track of whether a mouth has been detected or not const [mouthDetected, setMouthDetected] = useState(false) // Flag to check if the current orientation is portrait @@ -51,11 +59,14 @@ const DetectingFaceSubcomponent = (props) => { 0.5 if (distance > min_face_distance && distance < max_face_distance) { setMouthDetected(true) + setMoveToMouthActionGoal({ + face_detection: message + }) faceDetectedCallback() } } }, - [props.faceDetectedCallback, setMouthDetected] + [props.faceDetectedCallback, setMouthDetected, setMoveToMouthActionGoal] ) useEffect(() => { let topic = subscribeToROSTopic(ros.current, FACE_DETECTION_TOPIC, FACE_DETECTION_TOPIC_MSG, faceDetectionCallback) @@ -69,37 +80,37 @@ const DetectingFaceSubcomponent = (props) => { } }, [faceDetectionCallback]) - /** - * Create the ROS Service. This is created in as a ref to avoid re-creating - * it upon every re-render. - */ - let { serviceName, messageType } = ROS_SERVICE_NAMES[MEAL_STATE.R_DetectingFace] - let toggleFaceDetectionService = useRef(createROSService(ros.current, serviceName, messageType)) + // /** + // * Create the ROS Service. This is created in as a ref to avoid re-creating + // * it upon every re-render. + // */ + // let { serviceName, messageType } = ROS_SERVICE_NAMES[MEAL_STATE.R_DetectingFace] + // let toggleFaceDetectionService = useRef(createROSService(ros.current, serviceName, messageType)) - /** - * Toggles face detection on the first time this component is rendered, but - * not upon additional re-renders. See here for more details on how `useEffect` - * achieves this goal: https://stackoverflow.com/a/69264685 - */ - useEffect(() => { - // Create a service request - let request = createROSServiceRequest({ data: true }) - // Call the service - let service = toggleFaceDetectionService.current - service.callService(request, (response) => console.log('Got toggle face detection service response', response)) + // /** + // * Toggles face detection on the first time this component is rendered, but + // * not upon additional re-renders. See here for more details on how `useEffect` + // * achieves this goal: https://stackoverflow.com/a/69264685 + // */ + // useEffect(() => { + // // Create a service request + // let request = createROSServiceRequest({ data: true }) + // // Call the service + // let service = toggleFaceDetectionService.current + // service.callService(request, (response) => console.log('Got toggle face detection service response', response)) - /** - * In practice, because the values passed in in the second argument of - * useEffect will not change on re-renders, this return statement will - * only be called when the component unmounts. - */ - return () => { - // Create a service request - let request = createROSServiceRequest({ data: false }) - // Call the service - service.callService(request, (response) => console.log('Got toggle face detection service response', response)) - } - }, [toggleFaceDetectionService]) + // /** + // * In practice, because the values passed in in the second argument of + // * useEffect will not change on re-renders, this return statement will + // * only be called when the component unmounts. + // */ + // return () => { + // // Create a service request + // let request = createROSServiceRequest({ data: false }) + // // Call the service + // service.callService(request, (response) => console.log('Got toggle face detection service response', response)) + // } + // }, [toggleFaceDetectionService]) // Render the component return ( @@ -125,7 +136,7 @@ const DetectingFaceSubcomponent = (props) => { height: '100%' }} > - + ) diff --git a/feedingwebapp/src/Pages/Home/VideoFeed.jsx b/feedingwebapp/src/Pages/Home/VideoFeed.jsx index b7809ddf..1a87cba9 100644 --- a/feedingwebapp/src/Pages/Home/VideoFeed.jsx +++ b/feedingwebapp/src/Pages/Home/VideoFeed.jsx @@ -7,9 +7,11 @@ import PropTypes from 'prop-types' import { View } from 'react-native' // Local Imports -import { CAMERA_FEED_TOPIC, REALSENSE_WIDTH, REALSENSE_HEIGHT } from '../Constants' +import { CAMERA_FEED_TOPIC, REALSENSE_WIDTH, REALSENSE_HEIGHT, ROS_SERVICE_NAMES } from '../Constants' import { useWindowSize } from '../../helpers' import { WebRTCConnection } from '../../webrtc/webrtc_helpers' +import { createROSService, createROSServiceRequest, useROS } from '../../ros/ros_helpers' +import { MEAL_STATE } from '../GlobalState' /** * Takes in an imageWidth and imageHeight, and returns a width and height that @@ -94,12 +96,31 @@ const VideoFeed = (props) => { let textFontSize = isPortrait ? 2.5 : 3.0 let sizeSuffix = 'vh' + /** + * Connect to ROS, if not already connected. Put this in useRef to avoid + * re-connecting upon re-renders. + */ + const ros = useRef(useROS().ros) + + /** + * Create the ROS Service Clients to toggle face detection. + */ + let { serviceName, messageType } = ROS_SERVICE_NAMES[MEAL_STATE.R_DetectingFace] + let toggleFaceDetectionService = useRef(createROSService(ros.current, serviceName, messageType)) + /** * Create the peer connection */ useEffect(() => { + // Toggle on face detection if specified + let service = toggleFaceDetectionService.current + if (props.toggleFaceDetection) { + let request = createROSServiceRequest({ data: true }) + service.callService(request, (response) => console.log('VideoFeed got toggle face detection on service response', response)) + } + // Create the peer connection - console.log('Creating peer connection', props.webrtcURL, refreshCount) + console.log('Creating peer connection', props.webrtcURL, refreshCount, props.externalRefreshCount) const webRTCConnection = new WebRTCConnection({ url: props.webrtcURL + '/subscribe', topic: props.topic, @@ -115,56 +136,75 @@ const VideoFeed = (props) => { }) return () => { + // Close the peer connection webRTCConnection.close() + + // Toggle off face detection if specified + if (props.toggleFaceDetection) { + let request = createROSServiceRequest({ data: false }) + service.callService(request, (response) => console.log('VideoFeed got toggle face detection off service response', response)) + } } - }, [props.topic, props.webrtcURL, refreshCount, videoRef]) + }, [ + props.externalRefreshCount, + props.toggleFaceDetection, + props.topic, + props.webrtcURL, + refreshCount, + toggleFaceDetectionService, + videoRef + ]) // Callback to resize the image based on the parent width and height - const resizeImage = useCallback(() => { - console.log('Resizing image', parentRef.current) - if (!parentRef.current) { - return - } - // Get the width and height of the parent DOM element - let parentWidth = parentRef.current.clientWidth - let parentHeight = parentRef.current.clientHeight + const resizeImage = useCallback( + (delay_ms = 10) => { + if (!parentRef.current) { + return + } + // Get the width and height of the parent DOM element + let parentWidth = parentRef.current.clientWidth + let parentHeight = parentRef.current.clientHeight - // Calculate the width and height of the video feed - let { - width: childWidth, - height: childHeight, - scaleFactor: childScaleFactor - } = scaleWidthHeightToWindow( - parentWidth, - parentHeight, - REALSENSE_WIDTH, - REALSENSE_HEIGHT, - props.marginTop, - props.marginBottom, - props.marginLeft, - props.marginRight - ) + // Calculate the width and height of the video feed + let { + width: childWidth, + height: childHeight, + scaleFactor: childScaleFactor + } = scaleWidthHeightToWindow( + parentWidth, + parentHeight, + REALSENSE_WIDTH, + REALSENSE_HEIGHT, + props.marginTop, + props.marginBottom, + props.marginLeft, + props.marginRight + ) - // Set the width and height of the video feed - setImgWidth(childWidth * props.zoom) - setImgHeight(childHeight * props.zoom) - setScaleFactor(childScaleFactor * props.zoom) - }, [parentRef, props.marginTop, props.marginBottom, props.marginLeft, props.marginRight, props.zoom]) + // Set the width and height of the video feed + setImgWidth(childWidth * props.zoom) + setImgHeight(childHeight * props.zoom) + setScaleFactor(childScaleFactor * props.zoom) - /** When the resize event is triggered, the elements have not yet been laid out, - * and hence the parent width/height might not be accurate yet based on the - * specified flex layout. Hence, we wait until the next event cycle to resize - * the video feed. - */ - const resizeImageNextEventCycle = useCallback(() => { - setTimeout(resizeImage, 0) - }, [resizeImage]) - useWindowSize(resizeImageNextEventCycle) + // If the width or height is zero, schedule another resize event in the next + // event cycle. This is because initially the elements have not been laid out, + // and it might take a few event cycles to do so. + if (childWidth === 0.0 || childHeight === 0.0) { + setTimeout(resizeImage, delay_ms) + } + }, + [parentRef, props.marginTop, props.marginBottom, props.marginLeft, props.marginRight, props.zoom] + ) + + // Resize the element when the window is resized + useWindowSize(resizeImage) - // When the component is first mounted, resize the image + // When the component is first mounted and when the reload button is clicked, + // resize the image useEffect(() => { - resizeImageNextEventCycle() - }, [resizeImageNextEventCycle]) + console.log('Resizing image', refreshCount, props.externalRefreshCount) + resizeImage() + }, [props.externalRefreshCount, refreshCount, resizeImage]) // The callback for when the image is clicked. const imageClicked = useCallback( @@ -329,7 +369,7 @@ const VideoFeed = (props) => { fontSize: textFontSize.toString() + sizeSuffix, color: 'black' }} - onClick={() => setRefreshCount(refreshCount + 1)} + onClick={() => setRefreshCount((x) => x + 1)} > Reload Video @@ -344,8 +384,13 @@ VideoFeed.propTypes = { marginBottom: PropTypes.number, marginLeft: PropTypes.number, marginRight: PropTypes.number, + // A number that changes when some external entity wants this component to refresh. + externalRefreshCount: PropTypes.number, // The topic of the video feed topic: PropTypes.string.isRequired, + // Whether this component should toggle face detection on when it is mounted and + // the reload button is clicked, and toggle it off when it is unmounted + toggleFaceDetection: PropTypes.bool, /** * An optional callback function for when the user clicks on the video feed. * This function should take in two parameters, `x` and `y`, which are the @@ -368,7 +413,9 @@ VideoFeed.defaultProps = { marginBottom: 0, marginLeft: 0, marginRight: 0, + externalRefreshCount: 0, topic: CAMERA_FEED_TOPIC, + toggleFaceDetection: false, zoom: 1.0, zoomMin: 1.0, zoomMax: 2.0 diff --git a/feedingwebapp/src/Pages/Settings/BiteTransfer.jsx b/feedingwebapp/src/Pages/Settings/BiteTransfer.jsx index 861fdcbc..b1042edc 100644 --- a/feedingwebapp/src/Pages/Settings/BiteTransfer.jsx +++ b/feedingwebapp/src/Pages/Settings/BiteTransfer.jsx @@ -22,19 +22,25 @@ const BiteTransfer = (props) => { const setSettingsState = useGlobalState((state) => state.setSettingsState) const globalMealState = useGlobalState((state) => state.mealState) const setPaused = useGlobalState((state) => state.setPaused) - const biteTransferPageAtFace = useGlobalState((state) => state.biteTransferPageAtFace) - const setBiteTransferPageAtFace = useGlobalState((state) => state.setBiteTransferPageAtFace) + const settingsPageAtFace = useGlobalState((state) => state.settingsPageAtFace) + const setSettingsPageAtFace = useGlobalState((state) => state.setSettingsPageAtFace) + const moveToMouthActionGoal = useGlobalState((state) => state.moveToMouthActionGoal) // Create relevant local state variables // Configure the parameters for SettingsPageParent const paramNames = useMemo(() => [DISTANCE_TO_MOUTH_PARAM], []) const [currentDistanceToMouth, setCurrentDistanceToMouth] = useState([null]) const [localCurrAndNextMealState, setLocalCurrAndNextMealState] = useState( - globalMealState === MEAL_STATE.U_BiteDone || globalMealState === MEAL_STATE.R_DetectingFace || biteTransferPageAtFace + globalMealState === MEAL_STATE.U_BiteDone || globalMealState === MEAL_STATE.R_DetectingFace || settingsPageAtFace ? [MEAL_STATE.R_MovingFromMouth, null] : [MEAL_STATE.R_MovingToStagingConfiguration, null] ) - const actionInput = useMemo(() => ({}), []) + const actionInput = useMemo(() => { + if (localCurrAndNextMealState[0] === MEAL_STATE.R_MovingToMouth) { + return moveToMouthActionGoal + } + return {} + }, [localCurrAndNextMealState, moveToMouthActionGoal]) const doneButtonIsClicked = useRef(false) // Flag to check if the current orientation is portrait @@ -53,9 +59,7 @@ const BiteTransfer = (props) => { (newLocalCurrMealState, newLocalNextMealState = null) => { let oldLocalCurrMealState = localCurrAndNextMealState[0] // If the oldlocalCurrMealState was R_MovingToMouth, then the robot is at the mouth - setBiteTransferPageAtFace( - newLocalCurrMealState === null && (biteTransferPageAtFace || oldLocalCurrMealState === MEAL_STATE.R_MovingToMouth) - ) + setSettingsPageAtFace(newLocalCurrMealState === null && (settingsPageAtFace || oldLocalCurrMealState === MEAL_STATE.R_MovingToMouth)) // Start in a moving state, not a paused state setPaused(false) console.log('setLocalCurrMealStateWrapper', newLocalCurrMealState, newLocalNextMealState, doneButtonIsClicked.current) @@ -69,10 +73,10 @@ const BiteTransfer = (props) => { } }, [ - biteTransferPageAtFace, + settingsPageAtFace, localCurrAndNextMealState, setLocalCurrAndNextMealState, - setBiteTransferPageAtFace, + setSettingsPageAtFace, doneButtonIsClicked, setPaused, setSettingsState @@ -97,10 +101,10 @@ const BiteTransfer = (props) => { useEffect(() => { doneButtonIsClicked.current = false // Since we start by moving to staging, this should be initialized to false - setBiteTransferPageAtFace(false) + setSettingsPageAtFace(false) // Start in a moving state, not a paused state setPaused(false) - }, [setBiteTransferPageAtFace, setPaused, doneButtonIsClicked]) + }, [setSettingsPageAtFace, setPaused, doneButtonIsClicked]) // Callback to move the robot to the mouth const moveToMouthButtonClicked = useCallback(() => { @@ -123,7 +127,7 @@ const BiteTransfer = (props) => { // To get to Settings, the globalMealState must be one of the NON_MOVING_STATES switch (globalMealState) { case MEAL_STATE.U_BiteDone: - if (biteTransferPageAtFace) { + if (settingsPageAtFace) { localCurrMealState = null localNextMealState = null } else { @@ -148,7 +152,7 @@ const BiteTransfer = (props) => { break } setLocalCurrMealStateWrapper(localCurrMealState, localNextMealState) - }, [biteTransferPageAtFace, globalMealState, setLocalCurrMealStateWrapper, doneButtonIsClicked]) + }, [settingsPageAtFace, globalMealState, setLocalCurrMealStateWrapper, doneButtonIsClicked]) // Callback for when the user changes the distance to mouth const onDistanceToMouthChange = useCallback( diff --git a/feedingwebapp/src/Pages/Settings/CustomizeConfiguration.jsx b/feedingwebapp/src/Pages/Settings/CustomizeConfiguration.jsx index 74d85e31..46e82d11 100644 --- a/feedingwebapp/src/Pages/Settings/CustomizeConfiguration.jsx +++ b/feedingwebapp/src/Pages/Settings/CustomizeConfiguration.jsx @@ -14,8 +14,7 @@ import { GET_ROBOT_STATE_SERVICE_TYPE, ROBOT_BASE_LINK, ROBOT_END_EFFECTOR, - ROBOT_JOINTS, - ROS_SERVICE_NAMES + ROBOT_JOINTS } from '../Constants' import { useGlobalState, MEAL_STATE, SETTINGS_STATE } from '../GlobalState' import RobotMotion from '../Home/MealStates/RobotMotion' @@ -71,19 +70,27 @@ const CustomizeConfiguration = (props) => { const setSettingsState = useGlobalState((state) => state.setSettingsState) const globalMealState = useGlobalState((state) => state.mealState) const setPaused = useGlobalState((state) => state.setPaused) - const biteTransferPageAtFace = useGlobalState((state) => state.biteTransferPageAtFace) + const settingsPageAtFace = useGlobalState((state) => state.settingsPageAtFace) + const setSettingsPageAtFace = useGlobalState((state) => state.setSettingsPageAtFace) + const moveToMouthActionGoal = useGlobalState((state) => state.moveToMouthActionGoal) // Create relevant local state variables // Configure the parameters for SettingsPageParent const [currentConfigurationParams, setCurrentConfigurationParams] = useState(props.paramNames.map(() => null)) const [localCurrAndNextMealState, setLocalCurrAndNextMealState] = useState( - globalMealState === MEAL_STATE.U_BiteDone || biteTransferPageAtFace + globalMealState === MEAL_STATE.U_BiteDone || settingsPageAtFace ? [MEAL_STATE.R_MovingFromMouth, props.startingMealState] : [props.startingMealState, null] ) - const actionInput = useMemo(() => ({}), []) + const actionInput = useMemo(() => { + if (localCurrAndNextMealState[0] === MEAL_STATE.R_MovingToMouth) { + return moveToMouthActionGoal + } + return {} + }, [localCurrAndNextMealState, moveToMouthActionGoal]) const doneButtonIsClicked = useRef(false) const [zoomLevel, setZoomLevel] = useState(1.0) + const [videoFeedRefreshCount, setVideoFeedrefreshCount] = useState(0) const [mountTeleopSubcomponent, setMountTeleopSubcomponent] = useState(false) const unmountTeleopSubcomponentCallback = useRef(() => {}) @@ -96,14 +103,38 @@ const CustomizeConfiguration = (props) => { let textFontSize = 3.5 let sizeSuffix = isPortrait ? 'vh' : 'vw' + /** + * Connect to ROS, if not already connected. Put this in useRef to avoid + * re-connecting upon re-renders. + */ + const ros = useRef(useROS().ros) + + /** + * Create the ROS Service Clients to get joint states + */ + let getRobotStateService = useRef(createROSService(ros.current, GET_ROBOT_STATE_SERVICE_NAME, GET_ROBOT_STATE_SERVICE_TYPE)) + // Update other state variables that are related to the local meal state const setLocalCurrMealStateWrapper = useCallback( (newLocalCurrMealState, newLocalNextMealState = null) => { - console.log('setLocalCurrMealStateWrapper evaluated') + console.log('setLocalCurrMealStateWrapper evaluated', newLocalCurrMealState, newLocalNextMealState) let oldLocalCurrMealState = localCurrAndNextMealState[0] - // Only mount the teleop subcomponent if the robot finished the prereq motion for this page - if (newLocalCurrMealState === null && oldLocalCurrMealState === props.startingMealState) { - setMountTeleopSubcomponent(true) + + if (newLocalCurrMealState === null) { + // If toggling face detection is enabled, refresh the video feed. This is + // because other elements/actions might have toggled face detection off. + if (props.toggleFaceDetection) { + setVideoFeedrefreshCount((x) => x + 1) + } + + // Only mount the teleop subcomponent if the robot finished the prereq motion for this page + // Treat MoveFromMouth and MoveToStaging as the same. + if ( + oldLocalCurrMealState === props.startingMealState || + (props.startingMealState === MEAL_STATE.R_MovingToStagingConfiguration && oldLocalCurrMealState === MEAL_STATE.R_MovingFromMouth) + ) { + setMountTeleopSubcomponent(true) + } } // Start in a moving state, not a paused state setPaused(false) @@ -115,15 +146,21 @@ const CustomizeConfiguration = (props) => { } else { setLocalCurrAndNextMealState([newLocalCurrMealState, newLocalNextMealState]) } + // If the oldlocalCurrMealState was R_MovingToMouth, then the robot is at the mouth + setSettingsPageAtFace(newLocalCurrMealState === null && (settingsPageAtFace || oldLocalCurrMealState === MEAL_STATE.R_MovingToMouth)) }, [ doneButtonIsClicked, localCurrAndNextMealState, props.startingMealState, + props.toggleFaceDetection, setLocalCurrAndNextMealState, setMountTeleopSubcomponent, setPaused, - setSettingsState + settingsPageAtFace, + setSettingsPageAtFace, + setSettingsState, + setVideoFeedrefreshCount ] ) @@ -167,47 +204,12 @@ const CustomizeConfiguration = (props) => { } }, [localCurrAndNextMealState, setLocalCurrMealStateWrapper, actionInput]) - /** - * Connect to ROS, if not already connected. Put this in useRef to avoid - * re-connecting upon re-renders. - */ - const ros = useRef(useROS().ros) - - /** - * Create the ROS Service Clients to get/set parameters and to toggle face detection. - */ - let getRobotStateService = useRef(createROSService(ros.current, GET_ROBOT_STATE_SERVICE_NAME, GET_ROBOT_STATE_SERVICE_TYPE)) - let { serviceName, messageType } = ROS_SERVICE_NAMES[MEAL_STATE.R_DetectingFace] - let toggleFaceDetectionService = useRef(createROSService(ros.current, serviceName, messageType)) - // Reset state the first time the page is rendered useEffect(() => { doneButtonIsClicked.current = false // Start in a moving state, not a paused state setPaused(false) - // Toggle face detection is specified - let service = toggleFaceDetectionService.current - if (props.toggleFaceDetection) { - // Create a service request - let request = createROSServiceRequest({ data: true }) - // Call the service - service.callService(request, (response) => console.log('Got toggle face detection service response', response)) - } - - /** - * In practice, because the values passed in in the second argument of - * useEffect will not change on re-renders, this return statement will - * only be called when the component unmounts. - */ - return () => { - if (props.toggleFaceDetection) { - // Create a service request - let request = createROSServiceRequest({ data: false }) - // Call the service - service.callService(request, (response) => console.log('Got toggle face detection service response', response)) - } - } - }, [doneButtonIsClicked, props.toggleFaceDetection, setPaused, toggleFaceDetectionService]) + }, [doneButtonIsClicked, setPaused]) // Get the current joint states and store them as the above plate param const storeJointStatesAsLocalParam = useCallback(() => { @@ -227,13 +229,26 @@ const CustomizeConfiguration = (props) => { }) }, [getRobotStateService, props.getEndEffectorPose, props.getParamValues, props.paramNames, setCurrentConfigurationParams]) - // Callback to move the robot to another configuration + // Callback to move the robot to another configuration. If the robot is at the user's face, + // first moves back from their mouth. const moveToButtonClicked = useCallback( (nextMealState) => { doneButtonIsClicked.current = false - unmountTeleopSubcomponentCallback.current = getSetLocalCurrMealStateWrapper(nextMealState) + let nextNextMealState = null + if (settingsPageAtFace) { + // MoveIt often fails to execute small trajectories, which are often planned + // when doing MovetoStaging immediately following MoveFromMouth. Thus, we + // leave the robot in the MoveFromMouth configuration. It is not technically + // the actual staging configuration, but it is the best we can do given + // the MoveIt limitation. + if (nextMealState !== MEAL_STATE.R_MovingToStagingConfiguration) { + nextNextMealState = nextMealState + } + nextMealState = MEAL_STATE.R_MovingFromMouth + } + unmountTeleopSubcomponentCallback.current = getSetLocalCurrMealStateWrapper(nextMealState, nextNextMealState) }, - [getSetLocalCurrMealStateWrapper, doneButtonIsClicked, unmountTeleopSubcomponentCallback] + [getSetLocalCurrMealStateWrapper, doneButtonIsClicked, settingsPageAtFace, unmountTeleopSubcomponentCallback] ) // Callback to return to the main settings page @@ -282,7 +297,15 @@ const CustomizeConfiguration = (props) => { }} > - + @@ -315,7 +338,7 @@ const CustomizeConfiguration = (props) => { {props.otherButtonConfigs.map(({ name, mealState }) => ( diff --git a/feedingwebapp/src/robot/VideoStream.jsx b/feedingwebapp/src/robot/VideoStream.jsx index 94d27d21..d9c736d0 100644 --- a/feedingwebapp/src/robot/VideoStream.jsx +++ b/feedingwebapp/src/robot/VideoStream.jsx @@ -62,7 +62,7 @@ function VideoStream(props) { } img.src = URL.createObjectURL(dataURItoBlob('data:image/jpg;base64,' + message.data)) }, - [img/*, props.topic*/] + [img /*, props.topic*/] ) // Subscribe to the image topic From 86314d06d7a57e9c4230ef481004ac172f8e126a Mon Sep 17 00:00:00 2001 From: Amal Nanavati Date: Sun, 5 May 2024 19:22:49 -0700 Subject: [PATCH 10/10] Renamed settingsPageAtFace to settingsPageAtMouth --- feedingwebapp/src/Pages/GlobalState.jsx | 14 ++++++------ .../src/Pages/Settings/BiteTransfer.jsx | 22 ++++++++++--------- .../Pages/Settings/CustomizeConfiguration.jsx | 18 ++++++++------- 3 files changed, 29 insertions(+), 25 deletions(-) diff --git a/feedingwebapp/src/Pages/GlobalState.jsx b/feedingwebapp/src/Pages/GlobalState.jsx index 4409ea4c..53baf507 100644 --- a/feedingwebapp/src/Pages/GlobalState.jsx +++ b/feedingwebapp/src/Pages/GlobalState.jsx @@ -141,11 +141,11 @@ export const useGlobalState = create( biteAcquisitionCheckAutoContinueSecs: 3.0, biteAcquisitionCheckAutoContinueProbThreshLower: 0.25, biteAcquisitionCheckAutoContinueProbThreshUpper: 0.75, - // Whether the settings bite transfer page is currently at the user's face + // Whether any of the settings pages is currently at the user's mouth // 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. - settingsPageAtFace: false, + // mouth, the settings page is, and the user refreshes -- the page should + // call MoveFromMouth instead of just MoveToStaging. + settingsPageAtMouth: 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. @@ -169,7 +169,7 @@ export const useGlobalState = create( let retval = { mealState: mealState, mealStateTransitionTime: Date.now(), - settingsPageAtFace: false // Reset this flag when the meal state changes + settingsPageAtMouth: false // Reset this flag when the meal state changes } // Only update the previous state if it is not a self-transition (to // account for cases where a MoveTo action result message is reveived twice) @@ -280,9 +280,9 @@ export const useGlobalState = create( set(() => ({ biteAcquisitionCheckAutoContinueProbThreshUpper: biteAcquisitionCheckAutoContinueProbThreshUpper })), - setSettingsPageAtFace: (settingsPageAtFace) => + setSettingsPageAtMouth: (settingsPageAtMouth) => set(() => ({ - settingsPageAtFace: settingsPageAtFace + settingsPageAtMouth: settingsPageAtMouth })), setBiteSelectionZoom: (biteSelectionZoom) => set(() => ({ diff --git a/feedingwebapp/src/Pages/Settings/BiteTransfer.jsx b/feedingwebapp/src/Pages/Settings/BiteTransfer.jsx index b1042edc..e0c3d249 100644 --- a/feedingwebapp/src/Pages/Settings/BiteTransfer.jsx +++ b/feedingwebapp/src/Pages/Settings/BiteTransfer.jsx @@ -22,8 +22,8 @@ const BiteTransfer = (props) => { const setSettingsState = useGlobalState((state) => state.setSettingsState) const globalMealState = useGlobalState((state) => state.mealState) const setPaused = useGlobalState((state) => state.setPaused) - const settingsPageAtFace = useGlobalState((state) => state.settingsPageAtFace) - const setSettingsPageAtFace = useGlobalState((state) => state.setSettingsPageAtFace) + const settingsPageAtMouth = useGlobalState((state) => state.settingsPageAtMouth) + const setSettingsPageAtMouth = useGlobalState((state) => state.setSettingsPageAtMouth) const moveToMouthActionGoal = useGlobalState((state) => state.moveToMouthActionGoal) // Create relevant local state variables @@ -31,7 +31,7 @@ const BiteTransfer = (props) => { const paramNames = useMemo(() => [DISTANCE_TO_MOUTH_PARAM], []) const [currentDistanceToMouth, setCurrentDistanceToMouth] = useState([null]) const [localCurrAndNextMealState, setLocalCurrAndNextMealState] = useState( - globalMealState === MEAL_STATE.U_BiteDone || globalMealState === MEAL_STATE.R_DetectingFace || settingsPageAtFace + globalMealState === MEAL_STATE.U_BiteDone || globalMealState === MEAL_STATE.R_DetectingFace || settingsPageAtMouth ? [MEAL_STATE.R_MovingFromMouth, null] : [MEAL_STATE.R_MovingToStagingConfiguration, null] ) @@ -59,7 +59,9 @@ const BiteTransfer = (props) => { (newLocalCurrMealState, newLocalNextMealState = null) => { let oldLocalCurrMealState = localCurrAndNextMealState[0] // If the oldlocalCurrMealState was R_MovingToMouth, then the robot is at the mouth - setSettingsPageAtFace(newLocalCurrMealState === null && (settingsPageAtFace || oldLocalCurrMealState === MEAL_STATE.R_MovingToMouth)) + setSettingsPageAtMouth( + newLocalCurrMealState === null && (settingsPageAtMouth || oldLocalCurrMealState === MEAL_STATE.R_MovingToMouth) + ) // Start in a moving state, not a paused state setPaused(false) console.log('setLocalCurrMealStateWrapper', newLocalCurrMealState, newLocalNextMealState, doneButtonIsClicked.current) @@ -73,10 +75,10 @@ const BiteTransfer = (props) => { } }, [ - settingsPageAtFace, + settingsPageAtMouth, localCurrAndNextMealState, setLocalCurrAndNextMealState, - setSettingsPageAtFace, + setSettingsPageAtMouth, doneButtonIsClicked, setPaused, setSettingsState @@ -101,10 +103,10 @@ const BiteTransfer = (props) => { useEffect(() => { doneButtonIsClicked.current = false // Since we start by moving to staging, this should be initialized to false - setSettingsPageAtFace(false) + setSettingsPageAtMouth(false) // Start in a moving state, not a paused state setPaused(false) - }, [setSettingsPageAtFace, setPaused, doneButtonIsClicked]) + }, [setSettingsPageAtMouth, setPaused, doneButtonIsClicked]) // Callback to move the robot to the mouth const moveToMouthButtonClicked = useCallback(() => { @@ -127,7 +129,7 @@ const BiteTransfer = (props) => { // To get to Settings, the globalMealState must be one of the NON_MOVING_STATES switch (globalMealState) { case MEAL_STATE.U_BiteDone: - if (settingsPageAtFace) { + if (settingsPageAtMouth) { localCurrMealState = null localNextMealState = null } else { @@ -152,7 +154,7 @@ const BiteTransfer = (props) => { break } setLocalCurrMealStateWrapper(localCurrMealState, localNextMealState) - }, [settingsPageAtFace, globalMealState, setLocalCurrMealStateWrapper, doneButtonIsClicked]) + }, [settingsPageAtMouth, globalMealState, setLocalCurrMealStateWrapper, doneButtonIsClicked]) // Callback for when the user changes the distance to mouth const onDistanceToMouthChange = useCallback( diff --git a/feedingwebapp/src/Pages/Settings/CustomizeConfiguration.jsx b/feedingwebapp/src/Pages/Settings/CustomizeConfiguration.jsx index 45bc68fe..34fe4415 100644 --- a/feedingwebapp/src/Pages/Settings/CustomizeConfiguration.jsx +++ b/feedingwebapp/src/Pages/Settings/CustomizeConfiguration.jsx @@ -70,15 +70,15 @@ const CustomizeConfiguration = (props) => { const setSettingsState = useGlobalState((state) => state.setSettingsState) const globalMealState = useGlobalState((state) => state.mealState) const setPaused = useGlobalState((state) => state.setPaused) - const settingsPageAtFace = useGlobalState((state) => state.settingsPageAtFace) - const setSettingsPageAtFace = useGlobalState((state) => state.setSettingsPageAtFace) + const settingsPageAtMouth = useGlobalState((state) => state.settingsPageAtMouth) + const setSettingsPageAtMouth = useGlobalState((state) => state.setSettingsPageAtMouth) const moveToMouthActionGoal = useGlobalState((state) => state.moveToMouthActionGoal) // Create relevant local state variables // Configure the parameters for SettingsPageParent const [currentConfigurationParams, setCurrentConfigurationParams] = useState(props.paramNames.map(() => null)) const [localCurrAndNextMealState, setLocalCurrAndNextMealState] = useState( - globalMealState === MEAL_STATE.U_BiteDone || settingsPageAtFace + globalMealState === MEAL_STATE.U_BiteDone || settingsPageAtMouth ? [MEAL_STATE.R_MovingFromMouth, props.startingMealState] : [props.startingMealState, null] ) @@ -147,7 +147,9 @@ const CustomizeConfiguration = (props) => { setLocalCurrAndNextMealState([newLocalCurrMealState, newLocalNextMealState]) } // If the oldlocalCurrMealState was R_MovingToMouth, then the robot is at the mouth - setSettingsPageAtFace(newLocalCurrMealState === null && (settingsPageAtFace || oldLocalCurrMealState === MEAL_STATE.R_MovingToMouth)) + setSettingsPageAtMouth( + newLocalCurrMealState === null && (settingsPageAtMouth || oldLocalCurrMealState === MEAL_STATE.R_MovingToMouth) + ) }, [ doneButtonIsClicked, @@ -157,8 +159,8 @@ const CustomizeConfiguration = (props) => { setLocalCurrAndNextMealState, setMountTeleopSubcomponent, setPaused, - settingsPageAtFace, - setSettingsPageAtFace, + settingsPageAtMouth, + setSettingsPageAtMouth, setSettingsState, setVideoFeedrefreshCount ] @@ -236,7 +238,7 @@ const CustomizeConfiguration = (props) => { doneButtonIsClicked.current = false let nextNextMealState = null // If we are at the user's mouth, prepend MoveFromMouth to the motion. - if (settingsPageAtFace) { + if (settingsPageAtMouth) { // MoveIt often fails to execute small trajectories, which are often planned // when doing MovetoStaging immediately following MoveFromMouth. Thus, we // leave the robot in the MoveFromMouth configuration. It is not technically @@ -249,7 +251,7 @@ const CustomizeConfiguration = (props) => { } unmountTeleopSubcomponentCallback.current = getSetLocalCurrMealStateWrapper(nextMealState, nextNextMealState) }, - [getSetLocalCurrMealStateWrapper, doneButtonIsClicked, settingsPageAtFace, unmountTeleopSubcomponentCallback] + [getSetLocalCurrMealStateWrapper, doneButtonIsClicked, settingsPageAtMouth, unmountTeleopSubcomponentCallback] ) // Callback to return to the main settings page