Skip to content

Improve Acquisition Report #145

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

Merged
merged 3 commits into from
Sep 1, 2024
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
8 changes: 4 additions & 4 deletions feedingwebapp/src/Pages/GlobalState.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -130,8 +130,8 @@ export const useGlobalState = create(
// message received from the face detection node where a
// face was detected and within the distance bounds of the camera.
moveToMouthActionGoal: null,
// Last RobotMotion action response
lastMotionActionResponse: null,
// Last RobotMotion action feedback message
lastMotionActionFeedback: null,
// Whether or not the currently-executing robot motion was paused by the user.
// NOTE: `paused` may no longer need to be in global state now that we have
// the `inNonMovingState` flag.
Expand Down Expand Up @@ -218,9 +218,9 @@ export const useGlobalState = create(
set(() => ({
biteAcquisitionActionGoal: biteAcquisitionActionGoal
})),
setLastMotionActionResponse: (lastMotionActionResponse) =>
setLastMotionActionFeedback: (lastMotionActionFeedback) =>
set(() => ({
lastMotionActionResponse: lastMotionActionResponse
lastMotionActionFeedback: lastMotionActionFeedback
})),
setMoveToMouthActionGoal: (moveToMouthActionGoal) =>
set(() => {
Expand Down
60 changes: 56 additions & 4 deletions feedingwebapp/src/Pages/Home/Home.jsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
// React imports
import React, { useCallback, useEffect, useMemo } from 'react'
import React, { useCallback, useEffect, useMemo, useRef } from 'react'
import PropTypes from 'prop-types'
import { toast } from 'react-toastify'
import { View } from 'react-native'

// Local imports
import './Home.css'
import { createROSService, createROSServiceRequest, useROS } from '../../ros/ros_helpers'
import { useGlobalState, MEAL_STATE } from '../GlobalState'
import BiteAcquisitionCheck from './MealStates/BiteAcquisitionCheck'
import BiteDone from './MealStates/BiteDone'
Expand All @@ -13,7 +15,13 @@ import DetectingFace from './MealStates/DetectingFace'
import PostMeal from './MealStates/PostMeal'
import PreMeal from './MealStates/PreMeal'
import RobotMotion from './MealStates/RobotMotion'
import { getRobotMotionText, TIME_TO_RESET_MS } from '../Constants'
import {
ACQUISITION_REPORT_SERVICE_NAME,
ACQUISITION_REPORT_SERVICE_TYPE,
getRobotMotionText,
REGULAR_CONTAINER_ID,
TIME_TO_RESET_MS
} from '../Constants'

/**
* The Home component displays the state of the meal, solicits user input as
Expand Down Expand Up @@ -78,6 +86,47 @@ function Home(props) {
const moveToMouthActionInput = useMemo(() => moveToMouthActionGoal, [moveToMouthActionGoal])
const moveToStowPositionActionInput = useMemo(() => ({}), [])

/**
* Create callbacks for acquisition success and failure. This is done here because these
* callbacks can be called during BiteAcquisition or the BiteAcquisitionCheck.
*/
const lastMotionActionFeedback = useGlobalState((state) => state.lastMotionActionFeedback)
const ros = useRef(useROS().ros)
let acquisitionReportService = useRef(createROSService(ros.current, ACQUISITION_REPORT_SERVICE_NAME, ACQUISITION_REPORT_SERVICE_TYPE))
let acquisitionResponse = useCallback(
(success) => {
if (!lastMotionActionFeedback.action_info_populated) {
console.info('Cannot report acquisition success or failure without action_info_populated.')
return
}
let msg, loss
if (success) {
msg = 'Reporting Food Acquisition Success!'
loss = 0.0
} else {
msg = 'Reporting Food Acquisition Failure.'
loss = 1.0
}
// NOTE: This uses the ToastContainer in Header
console.log(msg)
toast.info(msg, {
containerId: REGULAR_CONTAINER_ID,
toastId: msg
})
// Create a service request
let request = createROSServiceRequest({
loss: loss,
action_index: lastMotionActionFeedback.action_index,
posthoc: lastMotionActionFeedback.posthoc,
id: lastMotionActionFeedback.selection_id
})
// Call the service
let service = acquisitionReportService.current
service.callService(request, (response) => console.log('Got acquisition report response', response))
},
[lastMotionActionFeedback]
)

/**
* Determines what screen to render based on the meal state.
*/
Expand Down Expand Up @@ -119,7 +168,8 @@ function Home(props) {
let nextMealState = MEAL_STATE.U_BiteAcquisitionCheck
let backMealState = MEAL_STATE.R_MovingAbovePlate
// TODO: Add an icon for this errorMealState!
let errorMealState = MEAL_STATE.R_MovingToRestingPosition
let errorMealState = MEAL_STATE.R_MovingToStagingConfiguration
let errorCallback = () => acquisitionResponse(true) // Success if the user skips acquisition
let errorMealStateDescription = 'Skip Acquisition'
return (
<RobotMotion
Expand All @@ -132,6 +182,7 @@ function Home(props) {
waitingText={getRobotMotionText(currentMealState)}
allowRetry={false} // Don't allow retrying bite acquisition
errorMealState={errorMealState}
errorCallback={errorCallback}
errorMealStateDescription={errorMealStateDescription}
/>
)
Expand All @@ -153,7 +204,7 @@ function Home(props) {
)
}
case MEAL_STATE.U_BiteAcquisitionCheck: {
return <BiteAcquisitionCheck debug={props.debug} />
return <BiteAcquisitionCheck debug={props.debug} acquisitionResponse={acquisitionResponse} />
}
case MEAL_STATE.R_MovingToStagingConfiguration: {
/**
Expand Down Expand Up @@ -257,6 +308,7 @@ function Home(props) {
props.debug,
props.webrtcURL,
biteAcquisitionActionInput,
acquisitionResponse,
mostRecentBiteDoneResponse,
moveAbovePlateActionInput,
moveToMouthActionInput,
Expand Down
67 changes: 17 additions & 50 deletions feedingwebapp/src/Pages/Home/MealStates/BiteAcquisitionCheck.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,28 +2,22 @@
import React, { useCallback, useEffect, useState, useRef } from 'react'
import Button from 'react-bootstrap/Button'
import { useMediaQuery } from 'react-responsive'
import { toast } from 'react-toastify'
import { View } from 'react-native'
// PropTypes is used to validate that the used props are in fact passed to this Component
import PropTypes from 'prop-types'

// Local Imports
import '../Home.css'
import { useGlobalState, MEAL_STATE } from '../../GlobalState'
import { MOVING_STATE_ICON_DICT } from '../../Constants'
import { useROS, createROSService, createROSServiceRequest, subscribeToROSTopic, unsubscribeFromROSTopic } from '../../../ros/ros_helpers'
import {
ACQUISITION_REPORT_SERVICE_NAME,
ACQUISITION_REPORT_SERVICE_TYPE,
FOOD_ON_FORK_DETECTION_TOPIC,
FOOD_ON_FORK_DETECTION_TOPIC_MSG,
REGULAR_CONTAINER_ID,
ROS_SERVICE_NAMES
} from '../../Constants'
import { FOOD_ON_FORK_DETECTION_TOPIC, FOOD_ON_FORK_DETECTION_TOPIC_MSG, ROS_SERVICE_NAMES } from '../../Constants'

/**
* The BiteAcquisitionCheck component appears after the robot has attempted to
* acquire a bite, and asks the user whether it succeeded at acquiring the bite.
*/
const BiteAcquisitionCheck = () => {
const BiteAcquisitionCheck = (props) => {
// Store the remining time before auto-continuing
const [remainingSeconds, setRemainingSeconds] = useState(null)
// Get the relevant global variables
Expand All @@ -50,17 +44,11 @@ const BiteAcquisitionCheck = () => {
let iconWidth = isPortrait ? '28vh' : '28vw'
let iconHeight = isPortrait ? '18vh' : '18vw'

// Configure AcquisitionReport service
const lastMotionActionResponse = useGlobalState((state) => state.lastMotionActionResponse)
/**
* 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 Client for reporting success/failure
*/
let acquisitionReportService = useRef(createROSService(ros.current, ACQUISITION_REPORT_SERVICE_NAME, ACQUISITION_REPORT_SERVICE_TYPE))
/**
* Create the ROS Service. This is created in local state to avoid re-creating
* it upon every re-render.
Expand All @@ -73,48 +61,20 @@ const BiteAcquisitionCheck = () => {
* succeeded.
*/
const acquisitionSuccess = useCallback(() => {
console.log('acquisitionSuccess')
// NOTE: This uses the ToastContainer in Header
toast.info('Reporting Food Acquisition Success!', {
containerId: REGULAR_CONTAINER_ID,
toastId: 'foodAcquisitionSuccess'
})
// Create a service request
let request = createROSServiceRequest({
loss: 0.0,
action_index: lastMotionActionResponse.action_index,
posthoc: lastMotionActionResponse.posthoc,
id: lastMotionActionResponse.selection_id
})
// Call the service
let service = acquisitionReportService.current
service.callService(request, (response) => console.log('Got acquisition report response', response))
let acquisitionResponse = props.acquisitionResponse
acquisitionResponse(true)
setMealState(MEAL_STATE.R_MovingToStagingConfiguration)
}, [lastMotionActionResponse, setMealState])
}, [props.acquisitionResponse, setMealState])

/**
* Callback function for when the user indicates that the bite acquisition
* failed.
*/
const acquisitionFailure = useCallback(() => {
console.log('acquisitionFailure')
// NOTE: This uses the ToastContainer in Header
toast.info('Reporting Food Acquisition Failure.', {
containerId: REGULAR_CONTAINER_ID,
toastId: 'foodAcquisitionFailure'
})
// Create a service request
let request = createROSServiceRequest({
loss: 1.0,
action_index: lastMotionActionResponse.action_index,
posthoc: lastMotionActionResponse.posthoc,
id: lastMotionActionResponse.selection_id
})
// Call the service
let service = acquisitionReportService.current
service.callService(request, (response) => console.log('Got acquisition report response', response))
let acquisitionResponse = props.acquisitionResponse
acquisitionResponse(false)
setMealState(MEAL_STATE.R_MovingAbovePlate)
}, [lastMotionActionResponse, setMealState])
}, [props.acquisitionResponse, setMealState])

/*
* Create refs to store the interval for the food-on-fork detection timers.
Expand Down Expand Up @@ -429,4 +389,11 @@ const BiteAcquisitionCheck = () => {
return <>{fullPageView()}</>
}

BiteAcquisitionCheck.propTypes = {
debug: PropTypes.bool,
// A function that takes a boolean indicating whether the robot succeeded at acquiring the bite,
// and processes the response. Note that it does not transition to the next state.
acquisitionResponse: PropTypes.func.isRequired
}

export default BiteAcquisitionCheck
19 changes: 12 additions & 7 deletions feedingwebapp/src/Pages/Home/MealStates/RobotMotion.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,8 @@ const RobotMotion = (props) => {
const paused = useGlobalState((state) => state.paused)
const setPaused = useGlobalState((state) => state.setPaused)

// Setter for last motion action response
const setLastMotionActionResponse = useGlobalState((state) => state.setLastMotionActionResponse)
// Setter for last motion action feedback msg
const setLastMotionActionFeedback = useGlobalState((state) => state.setLastMotionActionFeedback)

/**
* Connect to ROS, if not already connected. Put this in useRef to avoid
Expand Down Expand Up @@ -115,23 +115,27 @@ const RobotMotion = (props) => {
const feedbackCallback = useCallback(
(feedbackMsg) => {
console.log('Got feedback message', feedbackMsg)
setLastMotionActionFeedback(feedbackMsg.values.feedback)
setActionStatus({
actionStatus: ROS_ACTION_STATUS_EXECUTE,
feedback: feedbackMsg.values.feedback
})
},
[setActionStatus]
[setActionStatus, setLastMotionActionFeedback]
)

/**
* Callback function to change the meal state.
*/
const changeMealState = useCallback(
(nextMealState, msg = null) => {
(nextMealState, msg = null, callback = null) => {
if (msg) {
console.log(msg)
}
setPaused(false)
if (callback) {
callback()
}
let setMealState = props.setMealState
setMealState(nextMealState)
},
Expand Down Expand Up @@ -164,7 +168,6 @@ const RobotMotion = (props) => {
setActionStatus({
actionStatus: ROS_ACTION_STATUS_SUCCEED
})
setLastMotionActionResponse(response.values)
robotMotionDone()
} else {
if (
Expand All @@ -185,7 +188,7 @@ const RobotMotion = (props) => {
}
}
},
[setLastMotionActionResponse, setActionStatus, setPaused, robotMotionDone]
[setActionStatus, setPaused, robotMotionDone]
)

/**
Expand Down Expand Up @@ -327,7 +330,7 @@ const RobotMotion = (props) => {
variant='warning'
className='mx-2 btn-huge'
size='lg'
onClick={() => changeMealState(props.errorMealState, 'errorMealState')}
onClick={() => changeMealState(props.errorMealState, 'errorMealState', props.errorCallback)}
style={{
width: '90%',
height: '20%'
Expand Down Expand Up @@ -363,6 +366,7 @@ const RobotMotion = (props) => {
props.waitingText,
props.allowRetry,
props.errorMealState,
props.errorCallback,
props.errorMealStateDescription,
motionTextFontSize,
waitingTextFontSize,
Expand Down Expand Up @@ -501,6 +505,7 @@ RobotMotion.propTypes = {
allowRetry: PropTypes.bool,
// If error, show the user the option to transition to this meal state
errorMealState: PropTypes.string,
errorCallback: PropTypes.func,
errorMealStateDescription: PropTypes.string
}

Expand Down
Loading
Loading