rosruby 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|