ruby-player 0.0.1 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,59 @@
1
+ # Ruby Player - Ruby client library for Player (tools for robots)
2
+ #
3
+ # Copyright (C) 2012 Timin Aleksey
4
+ #
5
+ # This program is free software: you can redistribute it and/or modify
6
+ # it under the terms of the GNU General Public License as published by
7
+ # the Free Software Foundation, either version 3 of the License, or
8
+ # (at your option) any later version.
9
+ #
10
+ # This program is distributed in the hope that it will be useful,
11
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ # GNU General Public License for more details.
14
+
15
+ module Player
16
+ class DevAddr
17
+ include Common
18
+
19
+ attr_reader :host, :robot, :interface, :index
20
+
21
+ def initialize(addr = {})
22
+ @host = addr[:host].to_i
23
+ @robot = addr[:robot].to_i
24
+ @interface = addr[:interface].to_i
25
+ @index = addr[:index].to_i
26
+ end
27
+
28
+ def DevAddr.decode(str)
29
+ ary = str.unpack("NNNN")
30
+ DevAddr.new(host: ary[0], robot: ary[1], interface: ary[2], index: ary[3])
31
+ end
32
+
33
+ def encode
34
+ [@host, @robot, @interface, @index].pack("NNNN")
35
+ end
36
+
37
+ def interface_name
38
+ @interface_name ||= search_interface_name(@interface)
39
+ end
40
+
41
+ def ==(other)
42
+ @host == other.host and @robot == other.robot and @interface == other.interface and @index == other.index
43
+ end
44
+
45
+ def to_s
46
+ "device addr [#@host:#@robot] #@interface_name:#@index"
47
+ end
48
+
49
+ def to_a
50
+ [@host, @robot, @interface, @index]
51
+ end
52
+
53
+ private
54
+ def search_interface_name(code)
55
+ name = search_const_name code, /PLAYER_[\w]*_CODE/
56
+ name.to_s.scan(/PLAYER_([\w]*)_CODE/).join.downcase
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,60 @@
1
+ # Ruby Player - Ruby client library for Player (tools for robots)
2
+ #
3
+ # Copyright (C) 2012 Timin Aleksey
4
+ #
5
+ # This program is free software: you can redistribute it and/or modify
6
+ # it under the terms of the GNU General Public License as published by
7
+ # the Free Software Foundation, either version 3 of the License, or
8
+ # (at your option) any later version.
9
+ #
10
+ # This program is distributed in the hope that it will be useful,
11
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ # GNU General Public License for more details.
14
+ require "socket"
15
+
16
+ module Player
17
+ class Device
18
+ include Common
19
+
20
+ # Device address
21
+ attr_reader :addr
22
+
23
+ # Device geometry
24
+ # @return [Hash] geometry { :px, :py. :pz, :proll, :ppitch, :pyaw, :sw, :sl, :sh }
25
+ attr_reader :geom
26
+
27
+
28
+ def initialize(addr, client, log_level)
29
+ @addr, @client = addr, client
30
+ @log_level = log_level
31
+
32
+ @geom = {px: 0.0, py: 0.0, pz: 0.0, proll: 0.0, ppitch: 0.0, pyaw: 0.0, sw: 0.0, sl: 0.0, sh: 0.0}
33
+ end
34
+
35
+ def fill(hdr,msg)
36
+ raise_error "Method `fill` isn't implemented for `#{self.class}`"
37
+ end
38
+
39
+ def handle_response(hdr, msg)
40
+ raise_error "Method `handle_response` isn't implemented for `#{self.class}`"
41
+ end
42
+
43
+ private
44
+ def send_message(type, subtype, msg="")
45
+ @client.write( Header.new(
46
+ dev_addr: @addr,
47
+ type: type,
48
+ subtype: subtype,
49
+ size: msg.bytesize), msg)
50
+ end
51
+
52
+ def read_geom(msg)
53
+ data = msg.unpack("G*")
54
+ [:px,:py,:pz, :proll,:ppitch,:pyaw, :sw,:sl,:sh].each_with_index do |k,i|
55
+ @geom[k] = data[i]
56
+ end
57
+ debug("Get geom px=%.2f py=%.2f pz=%.2f; proll=%.2f, ppitch=%.2f, pyaw=%.2f, sw=%.2f, sl=%.2f, sh=%.2f" % @geom.values)
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,81 @@
1
+ # Ruby Player - Ruby client library for Player (tools for robots)
2
+ #
3
+ # Copyright (C) 2012 Timin Aleksey
4
+ #
5
+ # This program is free software: you can redistribute it and/or modify
6
+ # it under the terms of the GNU General Public License as published by
7
+ # the Free Software Foundation, either version 3 of the License, or
8
+ # (at your option) any later version.
9
+ #
10
+ # This program is distributed in the hope that it will be useful,
11
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ # GNU General Public License for more details.
14
+
15
+ module Player
16
+ class Header
17
+ include Common
18
+
19
+ attr_reader :dev_addr, :type, :subtype, :time, :seq, :size
20
+
21
+ def initialize(param = {})
22
+ @dev_addr = param[:dev_addr]
23
+ @type = param[:type].to_i
24
+ @subtype = param[:subtype].to_i
25
+ @time = param[:time] || Time.now.to_f / 1000
26
+ @seq = param[:seq].to_i
27
+ @size = param[:size].to_i
28
+
29
+ end
30
+
31
+ def Header.decode(str)
32
+ dev_addr = DevAddr.decode(str[0,16])
33
+ type, subtype, time, seq, size = str[16,24].unpack("NNGNN")
34
+ Header.new(dev_addr: dev_addr, type: type, subtype: subtype, time: time, seq: seq, size: size)
35
+ end
36
+
37
+ def Header.from_a(ary)
38
+ Header.decode ary.pack("NNNNNNGNN")
39
+ end
40
+
41
+ def encode
42
+ @dev_addr.encode + [@type, @subtype, @time, @seq, @size].pack("NNGNN")
43
+ end
44
+
45
+ def ==(other)
46
+ @dev_addr == other.dev_addr and @type == other.type and @subtype == other.subtype and @time == other.time and @seq == other.seq and @size == other.size
47
+ end
48
+
49
+ def to_s
50
+ "header to #@dev_addr of message [type=#{type_name}, subtype=#{subtype_name}, size=#@size]"
51
+ end
52
+
53
+ def type_name
54
+ @type_name ||= search_msg_type_name(@type)
55
+ end
56
+
57
+ def subtype_name
58
+ @subtype_name ||= search_msg_subtype_name(dev_addr.interface_name, type_name, @subtype)
59
+ end
60
+
61
+ private
62
+ def search_msg_type_name(type)
63
+ name = search_const_name type, /PLAYER_MSGTYPE_[\w]*/
64
+ if name != ""
65
+ name[7..-1]
66
+ else
67
+ type.to_s
68
+ end
69
+ end
70
+
71
+ def search_msg_subtype_name(interface_name, type_name, subtype)
72
+ type_prefix = type_name == "MSGTYPE_RESP_ACK" ? "" : type_name.split("_")[-1]
73
+ name = search_const_name subtype, "PLAYER_" + interface_name.upcase + "_" + type_prefix + ".*"
74
+ if name != ""
75
+ name[7..-1]
76
+ else
77
+ subtype.to_s
78
+ end
79
+ end
80
+ end
81
+ end
@@ -17,146 +17,192 @@ module Player
17
17
  #
