hybridgroup-crubyflie 0.1.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +18 -0
  3. data/.travis.yml +8 -0
  4. data/Gemfile +5 -0
  5. data/LICENSE.txt +674 -0
  6. data/README.md +127 -0
  7. data/Rakefile +15 -0
  8. data/bin/crubyflie +94 -0
  9. data/configs/joystick_default.yaml +106 -0
  10. data/crubyflie.gemspec +50 -0
  11. data/examples/params_and_logging.rb +87 -0
  12. data/lib/crubyflie/crazyflie/commander.rb +54 -0
  13. data/lib/crubyflie/crazyflie/console.rb +67 -0
  14. data/lib/crubyflie/crazyflie/log.rb +383 -0
  15. data/lib/crubyflie/crazyflie/log_conf.rb +57 -0
  16. data/lib/crubyflie/crazyflie/param.rb +220 -0
  17. data/lib/crubyflie/crazyflie/toc.rb +239 -0
  18. data/lib/crubyflie/crazyflie/toc_cache.rb +87 -0
  19. data/lib/crubyflie/crazyflie.rb +282 -0
  20. data/lib/crubyflie/crazyradio/crazyradio.rb +301 -0
  21. data/lib/crubyflie/crazyradio/radio_ack.rb +48 -0
  22. data/lib/crubyflie/crubyflie_logger.rb +74 -0
  23. data/lib/crubyflie/driver/crtp_packet.rb +146 -0
  24. data/lib/crubyflie/driver/radio_driver.rb +363 -0
  25. data/lib/crubyflie/exceptions.rb +36 -0
  26. data/lib/crubyflie/input/input_reader.rb +190 -0
  27. data/lib/crubyflie/input/joystick_input_reader.rb +328 -0
  28. data/lib/crubyflie/version.rb +22 -0
  29. data/lib/crubyflie.rb +36 -0
  30. data/spec/commander_spec.rb +67 -0
  31. data/spec/console_spec.rb +76 -0
  32. data/spec/crazyflie_spec.rb +176 -0
  33. data/spec/crazyradio_spec.rb +228 -0
  34. data/spec/crtp_packet_spec.rb +79 -0
  35. data/spec/crubyflie_logger_spec.rb +39 -0
  36. data/spec/crubyflie_spec.rb +21 -0
  37. data/spec/input_reader_spec.rb +136 -0
  38. data/spec/joystick_cfg.yaml +44 -0
  39. data/spec/joystick_input_reader_spec.rb +323 -0
  40. data/spec/log_spec.rb +266 -0
  41. data/spec/param_spec.rb +166 -0
  42. data/spec/radio_ack_spec.rb +43 -0
  43. data/spec/radio_driver_spec.rb +227 -0
  44. data/spec/spec_helper.rb +53 -0
  45. data/spec/toc_cache_spec.rb +87 -0
  46. data/spec/toc_spec.rb +187 -0
  47. data/tools/sdl-joystick-axis.rb +69 -0
  48. metadata +225 -0
