Skip to content

Commit fcbbb18

Browse files
authored
Allow users to set multiple parameter presets (#131)
* Allow users to change the parameter preset from the Main Settings menu * Change default setting of dummy face detection for easier testing * Finalized the feature * Updates from testing
1 parent 12f2904 commit fcbbb18

File tree

5 files changed

+358
-118
lines changed

5 files changed

+358
-118
lines changed

feeding_web_app_ros2_test/feeding_web_app_ros2_test/FaceDetection.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,10 @@
1414
class FaceDetectionNode(Node):
1515
def __init__(
1616
self,
17-
face_detection_interval=90,
18-
num_images_with_face=60,
19-
open_mouth_interval=90,
20-
num_images_with_open_mouth=30,
17+
face_detection_interval=45,
18+
num_images_with_face=30,
19+
open_mouth_interval=45,
20+
num_images_with_open_mouth=15,
2121
):
2222
"""
2323
Initializes the FaceDetection node, which exposes a SetBool

feedingwebapp/src/Pages/Constants.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,9 @@ export const GET_PARAMETERS_SERVICE_TYPE = 'rcl_interfaces/srv/GetParameters'
126126
export const SET_PARAMETERS_SERVICE_NAME = 'ada_feeding_action_servers/set_parameters'
127127
export const SET_PARAMETERS_SERVICE_TYPE = 'rcl_interfaces/srv/SetParameters'
128128

129+
// The names of parameters users can change in the settings menu
130+
export const DISTANCE_TO_MOUTH_PARAM = 'MoveToMouth.tree_kwargs.plan_distance_from_mouth'
131+
129132
/**
130133
* The meaning of the status that motion actions return in their results.
131134
* These should match the action definition(s).

feedingwebapp/src/Pages/GlobalState.jsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,9 @@ export const SETTINGS_STATE = {
7575
BITE_TRANSFER: 'BITE_TRANSFER'
7676
}
7777

78+
// The name of the default parameter namespace
79+
export const DEFAULT_NAMESPACE = 'default'
80+
7881
/**
7982
* useGlobalState is a hook to store and manipulate web app state that we want
8083
* to persist across re-renders and refreshes. It won't persist if cookies are
@@ -93,6 +96,7 @@ export const useGlobalState = create(
9396
mealStateTransitionTime: Date.now(),
9497
// The currently displayed settings page
9598
settingsState: SETTINGS_STATE.MAIN,
99+
settingsPresets: { current: DEFAULT_NAMESPACE, customNames: [] },
96100
// The goal for the bite acquisition action, including the most recent
97101
// food item that the user selected in "bite selection"
98102
biteAcquisitionActionGoal: null,
@@ -156,6 +160,10 @@ export const useGlobalState = create(
156160
set(() => ({
157161
settingsState: settingsState
158162
})),
163+
setSettingsPresets: (settingsPresets) =>
164+
set(() => ({
165+
settingsPresets: settingsPresets
166+
})),
159167
setBiteAcquisitionActionGoal: (biteAcquisitionActionGoal) =>
160168
set(() => ({
161169
biteAcquisitionActionGoal: biteAcquisitionActionGoal

feedingwebapp/src/Pages/Settings/BiteTransfer.jsx

Lines changed: 85 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
33
import PropTypes from 'prop-types'
44
import { useId, Label, SpinButton } from '@fluentui/react-components'
55
import Button from 'react-bootstrap/Button'
6+
import Dropdown from 'react-bootstrap/Dropdown'
7+
import SplitButton from 'react-bootstrap/SplitButton'
68
// The Modal is a screen that appears on top of the main app, and can be toggled
79
// on and off.
810
import Modal from 'react-bootstrap/Modal'
@@ -14,9 +16,10 @@ import {
1416
GET_PARAMETERS_SERVICE_NAME,
1517
GET_PARAMETERS_SERVICE_TYPE,
1618
SET_PARAMETERS_SERVICE_NAME,
17-
SET_PARAMETERS_SERVICE_TYPE
19+
SET_PARAMETERS_SERVICE_TYPE,
20+
DISTANCE_TO_MOUTH_PARAM
1821
} from '../Constants'
19-
import { useGlobalState, MEAL_STATE, SETTINGS_STATE } from '../GlobalState'
22+
import { useGlobalState, MEAL_STATE, SETTINGS_STATE, DEFAULT_NAMESPACE } from '../GlobalState'
2023
import RobotMotion from '../Home/MealStates/RobotMotion'
2124
import DetectingFaceSubcomponent from '../Home/MealStates/DetectingFaceSubcomponent'
2225

@@ -26,6 +29,7 @@ import DetectingFaceSubcomponent from '../Home/MealStates/DetectingFaceSubcompon
2629
*/
2730
const BiteTransfer = (props) => {
2831
// Get relevant global state variables
32+
const settingsPresets = useGlobalState((state) => state.settingsPresets)
2933
const setSettingsState = useGlobalState((state) => state.setSettingsState)
3034
const globalMealState = useGlobalState((state) => state.mealState)
3135
const setPaused = useGlobalState((state) => state.setPaused)
@@ -130,27 +134,27 @@ const BiteTransfer = (props) => {
130134
let service = getParametersService.current
131135
// First, attempt to get the current distance to mouth
132136
let currentRequest = createROSServiceRequest({
133-
names: ['current.MoveToMouth.tree_kwargs.plan_distance_from_mouth']
137+
names: [settingsPresets.current.concat('.', DISTANCE_TO_MOUTH_PARAM)]
134138
})
135139
service.callService(currentRequest, (response) => {
136140
console.log('Got current plan_distance_from_mouth response', response)
137-
if (response.values[0].type === 0) {
141+
if (response.values.length === 0 || response.values[0].type === 0) {
138142
// Parameter not set
139143
// Second, attempt to get the default distance to mouth
140144
let defaultRequest = createROSServiceRequest({
141-
names: ['default.MoveToMouth.tree_kwargs.plan_distance_from_mouth']
145+
names: [DEFAULT_NAMESPACE.concat('.', DISTANCE_TO_MOUTH_PARAM)]
142146
})
143147
service.callService(defaultRequest, (response) => {
144148
console.log('Got default plan_distance_from_mouth response', response)
145-
if (response.values.length > 0) {
149+
if (response.values.length > 0 && response.values[0].type === 8) {
146150
setCurrentDistanceToMouth(getParameterValue(response.values[0]))
147151
}
148152
})
149153
} else {
150154
setCurrentDistanceToMouth(getParameterValue(response.values[0]))
151155
}
152156
})
153-
}, [getParametersService, setCurrentDistanceToMouth, setDoneButtonIsClicked, setPaused])
157+
}, [getParametersService, setCurrentDistanceToMouth, setDoneButtonIsClicked, setPaused, settingsPresets])
154158

155159
// Callback to set the distance to mouth parameter
156160
const setDistanceToMouth = useCallback(
@@ -159,7 +163,7 @@ const BiteTransfer = (props) => {
159163
let request = createROSServiceRequest({
160164
parameters: [
161165
{
162-
name: 'current.MoveToMouth.tree_kwargs.plan_distance_from_mouth',
166+
name: settingsPresets.current.concat('.', DISTANCE_TO_MOUTH_PARAM),
163167
value: {
164168
type: 8, // double array
165169
double_array_value: fullDistanceToMouth
@@ -174,23 +178,29 @@ const BiteTransfer = (props) => {
174178
}
175179
})
176180
},
177-
[setParametersService, setCurrentDistanceToMouth]
181+
[setParametersService, setCurrentDistanceToMouth, settingsPresets]
178182
)
179183

180184
// Callback to restore the distance to mouth to the default
181-
const restoreToDefaultButtonClicked = useCallback(() => {
182-
let service = getParametersService.current
183-
// Attempt to get the default distance to mouth
184-
let defaultRequest = createROSServiceRequest({
185-
names: ['default.MoveToMouth.tree_kwargs.plan_distance_from_mouth']
186-
})
187-
service.callService(defaultRequest, (response) => {
188-
console.log('Got default plan_distance_from_mouth response', response)
189-
if (response.values.length > 0) {
190-
setDistanceToMouth(getParameterValue(response.values[0]))
191-
}
192-
})
193-
}, [getParametersService, setDistanceToMouth])
185+
const restoreToPreset = useCallback(
186+
(preset) => {
187+
console.log('restoreToPreset called with', preset)
188+
let service = getParametersService.current
189+
// Attempt to get the default distance to mouth
190+
let defaultRequest = createROSServiceRequest({
191+
names: [preset.concat('.', DISTANCE_TO_MOUTH_PARAM)]
192+
})
193+
service.callService(defaultRequest, (response) => {
194+
console.log('Got plan_distance_from_mouth response', response)
195+
if (response.values.length > 0 && response.values[0].type === 8) {
196+
setDistanceToMouth(getParameterValue(response.values[0]))
197+
} else {
198+
restoreToPreset(DEFAULT_NAMESPACE)
199+
}
200+
})
201+
},
202+
[getParametersService, setDistanceToMouth]
203+
)
194204

195205
// Callback to move the robot to the mouth
196206
const moveToMouthButtonClicked = useCallback(() => {
@@ -275,7 +285,8 @@ const BiteTransfer = (props) => {
275285
flexDirection: 'column',
276286
justifyContent: 'center',
277287
alignItems: 'center',
278-
width: '100%'
288+
width: '100%',
289+
zIndex: 1
279290
}}
280291
>
281292
<Label
@@ -306,7 +317,7 @@ const BiteTransfer = (props) => {
306317
size: 'large'
307318
}}
308319
/>
309-
<Button
320+
<SplitButton
310321
variant='warning'
311322
className='mx-2 mb-2 btn-huge'
312323
size='lg'
@@ -315,10 +326,17 @@ const BiteTransfer = (props) => {
315326
width: '60%',
316327
color: 'black'
317328
}}
318-
onClick={restoreToDefaultButtonClicked}
329+
title={'Set to '.concat(DEFAULT_NAMESPACE)}
330+
onClick={() => restoreToPreset(DEFAULT_NAMESPACE)}
319331
>
320-
Set to Default
321-
</Button>
332+
{settingsPresets.customNames
333+
.filter((x) => x !== settingsPresets.current)
334+
.map((preset) => (
335+
<Dropdown.Item key={preset} onClick={() => restoreToPreset(preset)}>
336+
Set to {preset}
337+
</Dropdown.Item>
338+
))}
339+
</SplitButton>
322340
</View>
323341
<View
324342
style={{
@@ -396,7 +414,8 @@ const BiteTransfer = (props) => {
396414
distanceToMouthId,
397415
moveToMouthButtonClicked,
398416
moveAwayFromMouthButtonClicked,
399-
restoreToDefaultButtonClicked
417+
restoreToPreset,
418+
settingsPresets
400419
])
401420

402421
// When a face is detected, switch to MoveToMouth
@@ -435,18 +454,52 @@ const BiteTransfer = (props) => {
435454
<>
436455
<View
437456
style={{
438-
flex: 2,
457+
flex: 4,
439458
flexDirection: 'column',
440459
justifyContent: 'center',
441460
alignItems: 'center',
442461
width: '100%'
443462
}}
444463
>
445-
<h5 style={{ textAlign: 'center', fontSize: textFontSize }}>Customize Bite Transfer</h5>
464+
<p style={{ textAlign: 'center', fontSize: textFontSize, margin: 0 }} className='txt-huge'>
465+
Bite Transfer Settings
466+
</p>
467+
</View>
468+
<View
469+
style={{
470+
flex: 3,
471+
flexDirection: 'row',
472+
justifyContent: 'center',
473+
alignItems: 'center',
474+
width: '100%'
475+
}}
476+
>
477+
<View
478+
style={{
479+
flex: 1,
480+
justifyContent: 'center',
481+
alignItems: 'end',
482+
width: '100%'
483+
}}
484+
>
485+
<p style={{ fontSize: textFontSize, textAlign: 'right', margin: '0rem' }}>Preset:</p>
486+
</View>
487+
<View
488+
style={{
489+
flex: 1,
490+
justifyContent: 'center',
491+
alignItems: 'start',
492+
width: '100%'
493+
}}
494+
>
495+
<Button variant='secondary' disabled size='lg' style={{ marginLeft: '1rem' }}>
496+
{settingsPresets.current}
497+
</Button>
498+
</View>
446499
</View>
447500
<View
448501
style={{
449-
flex: 16,
502+
flex: 32,
450503
flexDirection: 'column',
451504
justifyContent: 'center',
452505
alignItems: 'center',
@@ -457,7 +510,7 @@ const BiteTransfer = (props) => {
457510
</View>
458511
<View
459512
style={{
460-
flex: 2,
513+
flex: 4,
461514
flexDirection: 'column',
462515
justifyContent: 'center',
463516
alignItems: 'center',

0 commit comments

Comments
 (0)