rosruby 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/bin/rubyroscore +5 -0
- data/lib/ros.rb +25 -0
- data/lib/ros/duration.rb +63 -0
- data/lib/ros/graph_manager.rb +408 -0
- data/lib/ros/log.rb +72 -0
- data/lib/ros/master.rb +408 -0
- data/lib/ros/master_proxy.rb +256 -0
- data/lib/ros/message.rb +65 -0
- data/lib/ros/name.rb +88 -0
- data/lib/ros/node.rb +442 -0
- data/lib/ros/package.rb +144 -0
- data/lib/ros/parameter_manager.rb +127 -0
- data/lib/ros/parameter_subscriber.rb +47 -0
- data/lib/ros/publisher.rb +96 -0
- data/lib/ros/rate.rb +41 -0
- data/lib/ros/ros.rb +10 -0
- data/lib/ros/roscore.rb +29 -0
- data/lib/ros/service.rb +37 -0
- data/lib/ros/service_client.rb +83 -0
- data/lib/ros/service_server.rb +92 -0
- data/lib/ros/slave_proxy.rb +153 -0
- data/lib/ros/subscriber.rb +119 -0
- data/lib/ros/tcpros/client.rb +108 -0
- data/lib/ros/tcpros/header.rb +89 -0
- data/lib/ros/tcpros/message.rb +74 -0
- data/lib/ros/tcpros/server.rb +137 -0
- data/lib/ros/tcpros/service_client.rb +104 -0
- data/lib/ros/tcpros/service_server.rb +132 -0
- data/lib/ros/time.rb +109 -0
- data/lib/ros/topic.rb +47 -0
- data/lib/ros/xmlrpcserver.rb +40 -0
- data/samples/add_two_ints_client.rb +25 -0
- data/samples/add_two_ints_server.rb +20 -0
- data/samples/gui.rb +126 -0
- data/samples/sample_log.rb +16 -0
- data/samples/sample_param.rb +20 -0
- data/samples/sample_publisher.rb +20 -0
- data/samples/sample_subscriber.rb +19 -0
- data/scripts/genmsg_ruby.py +1135 -0
- data/scripts/genmsg_ruby.pyc +0 -0
- data/scripts/gensrv_ruby.py +105 -0
- data/scripts/gensrv_ruby.pyc +0 -0
- data/scripts/rosruby_genmsg.py +67 -0
- data/scripts/run-test.rb +21 -0
- data/test/test_header.rb +36 -0
- data/test/test_log.rb +45 -0
- data/test/test_master_proxy.rb +73 -0
- data/test/test_message.rb +13 -0
- data/test/test_node.rb +166 -0
- data/test/test_package.rb +10 -0
- data/test/test_param.rb +27 -0
- data/test/test_pubsub.rb +154 -0
- data/test/test_rate.rb +16 -0
- data/test/test_service.rb +34 -0
- data/test/test_slave_proxy.rb +49 -0
- data/test/test_time.rb +39 -0
- metadata +170 -0
@@ -0,0 +1,108 @@
|
|
1
|
+
# ros/tcpros/client.rb
|
2
|
+
#
|
3
|
+
# License: BSD
|
4
|
+
#
|
5
|
+
# Copyright (C) 2012 Takashi Ogura <t.ogura@gmail.com>
|
6
|
+
#
|
7
|
+
|
8
|
+
require 'socket'
|
9
|
+
require 'ros/tcpros/message'
|
10
|
+
require 'ros/tcpros/header'
|
11
|
+
|
12
|
+
module ROS::TCPROS
|
13
|
+
|
14
|
+
##
|
15
|
+
# rosrpc's client for subscriber
|
16
|
+
#
|
17
|
+
class Client
|
18
|
+
|
19
|
+
include ::ROS::TCPROS::Message
|
20
|
+
|
21
|
+
##
|
22
|
+
# @param [String] host host name
|
23
|
+
# @param [Integer] port port number
|
24
|
+
# @param [String] caller_id caller id of this node
|
25
|
+
# @param [String] topic_name name of this topic
|
26
|
+
# @param [Class] topic_type type of topic
|
27
|
+
# @param [String] target_uri URI of connection target
|
28
|
+
# @param [Boolean] tcp_no_delay use tcp no delay option or not
|
29
|
+
def initialize(host, port,
|
30
|
+
caller_id, topic_name, topic_type, target_uri,
|
31
|
+
tcp_no_delay)
|
32
|
+
@caller_id = caller_id
|
33
|
+
@topic_name = topic_name
|
34
|
+
@topic_type = topic_type
|
35
|
+
@port = port
|
36
|
+
@host = host
|
37
|
+
@target_uri = target_uri
|
38
|
+
@msg_queue = Queue.new
|
39
|
+
@socket = TCPSocket.open(@host, @port)
|
40
|
+
@tcp_no_delay = tcp_no_delay
|
41
|
+
if tcp_no_delay
|
42
|
+
@socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
|
43
|
+
end
|
44
|
+
@byte_received = 0
|
45
|
+
@is_running = true
|
46
|
+
end
|
47
|
+
|
48
|
+
##
|
49
|
+
# build header data for subscription.
|
50
|
+
# @return [TCPROS::Header] built header
|
51
|
+
def build_header
|
52
|
+
header = Header.new
|
53
|
+
header.push_data("callerid", @caller_id)
|
54
|
+
header.push_data("topic", @topic_name)
|
55
|
+
header.push_data("md5sum", @topic_type.md5sum)
|
56
|
+
header.push_data("type", @topic_type.type)
|
57
|
+
if @tcp_no_delay
|
58
|
+
header.push_data("tcp_nodelay", '1')
|
59
|
+
else
|
60
|
+
header.push_data("tcp_nodelay", '0')
|
61
|
+
end
|
62
|
+
header
|
63
|
+
end
|
64
|
+
|
65
|
+
##
|
66
|
+
# start recieving data.
|
67
|
+
# The received messages are pushed into a message queue.
|
68
|
+
def start
|
69
|
+
write_header(@socket, build_header)
|
70
|
+
read_header(@socket)
|
71
|
+
@thread = Thread.start do
|
72
|
+
while @is_running
|
73
|
+
data = read_all(@socket)
|
74
|
+
msg = @topic_type.new
|
75
|
+
msg.deserialize(data)
|
76
|
+
@byte_received += data.length
|
77
|
+
@msg_queue.push(msg)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
##
|
83
|
+
# close the connection.
|
84
|
+
# kill the thread if it is not response.
|
85
|
+
def shutdown
|
86
|
+
@is_running = false
|
87
|
+
if not @thread.join(0.1)
|
88
|
+
Thread::kill(@thread)
|
89
|
+
end
|
90
|
+
if not @socket.closed?
|
91
|
+
@socket.close
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
# @return [Integer] port number of this client
|
96
|
+
attr_reader :port
|
97
|
+
# @return [String] host name
|
98
|
+
attr_reader :host
|
99
|
+
# @return [Queue] message queue
|
100
|
+
attr_reader :msg_queue
|
101
|
+
# @return [Integer] received byte
|
102
|
+
attr_reader :byte_received
|
103
|
+
# @return [String] URI of connection target
|
104
|
+
attr_reader :target_uri
|
105
|
+
# @return [String] id for slave API
|
106
|
+
attr_accessor :id
|
107
|
+
end
|
108
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
# ros/tcpros/message.rb
|
2
|
+
#
|
3
|
+
# License: BSD
|
4
|
+
#
|
5
|
+
# Copyright (C) 2012 Takashi Ogura <t.ogura@gmail.com>
|
6
|
+
#
|
7
|
+
|
8
|
+
require 'ros/ros'
|
9
|
+
|
10
|
+
module ROS::TCPROS
|
11
|
+
|
12
|
+
##
|
13
|
+
# header of rorpc's protocol
|
14
|
+
#
|
15
|
+
class Header
|
16
|
+
|
17
|
+
# rosrpc uses this wild card for cancel md5sum check or any.
|
18
|
+
WILD_CARD = '*'
|
19
|
+
|
20
|
+
# initialize with hash
|
21
|
+
# @param [Hash] data
|
22
|
+
def initialize(data={})
|
23
|
+
@data = data
|
24
|
+
end
|
25
|
+
|
26
|
+
# add key-value data to this header.
|
27
|
+
# @param [String] key key for header
|
28
|
+
# @param [String] value value for key
|
29
|
+
# @return [Header] self
|
30
|
+
def push_data(key, value)
|
31
|
+
if (not key.kind_of?(String)) or (not value.kind_of?(String))
|
32
|
+
raise ArgumentError::new('header key and value must be string')
|
33
|
+
end
|
34
|
+
@data[key] = value
|
35
|
+
self
|
36
|
+
end
|
37
|
+
|
38
|
+
# @param [String] key
|
39
|
+
# @return [String] value of key
|
40
|
+
def get_data(key)
|
41
|
+
@data[key]
|
42
|
+
end
|
43
|
+
|
44
|
+
alias_method :[]=, :push_data
|
45
|
+
alias_method :[], :get_data
|
46
|
+
|
47
|
+
# deserialize the data to header.
|
48
|
+
# this does not contain total byte number.
|
49
|
+
# @param [String] data
|
50
|
+
# @return [Header] self
|
51
|
+
def deserialize(data)
|
52
|
+
while data.length > 0
|
53
|
+
len, data = data.unpack('Va*')
|
54
|
+
msg = data[0..(len-1)]
|
55
|
+
equal_position = msg.index('=')
|
56
|
+
key = msg[0..(equal_position-1)]
|
57
|
+
value = msg[(equal_position+1)..-1]
|
58
|
+
@data[key] = value
|
59
|
+
data = data[(len)..-1]
|
60
|
+
end
|
61
|
+
self
|
62
|
+
end
|
63
|
+
|
64
|
+
##
|
65
|
+
# validate the key with the value.
|
66
|
+
# if it is WILD_CARD, it is ok!
|
67
|
+
# @param [String] key
|
68
|
+
# @param [String] value
|
69
|
+
# @return [Boolean] check result
|
70
|
+
def valid?(key, value)
|
71
|
+
(@data[key] == value) or value == WILD_CARD
|
72
|
+
end
|
73
|
+
|
74
|
+
# serialize the data into header.
|
75
|
+
# return the byte of the serialized data.
|
76
|
+
# @param [IO] buff where to write data
|
77
|
+
# @return [Integer] byte of serialized data
|
78
|
+
def serialize(buff)
|
79
|
+
serialized_data = ''
|
80
|
+
@data.each_pair do |key, value|
|
81
|
+
data_str = key + '=' + value
|
82
|
+
serialized_data = serialized_data + [data_str.length, data_str].pack('Va*')
|
83
|
+
end
|
84
|
+
total_byte = serialized_data.length
|
85
|
+
return buff.write([total_byte, serialized_data].pack('Va*'))
|
86
|
+
end
|
87
|
+
|
88
|
+
end
|
89
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
# ros/tcpros/message.rb
|
2
|
+
#
|
3
|
+
# License: BSD
|
4
|
+
#
|
5
|
+
# Copyright (C) 2012 Takashi Ogura <t.ogura@gmail.com>
|
6
|
+
#
|
7
|
+
# super class of TCPROS connections
|
8
|
+
# document is http://ros.org/wiki/ROS/TCPROS
|
9
|
+
#
|
10
|
+
require 'stringio'
|
11
|
+
require 'ros/tcpros/header'
|
12
|
+
|
13
|
+
|
14
|
+
# TCP connection between nodes.
|
15
|
+
# protocol document is http://ros.org/wiki/ROS/TCPROS
|
16
|
+
module ROS::TCPROS
|
17
|
+
|
18
|
+
# functions for TCPROS
|
19
|
+
module Message
|
20
|
+
|
21
|
+
##
|
22
|
+
# write message to socket.
|
23
|
+
# @param [IO] socket socket for writing
|
24
|
+
# @param [Message] msg message for writing
|
25
|
+
# @return [String] wrote data
|
26
|
+
def write_msg(socket, msg)
|
27
|
+
sio = StringIO.new('', 'r+')
|
28
|
+
len = msg.serialize(sio)
|
29
|
+
sio.rewind
|
30
|
+
data = sio.read
|
31
|
+
len = data.length
|
32
|
+
data = [len, data].pack("La#{len}")
|
33
|
+
socket.write(data)
|
34
|
+
data
|
35
|
+
end
|
36
|
+
|
37
|
+
##
|
38
|
+
# read the size of data and read it from socket.
|
39
|
+
# @param [IO] socket socket for reading
|
40
|
+
# @return [String] received data
|
41
|
+
def read_all(socket)
|
42
|
+
total_bytes = socket.recv(4).unpack("V")[0]
|
43
|
+
if total_bytes and total_bytes > 0
|
44
|
+
socket.recv(total_bytes)
|
45
|
+
else
|
46
|
+
''
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
##
|
51
|
+
# read a connection header from socket
|
52
|
+
# @param [String] socket socket for reading
|
53
|
+
# @return [Header] header
|
54
|
+
def read_header(socket)
|
55
|
+
header = ::ROS::TCPROS::Header.new
|
56
|
+
header.deserialize(read_all(socket))
|
57
|
+
header
|
58
|
+
end
|
59
|
+
|
60
|
+
##
|
61
|
+
# write a connection header to socket.
|
62
|
+
# @param [IO] socket socket for writing.
|
63
|
+
# @param [Header] header header data
|
64
|
+
def write_header(socket, header)
|
65
|
+
header.serialize(socket)
|
66
|
+
socket.flush
|
67
|
+
end
|
68
|
+
|
69
|
+
# @return [String] prototol string 'TCPROS'
|
70
|
+
def protocol
|
71
|
+
'TCPROS'
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,137 @@
|
|
1
|
+
# ros/subscriber.rb
|
2
|
+
#
|
3
|
+
# License: BSD
|
4
|
+
#
|
5
|
+
# Copyright (C) 2012 Takashi Ogura <t.ogura@gmail.com>
|
6
|
+
#
|
7
|
+
require 'ros/tcpros/message'
|
8
|
+
require 'gserver'
|
9
|
+
|
10
|
+
module ROS::TCPROS
|
11
|
+
|
12
|
+
##
|
13
|
+
# This is TCPROS connection for {ROS::Publihser}
|
14
|
+
#
|
15
|
+
class Server < ::GServer
|
16
|
+
|
17
|
+
include ::ROS::TCPROS::Message
|
18
|
+
|
19
|
+
# max number of connections with {TCPROS::Client}
|
20
|
+
MAX_CONNECTION = 100
|
21
|
+
|
22
|
+
##
|
23
|
+
# @param [String] caller_id caller id of this node
|
24
|
+
# @param [String] topic_name name of this topic
|
25
|
+
# @param [Class] topic_type type of topic
|
26
|
+
# @param [Hash] options :latched, :port, host, :last_published_msg
|
27
|
+
# @option [Boolean] options :latched (false)
|
28
|
+
# @option [Integer] options :port (0) tcp port number.
|
29
|
+
# @option [String] options :host (GServer::DEFAULT_HOST) host name
|
30
|
+
# @option [Message] options :last_published_msg
|
31
|
+
def initialize(caller_id, topic_name, topic_type, options={})
|
32
|
+
if options[:port]
|
33
|
+
port = options[:port]
|
34
|
+
else
|
35
|
+
port = 0
|
36
|
+
end
|
37
|
+
if options[:host]
|
38
|
+
host = options[:host]
|
39
|
+
else
|
40
|
+
host = GServer::DEFAULT_HOST
|
41
|
+
end
|
42
|
+
|
43
|
+
super(port, host, MAX_CONNECTION)
|
44
|
+
@caller_id = caller_id
|
45
|
+
@topic_name = topic_name
|
46
|
+
@topic_type = topic_type
|
47
|
+
@msg_queue = Queue.new
|
48
|
+
@is_latched = options[:latched]
|
49
|
+
@last_published_msg = options[:last_published_msg]
|
50
|
+
@byte_sent = 0
|
51
|
+
@num_sent = 0
|
52
|
+
end
|
53
|
+
|
54
|
+
##
|
55
|
+
# Is this latching publisher?
|
56
|
+
# @return [Boolean] is latched or not
|
57
|
+
def latching?
|
58
|
+
@is_latched
|
59
|
+
end
|
60
|
+
|
61
|
+
##
|
62
|
+
# send a message to reciever
|
63
|
+
# @param [IO] socket socket for writing
|
64
|
+
# @param [Class] msg msg class instance
|
65
|
+
def publish_msg(socket, msg)
|
66
|
+
data = write_msg(socket, msg)
|
67
|
+
@last_published_msg = msg
|
68
|
+
# for getBusStats
|
69
|
+
@byte_sent += data.length
|
70
|
+
@num_sent += 1
|
71
|
+
end
|
72
|
+
|
73
|
+
##
|
74
|
+
# this is called if a socket accept a connection.
|
75
|
+
# This is GServer's function
|
76
|
+
# @param [IO] socket given socket
|
77
|
+
def serve(socket) #:nodoc:
|
78
|
+
header = read_header(socket)
|
79
|
+
if check_header(header)
|
80
|
+
if header['tcp_nodelay'] == '1'
|
81
|
+
socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
|
82
|
+
end
|
83
|
+
begin
|
84
|
+
write_header(socket, build_header)
|
85
|
+
if latching?
|
86
|
+
if @last_published_msg
|
87
|
+
publish_msg(socket, @last_published_msg)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
loop do
|
91
|
+
@last_published_msg = @msg_queue.pop
|
92
|
+
publish_msg(socket, @last_published_msg)
|
93
|
+
end
|
94
|
+
rescue
|
95
|
+
socket.shutdown
|
96
|
+
end
|
97
|
+
else
|
98
|
+
socket.shutdown
|
99
|
+
p "header check error: #{header}"
|
100
|
+
raise 'header check error'
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
attr_reader :caller_id, :msg_queue, :byte_sent, :num_sent
|
105
|
+
|
106
|
+
# id for slave API
|
107
|
+
# @return [String]
|
108
|
+
attr_accessor :id
|
109
|
+
attr_accessor :last_published_msg
|
110
|
+
|
111
|
+
##
|
112
|
+
# validate header for this publisher
|
113
|
+
# @param [Header] header for checking
|
114
|
+
# @return [Boolean] ok(true) or not
|
115
|
+
def check_header(header)
|
116
|
+
header.valid?('type', @topic_type.type) and
|
117
|
+
header.valid?('md5sum', @topic_type.md5sum)
|
118
|
+
end
|
119
|
+
|
120
|
+
##
|
121
|
+
# build {TCPROS::Header} message for this publisher.
|
122
|
+
# It contains callerid, topic, md5sum, type, latching.
|
123
|
+
# @return [Header] built header
|
124
|
+
def build_header
|
125
|
+
header = Header.new
|
126
|
+
header["callerid"] = @caller_id
|
127
|
+
header["topic"] = @topic_name
|
128
|
+
header["md5sum"] = @topic_type.md5sum
|
129
|
+
header["type"] = @topic_type.type
|
130
|
+
if latching?
|
131
|
+
header["latching"] = '1'
|
132
|
+
end
|
133
|
+
header
|
134
|
+
end
|
135
|
+
|
136
|
+
end
|
137
|
+
end
|
@@ -0,0 +1,104 @@
|
|
1
|
+
# ros/tcpros/service_client.rb
|
2
|
+
#
|
3
|
+
# License: BSD
|
4
|
+
#
|
5
|
+
# Copyright (C) 2012 Takashi Ogura <t.ogura@gmail.com>
|
6
|
+
#
|
7
|
+
require 'socket'
|
8
|
+
require 'ros/tcpros/header'
|
9
|
+
require 'ros/tcpros/message'
|
10
|
+
|
11
|
+
module ROS::TCPROS
|
12
|
+
|
13
|
+
##
|
14
|
+
# TCPROS protocol service client connection
|
15
|
+
#
|
16
|
+
class ServiceClient
|
17
|
+
|
18
|
+
include ::ROS::TCPROS::Message
|
19
|
+
|
20
|
+
# @param [String] host host name
|
21
|
+
# @param [Integer] port port number
|
22
|
+
# @param [String] caller_id caller_id of this node
|
23
|
+
# @param [String] service_name name of service
|
24
|
+
# @param [Class] service_type class of this service
|
25
|
+
# @param [Boolean] persistent use persistent connection or not
|
26
|
+
def initialize(host, port, caller_id, service_name, service_type, persistent)
|
27
|
+
@caller_id = caller_id
|
28
|
+
@service_name = service_name
|
29
|
+
@service_type = service_type
|
30
|
+
@port = port
|
31
|
+
@host = host
|
32
|
+
@socket = TCPSocket.open(@host, @port)
|
33
|
+
@persistent = persistent
|
34
|
+
@socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
|
35
|
+
end
|
36
|
+
|
37
|
+
##
|
38
|
+
# build henader message for service client.
|
39
|
+
# It contains callerid, service, md5sum, type, persistent.
|
40
|
+
# @return [Header] header
|
41
|
+
def build_header
|
42
|
+
header = Header.new
|
43
|
+
header.push_data("callerid", @caller_id)
|
44
|
+
header.push_data("service", @service_name)
|
45
|
+
header.push_data("md5sum", @service_type.md5sum)
|
46
|
+
header.push_data("type", @service_type.type)
|
47
|
+
if @persistent
|
48
|
+
header.push_data("persistent", '1')
|
49
|
+
end
|
50
|
+
header
|
51
|
+
end
|
52
|
+
|
53
|
+
##
|
54
|
+
# call the service by sending srv request message,
|
55
|
+
# and receive response message.
|
56
|
+
# @param [Message] srv_request call with this request
|
57
|
+
# @param [Message] srv_response response is stored in this message
|
58
|
+
# @return [Boolean] result of call
|
59
|
+
def call(srv_request, srv_response)
|
60
|
+
write_header(@socket, build_header)
|
61
|
+
if check_header(read_header(@socket))
|
62
|
+
write_msg(@socket, srv_request)
|
63
|
+
@socket.flush
|
64
|
+
ok_byte = read_ok_byte
|
65
|
+
if ok_byte == 1
|
66
|
+
srv_response.deserialize(read_all(@socket))
|
67
|
+
return true
|
68
|
+
end
|
69
|
+
false
|
70
|
+
end
|
71
|
+
false
|
72
|
+
end
|
73
|
+
|
74
|
+
##
|
75
|
+
# read ok byte for boolean service result.
|
76
|
+
# @return [Integer] 1 for OK, 0 for NG
|
77
|
+
def read_ok_byte
|
78
|
+
@socket.recv(1).unpack('c')[0]
|
79
|
+
end
|
80
|
+
|
81
|
+
##
|
82
|
+
# check md5sum only.
|
83
|
+
# @param [Header] header received header
|
84
|
+
# @return [Boolean] true if it is ok.
|
85
|
+
def check_header(header)
|
86
|
+
header.valid?('md5sum', @service_type.md5sum)
|
87
|
+
end
|
88
|
+
|
89
|
+
##
|
90
|
+
# close the socket
|
91
|
+
def shutdown
|
92
|
+
@socket.close
|
93
|
+
end
|
94
|
+
|
95
|
+
# port number of this socket
|
96
|
+
# @return [Integer] port number
|
97
|
+
attr_reader :port
|
98
|
+
|
99
|
+
# host of this connection
|
100
|
+
# @return [String] host name
|
101
|
+
attr_reader :host
|
102
|
+
|
103
|
+
end
|
104
|
+
end
|