18
18
  # @example
19
19
  # # get proxy object
20
- # pos2d = client[:position2d, 0]
20
+ # pos2d = client.subcribe("position2d", index: 0)
21
21
  # # setup speed of robot
22
22
  # pos2d.set_speed(vx: 1.2, vy: 0.1, va: 0.3)
23
23
  #
24
24
  # #update data from server
25
- # client.read
25
+ # client.read!
26
26
  # #read velocityand position by X,Y and angle
27
- # pos2d.speed #=> { :vx => 1.2, :vy => 0.1, :va => 0.3 }
28
- # pos2d.odometry #=> { :px => 0.2321, :py => 0,01, :pa => 0.2 }
29
- # pos2d.stop
30
- class Position2d
31
- include CType
32
- include Common
27
+ # pos2d.position #=> { :px => 0.2321, :py => 0,01, :pa => 0.2, :vx => 1.2, :vy => 0.1, :va => 0.3, :stall => 1 }
28
+ # pos2d.stop!
29
+ class Position2d < Device
33
30
 
34
- module C
35
- extend FFI::Library
36
- ffi_lib "playerc"
31
+ # Position of robot
32
+ # @return [Hash] hash position {:px, :py, :pa, :vx, :vy, :va, :stall }
33
+ attr_reader :position
37
34
 
