hybridgroup-crubyflie 0.1.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +18 -0
- data/.travis.yml +8 -0
- data/Gemfile +5 -0
- data/LICENSE.txt +674 -0
- data/README.md +127 -0
- data/Rakefile +15 -0
- data/bin/crubyflie +94 -0
- data/configs/joystick_default.yaml +106 -0
- data/crubyflie.gemspec +50 -0
- data/examples/params_and_logging.rb +87 -0
- data/lib/crubyflie/crazyflie/commander.rb +54 -0
- data/lib/crubyflie/crazyflie/console.rb +67 -0
- data/lib/crubyflie/crazyflie/log.rb +383 -0
- data/lib/crubyflie/crazyflie/log_conf.rb +57 -0
- data/lib/crubyflie/crazyflie/param.rb +220 -0
- data/lib/crubyflie/crazyflie/toc.rb +239 -0
- data/lib/crubyflie/crazyflie/toc_cache.rb +87 -0
- data/lib/crubyflie/crazyflie.rb +282 -0
- data/lib/crubyflie/crazyradio/crazyradio.rb +301 -0
- data/lib/crubyflie/crazyradio/radio_ack.rb +48 -0
- data/lib/crubyflie/crubyflie_logger.rb +74 -0
- data/lib/crubyflie/driver/crtp_packet.rb +146 -0
- data/lib/crubyflie/driver/radio_driver.rb +363 -0
- data/lib/crubyflie/exceptions.rb +36 -0
- data/lib/crubyflie/input/input_reader.rb +190 -0
- data/lib/crubyflie/input/joystick_input_reader.rb +328 -0
- data/lib/crubyflie/version.rb +22 -0
- data/lib/crubyflie.rb +36 -0
- data/spec/commander_spec.rb +67 -0
- data/spec/console_spec.rb +76 -0
- data/spec/crazyflie_spec.rb +176 -0
- data/spec/crazyradio_spec.rb +228 -0
- data/spec/crtp_packet_spec.rb +79 -0
- data/spec/crubyflie_logger_spec.rb +39 -0
- data/spec/crubyflie_spec.rb +21 -0
- data/spec/input_reader_spec.rb +136 -0
- data/spec/joystick_cfg.yaml +44 -0
- data/spec/joystick_input_reader_spec.rb +323 -0
- data/spec/log_spec.rb +266 -0
- data/spec/param_spec.rb +166 -0
- data/spec/radio_ack_spec.rb +43 -0
- data/spec/radio_driver_spec.rb +227 -0
- data/spec/spec_helper.rb +53 -0
- data/spec/toc_cache_spec.rb +87 -0
- data/spec/toc_spec.rb +187 -0
- data/tools/sdl-joystick-axis.rb +69 -0
- metadata +225 -0
@@ -0,0 +1,363 @@
|
|
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
|
+
|
20
|
+
require 'thread'
|
21
|
+
|
22
|
+
require 'exceptions'
|
23
|
+
require 'driver/crtp_packet'
|
24
|
+
require 'crazyradio/crazyradio'
|
25
|
+
|
26
|
+
|
27
|
+
|
28
|
+
module Crubyflie
|
29
|
+
# Small URI class since Ruby URI < 1.9.3 gives problems parsing
|
30
|
+
# Crazyflie URIs
|
31
|
+
class CrubyflieURI
|
32
|
+
attr_reader :scheme, :dongle, :channel, :rate
|
33
|
+
# Initialize an URI
|
34
|
+
# @param uri_str [String] the URI
|
35
|
+
def initialize(uri_str)
|
36
|
+
@uri_str = uri_str
|
37
|
+
@scheme, @dongle, @channel, @rate = split()
|
38
|
+
if @scheme.nil? || @dongle.nil? || @channel.nil? || @rate.nil? ||
|
39
|
+
@scheme != 'radio'
|
40
|
+
raise InvalidURIException.new('Bad URI')
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# Return URI as string
|
45
|
+
# @return [String] a string representation of the URI
|
46
|
+
def to_s
|
47
|
+
@uri_str
|
48
|
+
end
|
49
|
+
|
50
|
+
# Quick, dirty uri split
|
51
|
+
def split
|
52
|
+
@uri_str.sub(':', '').sub('//','/').split('/')
|
53
|
+
end
|
54
|
+
private :split
|
55
|
+
end
|
56
|
+
|
57
|
+
|
58
|
+
# This layer takes care of connecting to the crazyradio and
|
59
|
+
# managing the incoming and outgoing queues. This is done
|
60
|
+
# by spawing a thread.
|
61
|
+
# It also provides the interface to scan for available crazyflies
|
62
|
+
# to which connect
|
63
|
+
class RadioDriver
|
64
|
+
# Currently used callbacks that can be passed to connect()
|
65
|
+
CALLBACKS = [:link_quality_cb,
|
66
|
+
:link_error_cb]
|
67
|
+
# Default size for the outgoing queue
|
68
|
+
OUT_QUEUE_MAX_SIZE = 50
|
69
|
+
# Default number of retries before disconnecting
|
70
|
+
RETRIES_BEFORE_DISCONNECT = 20
|
71
|
+
|
72
|
+
attr_reader :uri
|
73
|
+
attr_reader :retries_before_disconnect, :out_queue_max_size
|
74
|
+
|
75
|
+
# Initialize the driver. Creates new empty queues.
|
76
|
+
def initialize()
|
77
|
+
@uri = nil
|
78
|
+
@in_queue = Queue.new()
|
79
|
+
@out_queue = Queue.new()
|
80
|
+
Thread.abort_on_exception = true
|
81
|
+
@radio_thread = nil
|
82
|
+
@callbacks = {}
|
83
|
+
@crazyradio = nil
|
84
|
+
@out_queue_max_size = nil
|
85
|
+
@retries_before_disconnect = nil
|
86
|
+
@shutdown_thread = false
|
87
|
+
end
|
88
|
+
|
89
|
+
# Connect to a Crazyflie in the specified URI
|
90
|
+
# @param uri_s [String] a radio uri like radio://<dongle>/<ch>/<rate>
|
91
|
+
# @param callbacks [Hash] blocks to call (see CALLBACKS contant values)
|
92
|
+
# @param opts [Hash] options. Currently supported
|
93
|
+
# :retries_before_disconnect (defaults to 20) and
|
94
|
+
# :out_queue_max_size (defaults to 50)
|
95
|
+
# @raise [CallbackMissing] when a necessary callback is not provided
|
96
|
+
# (see CALLBACKS constant values)
|
97
|
+
# @raise [InvalidURIException] when the URI is not a valid radio URI
|
98
|
+
# @raise [OpenLink] when a link is already open
|
99
|
+
def connect(uri_s, callbacks={}, opts={})
|
100
|
+
# Check if apparently there is an open link
|
101
|
+
|
102
|
+
if @crazyradio
|
103
|
+
m = "Active link to #{@uri.to_s}. Disconnect first"
|
104
|
+
raise OpenLink.new(m)
|
105
|
+
end
|
106
|
+
|
107
|
+
# Parse URI to initialize Crazyradio
|
108
|
+
# @todo: better control input. It defaults to 0
|
109
|
+
@uri = CrubyflieURI.new(uri_s)
|
110
|
+
dongle_number = @uri.dongle.to_i
|
111
|
+
channel = @uri.channel.to_i
|
112
|
+
rate = @uri.rate
|
113
|
+
|
114
|
+
# @todo this should be taken care of in crazyradio
|
115
|
+
case rate
|
116
|
+
when "250K"
|
117
|
+
rate = CrazyradioConstants::DR_250KPS
|
118
|
+
when "1M"
|
119
|
+
rate = CrazyradioConstants::DR_1MPS
|
120
|
+
when "2M"
|
121
|
+
rate = CrazyradioConstants::DR_2MPS
|
122
|
+
else
|
123
|
+
raise InvalidURIException.new("Bad radio rate")
|
124
|
+
end
|
125
|
+
|
126
|
+
# Fill in the callbacks Hash
|
127
|
+
CALLBACKS.each do |cb|
|
128
|
+
if passed_cb = callbacks[cb]
|
129
|
+
@callbacks[cb] = passed_cb
|
130
|
+
else
|
131
|
+
raise CallbackMissing.new("Callback #{cb} mandatory")
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
@retries_before_disconnect = opts[:retries_before_disconnect] ||
|
136
|
+
RETRIES_BEFORE_DISCONNECT
|
137
|
+
@out_queue_max_size = opts[:out_queue_max_size] ||
|
138
|
+
OUT_QUEUE_MAX_SIZE
|
139
|
+
|
140
|
+
# Initialize Crazyradio and run thread
|
141
|
+
cradio_opts = {
|
142
|
+
:channel => channel,
|
143
|
+
:data_rate => rate
|
144
|
+
}
|
145
|
+
@crazyradio = Crazyradio.factory(cradio_opts)
|
146
|
+
start_radio_thread()
|
147
|
+
|
148
|
+
end
|
149
|
+
|
150
|
+
# Disconnects from the crazyradio
|
151
|
+
# @param force [TrueClass, FalseClass]. Kill the thread right away, or
|
152
|
+
# wait for out_queue to empty
|
153
|
+
def disconnect(force=nil)
|
154
|
+
kill_radio_thread(force)
|
155
|
+
@in_queue.clear()
|
156
|
+
@out_queue.clear()
|
157
|
+
|
158
|
+
return if !@crazyradio
|
159
|
+
@crazyradio.close()
|
160
|
+
@crazyradio = nil
|
161
|
+
end
|
162
|
+
|
163
|
+
# Place a packet in the outgoing queue
|
164
|
+
# When not connected it will do nothing
|
165
|
+
# @param packet [CRTPPacket] The packet to send
|
166
|
+
def send_packet(packet)
|
167
|
+
return if !@crazyradio
|
168
|
+
if (s = @out_queue.size) >= @out_queue_max_size
|
169
|
+
m = "Reached #{s} elements in outgoing queue"
|
170
|
+
@callbacks[:link_error_cb].call(m)
|
171
|
+
disconnect()
|
172
|
+
end
|
173
|
+
|
174
|
+
@out_queue << packet if !@shutdown_thread
|
175
|
+
end
|
176
|
+
|
177
|
+
# Fetch a packet from the incoming queue
|
178
|
+
# @return [CRTPPacket,nil] a packet from the queue,
|
179
|
+
# or nil when there is none
|
180
|
+
def receive_packet(non_block=true)
|
181
|
+
begin
|
182
|
+
return @in_queue.pop(non_block)
|
183
|
+
rescue ThreadError
|
184
|
+
return nil
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
# List available Crazyflies in the provided channels
|
189
|
+
# @param start [Integer] channel to start
|
190
|
+
# @param stop [Intenger] channel to stop
|
191
|
+
# @return [Array] list of channels where a Crazyflie was found
|
192
|
+
def scan_radio_channels(start = 0, stop = 125)
|
193
|
+
return @crazyradio.scan_channels(start, stop)
|
194
|
+
end
|
195
|
+
private :scan_radio_channels
|
196
|
+
|
197
|
+
# List available Crazyflies
|
198
|
+
# @return [Array] List of radio URIs where a crazyflie was found
|
199
|
+
# @raise [OpenLink] if the Crazyradio is connected already
|
200
|
+
def scan_interface
|
201
|
+
raise OpenLink.new("Cannot scan when link is open") if @crazyradio
|
202
|
+
begin
|
203
|
+
@crazyradio = Crazyradio.factory()
|
204
|
+
results = {}
|
205
|
+
@crazyradio[:arc] = 1
|
206
|
+
@crazyradio[:data_rate] = Crazyradio::DR_250KPS
|
207
|
+
results["250K"] = scan_radio_channels()
|
208
|
+
@crazyradio[:data_rate] = Crazyradio::DR_1MPS
|
209
|
+
results["1M"] = scan_radio_channels()
|
210
|
+
@crazyradio[:data_rate] = Crazyradio::DR_2MPS
|
211
|
+
results["2M"] = scan_radio_channels()
|
212
|
+
|
213
|
+
uris = []
|
214
|
+
results.each do |rate, channels|
|
215
|
+
channels.each do |ch|
|
216
|
+
uris << "radio://0/#{ch}/#{rate}"
|
217
|
+
end
|
218
|
+
end
|
219
|
+
return uris
|
220
|
+
rescue USBDongleException
|
221
|
+
raise
|
222
|
+
rescue Exception
|
223
|
+
retries ||= 0
|
224
|
+
logger.error("Unknown error scanning interface: #{$!}")
|
225
|
+
@crazyradio.reopen()
|
226
|
+
retries += 1
|
227
|
+
if retries < 2
|
228
|
+
logger.error("Retrying")
|
229
|
+
sleep 0.5
|
230
|
+
retry
|
231
|
+
end
|
232
|
+
return []
|
233
|
+
ensure
|
234
|
+
@crazyradio.close() if @crazyradio
|
235
|
+
@crazyradio = nil
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
|
240
|
+
# Get status from the crazyradio. @see Crazyradio#status
|
241
|
+
def get_status
|
242
|
+
return Crazyradio.status()
|
243
|
+
end
|
244
|
+
|
245
|
+
|
246
|
+
# Privates
|
247
|
+
# The body of the communication thread
|
248
|
+
# Sends packets and tries to read the ACK
|
249
|
+
# @todo it is long and ugly
|
250
|
+
# @todo why the heck do we care here if we need to wait? Should the
|
251
|
+
# crazyradio do the waiting?
|
252
|
+
def start_radio_thread
|
253
|
+
@radio_thread = Thread.new do
|
254
|
+
Thread.current.priority = 5
|
255
|
+
out_data = [0xFF]
|
256
|
+
retries = @retries_before_disconnect
|
257
|
+
should_sleep = 0
|
258
|
+
error = "Unknown"
|
259
|
+
while true do
|
260
|
+
begin
|
261
|
+
ack = @crazyradio.send_packet(out_data)
|
262
|
+
# possible outcomes
|
263
|
+
# -exception - no usb dongle?
|
264
|
+
# -nil - bad comm
|
265
|
+
# -AckStatus class
|
266
|
+
rescue Exception
|
267
|
+
error = "Error talking to Crazyradio: #{$!.to_s}"
|
268
|
+
break
|
269
|
+
end
|
270
|
+
|
271
|
+
if ack.nil?
|
272
|
+
error = "Dongle communication error (ack is nil)"
|
273
|
+
break
|
274
|
+
end
|
275
|
+
|
276
|
+
# Set this in function of the retries
|
277
|
+
quality = (10 - ack.retry_count) * 10
|
278
|
+
@callbacks[:link_quality_cb].call(quality)
|
279
|
+
|
280
|
+
# Retry if we have not reached the limit
|
281
|
+
if !ack.ack
|
282
|
+
retries -= 1
|
283
|
+
next if retries > 0
|
284
|
+
error = "Too many packets lost"
|
285
|
+
break
|
286
|
+
else
|
287
|
+
retries = @retries_before_disconnect
|
288
|
+
end
|
289
|
+
|
290
|
+
# If there is data we queue it in incoming
|
291
|
+
# Otherwise we increase should_sleep
|
292
|
+
# If there is no data for more than 10 times
|
293
|
+
# we will sleep 0.01s when our outgoing queue
|
294
|
+
# is empty. Otherwise, we just send what we have
|
295
|
+
# of the 0xFF packet
|
296
|
+
data = ack.data
|
297
|
+
if data.length > 0
|
298
|
+
@in_queue << CRTPPacket.unpack(data)
|
299
|
+
should_sleep = 0
|
300
|
+
else
|
301
|
+
should_sleep += 1
|
302
|
+
end
|
303
|
+
|
304
|
+
break if @shutdown_thread && @out_queue.empty?()
|
305
|
+
|
306
|
+
begin
|
307
|
+
out_packet = @out_queue.pop(true) # non-block
|
308
|
+
should_sleep += 1
|
309
|
+
rescue ThreadError
|
310
|
+
out_packet = CRTPPacket.new(0xFF)
|
311
|
+
sleep 0.01 if should_sleep >= 10
|
312
|
+
end
|
313
|
+
|
314
|
+
out_data = out_packet.pack
|
315
|
+
end
|
316
|
+
if !@shutdown_thread
|
317
|
+
# If we reach here it means we are dying because of
|
318
|
+
# an error. The callback will likely call disconnect, which
|
319
|
+
# tries to kills us, but cannot because we are running the
|
320
|
+
# callback. Therefore we set @radio_thread to nil and then
|
321
|
+
# run the callback.
|
322
|
+
@radio_thread = nil
|
323
|
+
@callbacks[:link_error_cb].call(error)
|
324
|
+
end
|
325
|
+
end
|
326
|
+
end
|
327
|
+
private :start_radio_thread
|
328
|
+
|
329
|
+
def kill_radio_thread(force=false)
|
330
|
+
if @radio_thread
|
331
|
+
if force
|
332
|
+
@radio_thread.kill()
|
333
|
+
else
|
334
|
+
@shutdown_thread = true
|
335
|
+
@radio_thread.join()
|
336
|
+
end
|
337
|
+
@radio_thread = nil
|
338
|
+
@shutdown_thread = false
|
339
|
+
end
|
340
|
+
end
|
341
|
+
private :kill_radio_thread
|
342
|
+
|
343
|
+
|
344
|
+
|
345
|
+
# def pause_radio_thread
|
346
|
+
# @radio_thread.stop if @radio_thread
|
347
|
+
# end
|
348
|
+
# private :pause_radio_thread
|
349
|
+
|
350
|
+
|
351
|
+
# def resume_radio_thread
|
352
|
+
# @radio_thread.run if @radio_thread
|
353
|
+
# end
|
354
|
+
# private :resume_radio_thread
|
355
|
+
|
356
|
+
|
357
|
+
# def restart_radio_thread
|
358
|
+
# kill_radio_thread()
|
359
|
+
# start_radio_thread()
|
360
|
+
# end
|
361
|
+
# private :restart_radio_thread
|
362
|
+
end
|
363
|
+
end
|
@@ -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
|
+
module Crubyflie
|
20
|
+
# Raised when the radio URI is invalid
|
21
|
+
class InvalidURIException < Exception; end
|
22
|
+
# Raised when an radio link is already open
|
23
|
+
class OpenLink < Exception; end
|
24
|
+
# Raised when a radio driver callback parameter is missing
|
25
|
+
class CallbackMissing < Exception; end
|
26
|
+
# Raised when no USB dongle can be found
|
27
|
+
class NoDongleFound < Exception; end
|
28
|
+
# Raised when a problem occurs with the USB dongle
|
29
|
+
class USBDongleException < Exception; end
|
30
|
+
# Raised when a problem happens in the radio driver communications thread
|
31
|
+
class RadioThreadException < Exception; end
|
32
|
+
# Expected a package but it took to long to get it
|
33
|
+
class WaitTimeoutException < Exception; end
|
34
|
+
# Raised when there is a problem initializing a joystick
|
35
|
+
class JoystickException < Exception; end
|
36
|
+
end
|
@@ -0,0 +1,190 @@
|
|
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
|
+
|
21
|
+
# This class provides functionality basic to all controllers.
|
22
|
+
# Specific controller classes inherit from here.
|
23
|
+
#
|
24
|
+
# To read an input we must declare axis and buttons.
|
25
|
+
# The axis are analog float readings (range decided by the
|
26
|
+
# controller) while the buttons are integer where <= 0 means not pressed
|
27
|
+
# and > 0 means pressed.
|
28
|
+
#
|
29
|
+
# The reading of the values is implemented by children classes.
|
30
|
+
#
|
31
|
+
# The InputReader will also apply the #INPUT_ACTIONS to a given
|
32
|
+
# Crazyflie. In order to do that it will go through all the
|
33
|
+
# read values and perform actions associated to them, like sending
|
34
|
+
# a setpoint, shutting down the connection or altering the calibration.
|
35
|
+
class InputReader
|
36
|
+
|
37
|
+
# List of current recognized actions that controllers can declare
|
38
|
+
INPUT_ACTIONS = [:roll, :pitch, :yaw, :thrust,
|
39
|
+
:roll_inc_cal, :roll_dec_cal,
|
40
|
+
:pitch_inc_cal, :pitch_dec_cal,
|
41
|
+
:switch_scaled_output_mode,
|
42
|
+
:switch_xmode, :close_link]
|
43
|
+
|
44
|
+
attr_reader :axis, :buttons, :axis_readings, :button_readings
|
45
|
+
attr_accessor :xmode
|
46
|
+
# An input is composed by several necessary axis and buttons.
|
47
|
+
# @param axis [Hash] A hash of keys identifying axis IDs
|
48
|
+
# (the controller should know to what the
|
49
|
+
# ID maps), and values from #INPUT_ACTIONS
|
50
|
+
# @param buttons [Hash] A hash of keys identifying button IDs (the
|
51
|
+
# controller should know to what the ID maps),
|
52
|
+
# and values from #INPUT_ACTIONS
|
53
|
+
def initialize(axis, buttons)
|
54
|
+
@axis = axis
|
55
|
+
@buttons = buttons
|
56
|
+
@calibrations = {}
|
57
|
+
@xmode = false
|
58
|
+
@output_scale = 0 # off
|
59
|
+
|
60
|
+
# Calibrate defaults to 0
|
61
|
+
INPUT_ACTIONS.each do |action|
|
62
|
+
@calibrations[action] = 0
|
63
|
+
end
|
64
|
+
|
65
|
+
@axis_readings = {}
|
66
|
+
@button_readings = {}
|
67
|
+
end
|
68
|
+
|
69
|
+
# Read inputs will call read_axis() on all the declared axis
|
70
|
+
# and read_button() on all the declared buttons.
|
71
|
+
# After obtaining the reading, it will apply calibrations to
|
72
|
+
# the result. Apply the read values with #apply_input
|
73
|
+
def read_input
|
74
|
+
poll() # In case we need to poll the device
|
75
|
+
actions_to_axis = @axis.invert()
|
76
|
+
actions_to_axis.each do |action, axis_id|
|
77
|
+
if !INPUT_ACTIONS.include?(action)
|
78
|
+
logger.error("Unknown action #{action}. Skipping")
|
79
|
+
next
|
80
|
+
end
|
81
|
+
@axis_readings[action] = read_axis(axis_id)
|
82
|
+
@axis_readings[action] += @calibrations[action]
|
83
|
+
end
|
84
|
+
|
85
|
+
actions_to_buttons = @buttons.invert()
|
86
|
+
actions_to_buttons.each do |action, button_id|
|
87
|
+
if !INPUT_ACTIONS.include?(action)
|
88
|
+
logger.error("Unknown action #{action}. Skipping")
|
89
|
+
next
|
90
|
+
end
|
91
|
+
@button_readings[action] = read_button(button_id)
|
92
|
+
@button_readings[action] += @calibrations[action]
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
# This will act on current axis readings (by sendint a setpoint to
|
97
|
+
# the crazyflie) and on button readings (by, for example, shutting
|
98
|
+
# down the link or modifying the calibrations).
|
99
|
+
# If the link to the crazyflie is down, it will not send anything.
|
100
|
+
# @param crazyflie [Crazyflie] A crazyflie instance to send the
|
101
|
+
# setpoint to.
|
102
|
+
def apply_input(crazyflie)
|
103
|
+
return if !crazyflie.active?
|
104
|
+
setpoint = {
|
105
|
+
:roll => nil,
|
106
|
+
:pitch => nil,
|
107
|
+
:yaw => nil,
|
108
|
+
:thrust => nil
|
109
|
+
}
|
110
|
+
|
111
|
+
@button_readings.each do |action, value|
|
112
|
+
case action
|
113
|
+
when :roll
|
114
|
+
setpoint[:roll] = value
|
115
|
+
when :pitch
|
116
|
+
setpoint[:pitch] = value
|
117
|
+
when :yaw
|
118
|
+
setpoint[:yaw] = value
|
119
|
+
when :thrust
|
120
|
+
setpoint[:thrust] = value
|
121
|
+
when :roll_inc_cal
|
122
|
+
@calibrations[:roll] += 1
|
123
|
+
when :roll_dec_cal
|
124
|
+
@calibrations[:roll] -= 1
|
125
|
+
when :pitch_inc_cal
|
126
|
+
@calibrations[:pitch] += 1
|
127
|
+
when :pitch_dec_cal
|
128
|
+
@calibrations[:pitch] -= 1
|
129
|
+
when :switch_xmode
|
130
|
+
@xmode = !@xmode if value > 0
|
131
|
+
logger.info("Xmode is #{@xmode}") if value > 0
|
132
|
+
when :switch_scaled_output_mode
|
133
|
+
if value > 0 && @output_scale == 0
|
134
|
+
logger.info("Scaling output: x#{value}")
|
135
|
+
@output_scale = value.to_f
|
136
|
+
elsif value > 0 && @output_scale > 0
|
137
|
+
logger.info("Scaling output disabled")
|
138
|
+
@output_scale = 0
|
139
|
+
end
|
140
|
+
when :close_link
|
141
|
+
crazyflie.close_link() if value > 0
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
return if !crazyflie.active?
|
146
|
+
|
147
|
+
@axis_readings.each do |action, value|
|
148
|
+
case action
|
149
|
+
when :roll
|
150
|
+
setpoint[:roll] = value
|
151
|
+
when :pitch
|
152
|
+
setpoint[:pitch] = value
|
153
|
+
when :yaw
|
154
|
+
setpoint[:yaw] = value
|
155
|
+
when :thrust
|
156
|
+
setpoint[:thrust] = value
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
setpoint.keys().each do |k|
|
161
|
+
next if k == :thrust
|
162
|
+
setpoint[k] *= @output_scale
|
163
|
+
end if @output_scale > 0
|
164
|
+
|
165
|
+
pitch = setpoint[:pitch]
|
166
|
+
roll = setpoint[:roll]
|
167
|
+
yaw = setpoint[:yaw]
|
168
|
+
thrust = setpoint[:thrust]
|
169
|
+
|
170
|
+
if pitch && roll && yaw && thrust
|
171
|
+
m = "Sending R: #{roll} P: #{pitch} Y: #{yaw} T: #{thrust}"
|
172
|
+
logger.debug(m)
|
173
|
+
crazyflie.commander.send_setpoint(roll, pitch, yaw, thrust,
|
174
|
+
@xmode)
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
private
|
179
|
+
def read_axis(axis_id)
|
180
|
+
raise Exception.new("Not implemented!")
|
181
|
+
end
|
182
|
+
|
183
|
+
def read_button(button_id)
|
184
|
+
raise Exception.new("Not implemented!")
|
185
|
+
end
|
186
|
+
|
187
|
+
def poll
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|