├── src ├── .eslintrc ├── components │ ├── ROS.test.js │ ├── ROSContext.js │ ├── examples │ │ ├── ToggleConnect.js │ │ ├── EchoTopic.js │ │ └── SimpleEcho.mdx │ └── ROS.js ├── index.js └── styles.module.css ├── .eslintignore ├── doczrc.js ├── .editorconfig ├── .prettierrc ├── .gitignore ├── .eslintrc.js ├── .github └── workflows │ ├── build.yml │ └── docz.yml ├── .eslintrc ├── README.md └── package.json /src/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "jest": true 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | build/ 2 | dist/ 3 | docs/ 4 | node_modules/ 5 | .snapshots/ 6 | *.min.js 7 | -------------------------------------------------------------------------------- /doczrc.js: -------------------------------------------------------------------------------- 1 | export default { 2 | dest: './docs/', 3 | base: '/react-ros/', 4 | title: 'react-ros', 5 | } 6 | -------------------------------------------------------------------------------- /src/components/ROS.test.js: -------------------------------------------------------------------------------- 1 | import { ROS } from './ROS' 2 | 3 | describe('ROS', () => { 4 | it('is truthy', () => { 5 | expect(ROS).toBeTruthy() 6 | }) 7 | }) 8 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import { ROS, useROS } from './components/ROS' 2 | export { ROS, useROS }; 3 | 4 | import { ROSContext, ROSProvider } from './components/ROSContext' 5 | export { ROSContext, ROSProvider } 6 | -------------------------------------------------------------------------------- /src/styles.module.css: -------------------------------------------------------------------------------- 1 | /* add css module styles here (optional) */ 2 | 3 | .test { 4 | margin: 2em; 5 | padding: 0.5em; 6 | border: 2px solid #000; 7 | font-size: 2em; 8 | text-align: center; 9 | } 10 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "jsxSingleQuote": true, 4 | "semi": false, 5 | "tabWidth": 2, 6 | "bracketSpacing": true, 7 | "jsxBracketSameLine": false, 8 | "arrowParens": "always", 9 | "trailingComma": "none" 10 | } 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # See https://help.github.com/ignore-files/ for more about ignoring files. 3 | 4 | # dependencies 5 | node_modules 6 | 7 | # builds 8 | build 9 | dist 10 | docs 11 | .docz 12 | .rpt2_cache 13 | 14 | # misc 15 | .DS_Store 16 | **log/ 17 | 18 | #.env 19 | .env.local 20 | .env.development.local 21 | .env.test.local 22 | .env.production.local 23 | .vscode 24 | 25 | npm-debug.log* 26 | yarn-debug.log* 27 | yarn-error.log* 28 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "env": { 3 | "browser": true, 4 | "es6": true 5 | }, 6 | "extends": [ 7 | "eslint:recommended", 8 | "plugin:react/recommended" 9 | ], 10 | "globals": { 11 | "Atomics": "readonly", 12 | "SharedArrayBuffer": "readonly" 13 | }, 14 | "parserOptions": { 15 | "ecmaFeatures": { 16 | "jsx": true 17 | }, 18 | "ecmaVersion": 2018, 19 | "sourceType": "module" 20 | }, 21 | "plugins": [ 22 | "react" 23 | ], 24 | "rules": { 25 | } 26 | }; -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: build & test 2 | 3 | on: 4 | push: 5 | branches-ignore: 6 | - main 7 | 8 | jobs: 9 | build: 10 | runs-on: ubuntu-latest 11 | strategy: 12 | matrix: 13 | node-version: [10.x, 12.x] 14 | steps: 15 | - name: Checkout code 16 | uses: actions/checkout@v2 17 | - name: Use Node.js ${{ matrix.node-version }} 18 | uses: actions/setup-node@v1 19 | with: 20 | node-version: ${{ matrix.node-version }} 21 | - run: npm install 22 | - run: npm run build --if-present 23 | - run: npm test 24 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "extends": [ 4 | "standard", 5 | "standard-react", 6 | "plugin:prettier/recommended", 7 | "prettier/standard", 8 | "prettier/react" 9 | ], 10 | "env": { 11 | "node": true 12 | }, 13 | "parserOptions": { 14 | "ecmaVersion": 2020, 15 | "ecmaFeatures": { 16 | "legacyDecorators": true, 17 | "jsx": true 18 | } 19 | }, 20 | "settings": { 21 | "react": { 22 | "version": "16" 23 | } 24 | }, 25 | "rules": { 26 | "space-before-function-paren": 0, 27 | "react/prop-types": 0, 28 | "react/jsx-handler-names": 0, 29 | "react/jsx-fragments": 0, 30 | "react/no-unused-prop-types": 0, 31 | "import/export": 0 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/components/ROSContext.js: -------------------------------------------------------------------------------- 1 | import React, { createContext, useState } from 'react' 2 | import PropTypes from 'prop-types' 3 | import ROSLIB from 'roslib' 4 | 5 | const rosObj = { 6 | ROS: new ROSLIB.Ros(), 7 | url: "ws://localhost:9090", 8 | isConnected: false, 9 | ROSConfirmedConnected: false, 10 | autoconnect: false, 11 | topics: [], 12 | services:[], 13 | listeners: [], 14 | } 15 | 16 | const ROSContext = createContext([{}, () => {}]); 17 | 18 | const ROSProvider = (props) => { 19 | const [ ros, setROS ] = useState(rosObj); 20 | return ( 21 | 22 | {props.children} 23 | 24 | ); 25 | } 26 | 27 | ROSProvider.propTypes = { 28 | children: PropTypes.node.isRequired, 29 | } 30 | 31 | export { ROSContext, ROSProvider }; 32 | -------------------------------------------------------------------------------- /.github/workflows/docz.yml: -------------------------------------------------------------------------------- 1 | name: Update Docs 2 | on: 3 | push: 4 | branches: main 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - name: Checkout repo 10 | uses: actions/checkout@v2 11 | - name: Setup Node.js 12 | uses: actions/setup-node@v1 13 | with: 14 | node-version: '12.x' 15 | - name: Setup Git 16 | run: | 17 | git config user.name $GITHUB_ACTOR 18 | git config user.email gh-actions-${GITHUB_ACTOR}@github.com 19 | git remote add gh-origin https://${GITHUB_ACTOR}:${GITHUB_TOKEN}@github.com/${GITHUB_REPOSITORY}.git 20 | env: 21 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 22 | - name: Install & Build the Docz 23 | run: npm run predeploy 24 | - name: Deploy Docz 25 | uses: JamesIves/github-pages-deploy-action@4.1.1 26 | with: 27 | branch: gh-pages 28 | folder: docs 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # react-ros 2 | Connect your website to your robot! 3 | 4 | [![NPM](https://img.shields.io/npm/v/react-ros.svg)](https://www.npmjs.com/package/react-ros) [![JavaScript Style Guide](https://img.shields.io/badge/code_style-standard-brightgreen.svg)](https://standardjs.com) 5 | 6 | ## Add react-ros to existing npm project 7 | 8 | ``` 9 | $ npm install react-ros 10 | ``` 11 | 12 | ## Install react-ros as standalone dev 13 | 14 | ``` 15 | $ npm install 16 | ``` 17 | 18 | ## Usage 19 | 20 | To make your own components that use ROS, simply import `useROS` and place it inside the `ROS` component that provides the ROS environment. 21 | 22 | We've provided a [few working examples](https://flynneva.github.io/react-ros/examples/simple_echo/) to help get you started. 23 | 24 | ## Special Thanks 25 | This project builds on the amazing work already done by people working on various projects. Here are a few that are the inspiration behind this component library: 26 | - [ROS 2](https://github.com/ros) 27 | - [roslibjs](https://github.com/RobotWebTools/roslibjs) 28 | - [rclnodejs](https://github.com/RobotWebTools/rclnodejs) 29 | - [webviz from GM Cruise](https://github.com/) 30 | - [jviz from Michael Johnson](https://github.com/MJohnson459/jviz) 31 | 32 | ## License 33 | 34 | MIT © [flynneva](https://github.com/flynneva) 35 | -------------------------------------------------------------------------------- /src/components/examples/ToggleConnect.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from 'react' 2 | import { useROS } from '../ROS' 3 | 4 | function ToggleConnect() { 5 | const { isConnected, topics, url, changeUrl, toggleConnection, toggleAutoconnect} = useROS(); 6 | 7 | // Try replacing this with your own unique rosbridge webserver address 8 | const defaultURL = "ws://0.0.0.0:9090"; 9 | 10 | // only runs once when ToggleConnect is first rendered (mounted) 11 | useEffect(() => { 12 | console.log('ToggleConnect is mounted!'); 13 | if (url !== defaultURL) { 14 | changeUrl(defaultURL); 15 | } 16 | 17 | if (!isConnected) { 18 | toggleAutoconnect(); 19 | } 20 | },[]) 21 | 22 | // runs every time there is an update to any state/rerender 23 | useEffect(() => { 24 | //console.log('rerender ToggleConnect'); 25 | }) 26 | 27 | return ( 28 |
29 |

30 | Simple connect:
31 | ROS url input: changeUrl(event.target.value)} />
32 | ROS url to connect to: {url}
33 | Status of ROS: { isConnected ? "connected" : "not connected" }
34 | Topics detected:
35 | { topics.map((topic, i) =>

  • {topic.path}
  • )} 36 |

    37 |
    38 | ); 39 | } 40 | 41 | export default ToggleConnect; 42 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-ros", 3 | "version": "0.10.0", 4 | "description": "A React Component Library to help connect your website to your robot", 5 | "author": "flynneva", 6 | "license": "MIT", 7 | "homepage": "https://flynneva.github.io/react-ros/readme", 8 | "repository": "https://github.com/flynneva/react-ros", 9 | "bugs": "https://github.com/flynneva/react-ros/issues", 10 | "main": "dist/index.js", 11 | "module": "dist/index.modern.js", 12 | "source": "src/index.js", 13 | "engines": { 14 | "node": ">=10" 15 | }, 16 | "scripts": { 17 | "build": "microbundle-crl --no-compress --format modern,cjs", 18 | "start": "microbundle-crl watch --no-compress --format modern,cjs", 19 | "prepare": "run-s build", 20 | "test": "run-s test:unit test:lint test:build", 21 | "test:build": "run-s build", 22 | "test:lint": "eslint .", 23 | "test:unit": "cross-env CI=1 react-scripts test --env=jsdom", 24 | "test:watch": "react-scripts test --env=jsdom", 25 | "predeploy": "npm install && npm run build && npm run docz:build", 26 | "deploy": "gh-pages -d docs/", 27 | "docz:dev": "docz dev", 28 | "docz:build": "docz build", 29 | "docz:serve": "docz build && docz serve" 30 | }, 31 | "peerDependencies": { 32 | "react": "^16.0.0" 33 | }, 34 | "devDependencies": { 35 | "cross-env": "^7.0.3", 36 | "eslint": "^6.8.0", 37 | "eslint-config-prettier": "^6.7.0", 38 | "eslint-config-standard": "^14.1.0", 39 | "eslint-config-standard-react": "^9.2.0", 40 | "eslint-plugin-import": "^2.24.1", 41 | "eslint-plugin-node": "^11.0.0", 42 | "eslint-plugin-prettier": "^3.4.1", 43 | "eslint-plugin-promise": "^4.3.1", 44 | "eslint-plugin-react": "^7.24.0", 45 | "eslint-plugin-standard": "^4.0.1", 46 | "gh-pages": "^2.2.0", 47 | "microbundle-crl": "^0.13.11", 48 | "npm-run-all": "^4.1.5", 49 | "prettier": "^2.3.2", 50 | "react": "^16.13.1", 51 | "react-dom": "^16.13.1", 52 | "react-scripts": "3.4.1", 53 | "uws": "^10.148.2" 54 | }, 55 | "files": [ 56 | "dist" 57 | ], 58 | "dependencies": { 59 | "acorn": "^8.4.1", 60 | "docz": "^2.3.1", 61 | "roslib": "^1.1.0" 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/components/examples/EchoTopic.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react' 2 | import { useROS } from '../ROS' 3 | 4 | var listener = null; 5 | 6 | function EchoTopic() { 7 | const { createListener, topics } = useROS(); 8 | const [ topic, setTopic ] = useState('/'); 9 | const [ queue, setQueue ] = useState(0); 10 | const [ compression, setCompression ] = useState('none'); 11 | 12 | useEffect(() => { 13 | handleTopic(topic); 14 | }); 15 | 16 | const unsubscribe = () => { 17 | if (listener) { 18 | console.log("Unsubscribing"); 19 | listener.unsubscribe(); 20 | } 21 | } 22 | 23 | const handleTopic = (topicInput) => { 24 | if (topic !== topicInput) { 25 | setTopic(topicInput); 26 | unsubscribe(); 27 | return; 28 | } 29 | 30 | unsubscribe(); 31 | listener = null; 32 | 33 | for (var i in topics) { 34 | if (topics[i].path == topicInput) { 35 | listener = createListener( topics[i].path, 36 | topics[i].msgType, 37 | Number(queue), 38 | compression); 39 | break; 40 | } 41 | } 42 | 43 | if (listener) { 44 | console.log("Subscribing to messages..."); 45 | listener.subscribe(handleMsg); 46 | } else { 47 | console.log("Topic '" + topic + "' not found...make sure to input the full topic path - including the leading '/'"); 48 | } 49 | } 50 | 51 | const handleQueue = (queueInput) => { 52 | setQueue(queueInput); 53 | } 54 | 55 | const handleCompression = (compInput) => { 56 | setCompression(compInput); 57 | } 58 | 59 | const handleMsg = (msg) => { 60 | console.log(msg); 61 | } 62 | 63 | return ( 64 |
    65 | Message Queue Length: handleQueue(event.target.value)} />
    66 | Compression: handleCompression(event.target.value)} />
    67 | Topic to echo: handleTopic(event.target.value)} />
    68 |
    69 | ); 70 | } 71 | 72 | export default EchoTopic; 73 | -------------------------------------------------------------------------------- /src/components/examples/SimpleEcho.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | name: Echo Topic 3 | route: /examples/simple_echo/ 4 | menu: Examples 5 | --- 6 | 7 | ## Getting Started 8 | 9 | Throughout these examples we will assume you've already gone through the process of installing [ROS](http://wiki.ros.org/noetic/Installation/Ubuntu) or [ROS2](https://index.ros.org/doc/ros2/Installation/Foxy/) and have a [ROS websocket server](https://github.com/RobotWebTools/rosbridge_suite) running and available to connect with. 10 | 11 | The first step in connecting ROS with a website will always be establishing a connection. With `react-ros`, we make this easy by providing a very simple and easy-to-use context provider called `useROS` to get you started. 12 | 13 | ## Connect to ROS 14 | 15 | Here is an example of how you can use the `useROS` component and connect your website to your robot! In this example we provide a `ToggleConnect` component that uses the `useROS` context provider to quickly and easily integrate ROS into any button or status indicator you could want to use. 16 | 17 | ```javascript 18 | import React from 'react' 19 | import { useROS } from '../ROS' 20 | 21 | function ToggleConnect() { 22 | const { isConnected, topics, url, changeUrl, toggleConnection } = useROS(); 23 | 24 | return ( 25 |
    26 |

    27 | Simple connect:
    28 | ROS url input: changeUrl(event.target.value)} />
    29 | ROS url to connect to: {url}
    30 | Status of ROS: { isConnected ? "connected" : "not connected" }
    31 | Topics detected:
    32 | { topics.map((topic, i) =>

  • {topic.path}
  • )} 33 |

    34 |
    35 | ); 36 | } 37 | 38 | export default ToggleConnect; 39 | ``` 40 | 41 | ## ToggleConnect in use 42 | To use the simple `ToggleConnect` component we made above, wrap it in the provided `ROS` context provider component from `react-ros` and everything should just work. Go ahead and try it live below on your robot! 43 | 44 | Here is a simple echo example that prints out messages on a specific topic to the console: 45 | 46 | ```javascript 47 | // EchoTopic.js 48 | import React, { useState, useEffect } from 'react' 49 | import { useROS } from 'react-ros' 50 | 51 | var listener = null; 52 | 53 | function EchoTopic() { 54 | const { createListener, topics } = useROS(); 55 | const [ topic, setTopic ] = useState('/'); 56 | const [ queue, setQueue ] = useState(0); 57 | const [ compression, setCompression ] = useState('none'); 58 | 59 | useEffect(() => { 60 | handleTopic(topic); 61 | }); 62 | 63 | const unsubscribe = () => { 64 | if (listener) { 65 | console.log("Unsubscribing"); 66 | listener.unsubscribe(); 67 | } 68 | } 69 | 70 | const handleTopic = (topicInput) => { 71 | if (topic !== topicInput) { 72 | setTopic(topicInput); 73 | unsubscribe(); 74 | return; 75 | } 76 | 77 | unsubscribe(); 78 | listener = null; 79 | 80 | for (var i in topics) { 81 | if (topics[i].path == topicInput) { 82 | listener = createListener( topics[i].path, 83 | topics[i].msgType, 84 | Number(queue), 85 | compression); 86 | break; 87 | } 88 | } 89 | 90 | if (listener) { 91 | console.log("Subscribing to messages..."); 92 | listener.subscribe(handleMsg); 93 | } else { 94 | console.log("Topic '" + topic + "' not found...make sure to input the full topic path - including the leading '/'"); 95 | } 96 | } 97 | 98 | const handleQueue = (queueInput) => { 99 | setQueue(queueInput); 100 | } 101 | 102 | const handleCompression = (compInput) => { 103 | setCompression(compInput); 104 | } 105 | 106 | const handleMsg = (msg) => { 107 | console.log(msg); 108 | } 109 | 110 | return ( 111 |
    112 | Message Queue Length: handleQueue(event.target.value)} />
    113 | Compression: handleCompression(event.target.value)} />
    114 | Topic to echo: handleTopic(event.target.value)} />
    115 |
    116 | ); 117 | } 118 | 119 | export default EchoTopic; 120 | ``` 121 | 122 | And then we use this EchoTopic component within the ROS context provider like below. 123 | 124 | **Note:** not shown in the code below is `import { ROS } from 'react-ros'` and `import EchoTopic from './EchoTopic'` at the top of the file. This is needed in order to use the `ROS` context provider component and the `EchoTopic` component. 125 | 126 | 127 | import { Playground } from 'docz' 128 | import { ROS } from '../ROS' 129 | 130 | import ToggleConnect from './ToggleConnect' 131 | import EchoTopic from './EchoTopic' 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | ## Subscribe to a topic 142 | 143 | Taking the toggle example even further - once connected, you should be able to subscribe to a topic and print out messages as they are received. 144 | 145 | -------------------------------------------------------------------------------- /src/components/ROS.js: -------------------------------------------------------------------------------- 1 | import React, { useContext, useEffect } from 'react' 2 | import ROSLIB from 'roslib' 3 | import { ROSContext, ROSProvider } from './ROSContext' 4 | import PropTypes from 'prop-types' 5 | 6 | // ROS Hook that lets others use ROS websocket connection 7 | // returns some useful functions & values 8 | function useROS() { 9 | const [ros, setROS] = useContext(ROSContext); 10 | 11 | useEffect(() => { 12 | if (!ros.isConnected) { 13 | if(ros.autoconnect) { 14 | console.log('autoconnecting'); 15 | handleConnect(); 16 | } 17 | } 18 | }) 19 | 20 | function checkConnection() { 21 | if (ros.ROS){ 22 | if (ros.isConnected) { 23 | if (!ros.ROSConfirmedConnected && ros.ROS.isConnected) { 24 | setROS(ros => ({ ...ros, ROSConfirmedConnected: ros.ROS.isConnected })) 25 | console.log("Both react-ros and roslibjs have confirmed connection.") 26 | } 27 | // Once we have that "confirmation" we need to continously check for good connection 28 | else if (ros.ROSConfirmedConnected && !ros.ROS.isConnected) { 29 | setROS(ros => ({ ...ros, isConnected: false })); 30 | handleDisconnect(); 31 | } 32 | else if (!ros.ROS.isConnected) { 33 | console.log("React-ros has confirmed the connection, roslibjs has not yet.") 34 | } 35 | } 36 | } 37 | else{ 38 | console.log("Initial connection not established yet") 39 | } 40 | } 41 | 42 | 43 | function toggleConnection() { 44 | if (ros.isConnected) { 45 | handleDisconnect(); 46 | } else if (!ros.isConnected) { 47 | handleConnect(); 48 | } 49 | } 50 | 51 | function toggleAutoconnect() { 52 | if (ros.autoconnect) { 53 | setROS(ros => ({ ...ros, autoconnect: false })); 54 | } else if (!ros.autoconnect) { 55 | setROS(ros => ({ ...ros, autoconnect: true })); 56 | } 57 | } 58 | 59 | function changeUrl(new_url) { 60 | setROS(ros => ({ ...ros, url: new_url })); 61 | } 62 | 63 | function getTopics() { 64 | const topicsPromise = new Promise((resolve, reject) => { 65 | ros.ROS.getTopics((topics) => { 66 | const topicList = topics.topics.map((topicName, i) => { 67 | return { 68 | path: topicName, 69 | msgType: topics.types[i], 70 | type: "topic", 71 | } 72 | }); 73 | resolve({ 74 | topics: topicList 75 | }); 76 | reject({ 77 | topics: [] 78 | }); 79 | }, (message) => { 80 | console.error("Failed to get topic", message) 81 | ros.topics = [] 82 | }); 83 | }); 84 | topicsPromise.then((topics) => setROS(ros => ({ ...ros, topics: topics.topics }))); 85 | return ros.topics; 86 | } 87 | 88 | function getServices() { 89 | const servicesPromise = new Promise((resolve, reject) => { 90 | ros.ROS.getServices((services) => { 91 | const serviceList = services.map((serviceName) => { 92 | return { 93 | path: serviceName, 94 | type: "service", 95 | } 96 | }); 97 | resolve({ 98 | services: serviceList 99 | }); 100 | reject({ 101 | services: [] 102 | }); 103 | }, (message) => { 104 | console.error("Failed to get services", message) 105 | ros.services = [] 106 | }); 107 | }); 108 | servicesPromise.then((services) => setROS(ros => ({ ...ros, services: services.services }))); 109 | return ros.services; 110 | } 111 | 112 | function createListener(topic, msg_type, to_queue, compression_type) { 113 | var newListener = new ROSLIB.Topic({ 114 | ros: ros.ROS, 115 | name: topic, 116 | messageType: msg_type, 117 | queue_length: to_queue, 118 | compression: compression_type, 119 | }) 120 | 121 | for (var listener in ros.listeners) { 122 | if (newListener.name === ros.listeners[listener].name) { 123 | console.log('Listener already available in ros.listeners[' + listener + ']'); 124 | return ros.listeners[listener]; 125 | } 126 | } 127 | ros.listeners.push(newListener); 128 | console.log('Listener ' + newListener.name + ' created'); 129 | return newListener; 130 | } 131 | 132 | const handleConnect = () => { 133 | try { 134 | ros.ROS.connect(ros.url) 135 | ros.ROS.on('connection', () => { 136 | // console.log(connect) 137 | setROS(ros => ({ ...ros, isConnected: true })); // seems to take awhile for the roslibjs library to report connected 138 | setROS(ros => ({ ...ros, ROSConfirmedConnected: false })); 139 | getTopics(); 140 | getServices(); 141 | }) 142 | 143 | ros.ROS.on('error', (error) => { //gets a little annoying on the console, but probably ok for now 144 | console.log(error); 145 | }) 146 | } catch (e) { 147 | console.log(e); 148 | } 149 | } 150 | 151 | const handleDisconnect = () => { 152 | try { 153 | ros.ROS.close(); 154 | setROS(ros => ({ ...ros, isConnected: false })); 155 | setROS(ros => ({ ...ros, topics: [] })); 156 | setROS(ros => ({ ...ros, listeners: [] })); 157 | setROS(ros => ({ ...ros, ROSConfirmedConnected: false })); 158 | } catch (e) { 159 | console.log(e); 160 | } 161 | console.log('Disconnected'); 162 | } 163 | 164 | const removeAllListeners = () => { 165 | for (var mlistener in ros.listeners) { 166 | ros.listeners[mlistener].removeAllListeners(); 167 | } 168 | setROS(ros => ({ ...ros, listeners: [] })); 169 | } 170 | 171 | function removeListener(listener) { 172 | for (var mlistener in ros.listeners) { 173 | if (listener.name === ros.listeners[mlistener].name) { 174 | console.log('Listener: ' + listener.name + ' is removed') 175 | ros.listeners.splice(mlistener, 1) 176 | listener.removeAllListeners(); 177 | return 178 | } 179 | } 180 | console.log('Listener: ' + listener + ' is not a listener') 181 | } 182 | 183 | return { 184 | toggleConnection, 185 | changeUrl, 186 | getTopics, 187 | getServices, 188 | createListener, 189 | toggleAutoconnect, 190 | removeAllListeners, 191 | removeListener, 192 | checkConnection, 193 | ros: ros.ROS, 194 | isConnected: ros.isConnected, 195 | autoconnect: ros.autoconnect, 196 | url: ros.url, 197 | topics: ros.topics, 198 | services: ros.services, 199 | listeners: ros.listeners, 200 | } 201 | } 202 | 203 | // ROS Component to manage ROS websocket connection and provide 204 | // it to children props 205 | function ROS(props) { 206 | return ( 207 | 208 | {props.children} 209 | 210 | ); 211 | } 212 | 213 | ROS.propTypes = { 214 | children: PropTypes.node.isRequired, 215 | } 216 | 217 | export { useROS, ROS }; 218 | --------------------------------------------------------------------------------