38
- attach_function :playerc_position2d_create, [:pointer, :int], :pointer
39
- attach_function :playerc_position2d_destroy, [:pointer], :void
40
- attach_function :playerc_position2d_subscribe, [:pointer, :int], :int
41
- attach_function :playerc_position2d_unsubscribe, [:pointer], :int
35
+ def initialize(addr, client, log_level)
36
+ super
37
+ @position = {px: 0.0, py: 0.0, pa: 0.0, vx: 0.0, vy: 0.0, va: 0.0, stall: 0}
38
+ end
42
39
 
43
- attach_function :playerc_position2d_get_geom, [:pointer], :int
44
- attach_function :playerc_position2d_set_cmd_vel, [:pointer, :double, :double, :double, :int], :int
45
- attach_function :playerc_position2d_set_cmd_car, [:pointer, :double, :double], :int
46
- attach_function :playerc_position2d_enable, [:pointer, :int], :int
47
- attach_function :playerc_position2d_set_odom, [:pointer, :double, :double, :double], :int
40
+ # Query robot geometry
41
+ # @return self
42
+ def query_geom
43
+ send_message(PLAYER_MSGTYPE_REQ, PLAYER_POSITION2D_REQ_GET_GEOM)
44
+ self
45
+ end
46
+
47
+ # Turn on motor
48
+ # @return self
49
+ def turn_on!
50
+ send_message(PLAYER_MSGTYPE_REQ, PLAYER_POSITION2D_REQ_MOTOR_POWER, [1].pack("N"))
51
+ self
52
+ end
53
+
54
+ # Turn off motor
55
+ def turn_off!
56
+ send_message(PLAYER_MSGTYPE_REQ, PLAYER_POSITION2D_REQ_MOTOR_POWER, [0].pack("N"))
57
+ self
48
58
  end
49
59
 
50
- def initialize(client, index)
51
- @pos2d = Position2dStruct.new(C.playerc_position2d_create(client, index))
52
- try_with_error C.playerc_position2d_subscribe(@pos2d, PLAYER_OPEN_MODE)
60
+ def direct_speed_control!
61
+ send_message(PLAYER_MSGTYPE_REQ, PLAYER_POSITION2D_REQ_VELOCITY_MODE, [0].pack("N"))
62
+ self
63
+ end
53
64
 
54
- ObjectSpace.define_finalizer(self, Position2d.finilazer(@pos2d))
65
+ def separate_speed_control!
66
+ send_message(PLAYER_MSGTYPE_REQ, PLAYER_POSITION2D_REQ_VELOCITY_MODE, [1].pack("N"))
67
+ self
55
68
  end
56
69
 
57
- # Odometry of robot
58
- # @return [Hash] hash odometry {:px, :py, :pa }
59
- def odometry
60
- {
61
- px: @pos2d[:px],
62
- py: @pos2d[:py],
63
- pa: @pos2d[:pa]
64
- }
70
+ def position_control!
71
+ send_message(PLAYER_MSGTYPE_REQ, PLAYER_POSITION2D_REQ_POSITION_MODE, [0].pack("N"))
72
+ self
65
73
  end
66
-
74
+
75
+ def speed_control!
76
+ send_message(PLAYER_MSGTYPE_REQ, PLAYER_POSITION2D_REQ_POSITION_MODE, [1].pack("N"))
77
+ self
78
+ end
79
+
67
80
  # Set odometry of robot.
68
- # @params [Hash] odometry
81
+ # @param [Hash] odometry
69
82
  # @option odometry :px x position (m)
70
83
  # @option odometry :py y position (m)
71
84
  # @option odometry :pa angle (rad).
72
85
  # @return self
