Skip to content

Commit

Permalink
Fixes through in-person testing
Browse files Browse the repository at this point in the history
  • Loading branch information
amalnanavati committed Apr 30, 2024
1 parent 9f22d7f commit d619691
Show file tree
Hide file tree
Showing 8 changed files with 141 additions and 80 deletions.
4 changes: 4 additions & 0 deletions feedingwebapp/src/Pages/Constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -196,3 +196,7 @@ export function getRobotMotionText(mealState) {
return 'Unknown meal state' + mealState.toString()
}
}

// Container IDs for multiple ToastContainers
export const REGULAR_CONTAINER_ID = 'RegularContainerID'
export const MODAL_CONTAINER_ID = 'ModalContainerID'
15 changes: 9 additions & 6 deletions feedingwebapp/src/Pages/Header/Header.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import 'react-toastify/dist/ReactToastify.css'
// ROS imports
import { useROS } from '../../ros/ros_helpers'
// Local imports
import { ROS_CHECK_INTERVAL_MS } from '../Constants'
import { REGULAR_CONTAINER_ID, ROS_CHECK_INTERVAL_MS } from '../Constants'
import { useGlobalState, APP_PAGE, NON_MOVING_STATES } from '../GlobalState'
import InfoModal from './InfoModal'

Expand All @@ -26,7 +26,7 @@ const Header = (props) => {
// TODO: Since this local state variable is in the header, the InfoModal
// continues showing even if the state changes. Is this desirable? Perhaps
// it should close if the state changes?
const [videoShow, setVideoShow] = useState(false)
const [infoModalShow, setInfoModalShow] = useState(false)
// useROS gives us access to functions to configure and interact with ROS.
let { ros } = useROS()
const [isConnected, setIsConncected] = useState(ros.isConnected)
Expand Down Expand Up @@ -69,7 +69,10 @@ const Header = (props) => {
if (inNonMovingState && NON_MOVING_STATES.has(mealState)) {
setAppPage(APP_PAGE.Settings)
} else {
toast('Wait for robot motion to complete before accessing Settings.')
toast.info('Wait for robot motion to complete before accessing Settings.', {
containerId: REGULAR_CONTAINER_ID,
toastId: 'noSettings'
})
}
}, [inNonMovingState, mealState, setAppPage])

Expand All @@ -80,7 +83,7 @@ const Header = (props) => {
* The ToastContainer is an alert that pops up on the top of the screen
* and has a timeout.
*/}
<ToastContainer style={{ fontSize: textFontSize }} />
<ToastContainer style={{ fontSize: textFontSize, zIndex: 9999 }} containerId={REGULAR_CONTAINER_ID} enableMultiContainer={true} />
{/**
* The NavBar has two elements, Home and Settings, on the left side and three
* elements, Lock, Robot Connection Icon and VideoVideo, on the right side.
Expand Down Expand Up @@ -152,7 +155,7 @@ const Header = (props) => {
)}
<Nav>
<Nav.Link
onClick={() => setVideoShow(true)}
onClick={() => setInfoModalShow(true)}
className='text-dark bg-info rounded mx-1 btn-lg btn-huge p-2'
style={{ fontSize: textFontSize }}
>
Expand All @@ -165,7 +168,7 @@ const Header = (props) => {
* The InfoModal toggles on and off with the Video button and shows the
* robot's live camera feed.
*/}
<InfoModal show={videoShow} onHide={() => setVideoShow(false)} webrtcURL={props.webrtcURL} />
<InfoModal show={infoModalShow} onHide={() => setInfoModalShow(false)} webrtcURL={props.webrtcURL} />
</>
)
}
Expand Down
31 changes: 22 additions & 9 deletions feedingwebapp/src/Pages/Header/InfoModal.jsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
// React imports
import React, { useCallback, useState } from 'react'
import React, { useCallback, useMemo, useState } from 'react'
import { useMediaQuery } from 'react-responsive'
import Modal from 'react-bootstrap/Modal'
import PropTypes from 'prop-types'
import { ToastContainer, toast } from 'react-toastify'
import { View } from 'react-native'

