diff --git a/CHANGELOG.md b/CHANGELOG.md index cff5168..def8780 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # param_ext version history +## 0.0.03 + +- Fix issue with empty parameters + +## 0.0.2 + +- Alphabetize nodes + ## 0.0.1 - Alpha testing diff --git a/README.md b/README.md index b458597..1c5c0a7 100644 --- a/README.md +++ b/README.md @@ -2,10 +2,17 @@ ## _A Foxglove Studio Extension_ -The **ROS2 Parameters Extension** provides parameter functionality for all your ROS2 nodes including: +The **ROS2 Parameters Extension** provides parameter functionality for all your ROS2 nodes including: - **View** a node's parameter names, types, and values in a table format - **Set** new parameter values for all types of parameters on a node - **Load** a previous configuration from a .yml file stored on your computer -Currently only works with a rosbridge connection +This plugin relies on the `/rosapi/nodes` service to be available on the robot in order to build a list of running nodes. +This can either be done by making sure [rosbridge_server](https://github.com/RobotWebTools/rosbridge_suite/blob/ros2/rosbridge_server/launch/rosbridge_websocket_launch.xml) is running: + +```ros2 launch rosbridge_server rosbridge_websocket_launch.xml``` + +or solely the [rosapi node](https://github.com/RobotWebTools/rosbridge_suite/blob/ros2/rosapi/scripts/rosapi_node): + +```ros2 run rosapi rosapi_node``` diff --git a/package.json b/package.json index 58f7910..f0c7303 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "description": "Interact with Parameters in ROS2", "publisher": "Daniel Clapp", "homepage": "https://github.com/danclapp4/ros2-parameter-extension", - "version": "0.0.1", + "version": "0.0.3", "license": "MIT", "main": "./dist/extension.js", "keywords": [], diff --git a/src/ExamplePanel.tsx b/src/ExamplePanel.tsx index c06b484..4fc03d0 100644 --- a/src/ExamplePanel.tsx +++ b/src/ExamplePanel.tsx @@ -28,9 +28,9 @@ function ExamplePanel({ context }: { context: PanelExtensionContext }): JSX.Elem useLayoutEffect( () => { - context.onRender = (renderState: RenderState, done) => { + context.onRender = (renderState: RenderState, done) => { - setRenderDone(() => done); + setRenderDone(() => done); updateNodeList(); //Manage some styling for light and dark theme @@ -53,7 +53,7 @@ function ExamplePanel({ context }: { context: PanelExtensionContext }): JSX.Elem }, []); // invoke the done callback once the render is complete - useEffect(() => { + useEffect(() => { renderDone?.(); }, [renderDone]); @@ -72,7 +72,7 @@ function ExamplePanel({ context }: { context: PanelExtensionContext }): JSX.Elem /** * determines if a string[] contains exlusively booleans - * @param strArr string[] to check + * @param strArr string[] to check * @returns true if strArr only contains booleans, false otherwise */ const isBooleanArr = (strArr: string[]) => { @@ -114,13 +114,13 @@ function ExamplePanel({ context }: { context: PanelExtensionContext }): JSX.Elem const updateNodeList = () => { setStatus("retreiving nodes...") context.callService?.("/rosapi/nodes", {}) - .then((_values: unknown) =>{ - setNodeList((_values as any).nodes as string[]); - setStatus("nodes retreived"); + .then((_values: unknown) =>{ + setNodeList(((_values as any).nodes as string[]).sort()); + setStatus("nodes retreived"); }) .catch((_error: Error) => { setStatus(_error.toString()); }); } - + /** * Retrieves a list of all parameters for the current node and their values */ @@ -138,7 +138,7 @@ function ExamplePanel({ context }: { context: PanelExtensionContext }): JSX.Elem for (let i = 0; i < paramNameList.length; i++) { tempList.push({name: paramNameList[i]!, value: paramValList[i]!}); } - if(tempList.length > 0) + if(tempList.length > 0) setParamList(tempList); if(paramNameList !== undefined) { @@ -193,7 +193,7 @@ function ExamplePanel({ context }: { context: PanelExtensionContext }): JSX.Elem } /** - * Update the list of Parameters with new values to be set + * Update the list of Parameters with new values to be set * @param val The new value to be set * @param name The name of the parameter that will be set to 'val' */ @@ -206,28 +206,51 @@ function ExamplePanel({ context }: { context: PanelExtensionContext }): JSX.Elem const emptyP: SetSrvParam = {}; tempList[idx] = emptyP; } else { - let ssp: SetSrvParam = {}; + // Set default ssp to complete parameter structure (https://github.com/foxglove/ros-foxglove-bridge/issues/333) + let ssp: SetSrvParam = { + name: "", + value: { + type: 0, + bool_value: false, + integer_value: 0, + double_value: 0.0, + string_value: "", + byte_array_value: [], + bool_array_value: [], + integer_array_value: [], + double_array_value: [], + string_array_value: [], + }, + }; let valStrArr: string[] = []; switch (paramList![idx]?.value.type!) { - case 1: - ssp = { name: name, value: { type: 1, bool_value: stringToBoolean(val) }}; + case 1: + ssp.name = name; + ssp.value!.type = 1; + ssp.value!.bool_value = stringToBoolean(val); break; - case 2: - ssp = { name: name, value: { type: 2, integer_value: +val }}; + case 2: + ssp.name = name; + ssp.value!.type = 2; + ssp.value!.integer_value = +val; break; - case 3: - ssp = { name: name, value: { type: 3, double_value: +val }}; + case 3: + ssp.name = name; + ssp.value!.type = 3; + ssp.value!.double_value = +val; break; - case 4: - ssp = { name: name, value: { type: 4, string_value: val }}; + case 4: + ssp.name = name; + ssp.value!.type = 4; + ssp.value!.string_value = val; break; - + // TODO: Implement format for byte arrays - case 5: - //ssp = { name: name, value: { type: 5, byte_array_value: val as unknown as number[] }}; + case 5: + //ssp = { name: name, value: { type: 5, byte_array_value: val as unknown as number[] }}; break; case 6: @@ -238,26 +261,34 @@ function ExamplePanel({ context }: { context: PanelExtensionContext }): JSX.Elem return true; return false; }); - ssp = { name: name, value: { type: 6, bool_array_value: valBoolArr}}; + ssp.name = name; + ssp.value!.type = 6; + ssp.value!.bool_array_value = valBoolArr; } break; - case 7: - valStrArr = val.replace(" ", "").replace("[", "").replace("]", "").split(","); - ssp = { name: name, value: { type: 7, integer_array_value: valStrArr.map(Number) }}; + case 7: + valStrArr = val.replace(" ", "").replace("[", "").replace("]", "").split(","); + ssp.name = name; + ssp.value!.type = 7; + ssp.value!.integer_array_value = valStrArr.map(Number); break; case 8: valStrArr = val.replace(" ", "").replace("[", "").replace("]", "").split(","); - ssp = { name: name, value: { type: 8, double_array_value: valStrArr.map(Number) }}; + ssp.name = name; + ssp.value!.type = 8; + ssp.value!.double_array_value = valStrArr.map(Number); break; case 9: val.replace(" ", ""); - if(val.charAt(0) == '[' && val.charAt(val.length - 1) == ']') + if(val.charAt(0) == '[' && val.charAt(val.length - 1) == ']') val = val.substring(1, val.length - 1); valStrArr = val.split(","); - ssp = { name: name, value: { type: 9, string_array_value: valStrArr }}; + ssp.name = name; + ssp.value!.type = 9; + ssp.value!.string_array_value = valStrArr; break; default: ssp = {}; break; @@ -270,7 +301,7 @@ function ExamplePanel({ context }: { context: PanelExtensionContext }): JSX.Elem tempValList.push(getParameterValue(element)); }); } - + /** * Creates a dropdown input box if param is a boolean, creates a text input box otherwise * @param param The parameter that an input box is being created for @@ -290,7 +321,7 @@ function ExamplePanel({ context }: { context: PanelExtensionContext }): JSX.Elem ); } return( - { updateSrvParamList(param.name, event.target.value) }}/> + { updateSrvParamList(param.name, event.target.value) }}/> ); } @@ -299,10 +330,10 @@ function ExamplePanel({ context }: { context: PanelExtensionContext }): JSX.Elem * loads parameter values from a YAML file and sets all new values * @param files the YAML file to be uploaded */ - const loadFile = (files: FileList | null) => { + const loadFile = (files: FileList | null) => { if(files !== null) { files[0]?.text() - .then((value: string) => { + .then((value: string) => { value = value.replaceAll(/[^\S\r\n]/gi, ""); value = value.replace(node + ":\n", ""); value = value.replace("ros__parameters:\n", ""); @@ -435,7 +466,7 @@ function ExamplePanel({ context }: { context: PanelExtensionContext }): JSX.Elem borderRadius: "3px", }; - + inputStyle = { fontSize: "1rem", @@ -458,7 +489,7 @@ function ExamplePanel({ context }: { context: PanelExtensionContext }): JSX.Elem } const statusStyle = { - fontSize: "0.8rem", + fontSize: "0.8rem", padding: "5px", borderTop: "0.5px solid", } @@ -475,18 +506,18 @@ function ExamplePanel({ context }: { context: PanelExtensionContext }): JSX.Elem width: "100%" }; footerStyle; - + /////////////////////////////////////////////////////////////////// ///////////////////////// HTML PANEL ////////////////////////////// return (
-