73
- def set_odometry(odometry)
74
- args = [
75
- odometry[:px].to_f || @pos2d[:px],
76
- odometry[:py].to_f || @pos2d[:py],
77
- odometry[:pa].to_f || @pos2d[:pa]
86
+ def set_odometry(odom)
87
+ data = [
88
+ odom[:px].to_f,
89
+ odom[:py].to_f,
90
+ odom[:pa].to_f
78
91
  ]
92
+
93
+ send_message(PLAYER_MSGTYPE_REQ, PLAYER_POSITION2D_REQ_SET_ODOM, data.pack("GGG"))
94
+ self
95
+ end
79
96
 
80
- try_with_error C.playerc_position2d_set_odom(@pos2d, *args)
97
+ # Set speed PID paramters
98
+ # @param [Hash] params PID params
99
+ # @option params :kp P
100
+ # @option params :ki I
101
+ # @option params :kd D
102
+ # @return self
103
+ def set_speed_pid(params={})
104
+ data = [
105
+ params[:kp].to_f,
106
+ params[:ki].to_f,
107
+ params[:kd].to_f
108
+ ]
109
+ send_message(PLAYER_MSGTYPE_REQ, PLAYER_POSITION2D_REQ_SPEED_PID, data.pack("GGG"))
81
110
  self
82
111
  end
83
112
 
84
- # Reset odometry to zero
113
+ # Set position PID paramters
114
+ # @param [Hash] params PID params
115
+ # @option params :kp P
116
+ # @option params :ki I
117
+ # @option params :kd D
85
118
  # @return self
86
- def reset_odometry
87
- set_odometry(px: 0, py: 0, pa: 0)
119
+ def set_position_pid(params={})
120
+ data = [
121
+ params[:kp].to_f,
122
+ params[:ki].to_f,
123
+ params[:kd].to_f
124
+ ]
125
+ send_message(PLAYER_MSGTYPE_REQ, PLAYER_POSITION2D_REQ_POSITION_PID, data.pack("GGG"))
126
+ self
88
127
  end
89
128
 
90
- # Robot geometry
91
- # @return [Hash] position :px, :py, :pa, size :sx, :sy
92
- def geom
93
- try_with_error C.playerc_position2d_get_geom(@pos2d)
94
- p = @pos2d[:pose].to_a
95
- s = @pos2d[:size].to_a
96
- {
97
- px: p[0],
98
- py: p[1],
99
- pa: p[2],
100
- sx: s[0],
101
- sy: s[1]
102
- }
129
+ # Set speed profile
130
+ # @param [Hash] params profile prarams
131
+ # @option params :spped max speed (m/s)
132
+ # @option params :acc max acceleration (m/s^2)
133
+ # @return self
134
+ def set_speed_profile(params={})
135
+ data = [
136
+ params[:speed].to_f,
137
+ params[:acc].to_f
138
+ ]
139
+
140
+ send_message(PLAYER_MSGTYPE_REQ, PLAYER_POSITION2D_REQ_SPEED_PROF, data.pack("GG"))
141
+ self
103
142
  end
104
143
 
144
+ # Reset odometry to zero
145
+ # @return self
146
+ def reset_odometry
147
+ send_message(PLAYER_MSGTYPE_REQ, PLAYER_POSITION2D_REQ_RESET_ODOM)
148
+ self
149
+ end
105
150
 
106
151
  # Set speed of robot. All speeds are defined in the robot coordinate system.
107
- # @params [Hash] speeds
152
+ # @param [Hash] speeds
108
153
  # @option speeds :vx forward speed (m/s)
109
154
  # @option speeds :vy sideways speed (m/s); this field is used by omni-drive robots only.
110
155
  # @option speeds :va rotational speed (rad/s).
156
+ # @option speeds :stall state of motor
111
157
  # @return self
112
158
  def set_speed(speeds)
113
- args = [
114
- speeds[:vx].to_f || @pos2d[:vx],
115
- vy = speeds[:vy].to_f || @pos2d[:vy],
116
- va = speeds[:va].to_f || @pos2d[:va],
117
- 1
159
+ data = [
160
+ speeds[:vx] || @position[:vx],
161
+ speeds[:vy] || @position[:vy],
162
+ speeds[:va] || @position[:va],
163
+ speeds[:stall] || @position[:stall]
118
164
  ]
119
- try_with_error C.playerc_position2d_set_cmd_vel(@pos2d, *args)
165
+ send_message(PLAYER_MSGTYPE_CMD, PLAYER_POSITION2D_CMD_VEL, data.pack("GGGN"))
120
166
  self
121
167
  end
122
168
 
123
169
  # Set speed of carlike robot
124
- # @params [Hash] speeds
170
+ # @param [Hash] speeds
125
171
  # @option speeds :vx forward speed (m/s)
126
- # @option speeds :a angle robot (rad).
172
+ # @option speeds :a turning angle (rad).
127
173
  def set_car(speeds)
128
- args = [
129
- speeds[:vx].to_f || @pos2d[:vx],
130
- speeds[:a].to_f || @pos2d[:pa]
174
+ data = [
175
+ speeds[:vx] || @position[:vx],
176
+ speeds[:a] || @position[:pa]
131
177
  ]
132
- try_with_error C.playerc_position2d_set_cmd_car(@pos2d, *args)
178
+ send_message(PLAYER_MSGTYPE_CMD, PLAYER_POSITION2D_CMD_CAR, data.pack("GG"))
133
179
  self
134
180
  end
135
181
 
136
- # Velocity of robot
137
- # @return [Hash] hash of speeds
138
- def speed
139
- {
140
- vx: @pos2d[:vx],
141
- vy: @pos2d[:vy],
142
- va: @pos2d[:va]
143
- }
182
+ # The position interface accepts translational velocity + absolute heading commands
183
+ # (speed and angular position) for the robot's motors (only supported by some drivers)
184
+ # @param [Hash] speeds
185
+ # @option speeds :vx forward speed (m/s)
186
+ # @option speeds :a absolutle angle (rad).
187
+ def set_speed_head(speeds)
188
+ data = [
189
+ speeds[:vx] || @position[:vx],
190
+ speeds[:a] || @position[:pa]
191
+ ]
192
+ send_message(PLAYER_MSGTYPE_CMD, PLAYER_POSITION2D_CMD_VEL_HEAD, data.pack("GG"))
193
+ self
144
194
  end
145
195
 
196
+
146
197
  # State of motor
147
198
  # @return [Boolean] true - on
148
- def enable
149
- @pos2d[:stall] == 1
150
- end
151
-
152
- # Turn on\off motor
153
- # @param [Boolean] true - turn on
154
- def enable=(val)
155
- try_with_error C.playerc_position2d_enable(@pos2d, val ? 1 : 0)
199
+ def power
200
+ @position[:stall] == 1
156
201
  end
157
202
 
158
203
  # Stop robot set speed to 0
159
- def stop
204
+ # @return self
205
+ def stop!
160
206
  set_speed(vx: 0, vy: 0, va: 0)
161
207
  end
162
208
 
@@ -165,11 +211,35 @@ module Player
165
211
  speed[:vx] == 0 && speed[:vy] == 0 && speed[:va] == 0
166
212
  end
167
213
 
168
- def Position2d.finilazer(pos)
169
- lambda{
170
- try_with_error C.playerc_position2d_unsubscribe(pos)
171
- C.playerc_position2d_destroy(pos)
172
- }
214
+ def fill(hdr, msg)
215
+ case hdr.subtype
216
+ when PLAYER_POSITION2D_DATA_STATE
217
+ read_position(msg)
218
+ when PLAYER_POSITION2D_DATA_GEOM
219
+ read_geom(msg)
220
+ else
221
+ unexpected_message hdr
222
+ end
223
+ end
224
+
225
+ def handle_response(hdr, msg)
226
+ case hdr.subtype
227
+ when PLAYER_POSITION2D_REQ_GET_GEOM
228
+ read_geom(msg)
229
+ when 2..9
230
+ nil #null response
231
+ else
232
+ unexpected_message hdr
233
+ end
234
+ end
235
+
236
+ private
237
+ def read_position(msg)
238
+ data = msg.unpack("GGGGGGN")
239
+ @position.keys.each_with_index do |k,i|
240
+ @position[k] = data[i]
241
+ end
242
+ debug("Get position px=%.2f py=%.2f pa=%.2f; vx=%.2f, vy=%.2f, va=%.2f, stall=%d" % position.values)
173
243
  end
174
244
  end
175
245
  end