// Local imports
import { CAMERA_FEED_TOPIC } from '../Constants'
import { CAMERA_FEED_TOPIC, MODAL_CONTAINER_ID } from '../Constants'
import { useGlobalState } from '../GlobalState'
import TeleopSubcomponent from './TeleopSubcomponent'
import VideoFeed from '../Home/VideoFeed'
Expand All @@ -18,9 +18,9 @@ import ToggleButtonGroup from '../../buttons/ToggleButtonGroup'
*/
function InfoModal(props) {
// The three different modes of the info modal
const VIDEO_MODE = 'Video'
const TELEOP_MODE = 'Teleop'
const SYSTEM_STATUS_MODE = 'Status'
const VIDEO_MODE = useMemo(() => 'Video', [])
const TELEOP_MODE = useMemo(() => 'Teleop', [])
const SYSTEM_STATUS_MODE = useMemo(() => 'Status', [])
const [mode, setMode] = useState(VIDEO_MODE)

// Teleop don't allow changing to teleop mode if app is in a moving state
Expand All @@ -34,9 +34,12 @@ function InfoModal(props) {
if (inNonMovingState) {
setMode(TELEOP_MODE)
} else {
toast('Cannot switch to teleop until the app is on a non-moving page.')
toast.info('Cannot switch to teleop until the app is on a non-moving page.', {
containerId: MODAL_CONTAINER_ID,
toastId: 'noTeleop'
})
}
}, [inNonMovingState])
}, [inNonMovingState, TELEOP_MODE])

