├── .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 | *
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