@@ -0,0 +1,328 @@
1
+ # -*- coding: utf-8 -*-
2
+ # Copyright (C) 2013 Hector Sanjuan
3
+
4
+ # This file is part of Crubyflie.
5
+
6
+ # Crubyflie is free software: you can redistribute it and/or modify
7
+ # it under the terms of the GNU General Public License as published by
8
+ # the Free Software Foundation, either version 3 of the License, or
9
+ # (at your option) any later version.
10
+
11
+ # Crubyflie is distributed in the hope that it will be useful,
12
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
+ # GNU General Public License for more details.
15
+
16
+ # You should have received a copy of the GNU General Public License
17
+ # along with Crubyflie. If not, see <http://www.gnu.org/licenses/>
18
+
19
+ require 'rubygems'
20
+ require 'yaml'
21
+ require 'sdl'
22
+
23
+ require 'input/input_reader'
24
+
25
+ module Crubyflie
26
+
27
+ # Reads Joystick configuration and specific joystick input
28
+ # See the default Joystick configuration file in the configs/
29
+ # folder to have an idea what a configuration file looks like
30
+ class Joystick < InputReader
31
+ include Logging
32
+
33
+ # Configuration type for Joystick configuration
34
+ CONFIG_TYPE = "Joystick"
35
+ # Default SDL joystick input range for axis
36
+ DEFAULT_INPUT_RANGE = "-32768:32767"
37
+ # Default Crazyflie min/max angles in degrees
38
+ DEFAULT_OUTPUT_RANGE = "-30:30"
39
+ # Default dead zone range
40
+ DEFAULT_DEAD_ZONE = "0:0"
41
+ # Default configuration file
42
+ DEFAULT_CONFIG_PATH = File.join(File.dirname(__FILE__), "..","..","..",
43
+ "configs", "joystick_default.yaml")
44
+ THRUST_MAX = 60000
45
+ THRUST_MIN = 9500
46
+
47
+ attr_reader :config, :joystick_index
48
+ # Initializes the Joystick configuration and the SDL library
49
+ # leaving things ready to read values
50
+ # @param config_path [String] path to configuration file
51
+ # @param joystick_index [Integer] the index of the joystick in SDL
52
+ def initialize(config_path=DEFAULT_CONFIG_PATH, joystick_index = 0)
53
+ @config = nil
54
+ @joystick_index = joystick_index
55
+ @joystick = nil
56
+ axis, buttons = read_configuration(config_path)
57
+ super(axis, buttons)
58
+
59
+ end
60
+
61
+ # Closes the opened resources in SDL
62
+ def quit
63
+ SDL.quit()
64
+ end
65
+
66
+ # Parses a YAML Configuration files
67
+ # @param path [String] Path to the file
68
+ # @return [Array[Hash]] an array with axis and buttons and
69
+ # and their associated action
70
+ # @raise [JoystickException] on several configuration error cases
71
+ def read_configuration(path)
72
+ begin
73
+ config_h = YAML.load_file(path)
74
+ rescue
75
+ raise JoystickException.new("Could load YAML: #{$!}")
76
+ end
77
+
78
+ if config_h[:type] != CONFIG_TYPE
79
+ m = "Configuration is not of type #{CONFIG_TYPE}"
80
+ raise JoystickException.new(m)
81
+ end
82
+
83
+ axis = {}
84
+ if config_h[:axis].nil?
85
+ raise JoystickException.new("No axis section")
86
+ end
87
+ config_h[:axis].each do |id, axis_cfg|
88
+ action = axis_cfg[:action]
89
+ if action.nil?
90
+ raise JoystickException.new("Axis #{id} needs an action")
91
+ end
92
+
93
+ axis[id] = action
94
+
95
+ # Parse and fill in ranging values
96
+ [[:input_range, DEFAULT_INPUT_RANGE],
97
+ [:output_range, DEFAULT_OUTPUT_RANGE],
98
+ [:dead_zone, DEFAULT_DEAD_ZONE]].each do |id, default|
99
+ range_s = axis_cfg[id] || default
100
+ start, rend = range_s.split(':')
101
+ start = start.to_i; rend = rend.to_i
102
+ range = {
103
+ :start => start.to_f,
104
+ :end => rend.to_f,
105
+ :width => (Range.new(start,rend).to_a.size() - 1).to_f
106
+ }
107
+ axis_cfg[id] = range
108
+ end
109
+
110
+ # output value max jump per second. We covert to rate/ms
111
+ max_chrate = axis_cfg[:max_change_rate] || 10000
112
+ if action == :thrust
113
+ # Thrust expressed in %
114
+ w = THRUST_MAX - THRUST_MIN
115
+ max_chrate = (max_chrate.to_f * w /100) / 1000
116
+ else
117
+ max_chrate = max_chrate.to_f / 1000
118
+ end
119
+ axis_cfg[:max_change_rate] = max_chrate
120
+
121
+ axis_cfg[:last_poll] ||= 0
122
+ axis_cfg[:last_value] ||= 0
123
+ axis_cfg[:invert] ||= false
124
+ axis_cfg[:calibration] ||= 0
125
+
126
+ end
127
+
128
+ buttons = {}
129
+ config_h[:buttons] = {} if config_h[:buttons].nil?
130
+
131
+ config_h[:buttons].each do |id, button_cfg|
132
+ action = button_cfg[:action]
133
+ if action.nil?
134
+ raise JoystickException.new("Button #{id} needs an action")
135
+ end
136
+ buttons[id] = action
137
+ button_cfg[:value] ||= 1
138
+ end
139
+
140
+ @config = config_h
141
+
142
+ #logger.info "Loaded configuration correctly (#{path})"
143
+ return axis, buttons
144
+ end
145
+
146
+ # Init SDL and open the joystick
147
+ # @raise [JoystickException] if the joystick index is plainly wrong
148
+ def init_sdl
149
+ SDL.init(SDL::INIT_JOYSTICK)
150
+ SDL::Joystick.poll = false
151
+ n_joy = SDL::Joystick.num
152
+ logger.info("Joysticks found: #{n_joy}")
153
+
154
+ if @joystick_index >= n_joy
155
+ raise JoystickException.new("No valid joystick index")
156
+ end
157
+ @joystick = SDL::Joystick.open(@joystick_index)
158
+ name = SDL::Joystick.index_name(@joystick_index)
159
+ logger.info("Using Joystick: #{name}")
160
+ end
161
+ alias_method :init, :init_sdl
162
+
163
+ # Used to read the current state of an axis. This is a rather
164
+ # complicated operation. Raw value is first fit withing the input
165
+ # range limits, then set to 0 if it falls in the dead zone,
166
+ # then normalized to the output range that we will like to get (with
167
+ # special case for thrust, as ranges have different limits), then
168
+ # we check if the new value falls withing the change rate limit
169
+ # and modify it if not, finally we re-normalize the thrust if needed
170
+ # and return the reading, which should be good to be fit straight
171
+ # into the Crazyflie commander.
172
+ # @param axis_id [Integer] The SDL joystick axis to be read
173
+ # @return [Fixnum, Float] the correctly-normalized-value from the axis
174
+ def read_axis(axis_id)
175
+ return 0 if !@joystick
176
+ axis_conf = @config[:axis][axis_id]
177
+ return 0 if axis_conf.nil?
178
+ is_thrust = axis_conf[:action] == :thrust
179
+
180
+ last_poll = axis_conf[:last_poll]
181
+ last_value = axis_conf[:last_value]
182
+ invert = axis_conf[:invert]
183
+ calibration = axis_conf[:calibration]
184
+
185
+ input_range = axis_conf[:input_range]
186
+ output_range = axis_conf[:output_range]
187
+
188
+ max_chrate = axis_conf[:max_change_rate]
189
+
190
+ dead_zone = axis_conf[:dead_zone]
191
+
192
+ value = @joystick.axis(axis_id)
193
+
194
+ value *= -1 if invert
195
+ value += calibration
196
+
197
+
198
+ # Make sure input falls with the expected range and take care of
199
+ # the dead zone
200
+ if dead_zone[:start] < value && dead_zone[:end] > value
201
+ value = 0
202
+ elsif dead_zone[:start] >= value
203
+ value = value - dead_zone[:start]
204
+ elsif dead_zone[:end] <= value
205
+ value = value - dead_zone[:end]
206
+ end
207
+
208
+ if value > input_range[:end]
209
+ value = input_range[:end]
210
+ elsif value < input_range[:start]
211
+ value = input_range[:start]
212
+ end
213
+
214
+ # Convert
215
+ if is_thrust
216
+ value = normalize_thrust(value, input_range, output_range)
217
+ else
218
+ value = normalize(value, input_range, output_range)
219
+ end
220
+
221
+ # Check if we change too fast
222
+ current_time = Time.now.to_f
223
+ timespan = current_time - last_poll
224
+ # How many ms have passed since last time
225
+ timespan_ms = timespan * 1000
226
+ # How much have we changed/ms
227
+ change = (value - last_value) / timespan_ms.to_f
228
+
229
+ # Skip rate limitation if change is positive and this is thurst
230
+ if !is_thrust || (is_thrust && change <= 0)
231
+ # If the change rate exceeds the max change rate per ms...
232
+ if change.abs > max_chrate
233
+ # new value is the max change possible for the timespan
234
+ if change > 0
235
+ value = last_value + max_chrate * timespan_ms
236
+ elsif change < 0
237
+ value = last_value - max_chrate * timespan_ms
238
+ end
239
+ end
240
+ end
241
+
242
+ @config[:axis][axis_id][:last_poll] = current_time
243
+ @config[:axis][axis_id][:last_value] = value
244
+
245
+ return value
246
+ end
247
+
248
+
249
+ # Returns integer from 9.500 to 60.000 which is what the crazyflie
250
+ # expects
251
+ def normalize_thrust(value, input_range, output_range)
252
+ value = 0 if value < 0
253
+ range = {
254
+ :start => -100.0,
255
+ :end => 100.0,
256
+ :width => 200.0
257
+ }
258
+ value = normalize(value, input_range, range)
259
+
260
+ if value > output_range[:end] then value = output_range[:end]
261
+ elsif value < output_range[:start] then value = output_range[:start]
262
+ end
263
+
264
+ range = {
265
+ :start => 0.0,
266
+ :end => 100.0,
267
+ :width => 100.0
268
+ }
269
+
270
+ cf_range = {
271
+ :start => THRUST_MIN,
272
+ :end => THRUST_MAX,
273
+ :width => THRUST_MAX - THRUST_MIN
274
+ }
275
+ return normalize(value, range, cf_range).round
276
+ end
277
+ private :normalize_thrust
278
+
279
+ # Reads the specified joystick button. Since we may want to calibrate
280
+ # buttons etc, we return an integer. In only reads the button if
281
+ # the reading has not be done in the last 1 second to avoid
282
+ # flapping
283
+ # @param button_id [Integer] the SDL button number
284
+ # @return [Fixnum] -1 if the button is not pressed, 1 otherwise
285
+ def read_button(button_id)
286
+ return -1 if !@joystick
287
+
288
+ button = @config[:buttons][button_id]
289
+ last_poll = button[:last_poll] || 0
290
+ last_value = button[:last_value] || -1
291
+ pressed = @joystick.button(button_id)
292
+ value_pressed = button[:value]
293
+ current_time = Time.now.to_f
294
+
295
+ if (current_time - last_poll) > 0.5
296
+ button[:last_value] = value_pressed
297
+ button[:last_poll] = current_time
298
+ return pressed ? value_pressed : 0
299
+ else
300
+ return 0
301
+ end
302
+ end
303
+ private :read_button
304
+
305
+ # Called before reading
306
+ def poll
307
+ SDL::Joystick.update_all
308
+ end
309
+ private :poll
310
+
311
+ # Linear-transforms a value in one range to a different range
312
+ # @param value [Fixnum, Float] the value in the original range
313
+ # @param from_range [Hash] the range from which we want to normalize.
314
+ # a range must have :start, :end, :width keys
315
+ # @param to_range [Hash] the destination range
316
+ # @return [Float] the linear-corresponding value in the destination
317
+ # range
318
+ def normalize(value, from_range, to_range)
319
+ from_min = from_range[:start]
320
+ to_min = to_range[:start]
321
+ to_w = to_range[:width]
322
+ from_w = from_range[:width]
323
+ # puts "#{to_min}+(#{value.to_f}-#{from_min})*(#{to_w}/#{from_w})
324
+ r = to_min + (value.to_f - from_min) * (to_w / from_w)
325
+ return r.round(2)
326
+ end
327
+ end
328
+ end
@@ -0,0 +1,22 @@
1
+ # -*- coding: utf-8 -*-
2
+ # Copyright (C) 2013 Hector Sanjuan
3
+
4
+ # This file is part of Crubyflie.
5
+
6
+ # Crubyflie is free software: you can redistribute it and/or modify
7
+ # it under the terms of the GNU General Public License as published by
8
+ # the Free Software Foundation, either version 3 of the License, or
9
+ # (at your option) any later version.
10
+
11
+ # Crubyflie is distributed in the hope that it will be useful,
12
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
+ # GNU General Public License for more details.
15
+
16
+ # You should have received a copy of the GNU General Public License
17
+ # along with Crubyflie. If not, see <http://www.gnu.org/licenses/>
18
+
19
+ module Crubyflie
20
+ # Current gem version
21
+ VERSION = "0.1.4"
22
+ end
data/lib/crubyflie.rb ADDED
@@ -0,0 +1,36 @@
1
+ # -*- coding: utf-8 -*-
2
+ # Copyright (C) 2013 Hector Sanjuan
3
+
4
+ # This file is part of Crubyflie.
5
+
6
+ # Crubyflie is free software: you can redistribute it and/or modify
7
+ # it under the terms of the GNU General Public License as published by
8
+ # the Free Software Foundation, either version 3 of the License, or
9
+ # (at your option) any later version.
10
+
11
+ # Crubyflie is distributed in the hope that it will be useful,
12
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
+ # GNU General Public License for more details.
15
+
16
+ # You should have received a copy of the GNU General Public License
17
+ # along with Crubyflie. If not, see <http://www.gnu.org/licenses/>
18
+
19
+ $: << File.join(File.dirname(__FILE__), 'crubyflie')
20
+
21
+
22
+ require 'crubyflie_logger'
23
+ require 'crazyflie'
24
+ begin
25
+ require 'input/joystick_input_reader'
26
+ rescue LoadError
27
+ puts "NOTE: Install rubysdl for joystick support"
28
+ end
29
+ require 'version'
30
+
31
+ # The Crubyflie modules wraps all the Crubyflie code so we don't
32
+ # pollute the namespace.
33
+ module Crubyflie
34
+ $debug = false
35
+ include Logging
36
+ end
@@ -0,0 +1,67 @@
1
+ # -*- coding: utf-8 -*-
2
+ # Copyright (C) 2013 Hector Sanjuan
3
+
4
+ # This file is part of Crubyflie.
5
+
6
+ # Crubyflie is free software: you can redistribute it and/or modify
7
+ # it under the terms of the GNU General Public License as published by
8
+ # the Free Software Foundation, either version 3 of the License, or
9
+ # (at your option) any later version.
10
+
11
+ # Crubyflie is distributed in the hope that it will be useful,
12
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
+ # GNU General Public License for more details.
15
+
16
+ # You should have received a copy of the GNU General Public License
17
+ # along with Crubyflie. If not, see <http://www.gnu.org/licenses/>
18
+
19
+ require 'crazyflie/commander'
20
+
21
+ describe Commander do
22
+
23
+ before :each do
24
+ @crazyflie = double("Crazyflie")
25
+ @queue = Queue.new
26
+ allow(@crazyflie).to receive(:crtp_queues).and_return({:commander =>
27
+ @queue})
28
+
29
+ @commander = Commander.new(@crazyflie)
30
+ end
31
+
32
+ describe "#initialize" do
33
+ it "should initialize the facility" do
34
+ c = Commander.new(@crazyflie)
35
+ end
36
+ end
37
+
38
+ describe "#send_sendpoint" do
39
+ it "should send a sendpoint in normal mode" do
40
+ expect(@crazyflie).to receive(:send_packet) do |packet, want_answer|
41
+ want_answer.should == false
42
+ packet.port.should == Crazyflie::CRTP_PORTS[:commander]
43
+ packet.channel.should == 0
44
+ packet.data.size.should == 14
45
+ packet.data_repack.unpack('eeeS<').should == [1,-2,3,6]
46
+ end
47
+
48
+ @commander.send_setpoint(1,2,3,6)
49
+ end
50
+
51
+ it "should send a sendpoint in xmode" do
52
+ expect(@crazyflie).to receive(:send_packet) do |packet, want_answer|
53
+ want_answer.should == false
54
+ packet.port.should == Crazyflie::CRTP_PORTS[:commander]
55
+ packet.channel.should == 0
56
+ packet.data.size.should == 14
57
+ data = packet.data_repack.unpack('eeeS<')
58
+ data[0].round(3).should == -0.707
59
+ data[1].round(4).should == -0.9142
60
+ data[2].should == 3.0
61
+ data[3].should == 6
62
+ end
63
+
64
+ @commander.send_setpoint(1,2,3,6, true)
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,76 @@
1
+ # -*- coding: utf-8 -*-
2
+ # Copyright (C) 2013 Hector Sanjuan
3
+
4
+ # This file is part of Crubyflie.
5
+
6
+ # Crubyflie is free software: you can redistribute it and/or modify
7
+ # it under the terms of the GNU General Public License as published by
8
+ # the Free Software Foundation, either version 3 of the License, or
9
+ # (at your option) any later version.
10
+
11
+ # Crubyflie is distributed in the hope that it will be useful,
12
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
+ # GNU General Public License for more details.
15
+
16
+ # You should have received a copy of the GNU General Public License
17
+ # along with Crubyflie. If not, see <http://www.gnu.org/licenses/>
18
+
19
+ require 'crazyflie/console'
20
+
21
+ describe Console do
22
+ before :each do
23
+ @crazyflie = double("Crazyflie")
24
+ @queue = Queue.new
25
+ allow(@crazyflie).to receive(:crtp_queues).and_return({:console =>
26
+ @queue})
27
+
28
+ @console = Console.new(@crazyflie)
29
+ end
30
+ describe "#initialize" do
31
+ it "should intialize the facility" do
32
+ c = Console.new(@crazyflie)
33
+ expect_any_instance_of(Thread).not_to receive(:new)
34
+ end
35
+ end
36
+
37
+ describe "#read" do
38
+ it "should read all packets available from the queue" do
39
+ count = 1
40
+ p1 = CRTPPacket.new()
41
+ p1.data = "baa1".unpack('C*')
42
+ p2 = CRTPPacket.new()
43
+ p2.data = "baa2".unpack('C*')
44
+
45
+ @queue << p1
46
+ @queue << p2
47
+
48
+ @console.read do |message|
49
+ message.should == "baa#{count}"
50
+ count += 1
51
+ end
52
+ @queue.size.should == 0
53
+ end
54
+ end
55
+
56
+ describe "#start_reading" do
57
+ it "should read continuously" do
58
+ count = 1
59
+ p1 = CRTPPacket.new()
60
+ p1.data = "baa1".unpack('C*')
61
+ p2 = CRTPPacket.new()
62
+ p2.data = "baa2".unpack('C*')
63
+
64
+ @queue << p1
65
+ @queue << p2
66
+
67
+ @console.start_reading do |message|
68
+ message.should == "baa#{count}"
69
+ count += 1
70
+ end
71
+ sleep 0.3
72
+ @queue.size.should == 0
73
+ @console.stop_reading()
74
+ end
75
+ end
76
+ end