diff --git a/feedingwebapp/public/robot_state_imgs/table.svg b/feedingwebapp/public/robot_state_imgs/table.svg
new file mode 100644
index 00000000..c9818f40
--- /dev/null
+++ b/feedingwebapp/public/robot_state_imgs/table.svg
@@ -0,0 +1,13 @@
+
+
+
\ No newline at end of file
diff --git a/feedingwebapp/src/Pages/Constants.js b/feedingwebapp/src/Pages/Constants.js
index 0bf1cd4f..88cc6bac 100644
--- a/feedingwebapp/src/Pages/Constants.js
+++ b/feedingwebapp/src/Pages/Constants.js
@@ -29,6 +29,7 @@ MOVING_STATE_ICON_DICT[MEAL_STATE.R_MovingFromMouth] = '/robot_state_imgs/move_t
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 }
+export const TABLE_ICON = '/robot_state_imgs/table.svg'
// The names of the ROS topic(s)
export const CAMERA_FEED_TOPIC = '/local/camera/color/image_raw/compressed'
@@ -122,14 +123,21 @@ export const GET_PARAMETERS_SERVICE_NAME = 'ada_feeding_action_servers/get_param
export const GET_PARAMETERS_SERVICE_TYPE = 'rcl_interfaces/srv/GetParameters'
export const SET_PARAMETERS_SERVICE_NAME = 'ada_feeding_action_servers/set_parameters_atomically'
export const SET_PARAMETERS_SERVICE_TYPE = 'rcl_interfaces/srv/SetParametersAtomically'
+export const PLANNING_SCENE_GET_PARAMETERS_SERVICE_NAME = 'ada_planning_scene/get_parameters'
+export const PLANNING_SCENE_GET_PARAMETERS_SERVICE_TYPE = 'rcl_interfaces/srv/GetParameters'
// The names of parameters users can change in the settings menu
export const DISTANCE_TO_MOUTH_PARAM = 'MoveToMouth.tree_kwargs.plan_distance_from_mouth'
+export const MOVE_TO_MOUTH_SPEED_PARAM = 'MoveToMouth.tree_kwargs.max_linear_speed'
+export const MOVE_TO_MOUTH_SPEED_NEAR_MOUTH_PARAM = 'MoveToMouth.tree_kwargs.linear_speed_near_mouth'
+export const MOVE_FROM_MOUTH_SPEED_PARAM = 'MoveFromMouth.tree_kwargs.max_linear_speed_to_staging_configuration'
+export const MOVE_FROM_MOUTH_SPEED_NEAR_MOUTH_PARAM = 'MoveFromMouth.tree_kwargs.linear_speed_near_mouth'
+export const PLANNING_SCENE_PARAM = 'planning_scene_namespace_to_use'
export const ABOVE_PLATE_PARAM_JOINTS = 'MoveAbovePlate.tree_kwargs.joint_positions'
export const STAGING_PARAM_JOINTS = 'MoveToStagingConfiguration.tree_kwargs.goal_configuration'
export const STAGING_PARAM_POSITION = 'MoveFromMouth.tree_kwargs.staging_configuration_position'
export const STAGING_PARAM_ORIENTATION = 'MoveFromMouth.tree_kwargs.staging_configuration_quat_xyzw'
-// TODO: Eventually, we should break AcquireFood into two actionss to avoid these
+// TODO: Eventually, we should break AcquireFood into two actions to avoid these
// two different resting parameters.
export const RESTING_PARAM_JOINTS_1 = 'AcquireFood.tree_kwargs.resting_joint_positions'
// TODO: We may need to remove the orientation constraint from the below action.
diff --git a/feedingwebapp/src/Pages/GlobalState.jsx b/feedingwebapp/src/Pages/GlobalState.jsx
index 45308a71..c530b38d 100644
--- a/feedingwebapp/src/Pages/GlobalState.jsx
+++ b/feedingwebapp/src/Pages/GlobalState.jsx
@@ -76,8 +76,8 @@ export { NON_MOVING_STATES }
/**
* SETTINGS_STATE controls which settings page to display.
* - MAIN: The main page, with options to navigate to the other pages.
- * - DISTANCE_TO_MOUTH: Allow the user to customize how close the robot gets
- * to their mouth.
+ * - BITE_TRANSFER: Allow the user to customize how close the robot gets
+ * to their mouth and the speed of approach/departure.
* - ABOVE_PLATE: Allow the user to customize how high the fixed above plate
* arm configuration.
* - RESTING_CONFIGURATION: Allow the user to customize the fixed resting
@@ -89,11 +89,12 @@ export { NON_MOVING_STATES }
*/
export const SETTINGS_STATE = {
MAIN: 'MAIN',
- DISTANCE_TO_MOUTH: 'DISTANCE_TO_MOUTH',
+ BITE_TRANSFER: 'BITE_TRANSFER',
ABOVE_PLATE: 'ABOVE_PLATE',
RESTING_CONFIGURATION: 'RESTING_CONFIGURATION',
STAGING_CONFIGURATION: 'STAGING_CONFIGURATION',
- STOW_CONFIGURATION: 'STOW_CONFIGURATION'
+ STOW_CONFIGURATION: 'STOW_CONFIGURATION',
+ PLANNING_SCENE: 'PLANNING_SCENE'
}
// The name of the default parameter namespace
diff --git a/feedingwebapp/src/Pages/Settings/BiteTransfer.jsx b/feedingwebapp/src/Pages/Settings/BiteTransfer.jsx
index e0c3d249..bcfba22e 100644
--- a/feedingwebapp/src/Pages/Settings/BiteTransfer.jsx
+++ b/feedingwebapp/src/Pages/Settings/BiteTransfer.jsx
@@ -7,7 +7,14 @@ import Button from 'react-bootstrap/Button'
import { View } from 'react-native'
// Local imports
-import { getRobotMotionText, DISTANCE_TO_MOUTH_PARAM } from '../Constants'
+import {
+ getRobotMotionText,
+ DISTANCE_TO_MOUTH_PARAM,
+ MOVE_TO_MOUTH_SPEED_PARAM,
+ MOVE_TO_MOUTH_SPEED_NEAR_MOUTH_PARAM,
+ MOVE_FROM_MOUTH_SPEED_PARAM,
+ MOVE_FROM_MOUTH_SPEED_NEAR_MOUTH_PARAM
+} from '../Constants'
import { useGlobalState, MEAL_STATE, SETTINGS_STATE } from '../GlobalState'
import RobotMotion from '../Home/MealStates/RobotMotion'
import DetectingFaceSubcomponent from '../Home/MealStates/DetectingFaceSubcomponent'
@@ -28,8 +35,17 @@ const BiteTransfer = (props) => {
// Create relevant local state variables
// Configure the parameters for SettingsPageParent
- const paramNames = useMemo(() => [DISTANCE_TO_MOUTH_PARAM], [])
- const [currentDistanceToMouth, setCurrentDistanceToMouth] = useState([null])
+ const paramNames = useMemo(
+ () => [
+ DISTANCE_TO_MOUTH_PARAM,
+ MOVE_TO_MOUTH_SPEED_PARAM,
+ MOVE_TO_MOUTH_SPEED_NEAR_MOUTH_PARAM,
+ MOVE_FROM_MOUTH_SPEED_PARAM,
+ MOVE_FROM_MOUTH_SPEED_NEAR_MOUTH_PARAM
+ ],
+ []
+ )
+ const [currentParams, setCurrentParams] = useState([null, null, null, null, null])
const [localCurrAndNextMealState, setLocalCurrAndNextMealState] = useState(
globalMealState === MEAL_STATE.U_BiteDone || globalMealState === MEAL_STATE.R_DetectingFace || settingsPageAtMouth
? [MEAL_STATE.R_MovingFromMouth, null]
@@ -53,6 +69,8 @@ const BiteTransfer = (props) => {
// Get min and max distance to mouth
const minDistanceToMouth = 1 // cm
const maxDistanceToMouth = 10 // cm
+ const minLinearSpeed = 2 // cm/s
+ const maxLinearSpeed = 15 // cm/s
// When we set local meal state, also update bite transfer page at face
const setLocalCurrMealStateWrapper = useCallback(
@@ -166,16 +184,57 @@ const BiteTransfer = (props) => {
if (value > maxDistanceToMouth) {
value = maxDistanceToMouth
}
- let fullDistanceToMouth = [value / 100.0, currentDistanceToMouth[0][1], currentDistanceToMouth[0][2]]
- setCurrentDistanceToMouth([fullDistanceToMouth])
+ let distance_m = value / 100.0
+ setCurrentParams((currentParams) => [
+ [distance_m, currentParams[0][1], currentParams[0][2]],
+ currentParams[1],
+ currentParams[2],
+ currentParams[3],
+ currentParams[4]
+ ])
+ },
+ [minDistanceToMouth, maxDistanceToMouth]
+ )
+
+ // Callback for when the user changes the speed to the mouth
+ const onSpeedChange = useCallback(
+ (_ev, data, index) => {
+ let value = data.value !== null ? data.value : parseFloat(data.displayValue)
+ if (value < minLinearSpeed) {
+ value = minLinearSpeed
+ }
+ if (value > maxLinearSpeed) {
+ value = maxLinearSpeed
+ }
+ let speed_mps = value / 100.0
+ setCurrentParams((currentParams) => [
+ currentParams[0],
+ index === 1 ? speed_mps : currentParams[1],
+ index === 2 ? speed_mps : currentParams[2],
+ index === 3 ? speed_mps : currentParams[3],
+ index === 4 ? speed_mps : currentParams[4]
+ ])
},
- [currentDistanceToMouth, minDistanceToMouth, maxDistanceToMouth]
+ [minLinearSpeed, maxLinearSpeed]
)
// Callback to render the main contents of the page
const distanceToMouthId = useId()
+ const moveToMouthSpeedId = useId()
+ const moveToMouthSpeedNearMouthId = useId()
+ const moveFromMouthSpeedId = useId()
+ const moveFromMouthSpeedNearMouthId = useId()
+ const speedParameterIdsAndDescriptions = useMemo(
+ () => [
+ [moveToMouthSpeedId, 'Approach Speed (cm/s)'],
+ [moveToMouthSpeedNearMouthId, 'Approach Speed Near Mouth (cm/s)'],
+ [moveFromMouthSpeedId, 'Retreat Speed (cm/s)'],
+ [moveFromMouthSpeedNearMouthId, 'Retreat Speed Near Mouth (cm/s)']
+ ],
+ [moveToMouthSpeedId, moveToMouthSpeedNearMouthId, moveFromMouthSpeedId, moveFromMouthSpeedNearMouthId]
+ )
const renderBiteTransferSettings = useCallback(() => {
- if (currentDistanceToMouth[0] === null) {
+ if (currentParams.some((param) => param === null)) {
return (
<>
{
Distance To Mouth (cm)
{
size: 'large'
}}
/>
+ {speedParameterIdsAndDescriptions.map(([id, description], index) => (
+ <>
+
+ onSpeedChange(_ev, data, index + 1)}
+ appearance='filled-lighter'
+ style={{
+ fontSize: textFontSize,
+ width: '90%',
+ color: 'black'
+ }}
+ incrementButton={{
+ 'aria-label': 'Increase value by 1.0',
+ 'aria-roledescription': 'spinner',
+ size: 'large'
+ }}
+ />
+ >
+ ))}
{
}, [
dimension,
textFontSize,
- currentDistanceToMouth,
+ currentParams,
onDistanceToMouthChange,
+ onSpeedChange,
distanceToMouthId,
+ speedParameterIdsAndDescriptions,
moveToMouthButtonClicked,
moveAwayFromMouthButtonClicked
])
@@ -368,8 +461,8 @@ const BiteTransfer = (props) => {
modalOnHide={() => setLocalCurrMealStateWrapper(null)}
modalChildren={renderModalBody()}
paramNames={paramNames}
- localParamValues={currentDistanceToMouth}
- setLocalParamValues={setCurrentDistanceToMouth}
+ localParamValues={currentParams}
+ setLocalParamValues={setCurrentParams}
>
{renderBiteTransferSettings()}
diff --git a/feedingwebapp/src/Pages/Settings/Main.jsx b/feedingwebapp/src/Pages/Settings/Main.jsx
index 87cbbc94..a524163c 100644
--- a/feedingwebapp/src/Pages/Settings/Main.jsx
+++ b/feedingwebapp/src/Pages/Settings/Main.jsx
@@ -20,7 +20,8 @@ import {
MOVING_STATE_ICON_DICT,
REGULAR_CONTAINER_ID,
SET_PARAMETERS_SERVICE_NAME,
- SET_PARAMETERS_SERVICE_TYPE
+ SET_PARAMETERS_SERVICE_TYPE,
+ TABLE_ICON
} from '../Constants'
/**
@@ -163,9 +164,9 @@ const Main = () => {
// Configure the different options in the settings menu
let settingsConfig = [
{
- title: 'Distance to Mouth',
+ title: 'Motion to/from Mouth',
icon: moveToMouthConfigurationImage,
- onClick: () => onClickSettingsPage(SETTINGS_STATE.DISTANCE_TO_MOUTH)
+ onClick: () => onClickSettingsPage(SETTINGS_STATE.BITE_TRANSFER)
},
{
title: 'Above Plate',
@@ -186,6 +187,11 @@ const Main = () => {
title: 'Stow Position',
icon: moveToStowConfigurationImage,
onClick: () => onClickSettingsPage(SETTINGS_STATE.STOW_CONFIGURATION)
+ },
+ {
+ title: 'Planning Scene',
+ icon: TABLE_ICON,
+ onClick: () => onClickSettingsPage(SETTINGS_STATE.PLANNING_SCENE)
}
]
diff --git a/feedingwebapp/src/Pages/Settings/PlanningScene.jsx b/feedingwebapp/src/Pages/Settings/PlanningScene.jsx
new file mode 100644
index 00000000..5e458e7a
--- /dev/null
+++ b/feedingwebapp/src/Pages/Settings/PlanningScene.jsx
@@ -0,0 +1,131 @@
+// React imports
+import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
+import ButtonGroup from 'react-bootstrap/ButtonGroup'
+import Dropdown from 'react-bootstrap/Dropdown'
+import DropdownButton from 'react-bootstrap/DropdownButton'
+import { View } from 'react-native'
+
+// Local imports
+import { PLANNING_SCENE_PARAM, PLANNING_SCENE_GET_PARAMETERS_SERVICE_NAME, PLANNING_SCENE_GET_PARAMETERS_SERVICE_TYPE } from '../Constants'
+import { useGlobalState, SETTINGS_STATE } from '../GlobalState'
+import { useROS, createROSService, createROSServiceRequest, getValueFromParameter } from '../../ros/ros_helpers'
+import SettingsPageParent from './SettingsPageParent'
+
+/**
+ * The PlanningScene component allows users to change the planning scene that a particular
+ * settings namespace is configured with
+ */
+const PlanningScene = () => {
+ // Get relevant global state variables
+ const setSettingsState = useGlobalState((state) => state.setSettingsState)
+
+ // Rendering variables
+ let textFontSize = '3.5vh'
+
+ // Configure the parameters for SettingsPageParent
+ const paramNames = useMemo(() => [PLANNING_SCENE_PARAM], [])
+ const [currentParams, setCurrentParams] = useState([null])
+
+ /**
+ * Connect to ROS, if not already connected. Put this in useRef to avoid
+ * re-connecting upon re-renders.
+ */
+ const ros = useRef(useROS().ros)
+
+ // Get the options for the planning scene
+ let getParametersService = useRef(
+ createROSService(ros.current, PLANNING_SCENE_GET_PARAMETERS_SERVICE_NAME, PLANNING_SCENE_GET_PARAMETERS_SERVICE_TYPE)
+ )
+ const [planningSceneNamespaces, setPlanningSceneNamespaces] = useState([])
+ const getPlanningSceneNamespaces = useCallback(() => {
+ let service = getParametersService.current
+ let request = createROSServiceRequest({
+ names: ['namespaces']
+ })
+ console.log('PlanningScene: Requesting planning scene namespaces', service, request)
+ service.callService(request, (response) => {
+ console.log('PlanningScene: Received planning scene namespaces', request, response)
+ if (response.values.length > 0 && response.values[0].type === 9) {
+ setPlanningSceneNamespaces(getValueFromParameter(response.values[0]))
+ } else {
+ console.error('PlanningScene: Error getting planning scene namespaces')
+ }
+ })
+ }, [getParametersService, setPlanningSceneNamespaces])
+ useEffect(() => {
+ getPlanningSceneNamespaces()
+ }, [getPlanningSceneNamespaces])
+
+ // Render the settings for the planning scene
+ const renderPlanningSceneSettings = useCallback(() => {
+ if (currentParams.some((param) => param === null)) {
+ return (
+ <>
+
+ Loading...
+
+ >
+ )
+ } else {
+ return (
+
+ Select Planning Scene:
+ {
+ if (planningSceneNamespaces.length === 0) {
+ getPlanningSceneNamespaces()
+ }
+ }}
+ >
+ {planningSceneNamespaces.map((namespace) => (
+ setCurrentParams([namespace])} active={namespace === currentParams[0]}>
+ {namespace}
+
+ ))}
+
+
+ )
+ }
+ }, [currentParams, planningSceneNamespaces, getPlanningSceneNamespaces, setCurrentParams, textFontSize])
+
+ return (
+ setSettingsState(SETTINGS_STATE.MAIN)}
+ modalShow={false}
+ modalOnHide={null}
+ modalChildren={null}
+ paramNames={paramNames}
+ localParamValues={currentParams}
+ setLocalParamValues={setCurrentParams}
+ >
+ {renderPlanningSceneSettings()}
+
+ )
+}
+
+export default PlanningScene
diff --git a/feedingwebapp/src/Pages/Settings/Settings.jsx b/feedingwebapp/src/Pages/Settings/Settings.jsx
index 3bdec420..dcf30b1b 100644
--- a/feedingwebapp/src/Pages/Settings/Settings.jsx
+++ b/feedingwebapp/src/Pages/Settings/Settings.jsx
@@ -22,6 +22,7 @@ import {
STAGING_PARAM_POSITION,
STOW_PARAM_JOINTS
} from '../Constants'
+import PlanningScene from './PlanningScene'
/**
* The Settings components displays the appropriate settings page based on the
@@ -42,7 +43,7 @@ const Settings = (props) => {
switch (settingsState) {
case SETTINGS_STATE.MAIN:
return
- case SETTINGS_STATE.DISTANCE_TO_MOUTH:
+ case SETTINGS_STATE.BITE_TRANSFER:
return
case SETTINGS_STATE.ABOVE_PLATE:
return (
@@ -138,6 +139,8 @@ const Settings = (props) => {
webrtcURL={props.webrtcURL}
/>
)
+ case SETTINGS_STATE.PLANNING_SCENE:
+ return
default:
console.log('Invalid settings state', settingsState)
return