-
Notifications
You must be signed in to change notification settings - Fork 385
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
TCP connections to ROS bridge for node
- Loading branch information
Showing
7 changed files
with
213 additions
and
107 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
/** | ||
* ROSLIB Node exclusive extensions | ||
*/ | ||
var assign = require('object-assign'); | ||
|
||
module.exports = assign(require('./RosLib'), { | ||
Ros: require('./node/RosTCP.js') | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,114 @@ | ||
/** | ||
* Socket event handling utilities for handling events on either | ||
* WebSocket and TCP sockets | ||
* | ||
* Note to anyone reviewing this code: these functions are called | ||
* in the context of their parent object, unless bound | ||
*/ | ||
'use strict'; | ||
|
||
var Canvas = require('canvas'); | ||
var Image = Canvas.Image || global.Image; | ||
var WebSocket = require('ws'); | ||
|
||
/** | ||
* If a message was compressed as a PNG image (a compression hack since | ||
* gzipping over WebSockets * is not supported yet), this function places the | ||
* "image" in a canvas element then decodes the * "image" as a Base64 string. | ||
* | ||
* @param data - object containing the PNG data. | ||
* @param callback - function with params: | ||
* * data - the uncompressed data | ||
*/ | ||
function decompressPng(data, callback) { | ||
// Uncompresses the data before sending it through (use image/canvas to do so). | ||
var image = new Image(); | ||
// When the image loads, extracts the raw data (JSON message). | ||
image.onload = function() { | ||
// Creates a local canvas to draw on. | ||
var canvas = new Canvas(); | ||
var context = canvas.getContext('2d'); | ||
|
||
// Sets width and height. | ||
canvas.width = image.width; | ||
canvas.height = image.height; | ||
|
||
// Puts the data into the image. | ||
context.drawImage(image, 0, 0); | ||
// Grabs the raw, uncompressed data. | ||
var imageData = context.getImageData(0, 0, image.width, image.height).data; | ||
|
||
// Constructs the JSON. | ||
var jsonData = ''; | ||
for (var i = 0; i < imageData.length; i += 4) { | ||
// RGB | ||
jsonData += String.fromCharCode(imageData[i], imageData[i + 1], imageData[i + 2]); | ||
} | ||
callback(JSON.parse(jsonData)); | ||
}; | ||
// Sends the image data to load. | ||
image.src = 'data:image/png;base64,' + data.data; | ||
} | ||
|
||
/** | ||
* Events listeners for a WebSocket or TCP socket to a JavaScript | ||
* ROS Client. Sets up Messages for a given topic to trigger an | ||
* event on the ROS client. | ||
*/ | ||
function SocketAdapter(client) { | ||
function handleMessage(message) { | ||
if (message.op === 'publish') { | ||
client.emit(message.topic, message.msg); | ||
} else if (message.op === 'service_response') { | ||
client.emit(message.id, message); | ||
} | ||
} | ||
|
||
return { | ||
/** | ||
* Emits a 'connection' event on WebSocket connection. | ||
* | ||
* @param event - the argument to emit with the event. | ||
*/ | ||
onopen: function onOpen(event) { | ||
client.isConnected = true; | ||
client.emit('connection', event); | ||
}, | ||
|
||
/** | ||
* Emits a 'close' event on WebSocket disconnection. | ||
* | ||
* @param event - the argument to emit with the event. | ||
*/ | ||
onclose: function onClose(event) { | ||
client.isConnected = false; | ||
client.emit('close', event); | ||
}, | ||
|
||
/** | ||
* Emits an 'error' event whenever there was an error. | ||
* | ||
* @param event - the argument to emit with the event. | ||
*/ | ||
onerror: function onError(event) { | ||
client.emit('error', event); | ||
}, | ||
|
||
/** | ||
* Parses message responses from rosbridge and sends to the appropriate | ||
* topic, service, or param. | ||
* | ||
* @param message - the raw JSON message from rosbridge. | ||
*/ | ||
onmessage: function onMessage(message) { | ||
var data = JSON.parse(typeof message === 'string' ? message : message.data); | ||
if (data.op === 'png') { | ||
decompressPng(data, handleMessage); | ||
} else { | ||
handleMessage(data); | ||
} | ||
} | ||
}; | ||
} | ||
|
||
module.exports = SocketAdapter; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
var Ros = require('../core/Ros'); | ||
var net = require('net'); | ||
var socketAdapter = require('../core/SocketAdapter.js'); | ||
var util = require('util'); | ||
|
||
/** | ||
* Same as core Ros except supports TCP connections | ||
*/ | ||
function RosTCP(options) { | ||
options = options || {}; | ||
if (!options.encoding) { | ||
util.debug('ROSLib uses utf8 encoding by default.' + | ||
'It would be more efficent to use ascii (if possible)'); | ||
} | ||
this.encoding = options.encoding || 'utf8'; | ||
Ros.call(this, options); | ||
|
||
if (!this.socket && (options.host || options.port)) { | ||
this.connect({ | ||
host: options.host, | ||
port: options.port | ||
}); | ||
} | ||
} | ||
|
||
util.inherits(RosTCP, Ros); | ||
|
||
/** | ||
* Connects to a live socket | ||
* | ||
* * url (String|Int|Object): Address and port to connect to (see http://nodejs.org/api/net.html) | ||
* format {host: String, port: Int} or (port:Int), or "host:port" | ||
*/ | ||
RosTCP.prototype.connect = function(url) { | ||
if (typeof url === 'string' && url.slice(0, 5) === 'ws://') { | ||
Ros.prototype.connect.call(this, url); | ||
} else { | ||
var events = socketAdapter(this); | ||
this.socket = net.connect(url) | ||
.on('data', events.onmessage) | ||
.on('close', events.onclose) | ||
.on('error', events.onerror) | ||
.on('connect', events.onopen); | ||
this.socket.setEncoding(this.encoding); | ||
this.socket.setTimeout(0); | ||
|
||
// Little hack for call on connection | ||
this.socket.send = this.socket.write; | ||
// Similarly for close | ||
this.socket.close = this.socket.end; | ||
} | ||
}; | ||
|
||
module.exports = RosTCP; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
var expect = require('chai').expect; | ||
var ROSLIB = require('../..'); | ||
|
||
var expectedTopics = [ | ||
// '/turtle1/cmd_vel', '/turtle1/color_sensor', '/turtle1/pose', | ||
// '/turtle2/cmd_vel', '/turtle2/color_sensor', '/turtle2/pose', | ||
'/tf2_web_republisher/status', '/tf2_web_republisher/feedback', | ||
// '/tf2_web_republisher/goal', '/tf2_web_republisher/result', | ||
'/fibonacci/feedback', '/fibonacci/status', '/fibonacci/result' | ||
]; | ||
|
||
describe('Example topics are live', function(done) { | ||
it('getTopics', function(done) { | ||
var ros = new ROSLIB.Ros({ | ||
port: 9090 | ||
}); | ||
ros.getTopics(function(topics) { | ||
expectedTopics.forEach(function(topic) { | ||
expect(topics).to.contain(topic, 'Couldn\'t find topic: ' + topic); | ||
}); | ||
done(); | ||
}); | ||
}); | ||
}); |