// Flag to check if the current orientation is portrait
const isPortrait = useMediaQuery({ query: '(orientation: portrait)' })
Expand All @@ -45,10 +48,20 @@ function InfoModal(props) {
// Text font size for portrait and landscape orientations
let textFontSize = isPortrait ? '3vh' : '6vh'

/**
* When the InfoModal is closed, switch to Video mode and call the parent-specified
* callback.
*/
const onModalHide = useCallback(() => {
setMode(VIDEO_MODE)
let onHide = props.onHide
onHide()
}, [props.onHide, setMode, VIDEO_MODE])

return (
<Modal
show={props.show}
onHide={props.onHide}
onHide={onModalHide}
size='lg'
aria-labelledby='contained-modal-title-vcenter'
backdrop='static'
Expand Down Expand Up @@ -78,7 +91,7 @@ function InfoModal(props) {
* The ToastContainer is an alert that pops up on the top of the screen
* and has a timeout.
*/}
<ToastContainer style={{ fontSize: textFontSize }} />
<ToastContainer style={{ fontSize: textFontSize }} containerId={MODAL_CONTAINER_ID} enableMultiContainer={true} />
<View
style={{
flex: 2,
Expand Down
76 changes: 45 additions & 31 deletions feedingwebapp/src/Pages/Header/TeleopSubcomponent.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,8 @@ const TeleopSubcomponent = (props) => {

// Style configuration
const isPortrait = useMediaQuery({ query: '(orientation: portrait)' })
let textFontSize = isPortrait ? '2.2vh' : '2.5vw'
let textFontSize = isPortrait ? 2.2 : 2.5
let sizeSuffix = isPortrait ? 'vh' : 'vw'
const buttonStyle = useMemo(() => {
return {
width: '100%',
Expand All @@ -67,7 +68,8 @@ const TeleopSubcomponent = (props) => {
// Callback for when the user changes the speed
const onSpeedChange = useCallback(
(_ev, data) => {
let value = data.value ? data.value : parseFloat(data.displayValue)
let value = data.value !== null ? data.value : parseFloat(data.displayValue)
console.log('teleop value', value, data)
let min_val =
teleopMode === CARTESIAN_LINEAR_MODE
? LINEAR_MIN_SPEED
Expand Down Expand Up @@ -135,12 +137,19 @@ const TeleopSubcomponent = (props) => {
}, [])

/**
* When the component is mounted, start servo. Stop it when the component is
* unmounted.
* When the component is mounted and when the refresh button is pressed, start servo.
*/
useEffect(() => {
console.log('Starting servo', refreshCount)
callROSAction(startServoAction, createROSMessage({}))
callROSAction(startServoAction, createROSMessage({}), null, () => {
console.log('Succesfully started servo.')
})
}, [refreshCount, startServoAction])

/**
* When the component is unmounted, stop servo.
*/
useEffect(() => {
let stopServoSuccessCallback = props.stopServoSuccessCallback
return () => {
console.log('Stopping servo.')
Expand All @@ -151,7 +160,7 @@ const TeleopSubcomponent = (props) => {
stopServoSuccessCallback.current()
})
}
}, [refreshCount, startServoAction, stopServoAction, props.stopServoSuccessCallback])
}, [startServoAction, stopServoAction, props.stopServoSuccessCallback])

/**
* Callback function to publish constant cartesian cartesian velocity commands.
Expand Down Expand Up @@ -190,14 +199,14 @@ const TeleopSubcomponent = (props) => {
* Callback function to publish a JointJog command to move a single joint.
*/
const publishJointJog = useCallback(
(joint, velocity) => {
(joints, velocities) => {
console.log('Publishing joint jog.')
let msg = createROSMessage({
header: {
frame_id: ROBOT_BASE_LINK
},
joint_names: [joint],
velocities: [teleopJointSpeed * velocity]
joint_names: joints,
velocities: velocities.map((velocity) => teleopJointSpeed * velocity)
})
jointTopic.publish(msg)
},
Expand Down Expand Up @@ -379,12 +388,21 @@ const TeleopSubcomponent = (props) => {
)
}, [])

/**
* Callback for when a teleop button gets released.
*/
const stopTeleop = useCallback(() => {
let teleopButtonOnReleaseCallback = props.teleopButtonOnReleaseCallback
publishCartesianVelocity(0.0, 0.0, 0.0, 0.0, 0.0, 0.0)
publishJointJog(ROBOT_JOINTS, [0.0, 0.0, 0.0, 0.0, 0.0, 0.0])
teleopButtonOnReleaseCallback()
}, [publishCartesianVelocity, publishJointJog, props.teleopButtonOnReleaseCallback])

/**
* Callback to get cartesian hold buttons
*/
const getCartesianHoldButton = useCallback(
(x, y, z, rx, ry, rz, text) => {
let teleopButtonOnReleaseCallback = props.teleopButtonOnReleaseCallback
return (
<HoldButton
rate_hz={10.0}
Expand All @@ -398,42 +416,35 @@ const TeleopSubcomponent = (props) => {
teleopAngularSpeed * rz
)
}}
cleanupCallback={() => {
publishCartesianVelocity(0.0, 0.0, 0.0, 0.0, 0.0, 0.0)
teleopButtonOnReleaseCallback()
}}
cleanupCallback={stopTeleop}
buttonStyle={buttonStyle}
>
{text}
</HoldButton>
)
},
[publishCartesianVelocity, buttonStyle, teleopLinearSpeed, teleopAngularSpeed, props.teleopButtonOnReleaseCallback]
[publishCartesianVelocity, buttonStyle, teleopLinearSpeed, teleopAngularSpeed, stopTeleop]
)

/**
* Callback to get joint hold buttons
*/
const getJointHoldButton = useCallback(
(joint, velocity, text) => {
let teleopButtonOnReleaseCallback = props.teleopButtonOnReleaseCallback
return (
<HoldButton
rate_hz={10.0}
holdCallback={() => {
publishJointJog(joint, velocity)
}}
cleanupCallback={() => {
publishCartesianVelocity(0.0, 0.0, 0.0, 0.0, 0.0, 0.0)
teleopButtonOnReleaseCallback()
publishJointJog([joint], [velocity])
}}
cleanupCallback={stopTeleop}
buttonStyle={buttonStyle}
>
{text}
</HoldButton>
)
},
[publishJointJog, buttonStyle, publishCartesianVelocity, props.teleopButtonOnReleaseCallback]
[publishJointJog, buttonStyle, stopTeleop]
)

/**
Expand All @@ -445,8 +456,8 @@ const TeleopSubcomponent = (props) => {
getCartesianHoldButton(0.0, 0.0, -1.0, 0.0, 0.0, 0.0, 'Down'),
getCartesianHoldButton(1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 'Left'),
getCartesianHoldButton(-1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 'Right'),
getCartesianHoldButton(0.0, -1.0, 0.0, 0.0, 0.0, 0.0, 'Forward'),
getCartesianHoldButton(0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 'Backward'),
getCartesianHoldButton(0.0, -1.0, 0.0, 0.0, 0.0, 0.0, 'Away From You'),
getCartesianHoldButton(0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 'Towards You'),
false
)
}, [trackpadArrangement, getCartesianHoldButton])
Expand All @@ -470,11 +481,14 @@ const TeleopSubcomponent = (props) => {
* Callback to render the joint teleop controls.
*/
const jointTeleop = useCallback(() => {
// Swap J1, J2, J4 so that positive is "towards the user" and negative is "away"
// for the above plate configuration
let jointMultipliers = [-1, -1, 1, -4, 1, 1]
let jointButtons = []
for (let i = 0; i < ROBOT_JOINTS.length; i++) {
jointButtons.push(
getJointHoldButton(ROBOT_JOINTS[i], 1.0, 'J' + (i + 1).toString() + '+'),
getJointHoldButton(ROBOT_JOINTS[i], -1.0, 'J' + (i + 1).toString() + '-')
getJointHoldButton(ROBOT_JOINTS[i], jointMultipliers[i] * 1.0, 'J' + (i + 1).toString() + '+'),
getJointHoldButton(ROBOT_JOINTS[i], jointMultipliers[i] * -1.0, 'J' + (i + 1).toString() + '-')
)
}
return arrayArrangement(ROBOT_JOINTS.length, 2, jointButtons, !isPortrait)
Expand All @@ -495,7 +509,7 @@ const TeleopSubcomponent = (props) => {
height: '100%'
}}
>
<p className='transitionMessage' style={{ marginBottom: '0px', fontSize: textFontSize }}>
<p className='transitionMessage' style={{ marginBottom: '0px', fontSize: textFontSize.toString() + sizeSuffix }}>
<input
name='teleopMode'
type='radio'
Expand All @@ -512,7 +526,7 @@ const TeleopSubcomponent = (props) => {
</View>
)
},
[teleopMode, setTeleopMode, textFontSize]
[teleopMode, setTeleopMode, textFontSize, sizeSuffix]
)
// Render the component
return (
Expand Down Expand Up @@ -564,7 +578,7 @@ const TeleopSubcomponent = (props) => {
className='mx-2 mb-2 btn-huge'
size='lg'
style={{
fontSize: textFontSize,
fontSize: (textFontSize * 1.0).toString() + sizeSuffix,
paddingTop: 0,
paddingBottom: 0,
margin: '0 !important'
Expand All @@ -576,7 +590,7 @@ const TeleopSubcomponent = (props) => {
<Label
htmlFor={speedSpinButtonID}
style={{
fontSize: textFontSize,
fontSize: textFontSize.toString() + sizeSuffix,
width: '90%',
color: 'black',
textAlign: 'center'
Expand All @@ -597,7 +611,7 @@ const TeleopSubcomponent = (props) => {
onChange={onSpeedChange}
appearance='filled-lighter'
style={{
fontSize: textFontSize,
fontSize: textFontSize.toString() + sizeSuffix,
width: '90%',
color: 'black'
}}
Expand Down
11 changes: 9 additions & 2 deletions feedingwebapp/src/Pages/Home/MealStates/BiteAcquisitionCheck.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
ACQUISITION_REPORT_SERVICE_TYPE,
FOOD_ON_FORK_DETECTION_TOPIC,
FOOD_ON_FORK_DETECTION_TOPIC_MSG,
REGULAR_CONTAINER_ID,
ROS_SERVICE_NAMES
} from '../../Constants'

Expand Down Expand Up @@ -74,7 +75,10 @@ const BiteAcquisitionCheck = () => {
const acquisitionSuccess = useCallback(() => {
console.log('acquisitionSuccess')
// NOTE: This uses the ToastContainer in Header
toast.info('Reporting Food Acquisition Success!')
toast.info('Reporting Food Acquisition Success!', {
containerId: REGULAR_CONTAINER_ID,
toastId: 'foodAcquisitionSuccess'
})
// Create a service request
let request = createROSServiceRequest({
loss: 0.0,
Expand All @@ -95,7 +99,10 @@ const BiteAcquisitionCheck = () => {
const acquisitionFailure = useCallback(() => {
console.log('acquisitionFailure')
// NOTE: This uses the ToastContainer in Header
toast.info('Reporting Food Acquisition Failure.')
toast.info('Reporting Food Acquisition Failure.', {
containerId: REGULAR_CONTAINER_ID,
toastId: 'foodAcquisitionFailure'
})
// Create a service request
let request = createROSServiceRequest({
loss: 1.0,
Expand Down
Loading

0 comments on commit d619691

Please sign in to comment.