From d2cfca027698b4ec7bec163e2d57ccc4d58f3824 Mon Sep 17 00:00:00 2001 From: Amal Nanavati Date: Mon, 19 Feb 2024 15:40:46 -0800 Subject: [PATCH] Upgrade to upstream rosbridge and roslibjs --- feedingwebapp/package-lock.json | 171 ++---------------- feedingwebapp/package.json | 2 +- feedingwebapp/src/Pages/Constants.js | 14 +- .../Pages/Home/MealStates/BiteSelection.jsx | 79 ++++---- .../src/Pages/Home/MealStates/RobotMotion.jsx | 78 ++++---- feedingwebapp/src/ros/ros_helpers.js | 48 ++--- 6 files changed, 134 insertions(+), 258 deletions(-) diff --git a/feedingwebapp/package-lock.json b/feedingwebapp/package-lock.json index 4cde9e55..130cad8b 100644 --- a/feedingwebapp/package-lock.json +++ b/feedingwebapp/package-lock.json @@ -36,7 +36,7 @@ "react-split-pane": "^0.1.92", "react-svg-path": "^1.14.0", "react-toastify": "^9.0.7", - "roslib": "github:personalrobotics/roslibjs", + "roslib": "github:RobotWebTools/roslibjs", "rosreact": "^0.2.0", "segfault-handler": "^1.3.0", "styled-components": "^5.3.9", @@ -5364,11 +5364,6 @@ "@sinonjs/commons": "^1.7.0" } }, - "node_modules/@socket.io/component-emitter": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz", - "integrity": "sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg==" - }, "node_modules/@surma/rollup-plugin-off-main-thread": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/@surma/rollup-plugin-off-main-thread/-/rollup-plugin-off-main-thread-2.2.3.tgz", @@ -5784,19 +5779,6 @@ "@types/node": "*" } }, - "node_modules/@types/cookie": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz", - "integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==" - }, - "node_modules/@types/cors": { - "version": "2.8.13", - "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.13.tgz", - "integrity": "sha512-RG8AStHlUiV5ysZQKq97copd2UmVYw3/pRMLefISZ3S1hK104Cwm7iLQ3fTKx+lsUH2CE8FlLaYeEA2LSeqYUA==", - "dependencies": { - "@types/node": "*" - } - }, "node_modules/@types/eslint": { "version": "8.37.0", "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.37.0.tgz", @@ -7228,14 +7210,6 @@ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, - "node_modules/base64id": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", - "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==", - "engines": { - "node": "^4.5.0 || >= 5.9" - } - }, "node_modules/batch": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", @@ -10233,11 +10207,6 @@ "node": ">= 0.6" } }, - "node_modules/eventemitter2": { - "version": "6.4.9", - "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-6.4.9.tgz", - "integrity": "sha512-JEPTiaOt9f04oa6NOkc4aH+nVp5I3wEjpHbIPqfgCdD5v5bUzy7xQqwcVO2aDQgOWhI28da57HksMrzK9HlRxg==" - }, "node_modules/eventemitter3": { "version": "4.0.7", "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", @@ -18408,134 +18377,36 @@ } }, "node_modules/roslib": { - "version": "1.3.0", - "resolved": "git+ssh://git@github.com/personalrobotics/roslibjs.git#af51b1550d4cc5241e19010ff2c4f980610f64b3", + "version": "1.4.1", + "resolved": "git+ssh://git@github.com/RobotWebTools/roslibjs.git#ac6620b8afd444f97a0ae0c6d9214521b0c79488", "license": "BSD-2-Clause", "dependencies": { "@xmldom/xmldom": "^0.8.0", "cbor-js": "^0.1.0", - "eventemitter2": "^6.4.0", + "eventemitter3": "^5.0.1", + "globals": "^14.0.0", "object-assign": "^4.0.0", "pngparse": "^2.0.0", - "socket.io": "^4.0.0", - "webworkify": "^1.5.0", - "webworkify-webpack": "^2.1.5", "ws": "^8.0.0" }, "engines": { "node": ">=0.10" } }, - "node_modules/roslib/node_modules/cookie": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", - "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/roslib/node_modules/engine.io": { - "version": "6.4.2", - "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.4.2.tgz", - "integrity": "sha512-FKn/3oMiJjrOEOeUub2WCox6JhxBXq/Zn3fZOMCBxKnNYtsdKjxhl7yR3fZhM9PV+rdE75SU5SYMc+2PGzo+Tg==", - "dependencies": { - "@types/cookie": "^0.4.1", - "@types/cors": "^2.8.12", - "@types/node": ">=10.0.0", - "accepts": "~1.3.4", - "base64id": "2.0.0", - "cookie": "~0.4.1", - "cors": "~2.8.5", - "debug": "~4.3.1", - "engine.io-parser": "~5.0.3", - "ws": "~8.11.0" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/roslib/node_modules/engine.io-parser": { - "version": "5.0.6", - "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.0.6.tgz", - "integrity": "sha512-tjuoZDMAdEhVnSFleYPCtdL2GXwVTGtNjoeJd9IhIG3C1xs9uwxqRNEu5WpnDZCaozwVlK/nuQhpodhXSIMaxw==", - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/roslib/node_modules/engine.io/node_modules/ws": { - "version": "8.11.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz", - "integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==", - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, - "node_modules/roslib/node_modules/socket.io": { - "version": "4.6.1", - "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.6.1.tgz", - "integrity": "sha512-KMcaAi4l/8+xEjkRICl6ak8ySoxsYG+gG6/XfRCPJPQ/haCRIJBTL4wIl8YCsmtaBovcAXGLOShyVWQ/FG8GZA==", - "dependencies": { - "accepts": "~1.3.4", - "base64id": "~2.0.0", - "debug": "~4.3.2", - "engine.io": "~6.4.1", - "socket.io-adapter": "~2.5.2", - "socket.io-parser": "~4.2.1" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/roslib/node_modules/socket.io-adapter": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.2.tgz", - "integrity": "sha512-87C3LO/NOMc+eMcpcxUBebGjkpMDkNBS9tf7KJqcDsmL936EChtVva71Dw2q4tQcuVC+hAUy4an2NO/sYXmwRA==", - "dependencies": { - "ws": "~8.11.0" - } + "node_modules/roslib/node_modules/eventemitter3": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", + "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==" }, - "node_modules/roslib/node_modules/socket.io-adapter/node_modules/ws": { - "version": "8.11.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz", - "integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==", + "node_modules/roslib/node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" + "node": ">=18" }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, - "node_modules/roslib/node_modules/socket.io-parser": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.2.tgz", - "integrity": "sha512-DJtziuKypFkMMHCm2uIshOYC7QaylbtzQwiMYDuCKy3OPkjLzu4B2vAhTlqipRHHzrI0NJeBAizTK7X+6m1jVw==", - "dependencies": { - "@socket.io/component-emitter": "~3.1.0", - "debug": "~4.3.1" - }, - "engines": { - "node": ">=10.0.0" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/roslib/node_modules/ws": { @@ -20669,16 +20540,6 @@ "node": ">=0.8.0" } }, - "node_modules/webworkify": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/webworkify/-/webworkify-1.5.0.tgz", - "integrity": "sha512-AMcUeyXAhbACL8S2hqqdqOLqvJ8ylmIbNwUIqQujRSouf4+eUFaXbG6F1Rbu+srlJMmxQWsiU7mOJi0nMBfM1g==" - }, - "node_modules/webworkify-webpack": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/webworkify-webpack/-/webworkify-webpack-2.1.5.tgz", - "integrity": "sha512-2akF8FIyUvbiBBdD+RoHpoTbHMQF2HwjcxfDvgztAX5YwbZNyrtfUMgvfgFVsgDhDPVTlkbb5vyasqDHfIDPQw==" - }, "node_modules/whatwg-encoding": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz", diff --git a/feedingwebapp/package.json b/feedingwebapp/package.json index 40abaa86..0c5c5883 100644 --- a/feedingwebapp/package.json +++ b/feedingwebapp/package.json @@ -31,7 +31,7 @@ "react-split-pane": "^0.1.92", "react-svg-path": "^1.14.0", "react-toastify": "^9.0.7", - "roslib": "github:personalrobotics/roslibjs", + "roslib": "github:RobotWebTools/roslibjs", "rosreact": "^0.2.0", "segfault-handler": "^1.3.0", "styled-components": "^5.3.9", diff --git a/feedingwebapp/src/Pages/Constants.js b/feedingwebapp/src/Pages/Constants.js index 3e237a49..4e2b5f2c 100644 --- a/feedingwebapp/src/Pages/Constants.js +++ b/feedingwebapp/src/Pages/Constants.js @@ -137,10 +137,12 @@ export const SEGMENTATION_STATUS_UNKNOWN = 99 /** * The meaning of ROS Action statuses. - * https://docs.ros2.org/latest/api/rclpy/api/actions.html#rclpy.action.server.GoalEvent + * https://github.com/ros2/rcl_interfaces/blob/humble/action_msgs/msg/GoalStatus.msg */ -export const ROS_ACTION_STATUS_EXECUTE = '1' -export const ROS_ACTION_STATUS_CANCEL_GOAL = '2' -export const ROS_ACTION_STATUS_SUCCEED = '3' -export const ROS_ACTION_STATUS_ABORT = '4' -export const ROS_ACTION_STATUS_CANCELED = '5' +export const ROS_ACTION_STATUS_UNKNOWN = 0 +export const ROS_ACTION_STATUS_ACCEPTED = 1 +export const ROS_ACTION_STATUS_EXECUTING = 2 +export const ROS_ACTION_STATUS_CANCELING = 3 +export const ROS_ACTION_STATUS_SUCCEED = 4 +export const ROS_ACTION_STATUS_CANCELED = 5 +export const ROS_ACTION_STATUS_ABORT = 6 diff --git a/feedingwebapp/src/Pages/Home/MealStates/BiteSelection.jsx b/feedingwebapp/src/Pages/Home/MealStates/BiteSelection.jsx index 5a3ec46a..306195b4 100644 --- a/feedingwebapp/src/Pages/Home/MealStates/BiteSelection.jsx +++ b/feedingwebapp/src/Pages/Home/MealStates/BiteSelection.jsx @@ -9,13 +9,13 @@ import PropTypes from 'prop-types' // Local Imports import '../Home.css' -import { useROS, createROSActionClient, callROSAction, destroyActionClient } from '../../../ros/ros_helpers' +import { useROS, createROSActionClient, callROSAction, cancelROSAction } from '../../../ros/ros_helpers' import { useWindowSize, convertRemToPixels } from '../../../helpers' import MaskButton from '../../../buttons/MaskButton' import { ROS_ACTIONS_NAMES, - ROS_ACTION_STATUS_CANCEL_GOAL, - ROS_ACTION_STATUS_EXECUTE, + ROS_ACTION_STATUS_CANCELING, + ROS_ACTION_STATUS_EXECUTING, ROS_ACTION_STATUS_SUCCEED, ROS_ACTION_STATUS_ABORT, ROS_ACTION_STATUS_CANCELED, @@ -33,6 +33,9 @@ import VideoFeed from '../VideoFeed' * simulatenously running the robot) or not */ const BiteSelection = (props) => { + // Create a local state variable to store goalID + const goalID = useRef(null) + // Get the relevant global variables const setMealState = useGlobalState((state) => state.setMealState) const setBiteAcquisitionActionGoal = useGlobalState((state) => state.setBiteAcquisitionActionGoal) @@ -61,7 +64,6 @@ const BiteSelection = (props) => { * image. */ const [actionResult, setActionResult] = useState(null) - const [numImageClicks, setNumImageClicks] = useState(0) /** * NOTE: We slightly abuse the ROS_ACTION_STATUS values in this local state * variable, by using it as a proxy for whether the robot is executing, has @@ -129,8 +131,22 @@ const BiteSelection = (props) => { const feedbackCallback = useCallback( (feedbackMsg) => { setActionStatus({ - actionStatus: ROS_ACTION_STATUS_EXECUTE, - feedback: feedbackMsg.values.feedback + actionStatus: ROS_ACTION_STATUS_EXECUTING, + feedback: feedbackMsg + }) + }, + [setActionStatus] + ) + + /** + * Callback function for when the action fails. It updates the actionStatus + * local state variable. + */ + const failureCallback = useCallback( + (result) => { + console.log('Action failed', result) + setActionStatus({ + actionStatus: ROS_ACTION_STATUS_ABORT }) }, [setActionStatus] @@ -148,30 +164,22 @@ const BiteSelection = (props) => { * action call (maybe they would refresh the page anyway, which might be ok). */ const responseCallback = useCallback( - (response) => { - if (response.response_type === 'result' && response.values.status === SEGMENTATION_STATUS_SUCCESS) { + (result) => { + if (result.status === ROS_ACTION_STATUS_SUCCEED && result.result.status === SEGMENTATION_STATUS_SUCCESS) { setActionStatus({ actionStatus: ROS_ACTION_STATUS_SUCCEED }) - console.log('Got result', response.values) - setActionResult(response.values) + console.log('Got result', result) + setActionResult(result.result) + } else if (result.status === ROS_ACTION_STATUS_CANCELING || result.status === ROS_ACTION_STATUS_CANCELED) { + setActionStatus({ + actionStatus: ROS_ACTION_STATUS_CANCELED + }) } else { - if ( - response.response_type === 'cancel' || - response.values === ROS_ACTION_STATUS_CANCEL_GOAL || - response.values === ROS_ACTION_STATUS_CANCELED - ) { - setActionStatus({ - actionStatus: ROS_ACTION_STATUS_CANCELED - }) - } else { - setActionStatus({ - actionStatus: ROS_ACTION_STATUS_ABORT - }) - } + failureCallback(result) } }, - [setActionStatus, setActionResult] + [setActionStatus, setActionResult, failureCallback] ) /** @@ -191,24 +199,20 @@ const BiteSelection = (props) => { const imageClicked = useCallback( (x_raw, y_raw) => { // Call the food segmentation ROS action - callROSAction( + goalID.current = callROSAction( segmentFromPointAction.current, { seed_point: { header: { stamp: { sec: 0, nanosec: 0 }, frame_id: 'image_frame' }, point: { x: x_raw, y: y_raw, z: 0.0 } } }, - /** - * Only register callbacks the first time to avoid multiple callbacks for - * the same action call. - */ - numImageClicks === 0 ? feedbackCallback : null, - numImageClicks === 0 ? responseCallback : null + feedbackCallback, + responseCallback, + failureCallback ) - setNumImageClicks(numImageClicks + 1) // The current implementation of food segmentation does not send feedback, // so we manually set the action status to executing. setActionStatus({ - actionStatus: ROS_ACTION_STATUS_EXECUTE + actionStatus: ROS_ACTION_STATUS_EXECUTING }) }, - [segmentFromPointAction, numImageClicks, feedbackCallback, responseCallback, setNumImageClicks, setActionStatus] + [segmentFromPointAction, feedbackCallback, responseCallback, failureCallback, setActionStatus, goalID] ) /** @@ -217,9 +221,10 @@ const BiteSelection = (props) => { useEffect(() => { let action = segmentFromPointAction.current return () => { - destroyActionClient(action) + console.log('Canceling goals due to useEffect cleanup') + cancelROSAction(action, goalID.current) } - }, [segmentFromPointAction]) + }, [segmentFromPointAction, goalID]) /** Get the continue button when debug mode is enabled * @@ -247,7 +252,7 @@ const BiteSelection = (props) => { */ const actionStatusText = useCallback(() => { switch (actionStatus.actionStatus) { - case ROS_ACTION_STATUS_EXECUTE: + case ROS_ACTION_STATUS_EXECUTING: if (actionStatus.feedback) { let elapsed_time = actionStatus.feedback.elapsed_time.sec + actionStatus.feedback.elapsed_time.nanosec / 10 ** 9 return 'Detecting food... ' + (Math.round(elapsed_time * 100) / 100).toString() + ' sec' diff --git a/feedingwebapp/src/Pages/Home/MealStates/RobotMotion.jsx b/feedingwebapp/src/Pages/Home/MealStates/RobotMotion.jsx index 607f0dd8..47033f96 100644 --- a/feedingwebapp/src/Pages/Home/MealStates/RobotMotion.jsx +++ b/feedingwebapp/src/Pages/Home/MealStates/RobotMotion.jsx @@ -11,7 +11,6 @@ import { createROSActionClient, callROSAction, cancelROSAction, - destroyActionClient, createROSService, createROSServiceRequest } from '../../../ros/ros_helpers' @@ -25,8 +24,8 @@ import { NON_RETRYABLE_STATES, ROS_ACTIONS_NAMES, MOTION_STATUS_SUCCESS, - ROS_ACTION_STATUS_CANCEL_GOAL, - ROS_ACTION_STATUS_EXECUTE, + ROS_ACTION_STATUS_CANCELING, + ROS_ACTION_STATUS_EXECUTING, ROS_ACTION_STATUS_SUCCEED, ROS_ACTION_STATUS_ABORT, ROS_ACTION_STATUS_CANCELED @@ -58,6 +57,7 @@ const RobotMotion = (props) => { const [actionStatus, setActionStatus] = useState({ actionStatus: null }) + const goalID = useRef(null) // Get the relevant global variables const paused = useGlobalState((state) => state.paused) @@ -111,8 +111,8 @@ const RobotMotion = (props) => { (feedbackMsg) => { console.log('Got feedback message', feedbackMsg) setActionStatus({ - actionStatus: ROS_ACTION_STATUS_EXECUTE, - feedback: feedbackMsg.values.feedback + actionStatus: ROS_ACTION_STATUS_EXECUTING, + feedback: feedbackMsg }) }, [setActionStatus] @@ -128,6 +128,23 @@ const RobotMotion = (props) => { setMealState(props.nextMealState) }, [props.nextMealState, props.setMealState]) + /** + * Callback function for when the action failed. It updates the actionStatus + * local state variable. + */ + const actionFailedCallback = useCallback( + (result) => { + console.log('Action failed', result) + setActionStatus({ + actionStatus: ROS_ACTION_STATUS_ABORT + }) + // In addition to displaying this error, we should also toggle the pause + // button to give the user options on what to do next. + setPaused(true) + }, + [setActionStatus, setPaused] + ) + /** * Callback function for when the action sends a response. It updates the * actionStatus local state variable and moves on to the next state if the @@ -140,34 +157,23 @@ const RobotMotion = (props) => { * action call (maybe they would refresh the page anyway, which might be ok). */ const responseCallback = useCallback( - (response) => { - console.log('Got response message', response) - if (response.response_type === 'result' && response.values.status === MOTION_STATUS_SUCCESS) { + (result) => { + console.log('Got result message', result) + if (result.status === ROS_ACTION_STATUS_SUCCEED && result.result.status === MOTION_STATUS_SUCCESS) { setActionStatus({ actionStatus: ROS_ACTION_STATUS_SUCCEED }) - setLastMotionActionResponse(response.values) + setLastMotionActionResponse(result.result) robotMotionDone() + } else if (result.status === ROS_ACTION_STATUS_CANCELING || result.status === ROS_ACTION_STATUS_CANCELED) { + setActionStatus({ + actionStatus: ROS_ACTION_STATUS_CANCELED + }) } else { - if ( - response.response_type === 'cancel' || - response.values === ROS_ACTION_STATUS_CANCEL_GOAL || - response.values === ROS_ACTION_STATUS_CANCELED - ) { - setActionStatus({ - actionStatus: ROS_ACTION_STATUS_CANCELED - }) - } else { - setActionStatus({ - actionStatus: ROS_ACTION_STATUS_ABORT - }) - // In addition to displaying this error, we should also toggle the - // pause button to give the user options on what to do next. - setPaused(true) - } + actionFailedCallback(result) } }, - [setLastMotionActionResponse, setActionStatus, setPaused, robotMotionDone] + [setLastMotionActionResponse, setActionStatus, robotMotionDone, actionFailedCallback] ) /** @@ -176,8 +182,8 @@ const RobotMotion = (props) => { */ const pauseCallback = useCallback(() => { setPaused(true) - cancelROSAction(robotMotionAction) - }, [robotMotionAction, setPaused]) + cancelROSAction(robotMotionAction, goalID.current) + }, [robotMotionAction, setPaused, goalID]) /** * Function to call the ROS action. Note that every time this function @@ -198,13 +204,13 @@ const RobotMotion = (props) => { (feedbackCb, responseCb) => { if (!paused) { setActionStatus({ - actionStatus: ROS_ACTION_STATUS_EXECUTE + actionStatus: ROS_ACTION_STATUS_EXECUTING }) console.log('Calling action with input', props.actionInput) - callROSAction(robotMotionAction, props.actionInput, feedbackCb, responseCb) + goalID.current = callROSAction(robotMotionAction, props.actionInput, feedbackCb, responseCb) } }, - [paused, robotMotionAction, props.actionInput] + [paused, robotMotionAction, props.actionInput, goalID] ) /** @@ -213,17 +219,17 @@ const RobotMotion = (props) => { * achieves this goal: https://stackoverflow.com/a/69264685 */ useEffect(() => { - callRobotMotionAction(feedbackCallback, responseCallback) + callRobotMotionAction(feedbackCallback, responseCallback, actionFailedCallback) /** * 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 () => { - console.log('Destroying action client') - destroyActionClient(robotMotionAction) + console.log('Canceling goals due to useEffect cleanup') + cancelROSAction(robotMotionAction, goalID.current) } - }, [callRobotMotionAction, robotMotionAction, feedbackCallback, responseCallback]) + }, [callRobotMotionAction, robotMotionAction, feedbackCallback, responseCallback, actionFailedCallback, goalID]) /** * Callback function for when the resume button is pressed. It calls the @@ -338,7 +344,7 @@ const RobotMotion = (props) => { let progress = null let retry = false switch (actionStatus.actionStatus) { - case ROS_ACTION_STATUS_EXECUTE: + case ROS_ACTION_STATUS_EXECUTING: if (actionStatus.feedback) { if (!actionStatus.feedback.is_planning) { let moving_elapsed_time = actionStatus.feedback.motion_time.sec + actionStatus.feedback.motion_time.nanosec / 10 ** 9 diff --git a/feedingwebapp/src/ros/ros_helpers.js b/feedingwebapp/src/ros/ros_helpers.js index 65c228e8..5ec32f44 100644 --- a/feedingwebapp/src/ros/ros_helpers.js +++ b/feedingwebapp/src/ros/ros_helpers.js @@ -115,6 +115,9 @@ export function createROSServiceRequest(data) { return new ROSLIB.ServiceRequest(data) } +// Cache for ROS Action Clients +let ACTION_CLIENT_CACHE = {} + /** * Create a ROS Action Client. * @@ -122,53 +125,52 @@ export function createROSServiceRequest(data) { * @param {string} serverName The name of the action server to call. * @param {string} actionType The type of the action the server takes. * - * @returns {object} The ROSLIB.ActionHandle, or null if ROS is not connected. + * @returns {object} The ROSLIB.ActionClient, or null if ROS is not connected. */ export function createROSActionClient(ros, serverName, actionType) { if (ros === null) { console.log('ROS is not connected') return null } - let actionClient = new ROSLIB.ActionHandle({ - ros: ros, - name: serverName, - actionType: actionType - }) - return actionClient + if (!(serverName in ACTION_CLIENT_CACHE)) { + let actionClient = new ROSLIB.Action({ + ros: ros, + name: serverName, + actionType: actionType + }) + ACTION_CLIENT_CACHE[serverName] = actionClient + } + return ACTION_CLIENT_CACHE[serverName] } /** * Calls a ROS Action. * - * @param {object} actionClient The ROSLIB.ActionHandle object. + * @param {object} actionClient The ROSLIB.ActionClient object. * @param {object} goal An object containing the exact attributes and types * expected by the ROS Action. * @param {function} feedbackCallback The callback function to call when * feedback is received. * @param {function} resultCallback The callback function to call when a * result is received. - */ -export function callROSAction(actionClient, goal, feedbackCallback, resultCallback) { - actionClient.createClient(goal, resultCallback, feedbackCallback) -} - -/** - * Cancels all the goals a ROS Action is currently executing. + * @param {function} failureCallback The callback function to call when the + * action fails. * - * @param {object} actionClient The ROSLIB.ActionHandle object. + * @returns {string} The goal_id of the action. */ -export function cancelROSAction(actionClient) { - actionClient.cancelGoal() +export function callROSAction(actionClient, goal, feedbackCallback, resultCallback, failureCallback) { + let goal_id = actionClient.sendGoal(goal, resultCallback, feedbackCallback, failureCallback) + return goal_id } /** - * Destroys an action client on the rosbridge end (e.g., so when the user returns - * to the same state, it doesn't create a second client for the same action.) + * Cancels all the goals a ROS Action is currently executing. * - * @param {object} actionClient The ROSLIB.ActionHandle object. + * @param {object} actionClient The ROSLIB.ActionClient object. */ -export function destroyActionClient(actionClient) { - actionClient.destroyClient() +export function cancelROSAction(actionClient, goal_id) { + console.log('Cancelling goal', goal_id) + actionClient.cancelGoal(goal_id) } /**