crubyflie 0.1.1 → 0.1.2

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: c53014aa0e2af1f0f401853e934227e5f2950f38
4
- data.tar.gz: 6729d96811026479a1a0984aa1316506be35af75
3
+ metadata.gz: 9f8c948aed7ee8dc5776a9d2b26ceb5ce6673792
4
+ data.tar.gz: 6314537e66334c3e403142c0c217af9bb832d54a
5
5
  SHA512:
6
- metadata.gz: 81452b7995d89199f233b55c132bded7b8790875754d020ada0831bef8cf9b100b4aa439c4ae340b197e8a3a3add072d9ee9cfb19ffbabc8d278b7bb35641cfe
7
- data.tar.gz: a4ca9a64c904644c1dd756c77a9f47875da126cc1ea72844b2a5a191ec2d849481645c33e64f733054df54425c04e1faeb3fe6dbd84f0d361f6e4f90fb7ca01f
6
+ metadata.gz: cbc016920486d8c234bf3efdc78f895dd70d38ad61f128afca3a80521efb8cfc758ab07441dec8e54e9168285f71f2b0d1d9c48f97f36ab0a1b0f4f6376bbc60
7
+ data.tar.gz: 2478f8cdcf14a77fdfbab4eaad8b6477916c9c4cee8cc92af55afe34214b48caba7a7aa8aa6a1ffb72f8074f705c64bbdd9fe62cb73c8b3a102c96c357c9e469
data/README.md CHANGED
@@ -7,6 +7,12 @@ Crubyflie is a Ruby rewrite of the [Crazyflie quadcopter](http://www.bitcraze.se
7
7
 
8
8
  The Crazyflie is awesome, but I did not know where to start contributing. Therefore I thought that rewriting the code in Ruby would be one way of knowing what is going on and how it works. Along the way I took the time to document all the code so that others can understand it and create tests.
9
9
 
10
+ You may be also interested in some other unofficial Crazyflie clients:
11
+
12
+ * C++: https://github.com/fairlight1337/libcflie
13
+ * Node.js: https://github.com/ceejbot/aerogel
14
+ * Haskell: https://github.com/orclev/crazyflie-haskell
15
+
10
16
  Disclaimer
11
17
  ----------
12
18
 
@@ -17,7 +23,8 @@ Features
17
23
 
18
24
  * Crubyflie can be used to fly a Crazyflie device using a Joystick and the Crazyradio USB dongle
19
25
  * Crubyflie exposes an API that allows to control the copter, read logging, parameters and console easily
20
- * Crubyflie runs headless
26
+ * Crubyflie runs headless
27
+ * Lightweight: If you just want to fly, Crubyflie consumes around 1/2 memory and 1/3 CPU compared to the original Python `cfheadless` utility.
21
28
 
22
29
  Not included...
23
30
  ----------------
@@ -45,6 +52,16 @@ If you are wondering about your Joystick's axis IDs, ranges etc, you will find a
45
52
 
46
53
  If you need help just open an issue or contact me.
47
54
 
55
+ Raspberri Pi
56
+ ------------
57
+
58
+ If you want to use Crubyflie in your Raspberry Pi you need to:
59
+
60
+ sudo apt-get install ruby ruby-dev libsdl-dev
61
+ sudo gem install crubyflie
62
+
63
+ This should provide everything you need to run the `crubyflie` command. Of course you might need to put your user in the `input` group and modify `udev` rules as explained in the [Crazyflie wiki](http://wiki.bitcraze.se/projects:crazyflie:hacks:rasberrypi).
64
+
48
65
  Using the Crazyflie API
49
66
  -----------------------
50
67
 
data/bin/crubyflie CHANGED
@@ -72,11 +72,16 @@ Signal.trap("SIGINT") do
72
72
  end
73
73
 
74
74
  while cf.active? && !exit do
75
- # We should be good sending 10 ticks per second
76
- # it says that somewhere in the docs
75
+ start_time = Time.now.to_f
77
76
  joystick.read_input()
78
77
  joystick.apply_input(cf)
79
- sleep 0.1
78
+ # We should be good sending 10 ticks per second,
79
+ # as it says that somewhere in the docs, so we have
80
+ # 1/10 secs of time per loop. If we are fast, we can sleep
81
+ # a little bit
82
+ consumed_time = Time.now.to_f - start_time
83
+ sleep_time = 0.1 - (consumed_time)
84
+ sleep sleep_time if sleep_time > 0
80
85
  end
81
86
 
82
87
  joystick.quit()
@@ -147,7 +147,7 @@ module Crubyflie
147
147
 
148
148
  if pitch && roll && yaw && thrust
149
149
  m = "Sending R: #{roll} P: #{pitch} Y: #{yaw} T: #{thrust}"
150
- #logger.debug(m)
150
+ logger.debug(m)
151
151
  crazyflie.commander.send_setpoint(roll, pitch, yaw, thrust,
152
152
  @xmode)
153
153
  end
@@ -36,6 +36,8 @@ module Crubyflie
36
36
  DEFAULT_INPUT_RANGE = "-32768:32767"
37
37
  # Default Crazyflie min/max angles in degrees
38
38
  DEFAULT_OUTPUT_RANGE = "-30:30"
39
+ # Default dead zone range
40
+ DEFAULT_DEAD_ZONE = "0:0"
39
41
  # Default configuration file
40
42
  DEFAULT_CONFIG_PATH = File.join(File.dirname(__FILE__), "..","..","..",
41
43
  "configs", "joystick_default.yaml")
@@ -87,12 +89,36 @@ module Crubyflie
87
89
  end
88
90
 
89
91
  axis[id] = action
92
+
93
+ # Parse and fill in ranging values
94
+ [[:input_range, DEFAULT_INPUT_RANGE],
95
+ [:output_range, DEFAULT_OUTPUT_RANGE],
96
+ [:dead_zone, DEFAULT_DEAD_ZONE]].each do |id, default|
97
+ range_s = axis_cfg[id] || default
98
+ start, rend = range_s.split(':')
99
+ start = start.to_i; rend = rend.to_i
100
+ range = {
101
+ :start => start.to_f,
102
+ :end => rend.to_f,
103
+ :width => (Range.new(start,rend).to_a.size() - 1).to_f
104
+ }
105
+ axis_cfg[id] = range
106
+ end
107
+
108
+ # output value max jump per second. We covert to rate/ms
109
+ max_chrate = axis_cfg[:max_change_rate] || 10000
110
+ axis_cfg[:max_change_rate] = max_chrate.to_f / 1000
111
+
112
+ axis_cfg[:last_poll] ||= 0
113
+ axis_cfg[:last_value] ||= 0
114
+ axis_cfg[:invert] ||= false
115
+ axis_cfg[:calibration] ||= 0
116
+
90
117
  end
91
118
 
92
119
  buttons = {}
93
- if config_h[:buttons].nil?
94
- raise JoystickException.new("No buttons section")
95
- end
120
+ config_h[:buttons] = buttons if config_h[:buttons].nil?
121
+
96
122
  config_h[:buttons].each do |id, button_cfg|
97
123
  action = button_cfg[:action]
98
124
  if action.nil?
@@ -141,44 +167,37 @@ module Crubyflie
141
167
  return 0 if axis_conf.nil?
142
168
  is_thrust = axis_conf[:action] == :thrust
143
169
 
170
+ last_poll = axis_conf[:last_poll]
171
+ last_value = axis_conf[:last_value]
172
+ invert = axis_conf[:invert]
173
+ calibration = axis_conf[:calibration]
144
174
 
145
- last_poll = axis_conf[:last_poll] || 0
146
- last_value = axis_conf[:last_value] || 0
147
- invert = axis_conf[:invert] || false
148
- calibration = axis_conf[:calibration] || 0
149
-
150
- input_range_s = axis_conf[:input_range] || DEFAULT_INPUT_RANGE
151
- ir_start, ir_end = input_range_s.split(':')
152
- input_range = Range.new(ir_start.to_i, ir_end.to_i)
153
-
154
- output_range_s = axis_conf[:output_range] || DEFAULT_OUTPUT_RANGE
155
- or_start, or_end = output_range_s.split(':')
156
- output_range = Range.new(or_start.to_i, or_end.to_i)
175
+ input_range = axis_conf[:input_range]
176
+ output_range = axis_conf[:output_range]
157
177
 
158
- # output value max jump per second. We covert to rate/ms
159
- max_chrate = axis_conf[:max_change_rate] || 10000
160
- max_chrate = max_chrate.to_f / 1000
178
+ max_chrate = axis_conf[:max_change_rate]
161
179
 
162
- dead_zone = axis_conf[:dead_zone] || "0:0" # % deadzone around 0
163
- dz_start, dz_end = dead_zone.split(':')
164
- dead_zone_range = Range.new(dz_start.to_i, dz_end.to_i)
180
+ dead_zone = axis_conf[:dead_zone]
165
181
 
166
182
  value = @joystick.axis(axis_id)
183
+
167
184
  value *= -1 if invert
168
185
  value += calibration
169
186
 
170
- # Make sure input falls with the expected range
171
- if value > input_range.last then value = input_range.last end
172
- if value < input_range.first then value = input_range.first end
173
- # Dead zone
174
187
 
175
- if dead_zone_range.first < value && dead_zone_range.last > value
188
+ # Make sure input falls with the expected range and take care of
189
+ # the dead zone
190
+ if dead_zone[:start] < value && dead_zone[:end] > value
176
191
  value = 0
192
+ elsif value > input_range[:end]
193
+ value = input_range[:end]
194
+ elsif value < input_range[:start]
195
+ value = input_range[:start]
177
196
  end
197
+
178
198
  # Convert
179
199
  if is_thrust
180
- value = pre_normalize_thrust(value, input_range, output_range)
181
- value = normalize_thrust(value)
200
+ value = normalize_thrust(value, input_range, output_range)
182
201
  else
183
202
  value = normalize(value, input_range, output_range)
184
203
  end
@@ -210,24 +229,34 @@ module Crubyflie
210
229
  return value
211
230
  end
212
231
 
213
- # The thrust axis is disabled for values < 0. What we do here is to
214
- # convert it to a thrust 0-100% value first and then make sure it
215
- # stays within the limits provided for output range
216
- def pre_normalize_thrust(value, input_range, output_range)
217
- value = normalize(value, input_range, (-100..100))
232
+
233
+ # Returns integer from 9.500 to 60.000 which is what the crazyflie
234
+ # expects
235
+ def normalize_thrust(value, input_range, output_range)
218
236
  value = 0 if value < 0
219
- if value > output_range.last then value = output_range.last
220
- elsif value < output_range.first then value = output_range.first
237
+ range = {
238
+ :start => -100.0,
239
+ :end => 100.0,
240
+ :width => 200.0
241
+ }
242
+ value = normalize(value, input_range, range)
243
+
244
+ if value > output_range[:end] then value = output_range[:end]
245
+ elsif value < output_range[:start] then value = output_range[:start]
221
246
  end
222
- return value
223
- end
224
- private :pre_normalize_thrust
225
247
 
226
-
227
- # Returns integer from 10.000 to 60.000 which is what the crazyflie
228
- # expects
229
- def normalize_thrust(value)
230
- return normalize(value, (0..100), (9500..60000)).round
248
+ range = {
249
+ :start => 0.0,
250
+ :end => 100.0,
251
+ :width => 100.0
252
+ }
253
+
254
+ cf_range = {
255
+ :start => 9500.0,
256
+ :end => 60000.0,
257
+ :width => 50500.0
258
+ }
259
+ return normalize(value, range, cf_range).round
231
260
  end
232
261
  private :normalize_thrust
233
262
 
@@ -263,15 +292,16 @@ module Crubyflie
263
292
 
264
293
  # Linear-transforms a value in one range to a different range
265
294
  # @param value [Fixnum, Float] the value in the original range
266
- # @param from_range [Range] the range from which we want to normalize
267
- # @param to_range [Range] the destination range
295
+ # @param from_range [Hash] the range from which we want to normalize.
296
+ # a range must have :start, :end, :width keys
297
+ # @param to_range [Hash] the destination range
268
298
  # @return [Float] the linear-corresponding value in the destination
269
299
  # range
270
300
  def normalize(value, from_range, to_range)
271
- from_w = from_range.to_a.size.to_f - 1
272
- to_w = to_range.to_a.size.to_f - 1
273
- from_min = from_range.first.to_f
274
- to_min = to_range.first.to_f
301
+ from_min = from_range[:start]
302
+ to_min = to_range[:start]
303
+ to_w = to_range[:width]
304
+ from_w = from_range[:width]
275
305
  # puts "#{to_min}+(#{value.to_f}-#{from_min})*(#{to_w}/#{from_w})
276
306
  r = to_min + (value.to_f - from_min) * (to_w / from_w)
277
307
  return r.round(3)
@@ -18,5 +18,5 @@
18
18
 
19
19
  module Crubyflie
20
20
  # Current gem version
21
- VERSION = "0.1.1"
21
+ VERSION = "0.1.2"
22
22
  end
@@ -91,12 +91,12 @@ describe Joystick do
91
91
  it "should raise exception if the axis or buttons are missing" do
92
92
  cfg = {
93
93
  :type => "Joystick",
94
- :axis => {0 => {:action => :yaw}},
94
+ :buttons => {0 => {:action => :yaw}},
95
95
  }
96
96
  expect(YAML).to receive(:load_file).and_return(cfg)
97
97
  expect {
98
98
  @joystick.read_configuration('baa')
99
- }.to raise_exception(JoystickException, "No buttons section")
99
+ }.to raise_exception(JoystickException, "No axis section")
100
100
  end
101
101
 
102
102
  it "should raise exception if an axis has no action" do
@@ -131,93 +131,178 @@ describe Joystick do
131
131
 
132
132
  describe "#read_axis" do
133
133
  it "should read the axis value with all the defaults" do
134
- @joystick.config[:axis][8] = {:action => :test}
134
+ config = {
135
+ :type => "Joystick",
136
+ :axis => {
137
+ 8 =>{
138
+ :action => :test,
139
+ }
140
+ }
141
+ }
142
+ expect(YAML).to receive(:load_file).and_return(config)
135
143
  expect(@sdl_joystick).to receive(:axis).with(8).and_return(-32768)
144
+ @joystick.read_configuration('baa')
136
145
  value = @joystick.read_axis(8)
137
146
  value.should == -30
138
147
  end
139
148
 
140
149
  it "should put values out of range in range" do
141
- @joystick.config[:axis][8] = {:action => :test}
150
+ config = {
151
+ :type => "Joystick",
152
+ :axis => {
153
+ 8 =>{
154
+ :action => :test,
155
+ }
156
+ }
157
+ }
158
+ expect(YAML).to receive(:load_file).and_return(config)
142
159
  expect(@sdl_joystick).to receive(:axis).with(8).and_return(-40000)
160
+ @joystick.read_configuration('baa')
143
161
  value = @joystick.read_axis(8)
144
162
  value.should == -30
145
163
  end
146
164
 
147
165
  it "should invert values if requested" do
148
- @joystick.config[:axis][8] = {:action => :test, :invert => true}
166
+ config = {
167
+ :type => "Joystick",
168
+ :axis => {
169
+ 8 =>{
170
+ :action => :test,
171
+ :invert => true
172
+ }
173
+ }
174
+ }
175
+ expect(YAML).to receive(:load_file).and_return(config)
149
176
  expect(@sdl_joystick).to receive(:axis).with(8).and_return(-32767)
177
+ @joystick.read_configuration('baa')
150
178
  value = @joystick.read_axis(8)
151
179
  value.should == 30
152
180
  end
153
181
 
154
182
  it "should put values in the deadzone as 0" do
155
- @joystick.config[:axis][8] = {
156
- :action => :test,
157
- :dead_zone => "-40:40"
183
+ config = {
184
+ :type => "Joystick",
185
+ :axis => {
186
+ 8 =>{
187
+ :action => :test,
188
+ :dead_zone => "-40:40"
189
+ }
190
+ }
158
191
  }
192
+ expect(YAML).to receive(:load_file).and_return(config)
159
193
  expect(@sdl_joystick).to receive(:axis).with(8).and_return(23)
194
+ @joystick.read_configuration('baa')
160
195
  value = @joystick.read_axis(8)
161
196
  value.should == 0
162
197
  end
163
198
 
164
199
  it "should normalize the thrust correctly" do
165
- @joystick.config[:axis][8] = {
166
- :action => :thrust,
167
- :output_range => "0:100"
200
+ config = {
201
+ :type => "Joystick",
202
+ :axis => {
203
+ 8 =>{
204
+ :action => :thrust,
205
+ :output_range => "0:100"
206
+ }
207
+ }
168
208
  }
209
+ expect(YAML).to receive(:load_file).and_return(config)
169
210
  expect(@sdl_joystick).to receive(:axis).with(8).and_return(32767)
211
+ @joystick.read_configuration('baa')
170
212
  value = @joystick.read_axis(8)
171
213
  value.should == 60000
172
214
  end
173
215
 
174
216
  it "should throotle the change rate correctly" do
175
- @joystick.config[:axis][8] = {
176
- :action => :test,
177
- :output_range => "100:200",
178
- :input_range => "0:100",
179
- :max_change_rate => 1,
180
- :last_poll => 0.500,
181
- :last_value => 150
217
+ config = {
218
+ :type => "Joystick",
219
+ :axis => {
220
+ 8 => {
221
+ :action => :thrust,
222
+ :output_range => "0:100",
223
+ :input_range => "100:200",
224
+ :max_change_rate => 1,
225
+ :last_poll => 0.500,
226
+ :last_value => 34750
227
+ }
228
+ }
182
229
  }
230
+ expect(YAML).to receive(:load_file).and_return(config)
231
+
183
232
  allow(Time).to receive(:now).and_return(1.0) # 0.5 secs after
184
- expect(@sdl_joystick).to receive(:axis).with(8).and_return(60)
233
+ expect(@sdl_joystick).to receive(:axis).with(8).and_return(140)
234
+ @joystick.read_configuration('baa')
185
235
  value = @joystick.read_axis(8)
186
- value.should == 150.5
236
+ value.should == 34749.5
187
237
  end
188
238
 
189
239
  it "should not throotle the change rate when increasing thrust" do
190
- @joystick.config[:axis][8] = {
191
- :action => :thrust,
192
- :output_range => "0:100",
193
- :input_range => "0:100",
194
- :max_change_rate => 1,
195
- :last_poll => 0.500,
196
- :last_value => 50
240
+ config = {
241
+ :type => "Joystick",
242
+ :axis => {
243
+ 8 =>{
244
+ :action => :thrust,
245
+ :output_range => "0:100",
246
+ :input_range => "0:100",
247
+ :max_change_rate => 1,
248
+ :last_poll => 0.500,
249
+ :last_value => 50
250
+ }
251
+ }
197
252
  }
253
+ expect(YAML).to receive(:load_file).and_return(config)
254
+
198
255
  allow(Time).to receive(:now).and_return(1.0) # 0.5 secs after
199
256
  expect(@sdl_joystick).to receive(:axis).with(8).and_return(100)
257
+ @joystick.read_configuration('baa')
200
258
  value = @joystick.read_axis(8)
201
259
  value.should == 60000
202
260
  end
203
261
  end
204
262
 
205
- describe "#pre_normalize_thrust" do
206
- it "should discard negative axis values" do
207
- v = @joystick.send(:pre_normalize_thrust, -12, (-20..20), (0..100))
208
- v.should == 0
263
+ describe "#normalize_thrust" do
264
+ it "should return values within the range expected by the CF" do
265
+ v = @joystick.send(:normalize_thrust, 5000,
266
+ {
267
+ :start => -10000.0,
268
+ :end => 10000.0,
269
+ :width => 20000.0
270
+ },{
271
+ :start => 0.0,
272
+ :end => 80.0,
273
+ :width => 80.0
274
+ })
275
+ v.should == 34750
276
+ v.should be_an_instance_of Fixnum
209
277
  end
210
278
 
211
- it "should pre-normalize to the expected" do
212
- v = @joystick.send(:pre_normalize_thrust, 5, (-10..10), (0..50))
213
- v.should == 50
279
+ it "should set values under the given range to the min" do
280
+ v = @joystick.send(:normalize_thrust, 0,
281
+ {
282
+ :start => -10000.0,
283
+ :end => 10000.0,
284
+ :width => 20000.to_f
285
+ },{
286
+ :start => 30.0,
287
+ :end => 80.0,
288
+ :width => 50.0
289
+ })
290
+ v.should == 24650 # 30% of 9500-60000
291
+ v.should be_an_instance_of Fixnum
214
292
  end
215
- end
216
293
 
217
- describe "#normalize_thrust" do
218
- it "should return values within the range expected by the CF" do
219
- v = @joystick.send(:normalize_thrust, 50)
220
- v.should == 34750
294
+ it "should set values over the given range to the max" do
295
+ v = @joystick.send(:normalize_thrust, 32767,
296
+ {
297
+ :start => -10000.0,
298
+ :end => +10000.0,
299
+ :width => 20000.to_f
300
+ },{
301
+ :start => 0.0,
302
+ :end => 80.0,
303
+ :width => 80.0
304
+ })
305
+ v.should == 49900 # 80% of 9500-60000
221
306
  v.should be_an_instance_of Fixnum
222
307
  end
223
308
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: crubyflie
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Hector Sanjuan