├── .gitignore ├── LICENSE ├── README.md ├── dist ├── java_rosbridge.jar └── java_rosbridge_all.jar ├── pom.xml └── src ├── ros ├── Publisher.java ├── RosBridge.java ├── RosListenDelegate.java ├── SubscriptionRequestMsg.java ├── msgs │ ├── geometry_msgs │ │ ├── Twist.java │ │ └── Vector3.java │ ├── sensor_msgs │ │ └── Image.java │ └── std_msgs │ │ ├── Header.java │ │ ├── PrimitiveMsg.java │ │ └── Time.java └── tools │ ├── MessageUnpacker.java │ └── PeriodicPublisher.java └── tests ├── RosTest.java ├── SimpleEchoClient.java └── SimpleEchoSocket.java /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | 9 | This version of the GNU Lesser General Public License incorporates 10 | the terms and conditions of version 3 of the GNU General Public 11 | License, supplemented by the additional permissions listed below. 12 | 13 | 0. Additional Definitions. 14 | 15 | As used herein, "this License" refers to version 3 of the GNU Lesser 16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU 17 | General Public License. 18 | 19 | "The Library" refers to a covered work governed by this License, 20 | other than an Application or a Combined Work as defined below. 21 | 22 | An "Application" is any work that makes use of an interface provided 23 | by the Library, but which is not otherwise based on the Library. 24 | Defining a subclass of a class defined by the Library is deemed a mode 25 | of using an interface provided by the Library. 26 | 27 | A "Combined Work" is a work produced by combining or linking an 28 | Application with the Library. The particular version of the Library 29 | with which the Combined Work was made is also called the "Linked 30 | Version". 31 | 32 | The "Minimal Corresponding Source" for a Combined Work means the 33 | Corresponding Source for the Combined Work, excluding any source code 34 | for portions of the Combined Work that, considered in isolation, are 35 | based on the Application, and not on the Linked Version. 36 | 37 | The "Corresponding Application Code" for a Combined Work means the 38 | object code and/or source code for the Application, including any data 39 | and utility programs needed for reproducing the Combined Work from the 40 | Application, but excluding the System Libraries of the Combined Work. 41 | 42 | 1. Exception to Section 3 of the GNU GPL. 43 | 44 | You may convey a covered work under sections 3 and 4 of this License 45 | without being bound by section 3 of the GNU GPL. 46 | 47 | 2. Conveying Modified Versions. 48 | 49 | If you modify a copy of the Library, and, in your modifications, a 50 | facility refers to a function or data to be supplied by an Application 51 | that uses the facility (other than as an argument passed when the 52 | facility is invoked), then you may convey a copy of the modified 53 | version: 54 | 55 | a) under this License, provided that you make a good faith effort to 56 | ensure that, in the event an Application does not supply the 57 | function or data, the facility still operates, and performs 58 | whatever part of its purpose remains meaningful, or 59 | 60 | b) under the GNU GPL, with none of the additional permissions of 61 | this License applicable to that copy. 62 | 63 | 3. Object Code Incorporating Material from Library Header Files. 64 | 65 | The object code form of an Application may incorporate material from 66 | a header file that is part of the Library. You may convey such object 67 | code under terms of your choice, provided that, if the incorporated 68 | material is not limited to numerical parameters, data structure 69 | layouts and accessors, or small macros, inline functions and templates 70 | (ten or fewer lines in length), you do both of the following: 71 | 72 | a) Give prominent notice with each copy of the object code that the 73 | Library is used in it and that the Library and its use are 74 | covered by this License. 75 | 76 | b) Accompany the object code with a copy of the GNU GPL and this license 77 | document. 78 | 79 | 4. Combined Works. 80 | 81 | You may convey a Combined Work under terms of your choice that, 82 | taken together, effectively do not restrict modification of the 83 | portions of the Library contained in the Combined Work and reverse 84 | engineering for debugging such modifications, if you also do each of 85 | the following: 86 | 87 | a) Give prominent notice with each copy of the Combined Work that 88 | the Library is used in it and that the Library and its use are 89 | covered by this License. 90 | 91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license 92 | document. 93 | 94 | c) For a Combined Work that displays copyright notices during 95 | execution, include the copyright notice for the Library among 96 | these notices, as well as a reference directing the user to the 97 | copies of the GNU GPL and this license document. 98 | 99 | d) Do one of the following: 100 | 101 | 0) Convey the Minimal Corresponding Source under the terms of this 102 | License, and the Corresponding Application Code in a form 103 | suitable for, and under terms that permit, the user to 104 | recombine or relink the Application with a modified version of 105 | the Linked Version to produce a modified Combined Work, in the 106 | manner specified by section 6 of the GNU GPL for conveying 107 | Corresponding Source. 108 | 109 | 1) Use a suitable shared library mechanism for linking with the 110 | Library. A suitable mechanism is one that (a) uses at run time 111 | a copy of the Library already present on the user's computer 112 | system, and (b) will operate properly with a modified version 113 | of the Library that is interface-compatible with the Linked 114 | Version. 115 | 116 | e) Provide Installation Information, but only if you would otherwise 117 | be required to provide such information under section 6 of the 118 | GNU GPL, and only to the extent that such information is 119 | necessary to install and execute a modified version of the 120 | Combined Work produced by recombining or relinking the 121 | Application with a modified version of the Linked Version. (If 122 | you use option 4d0, the Installation Information must accompany 123 | the Minimal Corresponding Source and Corresponding Application 124 | Code. If you use option 4d1, you must provide the Installation 125 | Information in the manner specified by section 6 of the GNU GPL 126 | for conveying Corresponding Source.) 127 | 128 | 5. Combined Libraries. 129 | 130 | You may place library facilities that are a work based on the 131 | Library side by side in a single library together with other library 132 | facilities that are not Applications and are not covered by this 133 | License, and convey such a combined library under terms of your 134 | choice, if you do both of the following: 135 | 136 | a) Accompany the combined library with a copy of the same work based 137 | on the Library, uncombined with any other library facilities, 138 | conveyed under the terms of this License. 139 | 140 | b) Give prominent notice with the combined library that part of it 141 | is a work based on the Library, and explaining where to find the 142 | accompanying uncombined form of the same work. 143 | 144 | 6. Revised Versions of the GNU Lesser General Public License. 145 | 146 | The Free Software Foundation may publish revised and/or new versions 147 | of the GNU Lesser General Public License from time to time. Such new 148 | versions will be similar in spirit to the present version, but may 149 | differ in detail to address new problems or concerns. 150 | 151 | Each version is given a distinguishing version number. If the 152 | Library as you received it specifies that a certain numbered version 153 | of the GNU Lesser General Public License "or any later version" 154 | applies to it, you have the option of following the terms and 155 | conditions either of that published version or of any later version 156 | published by the Free Software Foundation. If the Library as you 157 | received it does not specify a version number of the GNU Lesser 158 | General Public License, you may choose any version of the GNU Lesser 159 | General Public License ever published by the Free Software Foundation. 160 | 161 | If the Library as you received it specifies that a proxy can decide 162 | whether future versions of the GNU Lesser General Public License shall 163 | apply, that proxy's public statement of acceptance of any version is 164 | permanent authorization for you to choose that version for the 165 | Library. 166 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | java_rosbridge 2 | ============== 3 | 4 | A simple library using Jetty 9 to connect Java code to a [ROS Bridge server](http://wiki.ros.org/rosbridge_suite/). This library supports publishing and subscribing with different topic delegates and makes using Java code with ROS very trivial (we argue that it is easier than using ROSJava which has a number of complications and complexities). 5 | 6 | ##Linking 7 | java_rosbridge is indexed on Maven Central, so if you want to merely use it, all you need to do is include in the `` section of your project's pom.xml file: 8 | ``` 9 | 10 | edu.brown.cs.burlap 11 | java_rosbridge 12 | 2.0.1 13 | 14 | ``` 15 | and it will automatically be downloaded. Alternatively, you may compile and install the code directly (or modify as needed), as described in the compiling section of this readme. 16 | 17 | ## Compiling 18 | 19 | Compiling and usage is now performed with Maven. If you would like to compile with ant, use the ant branch of this repo. However, going forward, updated version of java_rosbridge will require Maven. 20 | 21 | If you do not have Maven installed on your system, get it from https://maven.apache.org/download.cgi 22 | 23 | Compile with: 24 | 25 | ``` 26 | mvn compile 27 | ``` 28 | 29 | Create the target jar and Java doc with 30 | 31 | ``` 32 | mvn package 33 | ``` 34 | 35 | Install into your local repo with 36 | 37 | ``` 38 | mvn install 39 | ``` 40 | 41 | Have other projects use java_rosbridge by adding the following to the projects pom.xml `` section: 42 | 43 | ``` 44 | 45 | edu.brown.cs.burlap 46 | java_rosbridge 47 | 2.0.1 48 | 49 | ``` 50 | 51 | ## Using the code 52 | 53 | Here we will provide a description of the library code, but you should consult the Java doc for more detailed information. 54 | 55 | ### Websockets test code 56 | 57 | The first thing to note is the `tests` package shows some very basic test code that you can run. The `SimpleEchoClient` class runs a very simple example of websockets running using Jetty; it does not have anything specifically to do with ROS and is a way of sanity checking the websocket code. It makes reference to the `SimpleEchoSocket` class for handling the communication. All this code was provided by the Jetty tutorial. The `echo.websocket.org` server to which the code connects is a simple websocket server for testing websockets (also specified for use in the Jetty tutorial). When you run it, you should get a long connect debug message printed to the terminal and the following which are the messages received from the `echo.websocket.org` server: 58 | 59 | ``` 60 | Got msg: Hello 61 | Got msg: Thanks for the conversation. 62 | Connection closed: 1000 - I'm done 63 | ``` 64 | 65 | 66 | ### ROS Bridge test code 67 | 68 | The `RosTest` code in the `tests` package provides a demonstration of connecting to ROS Bridge and publishing the string `hello from java` to the ROS topic `/java_to_ros` every 500 ms. The code will also subscribe to the `std_msgs/String` topic `/ros_to_java`. For convenience, we have also posted the code below in this readme. 69 | 70 | ``` 71 | import com.fasterxml.jackson.databind.JsonNode; 72 | import ros.Publisher; 73 | import ros.RosBridge; 74 | import ros.RosListenDelegate; 75 | import ros.msgs.std_msgs.PrimitiveMsg; 76 | import ros.tools.MessageUnpacker; 77 | 78 | /** 79 | * Example of connecting to rosbridge with publish/subscribe messages. Takes one argument: 80 | * the rosbridge websocket URI; for example: ws://localhost:9090. 81 | * @author James MacGlashan. 82 | */ 83 | public class RosTest { 84 | 85 | public static void main(String[] args) { 86 | 87 | if(args.length != 1){ 88 | System.out.println("Need the rosbridge websocket URI provided as argument. For example:\n\tws://localhost:9090"); 89 | System.exit(0); 90 | } 91 | 92 | RosBridge bridge = new RosBridge(); 93 | bridge.connect(args[0], true); 94 | 95 | bridge.subscribe(SubscriptionRequestMsg.generate("/ros_to_java") 96 | .setType("std_msgs/String") 97 | .setThrottleRate(1) 98 | .setQueueLength(1), 99 | new RosListenDelegate() { 100 | @Override 101 | public void receive(JsonNode data, String stringRep) { 102 | MessageUnpacker> unpacker = new MessageUnpacker>(PrimitiveMsg.class); 103 | PrimitiveMsg msg = unpacker.unpackRosMessage(data); 104 | System.out.println(msg.data); 105 | } 106 | } 107 | ); 108 | 109 | 110 | 111 | Publisher pub = new Publisher("/java_to_ros", "std_msgs/String", bridge); 112 | 113 | for(int i = 0; i < 100; i++) { 114 | pub.publish(new PrimitiveMsg("hello from java " + i)); 115 | try { 116 | Thread.sleep(500); 117 | } catch (InterruptedException e) { 118 | e.printStackTrace(); 119 | } 120 | } 121 | } 122 | 123 | } 124 | ``` 125 | 126 | 127 | This code takes as a command line argument the URI to the ROS Bridge server. Note that ROS Bridge by default runs on port 9090. An example URI is `ws://localhost:9090`. Obviously, you should launch ROS Bridge on the server before running this example code. (That is, before running the `RosTest` class, run `roslaunch rosbridge_server rosbridge_websocket.launch` on the ROS server. For more information on getting and starting ROS Bridge on your ROS server, see [here](http://wiki.ros.org/rosbridge_suite/Tutorials/RunningRosbridge).) To observe the Java publishing over ROS Bridge on the actual ROS Bridge server, run the ROS command 128 | 129 | ``` 130 | rostopic echo /java_to_ros 131 | ``` 132 | on the ROS server. You should find it outputting the published message `hello from java i` where `i` is an int representing which publish it is. 133 | 134 | To observe the subscrption code in action, on ROS you will want to make sure you're publishing a `std_msgs/String` message to the topic `ros_to_java`. To do that, try using the ROS command 135 | ``` 136 | rostopic pub ros_to_java std_msgs/String "hello from ros" 137 | ``` 138 | 139 | If you do so, then when you run the Java code, you will see that message printed to the terminal. 140 | 141 | 142 | ### Primary Library Classes 143 | The primary library classes consist of `RosBridge` for setting up the connection to ROS Bridge, `Publisher` for publishing messages to ROS over ROS Bridge, and `RosListenDelegate` for managing call backs when you subscribe to a ROS topic. 144 | 145 | ####RosBridge 146 | As the `RosTest` code illustrates, the main Java object you work with is an instance of the `RosBridge` class that is defined in the `ros` package. `RosBridge` objects are created using the static method `createConnection(String rosBridgeURI)` (which takes the URI of the ROS Bridge server). Note that after creating a connection it is a good idea to call the `waitForConnection()` method, which will block the client thread until the connection with the ROS Bridge server has been established. A client Java object interacts with a `RosBridge` object with publish commands and subscription requests, each of which are discussed below. 147 | 148 | ####Publish 149 | Publish commands can be given with the `publish(String topic, String type, Object msg)` method of the `RosBridge` class, which automatically advertises the publish topic if it has not yet already been advertised. A more streamlined and ROS-like way to handle publishing (and what is demonstrated in the `RosTest` code) is to create a `Publisher` object instance (also defined in the `ros` package), which maintains the topic name and ROS topic message type so that you only need to pass its `publish(Object)` method the message you wish to publish. 150 | 151 | The message object you pass the publish method can either be a [Java Bean](https://en.wikipedia.org/wiki/JavaBeans) whose data members correspond to the ROS message structure, or a Java Map data structure whose keys are strings representing the names of the ROS message fields and values are the corresponding data type or another nested Map. (Note that a [Java Bean](https://en.wikipedia.org/wiki/JavaBeans) is a Java object with a default constructor and either public data members or data members that have setter and getter methods following standard Java method name conventions.) 152 | 153 | `java_rosbridge` includes some Java Bean data structures for common ROS messages in the `msgs` package. For example, the `PrimitiveMsg` is a Java Bean that can be used for many of the primitive ROS message types found in the `std_msgs` ROS package by setting its generic type to the appropriate primitive. For example, `PrimitiveMsg` can be used for the `std_msgs/String` message. Alternatively, if you wanted to recreate the `std_msgs/String` message with a Java Map, the Map should contain one key-value entry of `data: stringValue`, where `stringValue` is whatever the ROS `std_msgs/String` data field value is. 154 | 155 | ####Subcribe 156 | You can subscribe to ROS topics via the `subscribe(SubscriptionRequestMsg request, RosListenDelegate delegate)` method (other variants of this method also exist). The SubscriptionRequestMsg object is a class for easily specifying the various optional fields you wish to include in the subscription method to manage the network details. The optional fields are message type, throttle_rate, queue_length, and fragment_size. This library currently does not support png compression. The other method argument is a `RosListenDelegate` (an interface defined in the `ros` package) which is a callback object that is passed the data sent from ROS Bridge for the specific topic to which you subscribed. You will need to implement your own `RosListenDelegate` object to process the received data, similar to how in normal Python ROS you implement a callback function to process subscriptions. 157 | 158 | **Recommendation**: if you are subscribing to a high frequency topic, you should set the subcribed `throttleRate` and `queueLength` to 1 (or some other small values), otherwise your received data will increasingly lag behind the current real time values. 159 | 160 | ### Implementing a RosListenDelegate 161 | 162 | A `RosListenDelegate` is an interface to define the callback function for subscribed topic messages sent over ROSBridge. It requires that you implement the method `receive(JsonNode data, String stringRep)`. The two parameters of this message present the ROSBridge message in two different formats. The latter, `stringRep`, is the string representation of the ROSBridge message which allows you to do anything you want with the raw data. The former, `data`, is a [JsonNode](http://fasterxml.github.io/jackson-databind/javadoc/2.2.0/com/fasterxml/jackson/databind/JsonNode.html) of the data sent over ROSBridge. `data` has four top-level JSON fields: 163 | 164 | - `op`: which kind of messag operation it was; should always be "publish" (ROSBridge is passing a published message) 165 | - `topic`: to which topic the message was published 166 | - `type`: the ROS message type of the topic 167 | - `msg`: the provided ros message in JSON format 168 | 169 | The `msg` field is the primary field of interest since it contains the actual ROS message. The simplest way to parse a ROS message is to unpack it into a corresponding Java Bean for the ROS message type. The `MessageUnpacker` class can help you do that. As shown in the example code, a `MessageUnpacker` takes a generic specifying the type of [Java Bean](https://en.wikipedia.org/wiki/JavaBeans) into which you're going to unpack the message and its constructor also requries you to give the Java .class reference. Then you can use its `unpackRosMessage(JsonNode)` method, providing it the `JsonNode` given to the `RosListenDelegate` `receive` method, as shown in the example code. (Note that you do not need to unpack the msg field first, the `MessageUnpacker` method will automatically do that for you.) 170 | 171 | Alternatively, if you do not have a Java Bean class into which you can trivially unpack the ROS message, you can also iterate through the fields of the `JsonNode` manually. Working with JSON data using the `JsonNode` data structure is very easy because it has getter methods for JSON fields and elements in JSON arrays. For example, if the ROS message is a `geometry_msgs/Twist.msg` message, and you want the linear x component, you can retreive it with the code: 172 | 173 | `double x = data.get("msg").get("linear").get("x").asDouble();`. 174 | 175 | If a field's value is an array, it still is returned as a `JsonNode`; however, `JsonNode` has convenient methods for working with it. To get the size of the array use `node.size()` where `node` is the current `JsonNode` element you're using. To get an element within it (also returned as a `JsonNode` for recrusion of non-primitives), use `node.get(i)`, where `i` is the index into the array. See the [JsonNode Java documentation](http://fasterxml.github.io/jackson-databind/javadoc/2.2.0/com/fasterxml/jackson/databind/JsonNode.html) for more information on working with a `JsonNode`. 176 | 177 | 178 | ### Large Message Sizes 179 | 180 | Some messages you may wish to recieve/send from/to ROS are very large, such as video feeds and they may be larger than the default buffer size that Jetty's websocket uses. One solution to this limitiation is to use fragementation in your subscription request. However, you may also increase your web socket message size buffer so that you do not need to use fragementation. Since we use Jetty for managing the websocket network, increasing the web socket buffer size is accomplished by subclassing the RosBridge object and annotating it with `@Websocket` with a larger parameter value for the message size. The subclass does not need to override or implement any methods; we extend the class purely to annotate it with a custom web socket buffer size. For example: 181 | 182 | ``` 183 | @WebSocket(maxTextMessageSize = 500 * 1024) public class BigMessageRosBridge extends RosBridge{} 184 | ``` 185 | 186 | Then you should instantiate and use your subclass instead of the top-level RosBridge. 187 | -------------------------------------------------------------------------------- /dist/java_rosbridge.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/h2r/java_rosbridge/e4186f06285770595185af50d4547cf0e767ba68/dist/java_rosbridge.jar -------------------------------------------------------------------------------- /dist/java_rosbridge_all.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/h2r/java_rosbridge/e4186f06285770595185af50d4547cf0e767ba68/dist/java_rosbridge_all.jar -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | 5 | edu.brown.cs.burlap 6 | java_rosbridge 7 | 2.0.2 8 | jar 9 | 10 | java_rosbridge 11 | 12 | This is a library for subscribing/publishing to topics on ros through ros_bridge. It contains both high-level and low-level message control. The websocket server is implement with Jetty. 13 | 14 | 15 | https://github.com/h2r/java_rosbridge 16 | 17 | 18 | 19 | GNU LGPL Version 3.0 20 | http://www.gnu.org/licenses/lgpl-3.0.en.html 21 | 22 | 23 | 24 | 25 | 26 | James MacGlashan 27 | jmacglashan@gmail.com 28 | Brown University 29 | http://www.brown.edu 30 | 31 | 32 | 33 | 34 | scm:git:git@github.com:h2r/java_rosbridge 35 | scm:git:git@github.com:h2r/java_rosbridge 36 | git@github.com:h2r/java_rosbridge 37 | 38 | 39 | 40 | 41 | 42 | 43 | org.eclipse.jetty.websocket 44 | websocket-server 45 | 9.2.6.v20141205 46 | 47 | 48 | 49 | 50 | com.fasterxml.jackson.core 51 | jackson-annotations 52 | 2.2.3 53 | 54 | 55 | 56 | com.fasterxml.jackson.core 57 | jackson-core 58 | 2.2.3 59 | 60 | 61 | 62 | com.fasterxml.jackson.core 63 | jackson-databind 64 | 2.2.3 65 | 66 | 67 | 68 | 69 | 70 | 71 | ossrh 72 | https://oss.sonatype.org/content/repositories/snapshots 73 | 74 | 75 | ossrh 76 | https://oss.sonatype.org/service/local/staging/deploy/maven2/ 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | org.apache.maven.plugins 85 | maven-source-plugin 86 | 2.2.1 87 | 88 | 89 | attach-sources 90 | 91 | jar-no-fork 92 | 93 | 94 | 95 | 96 | 97 | 98 | org.apache.maven.plugins 99 | maven-javadoc-plugin 100 | 2.9.1 101 | 102 | 103 | attach-javadocs 104 | 105 | jar 106 | 107 | 108 | 109 | 110 | 111 | 112 | org.codehaus.mojo 113 | build-helper-maven-plugin 114 | 1.7 115 | 116 | 117 | add-source 118 | generate-sources 119 | 120 | add-source 121 | 122 | 123 | 124 | src/ 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | org.apache.maven.plugins 133 | maven-assembly-plugin 134 | 2.2-beta-4 135 | 136 | 137 | jar-with-dependencies 138 | 139 | 140 | 141 | 142 | package 143 | 144 | single 145 | 146 | 147 | 148 | 149 | 150 | 151 | org.sonatype.plugins 152 | nexus-staging-maven-plugin 153 | 1.6.3 154 | true 155 | 156 | ossrh 157 | https://oss.sonatype.org/ 158 | true 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | release-sign-artifacts 171 | 172 | 173 | performRelease 174 | true 175 | 176 | 177 | 178 | 179 | 180 | org.apache.maven.plugins 181 | maven-gpg-plugin 182 | 1.5 183 | 184 | 185 | sign-artifacts 186 | verify 187 | 188 | sign 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | -------------------------------------------------------------------------------- /src/ros/Publisher.java: -------------------------------------------------------------------------------- 1 | package ros; 2 | 3 | /** 4 | * A wrapper class for streamlining ROS Topic publishing. Note that the {@link #advertise()} never *needs* to be explicitly 5 | * called. If you use the standard {@link #Publisher(String, String, RosBridge)} method, it will be automatically 6 | * called on construction and if you use the {@link #Publisher(String, String, RosBridge, boolean)} method 7 | * and set the advertiseNow flag to false, you still don't *need* to call it, because the first publish will 8 | * automatically make sure the topic has been advertised first. 9 | *

10 | * Publish messages using the {@link #publish(Object)} method. It takes an object containing the ROS message 11 | * to publish. Generally, the msg should either be a Javabean, such as one of the pre-included 12 | * messages in the {@link ros.msgs} package that has the same field structure as the target topic type 13 | * or a {@link java.util.Map} object 14 | * representing the ROS message type structure. For 15 | * example, if the ROS message type is "std_msgs/String" then msg should be a {@link ros.msgs.std_msgs.PrimitiveMsg} 16 | * with the generic of String, or a Map object 17 | * with one Map key-value entry of "data: stringValue" where stringValue is whatever the "std_msgs/String" 18 | * data field value is. 19 | * @author James MacGlashan. 20 | */ 21 | public class Publisher { 22 | 23 | protected String topic; 24 | protected String msgType; 25 | protected RosBridge rosBridge; 26 | 27 | 28 | /** 29 | * Constructs and automatically advertises publishing to this topic. 30 | * @param topic the topic to which messages will be published 31 | * @param msgType the ROS message type of the topic 32 | * @param rosBridge the {@link ros.RosBridge} that manages ROS Bridge interactions. 33 | */ 34 | public Publisher(String topic, String msgType, RosBridge rosBridge){ 35 | this.topic = topic; 36 | this.msgType = msgType; 37 | this.rosBridge = rosBridge; 38 | 39 | this.rosBridge.advertise(this.topic, this.msgType); 40 | 41 | } 42 | 43 | 44 | /** 45 | * Constructs and advertises if the advertiseNow flag is set to true. 46 | * @param topic the topic to which messages will be published 47 | * @param msgType the ROS message type of the topic 48 | * @param rosBridge the {@link ros.RosBridge} that manages ROS Bridge interactions. 49 | * @param advertiseNow if true, then the topic is advertised; if false then it is not yet advertised. 50 | */ 51 | public Publisher(String topic, String msgType, RosBridge rosBridge, boolean advertiseNow){ 52 | this.topic = topic; 53 | this.msgType = msgType; 54 | this.rosBridge = rosBridge; 55 | 56 | if(advertiseNow) { 57 | this.rosBridge.advertise(this.topic, this.msgType); 58 | } 59 | 60 | } 61 | 62 | 63 | /** 64 | * Advertises to ROS that this topic will have messages published to it. You never 65 | * *need* to call this method since publishes will always make sure it was advertised first, 66 | * but gives you control if you did not have the topic advertised at constructions. 67 | */ 68 | public void advertise(){ 69 | this.rosBridge.advertise(this.topic, this.msgType); 70 | } 71 | 72 | 73 | /** 74 | * Publishes the message. If this client is not already advertising for this topic, it automatically will first.

75 | * Generally, the msg should either be a Javabean, such as one of the pre-included 76 | * messages in the {@link ros.msgs} package that has the same field structure as the target topic type 77 | * or a {@link java.util.Map} object 78 | * representing the ROS message type structure. For 79 | * example, if the ROS message type is "std_msgs/String" then msg should be a {@link ros.msgs.std_msgs.PrimitiveMsg} 80 | * with the generic of String, or a Map object 81 | * with one Map key-value entry of "data: stringValue" where stringValue is whatever the "std_msgs/String" 82 | * data field value is. 83 | * @param msg the message to publish. 84 | */ 85 | public void publish(Object msg){ 86 | this.rosBridge.publish(this.topic, this.msgType, msg); 87 | } 88 | 89 | 90 | /** 91 | * Publishes a ROS message specified in a JSON string. 92 | * If this client is not already advertising for this topic, it automatically will first. 93 | * @param jsonMsg the ROS message specified in a JSON string. 94 | */ 95 | public void publishJsonMsg(String jsonMsg){ 96 | this.rosBridge.publishJsonMsg(this.topic, this.msgType, jsonMsg); 97 | } 98 | 99 | 100 | /** 101 | * Returns the topic topic to which this object publishes. 102 | * @return the topic topic to which this object publishes. 103 | */ 104 | public String getTopic() { 105 | return topic; 106 | } 107 | 108 | 109 | /** 110 | * Returns the ROS message type of the topic to which this object publishes. 111 | * @return the ROS message type of the topic to which this object publishes. 112 | */ 113 | public String getMsgType() { 114 | return msgType; 115 | } 116 | 117 | 118 | /** 119 | * Returns the {@link ros.RosBridge} object that manages the connection to the ROS Bridge server. 120 | * @return the {@link ros.RosBridge} object that manages the connection to the ROS Bridge server. 121 | */ 122 | public RosBridge getRosBridge() { 123 | return rosBridge; 124 | } 125 | 126 | 127 | /** 128 | * Unadvertises that you are publishing to the topic. 129 | */ 130 | public void unadvertise(){ 131 | this.rosBridge.unadvertise(this.topic); 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /src/ros/RosBridge.java: -------------------------------------------------------------------------------- 1 | package ros; 2 | 3 | 4 | import com.fasterxml.jackson.core.JsonFactory; 5 | import com.fasterxml.jackson.core.JsonGenerator; 6 | import com.fasterxml.jackson.databind.JsonNode; 7 | import com.fasterxml.jackson.databind.ObjectMapper; 8 | import org.eclipse.jetty.websocket.api.Session; 9 | import org.eclipse.jetty.websocket.api.annotations.OnWebSocketClose; 10 | import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect; 11 | import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage; 12 | import org.eclipse.jetty.websocket.api.annotations.WebSocket; 13 | import org.eclipse.jetty.websocket.client.ClientUpgradeRequest; 14 | import org.eclipse.jetty.websocket.client.WebSocketClient; 15 | 16 | import java.io.IOException; 17 | import java.io.StringWriter; 18 | import java.net.URI; 19 | import java.util.*; 20 | import java.util.concurrent.ConcurrentHashMap; 21 | import java.util.concurrent.CopyOnWriteArrayList; 22 | import java.util.concurrent.CountDownLatch; 23 | import java.util.concurrent.Future; 24 | import java.util.concurrent.TimeUnit; 25 | 26 | /** 27 | * 28 | * A socket for connecting to ros bridge that accepts subscribe and publish commands. 29 | * Subscribing to a topic using the {@link #subscribe(SubscriptionRequestMsg, RosListenDelegate)}. 30 | * The input {@link SubscriptionRequestMsg} allows you to iteratively build all the optional fields 31 | * you can set to detail the subscription, such as whether the messages should be fragmented in size, 32 | * the queue length, throttle rate, etc. If data is pushed quickly, it is recommended that you 33 | * set the throttle rate and queue length to 1 or you may observe increasing latency in the messages. 34 | * Png compression is currently not supported. If the message type 35 | * is not set, and the topic either does not exist or you have never subscribed to that topic previously, 36 | * Rosbridge may fail to subscribe. There are also additional methods for subscribing that take the parameters 37 | * of a subscription as arguments to the method. 38 | *

39 | * Publishing is also supported with the {@link #publish(String, String, Object)} method, but you should 40 | * consider using the {@link ros.Publisher} class wrapper for streamlining publishing. 41 | *

42 | * To create and connect to rosbridge, you can either instantiate with the default constructor 43 | * and then call {@link #connect(String)} or use the static method {@link #createConnection(String)} which 44 | * creates a RosBridge instance and then connects. 45 | * An example URI to provide as a parameter is: ws://localhost:9090, where 9090 is the default Rosbridge server port. 46 | *

47 | * If you need to handle messages with larger sizes, you should subclass RosBridge and annotate the class 48 | * with {@link WebSocket} with the parameter maxTextMessageSize set to the desired buffer size. For example: 49 | *

50 | * 51 | * {@literal @}WebSocket(maxTextMessageSize = 500 * 1024) 
public class BigRosBridge extends RosBridge{

} 52 | * 53 | *

54 | * Note that the subclass does not need to override any methods; subclassing is performed purely to set the 55 | * buffer size in the annotation value. Then you can instantiate BigRosBridge and call its inherited connect method. 56 | * 57 | * @author James MacGlashan. 58 | */ 59 | @WebSocket 60 | public class RosBridge { 61 | 62 | protected final CountDownLatch closeLatch; 63 | protected Session session; 64 | 65 | protected Map listeners = new ConcurrentHashMap(); 66 | protected Set publishedTopics = new HashSet(); 67 | 68 | protected Map fragementManagers = new HashMap(); 69 | 70 | protected boolean hasConnected = false; 71 | 72 | protected boolean printMessagesAsReceived = false; 73 | 74 | 75 | /** 76 | * Creates a default RosBridge and connects it to the ROS Bridge websocket server located at rosBridgeURI. 77 | * Note that it is recommend that you call the {@link #waitForConnection()} method 78 | * before publishing or subscribing. 79 | * @param rosBridgeURI the URI to the ROS Bridge websocket server. Note that ROS Bridge by default uses port 9090. An example URI is: ws://localhost:9090 80 | * @return the ROS Bridge socket that is connected to the indicated server. 81 | */ 82 | public static RosBridge createConnection(String rosBridgeURI){ 83 | 84 | final RosBridge socket = new RosBridge(); 85 | socket.connect(rosBridgeURI); 86 | return socket; 87 | 88 | } 89 | 90 | 91 | /** 92 | * Connects to the Rosbridge host at the provided URI. 93 | * @param rosBridgeURI the URI to the ROS Bridge websocket server. Note that ROS Bridge by default uses port 9090. An example URI is: ws://localhost:9090 94 | */ 95 | public void connect(String rosBridgeURI){ 96 | WebSocketClient client = new WebSocketClient(); 97 | try { 98 | client.start(); 99 | URI echoUri = new URI(rosBridgeURI); 100 | ClientUpgradeRequest request = new ClientUpgradeRequest(); 101 | client.connect(this, echoUri, request); 102 | System.out.printf("Connecting to : %s%n", echoUri); 103 | 104 | } catch (Throwable t) { 105 | t.printStackTrace(); 106 | } 107 | } 108 | 109 | /** 110 | * Connects to the Rosbridge host at the provided URI. 111 | * @param rosBridgeURI the URI to the ROS Bridge websocket server. Note that ROS Bridge by default uses port 9090. An example URI is: ws://localhost:9090 112 | * @param waitForConnection if true, then this method will block until the connection is established. If false, then return immediately. 113 | */ 114 | public void connect(String rosBridgeURI, boolean waitForConnection){ 115 | WebSocketClient client = new WebSocketClient(); 116 | try { 117 | client.start(); 118 | URI echoUri = new URI(rosBridgeURI); 119 | ClientUpgradeRequest request = new ClientUpgradeRequest(); 120 | client.connect(this, echoUri, request); 121 | System.out.printf("Connecting to : %s%n", echoUri); 122 | if(waitForConnection){ 123 | this.waitForConnection(); 124 | } 125 | 126 | } catch (Throwable t) { 127 | t.printStackTrace(); 128 | } 129 | 130 | } 131 | 132 | public RosBridge(){ 133 | this.closeLatch = new CountDownLatch(1); 134 | } 135 | 136 | 137 | /** 138 | * Blocks execution until a connection to the ros bridge server is established. 139 | */ 140 | public void waitForConnection(){ 141 | 142 | if(this.hasConnected){ 143 | return; //done 144 | } 145 | 146 | synchronized(this){ 147 | while(!this.hasConnected){ 148 | try { 149 | this.wait(); 150 | } catch(InterruptedException e) { 151 | e.printStackTrace(); 152 | } 153 | } 154 | } 155 | 156 | } 157 | 158 | 159 | /** 160 | * Indicates whether the connection has been made 161 | * @return a boolean indicating whether the connection has been made 162 | */ 163 | public boolean hasConnected(){ 164 | return this.hasConnected; 165 | } 166 | 167 | 168 | /** 169 | * Returns whether ROSBridge will print all ROSBridge messages as they are received to the command line. 170 | * @return if true, then ROSBridge will print all ROSBridge messages as they are received to the command line. Otherwise is silent. 171 | */ 172 | public boolean printMessagesAsReceived() { 173 | return printMessagesAsReceived; 174 | } 175 | 176 | /** 177 | * Sets whether ROSBridge should print all ROSBridge messages as they are received to the command. 178 | * @param printMessagesAsReceived if true, then ROSBridge will print all ROSBridge messages as they are received to the command line. Otherwise is silent. 179 | */ 180 | public void setPrintMessagesAsReceived(boolean printMessagesAsReceived) { 181 | this.printMessagesAsReceived = printMessagesAsReceived; 182 | } 183 | 184 | 185 | /** 186 | * Use this method to close the connection. Will automatically unsubscribe and unadvertise from all topics first. 187 | * Call the {@link #awaitClose(int, TimeUnit)} method if you want to block a thread until closing has finished up. 188 | */ 189 | public void closeConnection(){ 190 | this.unsubsribeUnAdvertiseAll(); 191 | this.session.close(); 192 | } 193 | 194 | /** 195 | * Use this to to wait for a connection to close, or a maximum amount of time. 196 | * @param duration the time in some units until closing. 197 | * @param unit the unit of time in which duration is measured. 198 | * @return the result of the {@link java.util.concurrent.CountDownLatch#await()} method. true if closing happened; 199 | * false if time ran out. 200 | * @throws InterruptedException 201 | */ 202 | public boolean awaitClose(int duration, TimeUnit unit) throws InterruptedException { 203 | return this.closeLatch.await(duration, unit); 204 | } 205 | 206 | @OnWebSocketClose 207 | public void onClose(int statusCode, String reason) { 208 | System.out.printf("Connection closed: %d - %s%n", statusCode, reason); 209 | this.session = null; 210 | this.closeLatch.countDown(); 211 | } 212 | 213 | @OnWebSocketConnect 214 | public void onConnect(Session session) { 215 | System.out.printf("Got connect for ros: %s%n", session); 216 | this.session = session; 217 | this.hasConnected = true; 218 | synchronized(this) { 219 | this.notifyAll(); 220 | } 221 | 222 | } 223 | 224 | @OnWebSocketMessage 225 | public void onMessage(String msg) { 226 | 227 | if(this.printMessagesAsReceived){ 228 | System.out.println(msg); 229 | } 230 | 231 | ObjectMapper mapper = new ObjectMapper(); 232 | JsonNode node = null; 233 | try { 234 | node = mapper.readTree(msg); 235 | if(node.has("op")){ 236 | String op = node.get("op").asText(); 237 | if(op.equals("publish")){ 238 | String topic = node.get("topic").asText(); 239 | RosBridgeSubscriber subscriber = this.listeners.get(topic); 240 | if(subscriber != null){ 241 | subscriber.receive(node, msg); 242 | } 243 | } 244 | else if(op.equals("fragment")){ 245 | this.processFragment(node); 246 | } 247 | } 248 | } catch(IOException e) { 249 | System.out.println("Could not parse ROSBridge web socket message into JSON data"); 250 | e.printStackTrace(); 251 | } 252 | 253 | 254 | 255 | } 256 | 257 | 258 | 259 | /** 260 | * Subscribes to a ros topic. New publish results will be reported to the provided delegate. 261 | * If message type is null, then the type will be inferred. When type is null, if a topic 262 | * does not already exist, subscribe will fail. 263 | * @param topic the to subscribe to 264 | * @param type the message type of the topic. Pass null for type inference. 265 | * @param delegate the delegate that receives updates to the topic 266 | */ 267 | public void subscribe(String topic, String type, RosListenDelegate delegate){ 268 | this.subscribe(SubscriptionRequestMsg.generate(topic).setType(type), delegate); 269 | } 270 | 271 | 272 | /** 273 | * Subscribes to a ros topic. New publish results will be reported to the provided delegate. 274 | * If message type is null, then the type will be inferred. When type is null, if a topic 275 | * does not already exist, subscribe will fail. 276 | * @param topic the to subscribe to 277 | * @param type the message type of the topic. Pass null for type inference. 278 | * @param delegate the delegate that receives updates to the topic 279 | * @param throttleRate the minimum amount of time (in ms) that must elapse between messages being sent from the server 280 | * @param queueLength the size of the queue to buffer messages. Messages are buffered as a result of the throttle_rate. 281 | */ 282 | public void subscribe(String topic, String type, RosListenDelegate delegate, int throttleRate, int queueLength){ 283 | 284 | this.subscribe(SubscriptionRequestMsg.generate(topic) 285 | .setType(type) 286 | .setThrottleRate(throttleRate) 287 | .setQueueLength(queueLength), 288 | delegate); 289 | 290 | } 291 | 292 | /** 293 | * Subscribes to a topic with the subscription parameters specified in the provided {@link SubscriptionRequestMsg}. 294 | * The {@link RosListenDelegate} will be notified every time there is a publish to the specified topic. 295 | * @param request the subscription request details. 296 | * @param delegate the delegate that will receive messages each time a message is published to the topic. 297 | */ 298 | public void subscribe(SubscriptionRequestMsg request, RosListenDelegate delegate){ 299 | 300 | if(this.session == null){ 301 | throw new RuntimeException("Rosbridge connection is closed. Cannot subscribe."); 302 | } 303 | 304 | String topic = request.getTopic(); 305 | 306 | //already have a subscription? just update delegate 307 | synchronized(this.listeners){ 308 | RosBridgeSubscriber subscriber = this.listeners.get(topic); 309 | if(subscriber!=null){ 310 | subscriber.addDelegate(delegate); 311 | return; 312 | } 313 | 314 | //otherwise setup the subscription and delegate 315 | this.listeners.put(topic, new RosBridgeSubscriber(delegate)); 316 | } 317 | 318 | String subMsg = request.generateJsonString(); 319 | Future fut; 320 | try{ 321 | fut = session.getRemote().sendStringByFuture(subMsg); 322 | fut.get(2, TimeUnit.SECONDS); 323 | }catch (Throwable t){ 324 | System.out.println("Error in sending subscription message to Rosbridge host for topic " + topic); 325 | t.printStackTrace(); 326 | } 327 | 328 | 329 | } 330 | 331 | 332 | /** 333 | * Stops a {@link RosListenDelegate} from receiving messages from Rosbridge. 334 | * @param topic the topic on which the listener subscribed. 335 | * @param delegate the delegate to remove. 336 | */ 337 | public void removeListener(String topic, RosListenDelegate delegate){ 338 | 339 | RosBridgeSubscriber subscriber = this.listeners.get(topic); 340 | if(subscriber != null){ 341 | subscriber.removeDelegate(delegate); 342 | 343 | if(subscriber.numDelegates() == 0){ 344 | this.unsubscribe(topic); 345 | } 346 | 347 | } 348 | 349 | } 350 | 351 | 352 | /** 353 | * Advertises that this object will be publishing to a ROS topic. 354 | * @param topic the topic to which this object will be publishing. 355 | * @param type the ROS message type of the topic. 356 | */ 357 | public void advertise(String topic, String type){ 358 | 359 | if(this.session == null){ 360 | throw new RuntimeException("Rosbridge connection is closed. Cannot advertise. Attempted Topic advertising: " + topic); 361 | } 362 | 363 | boolean advertised = false; 364 | synchronized(this.publishedTopics){ 365 | advertised = this.publishedTopics.contains(topic); 366 | if (!advertised) 367 | this.publishedTopics.add(topic); 368 | } 369 | if(!advertised){ 370 | 371 | //then start advertising first 372 | String adMsg = "{" + 373 | "\"op\": \"advertise\",\n" + 374 | "\"topic\": \"" + topic + "\",\n" + 375 | "\"type\": \"" + type + "\"\n" + 376 | "}"; 377 | 378 | Future fut; 379 | try{ 380 | fut = session.getRemote().sendStringByFuture(adMsg); 381 | fut.get(2, TimeUnit.SECONDS); 382 | }catch (Throwable t){ 383 | this.publishedTopics.remove(topic); 384 | System.out.println("Error in setting up advertisement to " + topic + " with message type: " + type); 385 | t.printStackTrace(); 386 | } 387 | 388 | } 389 | 390 | } 391 | 392 | /** 393 | * Unsubscribes from a topic. Note that if there are multiple {@link RosListenDelegate} 394 | * objects subscribed to a topic, they will all unsubscribe. If you want to remove only 395 | * one, instead use {@link #removeListener(String, RosListenDelegate)}. 396 | * @param topic the topic from which to unsubscribe. 397 | */ 398 | public void unsubscribe(String topic){ 399 | 400 | if(this.session == null){ 401 | throw new RuntimeException("Rosbridge connection is closed. Cannot unsubscribe. Attempted unsubscribe topic: " + topic); 402 | } 403 | 404 | String usMsg = "{" + 405 | "\"op\": \"unsubscribe\",\n" + 406 | "\"topic\": \"" + topic + "\"\n" + 407 | "}"; 408 | 409 | Future fut; 410 | try{ 411 | fut = session.getRemote().sendStringByFuture(usMsg); 412 | fut.get(2, TimeUnit.SECONDS); 413 | }catch (Throwable t){ 414 | System.out.println("Error in sending unsubscribe message for " + topic); 415 | t.printStackTrace(); 416 | } 417 | 418 | this.listeners.remove(topic); 419 | } 420 | 421 | 422 | /** 423 | * Unsubscribes from all topics. 424 | */ 425 | public void unsubscribeAll(){ 426 | List curTopics = new ArrayList(this.listeners.keySet()); 427 | for(String topic : curTopics){ 428 | this.unsubscribe(topic); 429 | } 430 | } 431 | 432 | 433 | /** 434 | * "Unadvertises" that you are publishing to a topic. 435 | * @param topic the topic to unadvertise 436 | */ 437 | public void unadvertise(String topic){ 438 | 439 | if(this.session == null){ 440 | throw new RuntimeException("Rosbridge connection is closed. Cannot unadvertise. Attempted unadvertise topic: " + topic); 441 | } 442 | 443 | String usMsg = "{" + 444 | "\"op\": \"unadvertise\",\n" + 445 | "\"topic\": \"" + topic + "\"\n" + 446 | "}"; 447 | 448 | Future fut; 449 | try{ 450 | fut = session.getRemote().sendStringByFuture(usMsg); 451 | fut.get(2, TimeUnit.SECONDS); 452 | }catch (Throwable t){ 453 | System.out.println("Error in sending unsubscribe message for " + topic); 454 | t.printStackTrace(); 455 | } 456 | 457 | synchronized(this.publishedTopics){ 458 | this.publishedTopics.remove(topic); 459 | } 460 | 461 | } 462 | 463 | /** 464 | * Unadvertises for all topics currently being published to. 465 | */ 466 | public void unadvertiseAll(){ 467 | List curPublishedTopics; 468 | synchronized(this.publishedTopics){ 469 | curPublishedTopics = new ArrayList(this.publishedTopics); 470 | } 471 | for(String topic : curPublishedTopics){ 472 | this.unadvertise(topic); 473 | } 474 | } 475 | 476 | /** 477 | * Unadvertises and unsubscribes from all topics. 478 | */ 479 | public void unsubsribeUnAdvertiseAll(){ 480 | this.unadvertiseAll(); 481 | this.unsubscribeAll(); 482 | } 483 | 484 | /** 485 | * Publishes to a topic. If the topic has not already been advertised on ros, it will automatically do so. 486 | * @param topic the topic to publish to 487 | * @param type the message type of the topic 488 | * @param msg should be a {@link java.util.Map} or a Java Bean, specifying the ROS message 489 | */ 490 | public void publish(String topic, String type, Object msg){ 491 | 492 | if(this.session == null){ 493 | throw new RuntimeException("Rosbridge connection is closed. Cannot publish. Attempted Topic Publish: " + topic); 494 | } 495 | 496 | this.advertise(topic, type); 497 | 498 | Map jsonMsg = new HashMap(); 499 | jsonMsg.put("op", "publish"); 500 | jsonMsg.put("topic", topic); 501 | jsonMsg.put("type", type); 502 | jsonMsg.put("msg", msg); 503 | 504 | JsonFactory jsonFactory = new JsonFactory(); 505 | StringWriter writer = new StringWriter(); 506 | JsonGenerator jsonGenerator; 507 | ObjectMapper objectMapper = new ObjectMapper(); 508 | 509 | try { 510 | jsonGenerator = jsonFactory.createGenerator(writer); 511 | objectMapper.writeValue(jsonGenerator, jsonMsg); 512 | } catch(Exception e){ 513 | System.out.println("Error"); 514 | } 515 | 516 | String jsonMsgString = writer.toString(); 517 | Future fut; 518 | try{ 519 | fut = session.getRemote().sendStringByFuture(jsonMsgString); 520 | fut.get(2, TimeUnit.SECONDS); 521 | }catch (Throwable t){ 522 | System.out.println("Error publishing to " + topic + " with message type: " + type); 523 | t.printStackTrace(); 524 | } 525 | 526 | } 527 | 528 | 529 | /** 530 | * Publishes to a topic with a ros message represented in its JSON string form. 531 | * If the topic has not already been advertised on ros, it will automatically do so. 532 | * @param topic the topic to publish to 533 | * @param type the message type of the topic 534 | * @param jsonMsg the JSON string of the ROS message. 535 | */ 536 | public void publishJsonMsg(String topic, String type, String jsonMsg){ 537 | 538 | if(this.session == null){ 539 | throw new RuntimeException("Rosbridge connection is closed. Cannot publish. Attempted Topic Publish: " + topic); 540 | } 541 | 542 | this.advertise(topic, type); 543 | 544 | String fullMsg = "{\"op\": \"publish\", \"topic\": \"" + topic + "\", \"type\": \"" + type + "\", " + 545 | "\"msg\": " + jsonMsg + "}"; 546 | 547 | 548 | Future fut; 549 | try{ 550 | fut = session.getRemote().sendStringByFuture(fullMsg); 551 | fut.get(2, TimeUnit.SECONDS); 552 | }catch (Throwable t){ 553 | System.out.println("Error publishing to " + topic + " with message type: " + type); 554 | t.printStackTrace(); 555 | } 556 | 557 | } 558 | 559 | 560 | /** 561 | * Sends the provided fully specified message to the ROS Bridge server. Since the RosBridge server 562 | * expects JSON messages, the string message should probably be in JSON format and adhere to the R 563 | * Rosbridge protocol, but this method will send whatever raw string you provide. 564 | * @param message the message to send to Rosbridge. 565 | */ 566 | public void sendRawMessage(String message){ 567 | 568 | if(this.session == null){ 569 | throw new RuntimeException("Rosbridge connection is closed. Cannot send message."); 570 | } 571 | 572 | Future fut; 573 | try{ 574 | fut = session.getRemote().sendStringByFuture(message); 575 | fut.get(2, TimeUnit.SECONDS); 576 | }catch (Throwable t){ 577 | System.out.println("Error sending raw message to RosBridge server: " + message); 578 | t.printStackTrace(); 579 | } 580 | 581 | } 582 | 583 | /** 584 | * Attempts to turn the the provided object into a JSON message and send it to the ROSBridge server. 585 | * If the object does not satisfy the Rosbridge protocol, it may have no affect. 586 | * @param o the object to turn into a JSON message and send. 587 | */ 588 | public void formatAndSend(Object o){ 589 | 590 | JsonFactory jsonFactory = new JsonFactory(); 591 | StringWriter writer = new StringWriter(); 592 | JsonGenerator jsonGenerator; 593 | ObjectMapper objectMapper = new ObjectMapper(); 594 | 595 | try { 596 | jsonGenerator = jsonFactory.createGenerator(writer); 597 | objectMapper.writeValue(jsonGenerator, o); 598 | } catch(Exception e){ 599 | System.out.println("Error parsing object into a JSON message."); 600 | } 601 | 602 | String jsonMsgString = writer.toString(); 603 | Future fut; 604 | try{ 605 | fut = session.getRemote().sendStringByFuture(jsonMsgString); 606 | fut.get(2, TimeUnit.SECONDS); 607 | }catch (Throwable t){ 608 | System.out.println("Error sending message to RosBridge server: " + jsonMsgString); 609 | t.printStackTrace(); 610 | } 611 | 612 | } 613 | 614 | 615 | 616 | protected void processFragment(JsonNode node){ 617 | String id = node.get("id").textValue(); 618 | FragmentManager manager; 619 | boolean complete = false; 620 | String fullMsg = null; 621 | synchronized(this.fragementManagers){ 622 | manager = this.fragementManagers.get(id); 623 | if(manager == null){ 624 | manager = new FragmentManager(node); 625 | this.fragementManagers.put(id, manager); 626 | } 627 | } 628 | synchronized(manager){ 629 | complete = manager.updateFragment(node); 630 | if(complete) 631 | fullMsg = manager.generateFullMessage(); 632 | } 633 | 634 | if(complete){ 635 | synchronized(this.fragementManagers){ 636 | this.fragementManagers.remove(id); 637 | } 638 | this.onMessage(fullMsg); 639 | } 640 | 641 | } 642 | 643 | /** 644 | * Class for managing all the listeners that have subscribed to a topic on Rosbridge. 645 | * Maintains a list of {@link RosListenDelegate} objects and informs them all 646 | * when a message has been received from Rosbridge. 647 | */ 648 | public static class RosBridgeSubscriber{ 649 | 650 | protected List delegates = new CopyOnWriteArrayList(); 651 | 652 | public RosBridgeSubscriber() { 653 | } 654 | 655 | /** 656 | * Initializes and adds all the input delegates to receive messages. 657 | * @param delegates the delegates to receive messages. 658 | */ 659 | public RosBridgeSubscriber(RosListenDelegate...delegates) { 660 | for(RosListenDelegate delegate : delegates){ 661 | this.delegates.add(delegate); 662 | } 663 | } 664 | 665 | /** 666 | * Adds a delegate to receive messages from Rosbridge. 667 | * @param delegate a delegate to receive messages from Rosbridge. 668 | */ 669 | public void addDelegate(RosListenDelegate delegate){ 670 | this.delegates.add(delegate); 671 | } 672 | 673 | 674 | /** 675 | * Removes a delegate from receiving messages from Rosbridge 676 | * @param delegate the delegate to stop receiving messages. 677 | */ 678 | public void removeDelegate(RosListenDelegate delegate){ 679 | this.delegates.remove(delegate); 680 | } 681 | 682 | /** 683 | * Receives a new published message to a subscribed topic and informs all listeners. 684 | * @param data the {@link com.fasterxml.jackson.databind.JsonNode} containing the JSON data received. 685 | * @param stringRep the string representation of the JSON object. 686 | */ 687 | public void receive(JsonNode data, String stringRep){ 688 | for(RosListenDelegate delegate : delegates){ 689 | delegate.receive(data, stringRep); 690 | } 691 | } 692 | 693 | /** 694 | * Returns the number of delegates listening to this topic. 695 | * @return the number of delegates listening to this topic. 696 | */ 697 | public int numDelegates(){ 698 | return this.delegates.size(); 699 | } 700 | 701 | } 702 | 703 | public static class FragmentManager{ 704 | 705 | protected String id; 706 | protected String [] fragments; 707 | protected Set completedFragements; 708 | 709 | public FragmentManager(JsonNode fragmentJson){ 710 | int total = fragmentJson.get("total").intValue(); 711 | this.fragments = new String[total]; 712 | this.completedFragements = new HashSet(total); 713 | this.id = fragmentJson.get("id").textValue(); 714 | } 715 | 716 | public boolean updateFragment(JsonNode fragmentJson){ 717 | String data = fragmentJson.get("data").asText(); 718 | int num = fragmentJson.get("num").intValue(); 719 | this.fragments[num] = data; 720 | completedFragements.add(num); 721 | return this.complete(); 722 | } 723 | 724 | public boolean complete(){ 725 | return this.completedFragements.size() == fragments.length; 726 | } 727 | 728 | public int numFragments(){ 729 | return this.fragments.length; 730 | } 731 | 732 | public int numCompletedFragments(){ 733 | return this.completedFragements.size(); 734 | } 735 | 736 | public String generateFullMessage(){ 737 | if(!this.complete()){ 738 | throw new RuntimeException("Cannot generate full message from fragments, because not all fragments have arrived."); 739 | } 740 | 741 | StringBuilder buf = new StringBuilder(fragments[0].length() * fragments.length); 742 | for(String frag : this.fragments){ 743 | buf.append(frag); 744 | } 745 | 746 | return buf.toString(); 747 | 748 | } 749 | 750 | } 751 | 752 | } 753 | -------------------------------------------------------------------------------- /src/ros/RosListenDelegate.java: -------------------------------------------------------------------------------- 1 | package ros; 2 | 3 | import com.fasterxml.jackson.core.JsonFactory; 4 | import com.fasterxml.jackson.core.JsonParseException; 5 | import com.fasterxml.jackson.core.type.TypeReference; 6 | import com.fasterxml.jackson.databind.JsonNode; 7 | import com.fasterxml.jackson.databind.ObjectMapper; 8 | 9 | import java.io.IOException; 10 | import java.util.HashMap; 11 | import java.util.Map; 12 | 13 | /** 14 | * This is a delegate interface for handling ros topic subscriptions. The {@link #receive(com.fasterxml.jackson.databind.JsonNode, String)} 15 | * is called every time the topic with which this delegate is associated has a new message published. 16 | * The JSON data, given as a {@link com.fasterxml.jackson.databind.JsonNode}, has four top-level fields:

17 | * op: what kind of operation it was; should always be "publish"

18 | * topic: to which topic the message was published

19 | * type: the ROS message type of the topic

20 | * msg: the provided ros message in JSON format. This the primary field you will work with.

21 | * There are generally two ways you can parse the message into a more usable Java object. The first involves manually 22 | * iterating through the JSON fields of the msg. For example, 23 | * for a geometry_msgs/Twist message, you can get out the linear x value as follows:

24 | * double x = data.get("msg").get("linear").get("x").asDouble();

25 | * (If an element is na array, JSON methods exist for handling it such as {@link com.fasterxml.jackson.databind.JsonNode#get(int)} 26 | * and {@link com.fasterxml.jackson.databind.JsonNode#size()}). The the other way is to let the Jackson library unpack 27 | * it into a JavaBean. The {@link ros.tools.MessageUnpacker} class further streamlines this process. See its documentation 28 | * for more information. 29 | * @author James MacGlashan. 30 | */ 31 | public interface RosListenDelegate { 32 | 33 | /** 34 | * Receives a new published message to a subscribed topic. The JSON data, given as a {@link com.fasterxml.jackson.databind.JsonNode}, has four top-level fields:

35 | * op: what kind of operation it was; should always be "publish"

36 | * topic: to which topic the message was published

37 | * type: the ROS message type of the topic

38 | * msg: the provided ros message in JSON format. This the primary field you will work with.

39 | * This method also receives the full string representation of the received JSON message from ROSBridge. 40 | * @param data the {@link com.fasterxml.jackson.databind.JsonNode} containing the JSON data received. 41 | * @param stringRep the string representation of the JSON object. 42 | */ 43 | void receive(JsonNode data, String stringRep); 44 | 45 | 46 | /** 47 | * A class for easy conversion to the legacy java_rosbridge {@link #receive(com.fasterxml.jackson.databind.JsonNode, String)} 48 | * message format that presented the JSON data 49 | * in a {@link java.util.Map} from {@link java.lang.String} to {@link java.lang.Object} instances 50 | * in which the values were ether primitives, {@link java.util.Map} objects themselves, or {@link java.util.List} 51 | * objects. 52 | */ 53 | public static class LegacyFormat{ 54 | 55 | /** 56 | * A method for easy conversion to the legacy java_rosbridge {@link #receive(com.fasterxml.jackson.databind.JsonNode, String)} 57 | * message format that presented the JSON data 58 | * in a {@link java.util.Map} from {@link java.lang.String} to {@link java.lang.Object} instances 59 | * in which the values were ether primitives, {@link java.util.Map} objects themselves, or {@link java.util.List} 60 | * objects. 61 | * @param jsonString the source JSON string message that was received 62 | * @return a {@link java.util.Map} data structure of the JSON data. 63 | */ 64 | public static Map legacyFormat(String jsonString){ 65 | 66 | JsonFactory jsonFactory = new JsonFactory(); 67 | Map messageData = new HashMap(); 68 | try { 69 | ObjectMapper objectMapper = new ObjectMapper(jsonFactory); 70 | TypeReference> listTypeRef = 71 | new TypeReference>() {}; 72 | messageData = objectMapper.readValue(jsonString, listTypeRef); 73 | } catch (JsonParseException e) { 74 | e.printStackTrace(); 75 | } catch (IOException e) { 76 | e.printStackTrace(); 77 | } 78 | 79 | return messageData; 80 | } 81 | } 82 | 83 | 84 | 85 | } 86 | -------------------------------------------------------------------------------- /src/ros/SubscriptionRequestMsg.java: -------------------------------------------------------------------------------- 1 | package ros; 2 | 3 | import com.fasterxml.jackson.core.JsonProcessingException; 4 | import com.fasterxml.jackson.databind.ObjectMapper; 5 | 6 | import java.util.HashMap; 7 | import java.util.Map; 8 | 9 | /** 10 | * A Subscription request builder. Supports all Rosbridge protocol fields for 11 | * a subscription request except png compression. Requires a topic to be set, 12 | * with the rest of the optional values set with setter methods that return 13 | * the this object so that you can chain settters on a single line. Use 14 | * the static {@link #generate(String)} to start the sequence. If a value is set 15 | * to null, it will be removed from the message. 16 | *

17 | * When everything is set, the JSON message can be retrieved with the {@link #generateJsonString()} 18 | * @author James MacGlashan. 19 | */ 20 | public class SubscriptionRequestMsg { 21 | 22 | protected Map keyValues = new HashMap(7); 23 | 24 | public static SubscriptionRequestMsg generate(String topic){ 25 | return new SubscriptionRequestMsg(topic); 26 | } 27 | 28 | public SubscriptionRequestMsg(String topic){ 29 | if(topic == null){ 30 | throw new RuntimeException("ROS topic cannot be null in subscription request."); 31 | } 32 | keyValues.put("op", "subscribe"); 33 | keyValues.put("topic", topic); 34 | } 35 | 36 | public SubscriptionRequestMsg setTopic(String topic){ 37 | this.setKeyValue("topic", topic); 38 | return this; 39 | } 40 | 41 | public SubscriptionRequestMsg setType(String type){ 42 | this.setKeyValue("type", type); 43 | return this; 44 | } 45 | 46 | public SubscriptionRequestMsg setThrottleRate(Integer throttleRate){ 47 | this.setKeyValue("throttle_rate", throttleRate); 48 | return this; 49 | } 50 | 51 | public SubscriptionRequestMsg setQueueLength(Integer queueLength){ 52 | this.setKeyValue("queue_length", queueLength); 53 | return this; 54 | } 55 | 56 | public SubscriptionRequestMsg setFragmentSize(Integer fragmentSize){ 57 | this.setKeyValue("fragment_size", fragmentSize); 58 | return this; 59 | } 60 | 61 | 62 | public SubscriptionRequestMsg setId(String id){ 63 | this.setKeyValue("id", id); 64 | return this; 65 | } 66 | 67 | public String getTopic(){ 68 | return (String)this.keyValues.get("topic"); 69 | } 70 | 71 | public String getType(){ 72 | return (String)this.keyValues.get("type"); 73 | } 74 | 75 | public Integer getThrottleRate(){ 76 | return (Integer)this.keyValues.get("throttle_rate"); 77 | } 78 | 79 | public Integer getQueueLength(){ 80 | return (Integer)this.keyValues.get("queue_length"); 81 | } 82 | 83 | public Integer getFragmentSize(){ 84 | return (Integer)this.keyValues.get("fragment_size"); 85 | } 86 | 87 | 88 | public String getId(){ 89 | return (String)this.keyValues.get("id"); 90 | } 91 | 92 | /** 93 | * Generates the JSON string for this subscription request. 94 | * @return the JSON string for this subscription request. 95 | */ 96 | public String generateJsonString(){ 97 | ObjectMapper mapper = new ObjectMapper(); 98 | String jsonString = null; 99 | try { 100 | jsonString = mapper.writeValueAsString(this.keyValues); 101 | } catch(JsonProcessingException e) { 102 | e.printStackTrace(); 103 | } 104 | 105 | return jsonString; 106 | } 107 | 108 | 109 | protected void setKeyValue(String key, Object value){ 110 | if(value == null){ 111 | this.keyValues.remove(key); 112 | } 113 | else{ 114 | this.keyValues.put(key, value); 115 | } 116 | } 117 | 118 | } 119 | -------------------------------------------------------------------------------- /src/ros/msgs/geometry_msgs/Twist.java: -------------------------------------------------------------------------------- 1 | package ros.msgs.geometry_msgs; 2 | 3 | /** 4 | * A Java Bean for the Vector3 ROS geometry_msgs/Twist message type. This can be used both for publishing Twist messages to 5 | * {@link ros.RosBridge} and unpacking Twist messages received from {@link ros.RosBridge} (see the {@link ros.tools.MessageUnpacker} 6 | * documentation for how to easily unpack a ROS Bridge message into a Java object). 7 | * @author James MacGlashan. 8 | */ 9 | public class Twist { 10 | public Vector3 linear = new Vector3(); 11 | public Vector3 angular = new Vector3(); 12 | 13 | public Twist(){} 14 | 15 | public Twist(Vector3 linear, Vector3 angular) { 16 | this.linear = linear; 17 | this.angular = angular; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/ros/msgs/geometry_msgs/Vector3.java: -------------------------------------------------------------------------------- 1 | package ros.msgs.geometry_msgs; 2 | 3 | /** 4 | * A Java Bean for the Vector3 ROS geometry_msgs/Vector3 message type. This can be used both for publishing Vector3 messages to 5 | * {@link ros.RosBridge} and unpacking Vector3 messages received from {@link ros.RosBridge} (see the {@link ros.tools.MessageUnpacker} 6 | * documentation for how to easily unpack a ROS Bridge message into a Java object). 7 | * @author James MacGlashan. 8 | */ 9 | public class Vector3 { 10 | public double x; 11 | public double y; 12 | public double z; 13 | 14 | public Vector3(){} 15 | 16 | public Vector3(double x, double y, double z) { 17 | this.x = x; 18 | this.y = y; 19 | this.z = z; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/ros/msgs/sensor_msgs/Image.java: -------------------------------------------------------------------------------- 1 | package ros.msgs.sensor_msgs; 2 | 3 | import ros.msgs.std_msgs.Header; 4 | 5 | import java.awt.image.BufferedImage; 6 | 7 | /** 8 | * Implementation of ROS sensor_msgs/Image.msg: 9 | * http://docs.ros.org/api/sensor_msgs/html/msg/Image.html. 10 | * This class can also decode the ROS Image into a Java Buffered Image for images that are encoded in either 11 | * bgr8, rgb8, or mono8, by using the {@link #toBufferedImage()} method. 12 | * @author James MacGlashan. 13 | */ 14 | public class Image { 15 | 16 | public Header header; 17 | public int height; 18 | public int width; 19 | public String encoding; 20 | public int is_bigendian; 21 | public int step; 22 | public byte[] data; 23 | 24 | public Image() { 25 | } 26 | 27 | public Image(Header header, int height, int width, String encoding, int is_bigendian, int step, byte[] data) { 28 | this.header = header; 29 | this.height = height; 30 | this.width = width; 31 | this.encoding = encoding; 32 | this.is_bigendian = is_bigendian; 33 | this.step = step; 34 | this.data = data; 35 | } 36 | 37 | /** 38 | * Constructs a {@link BufferedImage} from this ROS Image, provided the encoding is either rgb8, bgr8, or mono8. 39 | * If it is not one of those encodings, then a runtime exception will be thrown. 40 | * @return a {@link BufferedImage} representation of this image. 41 | */ 42 | public BufferedImage toBufferedImage(){ 43 | 44 | if(this.encoding.equals("bgr8") || this.encoding.equals("rgb8")){ 45 | return this.toBufferedImageFromRGB8(); 46 | } 47 | else if(this.encoding.equals("mono8")){ 48 | return this.toBufferedImageFromMono8(); 49 | } 50 | 51 | 52 | throw new RuntimeException("ROS Image does not currently decode " + this.encoding + ". See Java doc for support types."); 53 | } 54 | 55 | 56 | /** 57 | * Constructs a {@link BufferedImage} from this ROS Image assuming the encoding is mono8 58 | * @return a {@link BufferedImage} representation of this image. 59 | */ 60 | protected BufferedImage toBufferedImageFromMono8(){ 61 | int w = this.width; 62 | int h = this.height; 63 | 64 | BufferedImage i = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB); 65 | for (int y = 0; y < h; ++y) { 66 | for(int x = 0; x < w; ++x) { 67 | 68 | //row major 69 | int index = (y * w) + x; 70 | // combine to RGB format 71 | int anded = data[index++] & 0xFF; 72 | int rgb = anded | 73 | (anded << 8) | 74 | (anded << 16) | 75 | 0xFF000000; 76 | 77 | i.setRGB(x, y, rgb); 78 | } 79 | } 80 | 81 | return i; 82 | } 83 | 84 | 85 | /** 86 | * Constructs a {@link BufferedImage} representation from this ROS Image assuming the encoding is either rgb8 or bgr8. 87 | * @return a {@link BufferedImage} representation of this image. 88 | */ 89 | protected BufferedImage toBufferedImageFromRGB8(){ 90 | 91 | int w = this.width; 92 | int h = this.height; 93 | 94 | BufferedImage i = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB); 95 | for (int y = 0; y < h; ++y) { 96 | for (int x = 0; x < w; ++x) { 97 | 98 | //row major, consecutive channels 99 | int index = (y * w * 3) + (x * 3); 100 | // combine to RGB format 101 | int rgb; 102 | if(this.encoding.equals("bgr8")){ 103 | rgb = ((data[index++] & 0xFF)) | 104 | ((data[index++] & 0xFF) << 8) | 105 | ((data[index++] & 0xFF) << 16) | 106 | 0xFF000000; 107 | } 108 | else if(this.encoding.equals("rgb8")){ 109 | rgb = ((data[index++] & 0xFF) << 16) | 110 | ((data[index++] & 0xFF) << 8) | 111 | ((data[index++] & 0xFF)) | 112 | 0xFF000000; 113 | } 114 | else{ 115 | throw new RuntimeException("ROS Image toBufferedImageFromRGB8 does not decode " + this.encoding); 116 | } 117 | i.setRGB(x, y, rgb); 118 | } 119 | } 120 | 121 | return i; 122 | 123 | } 124 | 125 | } 126 | -------------------------------------------------------------------------------- /src/ros/msgs/std_msgs/Header.java: -------------------------------------------------------------------------------- 1 | package ros.msgs.std_msgs; 2 | 3 | /** 4 | * @author James MacGlashan. 5 | */ 6 | public class Header { 7 | 8 | public int seq; 9 | public Time stamp; 10 | public String frame_id; 11 | 12 | public Header() { 13 | } 14 | 15 | public Header(int seq, Time stamp, String frame_id) { 16 | this.seq = seq; 17 | this.stamp = stamp; 18 | this.frame_id = frame_id; 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /src/ros/msgs/std_msgs/PrimitiveMsg.java: -------------------------------------------------------------------------------- 1 | package ros.msgs.std_msgs; 2 | 3 | /** 4 | * A generic specified Java Bean for capturing many of the primitive data-type messages used by ROS in the std_msgs 5 | * package. The class has a single public data member called "data" that belongs to the specified primitive type. 6 | * @author James MacGlashan. 7 | */ 8 | public class PrimitiveMsg { 9 | public T data; 10 | public PrimitiveMsg(){} 11 | public PrimitiveMsg(T data){this.data = data;} 12 | } 13 | -------------------------------------------------------------------------------- /src/ros/msgs/std_msgs/Time.java: -------------------------------------------------------------------------------- 1 | package ros.msgs.std_msgs; 2 | 3 | /** 4 | * @author James MacGlashan. 5 | */ 6 | public class Time { 7 | public int secs; 8 | public int nsecs; 9 | 10 | public Time() { 11 | } 12 | 13 | public Time(int secs, int nsecs) { 14 | this.secs = secs; 15 | this.nsecs = nsecs; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/ros/tools/MessageUnpacker.java: -------------------------------------------------------------------------------- 1 | package ros.tools; 2 | 3 | import com.fasterxml.jackson.core.JsonProcessingException; 4 | import com.fasterxml.jackson.databind.JsonNode; 5 | import com.fasterxml.jackson.databind.ObjectMapper; 6 | 7 | /** 8 | * This is a helper class that may be used for taking a JSON message returned from Ros bridge, and unpacking 9 | * the actual ros message inside it into a Java bean object. (A Java bean is a Java object that has a default 10 | * constructor and getter and has either public data members or getter and setter methods for the data members 11 | * using standard Java naming conventions.) 12 | *

13 | * To use this class, type the generic to the Java Bean class to which the message will be unpacked and provide 14 | * the constructor the class of it as well. For example:

15 | * MessageUnpacker<Twist> unpacker = new MessageUnpacker<Twist>(Twist.class);. Then provide the 16 | * {@link #unpackRosMessage(com.fasterxml.jackson.databind.JsonNode)} method the {@link com.fasterxml.jackson.databind.JsonNode} 17 | * provided to a {@link ros.RosListenDelegate} to unpack the "msg" field into the Java Bean. This will also work 18 | * with Java Beans with generics. For example, you can do 19 | * MessageUnpacker<PrimitiveMsg<String>> unpacker = new MessageUnpacker<PrimitiveMsg<String>>(PrimitiveMsg.class);. 20 | * 21 | * @author James MacGlashan. 22 | */ 23 | public class MessageUnpacker { 24 | protected ObjectMapper mapper = new ObjectMapper(); 25 | protected Class javaClass; 26 | 27 | /** 28 | * Constructor. 29 | * @param javaClass a {@link java.lang.Class} specifying the class of the Java bean into which 30 | * a ros message will be unpacked. 31 | */ 32 | public MessageUnpacker(Class javaClass) { 33 | this.javaClass = javaClass; 34 | } 35 | 36 | /** 37 | * Unpacks a ros message into the appropriate Java bean from the JSON message returned by 38 | * ros bridge. Note that the Ros bridge JSON message contains header information and the actual 39 | * ROS message is stored in the 'msg' field in the JSON message. Therefore, this method should be provided the 40 | * {@link com.fasterxml.jackson.databind.JsonNode} that is given to a {@link ros.RosListenDelegate} 41 | * {@link ros.RosListenDelegate#receive(com.fasterxml.jackson.databind.JsonNode, String)} method. 42 | * If the provided {@link com.fasterxml.jackson.databind.JsonNode} argument of this method 43 | * does not have a "msg" field, then it will attempt to unpack from the whole JSON node. 44 | * @param rosBridgeMessage the {@link com.fasterxml.jackson.databind.JsonNode} from RosBridge. 45 | * @return the unpacked Java Bean of the ROS message. 46 | */ 47 | @SuppressWarnings("unchecked") 48 | public T unpackRosMessage(JsonNode rosBridgeMessage){ 49 | JsonNode rosMsgNode = rosBridgeMessage.get("msg"); 50 | if(rosMsgNode == null){ 51 | rosMsgNode = rosBridgeMessage; 52 | } 53 | T rosMsg = null; 54 | try { 55 | rosMsg = (T)mapper.treeToValue(rosMsgNode, javaClass); 56 | } catch(JsonProcessingException e) { 57 | e.printStackTrace(); 58 | } 59 | return rosMsg; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/ros/tools/PeriodicPublisher.java: -------------------------------------------------------------------------------- 1 | package ros.tools; 2 | 3 | import ros.Publisher; 4 | import ros.RosBridge; 5 | 6 | import java.util.Timer; 7 | import java.util.TimerTask; 8 | 9 | /** 10 | * This class is a tool for causing a ROS message to be periodically published at a given rate. To start periodic publishing 11 | * use the {@link #beginPublishing(int)} or {@link #beginPublishing(Object, int)} methods. Note that subsequent calls 12 | * to these method will automatically cancel the previous periodic publishing and start again at the newly specified rate. 13 | * To cancel periodic publishing, use the {@link #cancelPublishing()} method. You can also change the message that is 14 | * being published without stopping or restarting the publishing by using the {@link #setMsg(Object)} method. 15 | * @author James MacGlashan. 16 | */ 17 | public class PeriodicPublisher { 18 | 19 | 20 | /** 21 | * The {@link ros.Publisher} to which publish calls are made 22 | */ 23 | protected Publisher pub; 24 | 25 | /** 26 | * The ROS message that will be periodically published 27 | */ 28 | protected volatile Object msg = null; 29 | 30 | /** 31 | * The period at which this object is publishing; -1 if it is not currently publishing. 32 | */ 33 | protected int period = -1; 34 | 35 | /** 36 | * The timer task that calls the publish. Null if this object is not periodically publishing. 37 | */ 38 | protected PublisherTimerTask timerTask; 39 | 40 | /** 41 | * Initializes with a {@link ros.Publisher} for this action using the specified topic, message type, and {@link ros.RosBridge} connection. 42 | * @param topic the ros topic to which messages will be published 43 | * @param msgType the ros message type of th target topic 44 | * @param rosBridge the {@link ros.RosBridge} connection to use. 45 | */ 46 | public PeriodicPublisher(String topic, String msgType, RosBridge rosBridge){ 47 | this.pub = new Publisher(topic, msgType, rosBridge); 48 | } 49 | 50 | /** 51 | * Initializes with a {@link ros.Publisher} for this action using the specified topic, message type, and {@link ros.RosBridge} connection. 52 | * @param topic the ros topic to which messages will be published 53 | * @param msgType the ros message type of th target topic 54 | * @param rosBridge the {@link ros.RosBridge} connection to use. 55 | * @param msg the ROS message to periodically publish when publishing begins 56 | */ 57 | public PeriodicPublisher(String topic, String msgType, RosBridge rosBridge, Object msg){ 58 | this.pub = new Publisher(topic, msgType, rosBridge); 59 | } 60 | 61 | /** 62 | * Initializes with a {@link ros.Publisher} for publishing action messages to ROS. 63 | * @param pub the {@link ros.Publisher} to which to publish 64 | */ 65 | public PeriodicPublisher(Publisher pub){ 66 | this.pub = pub; 67 | } 68 | 69 | /** 70 | * Initializes with a {@link ros.Publisher} for publishing action messages to ROS. 71 | * @param pub the {@link ros.Publisher} to which to publish 72 | * @param msg the ROS message to periodically publish when publishing begins 73 | */ 74 | public PeriodicPublisher(Publisher pub, Object msg) { 75 | this.pub = pub; 76 | this.msg = msg; 77 | } 78 | 79 | public Publisher getPub() { 80 | return pub; 81 | } 82 | 83 | public void setPub(Publisher pub) { 84 | this.pub = pub; 85 | } 86 | 87 | /** 88 | * Returns the ROS message that is being or will be periodically published. 89 | * @return the ROS message that is being or will be periodically published. 90 | */ 91 | public Object getMsg() { 92 | return msg; 93 | } 94 | 95 | /** 96 | * Sets the ROS message that will be periodically published. Note that if the provided message is null and this 97 | * object is already periodically publishing, a runtime error will be thrown. 98 | * @param msg the ROS message that will be published. 99 | */ 100 | public void setMsg(Object msg) { 101 | if(msg == null && this.timerTask != null){ 102 | System.out.println("PeriodicPublisher is not setting message because new message is null and publisher is currently publishing."); 103 | } 104 | this.msg = msg; 105 | } 106 | 107 | /** 108 | * The delay in milliseconds between subsequent periodic publishes. Returns -1 if this object is not currently periodically publishing. 109 | * @return The delay in milliseconds between subsequent publishes. Returns -1 if this object is not currently periodically publishing. 110 | */ 111 | public int getPublishingPeriod(){ 112 | return this.period; 113 | } 114 | 115 | /** 116 | * Stops this object from periodically publishing, or does nothing if it is not currently periodically publishing. 117 | */ 118 | public void cancelPublishing(){ 119 | if(this.timerTask != null) { 120 | this.timerTask.cancel(); 121 | this.timerTask = null; 122 | this.period = -1; 123 | } 124 | } 125 | 126 | /** 127 | * Indicates whether this object is currently periodically publishing. 128 | * @return true if this object is periodically publishing; false if it is not. 129 | */ 130 | public boolean isPublishing(){ 131 | return this.timerTask != null; 132 | } 133 | 134 | 135 | /** 136 | * Causes this object to begin periodically publishing at the specified rate. If this object is 137 | * already publishing then the previous publishing rate is canceled and started at the new rate. 138 | * If the current message to 139 | * publish is not set, then a runtime exception will be thrown. The message can be set either with the 140 | * {@link #setMsg(Object)} method or by using the alternative {@link #beginPublishing(Object, int)} 141 | * method that takes the message to publish. 142 | * @param period the time in milliseconds between publishes 143 | */ 144 | public void beginPublishing(int period){ 145 | if(this.msg == null){ 146 | throw new RuntimeException("Cannot begin publishing because the message to publish is unset. " + 147 | "Use the setMsg method or beginPublishing(Object msg, int period)"); 148 | } 149 | if(this.timerTask != null){ 150 | this.timerTask.cancel(); 151 | } 152 | this.timerTask = new PublisherTimerTask(); 153 | Timer timer = new Timer(); 154 | timer.schedule(this.timerTask, 0, period); 155 | } 156 | 157 | 158 | /** 159 | * Causes this object to begin periodically publishing the specified message at the specified rate. 160 | * If this object is already publishing then the previous publishing rate is canceled and started at the new rate. 161 | * Note that if the provided message is null then a runtime exception will be thrown. 162 | * @param msg the ROS message to publish 163 | * @param period the time in milliseconds between publishes 164 | */ 165 | public void beginPublishing(Object msg, int period){ 166 | this.msg = msg; 167 | this.beginPublishing(period); 168 | } 169 | 170 | 171 | /** 172 | * The class that is called by Java's {@link java.util.Timer} and invokes the actual 173 | * publish call. 174 | */ 175 | protected class PublisherTimerTask extends TimerTask{ 176 | 177 | @Override 178 | public void run() { 179 | pub.publish(msg); 180 | } 181 | } 182 | 183 | } 184 | -------------------------------------------------------------------------------- /src/tests/RosTest.java: -------------------------------------------------------------------------------- 1 | package tests; 2 | 3 | import com.fasterxml.jackson.databind.JsonNode; 4 | import ros.Publisher; 5 | import ros.RosBridge; 6 | import ros.RosListenDelegate; 7 | import ros.SubscriptionRequestMsg; 8 | import ros.msgs.std_msgs.PrimitiveMsg; 9 | import ros.tools.MessageUnpacker; 10 | 11 | /** 12 | * Example of connecting to rosbridge with publish/subscribe messages. Takes one argument: 13 | * the rosbridge websocket URI; for example: ws://localhost:9090. 14 | * @author James MacGlashan. 15 | */ 16 | public class RosTest { 17 | 18 | public static void main(String[] args) { 19 | 20 | if(args.length != 1){ 21 | System.out.println("Need the rosbridge websocket URI provided as argument. For example:\n\tws://localhost:9090"); 22 | System.exit(0); 23 | } 24 | 25 | RosBridge bridge = new RosBridge(); 26 | bridge.connect(args[0], true); 27 | 28 | bridge.subscribe(SubscriptionRequestMsg.generate("/ros_to_java") 29 | .setType("std_msgs/String") 30 | .setThrottleRate(1) 31 | .setQueueLength(1), 32 | new RosListenDelegate() { 33 | 34 | public void receive(JsonNode data, String stringRep) { 35 | MessageUnpacker> unpacker = new MessageUnpacker>(PrimitiveMsg.class); 36 | PrimitiveMsg msg = unpacker.unpackRosMessage(data); 37 | System.out.println(msg.data); 38 | } 39 | } 40 | ); 41 | 42 | Publisher pub = new Publisher("/java_to_ros", "std_msgs/String", bridge); 43 | 44 | for(int i = 0; i < 100; i++) { 45 | pub.publish(new PrimitiveMsg("hello from java " + i)); 46 | try { 47 | Thread.sleep(500); 48 | } catch (InterruptedException e) { 49 | e.printStackTrace(); 50 | } 51 | } 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /src/tests/SimpleEchoClient.java: -------------------------------------------------------------------------------- 1 | package tests; 2 | 3 | import java.net.URI; 4 | import java.util.concurrent.TimeUnit; 5 | import org.eclipse.jetty.websocket.client.ClientUpgradeRequest; 6 | import org.eclipse.jetty.websocket.client.WebSocketClient; 7 | 8 | /** 9 | * Example of a simple Echo Client. 10 | */ 11 | public class SimpleEchoClient { 12 | 13 | public static void main(String[] args) { 14 | String destUri = "ws://echo.websocket.org"; 15 | if (args.length > 0) { 16 | destUri = args[0]; 17 | } 18 | WebSocketClient client = new WebSocketClient(); 19 | SimpleEchoSocket socket = new SimpleEchoSocket(); 20 | try { 21 | client.start(); 22 | URI echoUri = new URI(destUri); 23 | ClientUpgradeRequest request = new ClientUpgradeRequest(); 24 | client.connect(socket, echoUri, request); 25 | System.out.printf("Connecting to : %s%n", echoUri); 26 | socket.awaitClose(15, TimeUnit.SECONDS); 27 | } catch (Throwable t) { 28 | t.printStackTrace(); 29 | } finally { 30 | try { 31 | client.stop(); 32 | } catch (Exception e) { 33 | e.printStackTrace(); 34 | } 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/tests/SimpleEchoSocket.java: -------------------------------------------------------------------------------- 1 | package tests; 2 | 3 | import java.util.concurrent.CountDownLatch; 4 | import java.util.concurrent.Future; 5 | import java.util.concurrent.TimeUnit; 6 | import org.eclipse.jetty.websocket.api.Session; 7 | import org.eclipse.jetty.websocket.api.StatusCode; 8 | import org.eclipse.jetty.websocket.api.annotations.OnWebSocketClose; 9 | import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect; 10 | import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage; 11 | import org.eclipse.jetty.websocket.api.annotations.WebSocket; 12 | 13 | /** 14 | * Basic Echo Client Socket 15 | */ 16 | @WebSocket(maxTextMessageSize = 64 * 1024) 17 | public class SimpleEchoSocket { 18 | 19 | private final CountDownLatch closeLatch; 20 | 21 | @SuppressWarnings("unused") 22 | private Session session; 23 | 24 | public SimpleEchoSocket() { 25 | this.closeLatch = new CountDownLatch(1); 26 | } 27 | 28 | public boolean awaitClose(int duration, TimeUnit unit) throws InterruptedException { 29 | return this.closeLatch.await(duration, unit); 30 | } 31 | 32 | @OnWebSocketClose 33 | public void onClose(int statusCode, String reason) { 34 | System.out.printf("Connection closed: %d - %s%n", statusCode, reason); 35 | this.session = null; 36 | this.closeLatch.countDown(); 37 | } 38 | 39 | @OnWebSocketConnect 40 | public void onConnect(Session session) { 41 | System.out.printf("Got connect: %s%n", session); 42 | this.session = session; 43 | try { 44 | Future fut; 45 | fut = session.getRemote().sendStringByFuture("Hello"); 46 | fut.get(2, TimeUnit.SECONDS); 47 | fut = session.getRemote().sendStringByFuture("Thanks for the conversation."); 48 | fut.get(2, TimeUnit.SECONDS); 49 | session.close(StatusCode.NORMAL, "I'm done"); 50 | } catch (Throwable t) { 51 | t.printStackTrace(); 52 | } 53 | } 54 | 55 | @OnWebSocketMessage 56 | public void onMessage(String msg) { 57 | System.out.printf("Got msg: %s%n", msg); 58 | } 59 | } 60 | --------------------------------------------------------------------------------