crubyflie 0.1.0

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.
Files changed (47) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +18 -0
  3. data/Gemfile +4 -0
  4. data/LICENSE.txt +674 -0
  5. data/README.md +99 -0
  6. data/Rakefile +15 -0
  7. data/bin/crubyflie +85 -0
  8. data/configs/joystick_default.yaml +48 -0
  9. data/crubyflie.gemspec +50 -0
  10. data/examples/params_and_logging.rb +87 -0
  11. data/lib/crubyflie/crazyflie/commander.rb +54 -0
  12. data/lib/crubyflie/crazyflie/console.rb +67 -0
  13. data/lib/crubyflie/crazyflie/log.rb +383 -0
  14. data/lib/crubyflie/crazyflie/log_conf.rb +57 -0
  15. data/lib/crubyflie/crazyflie/param.rb +220 -0
  16. data/lib/crubyflie/crazyflie/toc.rb +239 -0
  17. data/lib/crubyflie/crazyflie/toc_cache.rb +87 -0
  18. data/lib/crubyflie/crazyflie.rb +282 -0
  19. data/lib/crubyflie/crazyradio/crazyradio.rb +301 -0
  20. data/lib/crubyflie/crazyradio/radio_ack.rb +48 -0
  21. data/lib/crubyflie/crubyflie_logger.rb +74 -0
  22. data/lib/crubyflie/driver/crtp_packet.rb +146 -0
  23. data/lib/crubyflie/driver/radio_driver.rb +333 -0
  24. data/lib/crubyflie/exceptions.rb +36 -0
  25. data/lib/crubyflie/input/input_reader.rb +168 -0
  26. data/lib/crubyflie/input/joystick_input_reader.rb +280 -0
  27. data/lib/crubyflie/version.rb +22 -0
  28. data/lib/crubyflie.rb +31 -0
  29. data/spec/commander_spec.rb +67 -0
  30. data/spec/console_spec.rb +76 -0
  31. data/spec/crazyflie_spec.rb +176 -0
  32. data/spec/crazyradio_spec.rb +226 -0
  33. data/spec/crtp_packet_spec.rb +79 -0
  34. data/spec/crubyflie_logger_spec.rb +39 -0
  35. data/spec/crubyflie_spec.rb +20 -0
  36. data/spec/input_reader_spec.rb +136 -0
  37. data/spec/joystick_cfg.yaml +48 -0
  38. data/spec/joystick_input_reader_spec.rb +238 -0
  39. data/spec/log_spec.rb +266 -0
  40. data/spec/param_spec.rb +166 -0
  41. data/spec/radio_ack_spec.rb +43 -0
  42. data/spec/radio_driver_spec.rb +227 -0
  43. data/spec/spec_helper.rb +51 -0
  44. data/spec/toc_cache_spec.rb +87 -0
  45. data/spec/toc_spec.rb +187 -0
  46. data/tools/sdl-joystick-axis.rb +69 -0
  47. metadata +222 -0
@@ -0,0 +1,301 @@
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 'libusb'
21
+
22
+ require 'exceptions'
23
+ require 'crazyradio/radio_ack'
24
+
25
+ module Crubyflie
26
+
27
+ # This module defines some Crazyradio-related constants
28
+ module CrazyradioConstants
29
+ # USB dongle vendor ID
30
+ CRAZYRADIO_VENDOR_ID = 0x1915
31
+ # USB dongle product ID
32
+ CRAZYRADIO_PRODUCT_ID = 0x7777
33
+
34
+ # Set radio channel instruction code
35
+ SET_RADIO_CHANNEL = 0x01
36
+ # Set address instruction code
37
+ SET_RADIO_ADDRESS = 0x02
38
+ # Set data rate instruction code. For values see below
39
+ SET_DATA_RATE = 0x03
40
+ # Set radio power instruction code. For valid values see below
41
+ SET_RADIO_POWER = 0x04
42
+ # Set ARD (Auto Retry Delay) instruction code
43
+ SET_RADIO_ARD = 0x05
44
+ # Set ARC (Auto Retry Count) instruction code
45
+ SET_RADIO_ARC = 0x06
46
+ # Set ack instruction code
47
+ ACK_ENABLE = 0x10
48
+ # Set control carrier instruction code
49
+ SET_CONT_CARRIER = 0x20
50
+ # Scan N channels instruction code
51
+ SCANN_CHANNELS = 0x21
52
+ # Launch bootloader instruction code
53
+ LAUNCH_BOOTLOADER = 0xFF
54
+
55
+ # Default channel to talk to a Crazyflie
56
+ DEFAULT_CHANNEL = 2
57
+
58
+ # 250 Kb/s datarate
59
+ DR_250KPS = 0
60
+ # 1 Mb/s datarate
61
+ DR_1MPS = 1
62
+ # 2 Mb/s datarate
63
+ DR_2MPS = 2
64
+
65
+ # 18db power attenuation
66
+ P_M18DBM = 0
67
+ # 12db power attenuation
68
+ P_M12DBM = 1
69
+ # 6db power attenuation
70
+ P_M6DBM = 2
71
+ # 0db power attenuation
72
+ P_0DBM = 3
73
+ end
74
+
75
+ # Driver for the USB crazyradio dongle
76
+ class Crazyradio
77
+ include CrazyradioConstants
78
+ # Default settings for Crazyradio
79
+ DEFAULT_SETTINGS = {
80
+ :data_rate => DR_2MPS,
81
+ :channel => 2,
82
+ :cont_carrier => false,
83
+ :address => [0xE7] * 5, #5 times 0xE7
84
+ :power => P_0DBM,
85
+ :arc => 3,
86
+ :ard_bytes => 32 # 32
87
+ }
88
+
89
+ attr_reader :device, :handle, :dev_handle
90
+ # Initialize a crazyradio
91
+ # @param device [LIBUSB::Device] A crazyradio USB device
92
+ # @param settings [Hash] Crazyradio settings. @see #DEFAULT_SETTINGS
93
+ # @raise [USBDongleException] when something goes wrong
94
+ def initialize(device=nil, settings={})
95
+ if device.nil? || !device.is_a?(LIBUSB::Device)
96
+ raise USBDongleException.new("Wrong USB device")
97
+ end
98
+
99
+ @device = device
100
+ reopen()
101
+ @settings = DEFAULT_SETTINGS
102
+ @settings.update(settings)
103
+ apply_settings()
104
+ end
105
+
106
+ # Initializes the device and the USB handle
107
+ # If they are open, it releases the resources first
108
+ def reopen
109
+ close()
110
+ @handle = @device.open()
111
+ # USB configuration 0 means unconfigured state
112
+ @handle.configuration = 1 # hardcoded
113
+ @handle.claim_interface(0) # hardcoded
114
+ end
115
+
116
+ # Return some information as string
117
+ # @return [String] Dongle information
118
+ def self.status
119
+ cr = Crazyradio.factory()
120
+ serial = cr.device.serial_number
121
+ manufacturer = cr.device.manufacturer
122
+ cr.close()
123
+ return "Found #{serial} USB dongle from #{manufacturer}"
124
+ end
125
+
126
+ # Release interface, reset device and close the handle
127
+ def close
128
+ @handle.release_interface(0) if @handle
129
+ @handle.reset_device() if @handle
130
+ # WARNING: This hangs badly and randomly!!!
131
+ # @handle.close() if @handle
132
+ @handle = nil
133
+ end
134
+
135
+ # Determines if the dongle has hardware scanning.
136
+ # @return [nil] defaults to nil to mitigate a dongle bug
137
+ def has_fw_scan
138
+ # it seems there is a bug on fw scan
139
+ nil
140
+ end
141
+
142
+ # Scans channels for crazyflies
143
+ def scan_channels(start, stop, packet=[0xFF])
144
+ if has_fw_scan()
145
+ send_vendor_setup(SCANN_CHANNELS, start, stop, packet)
146
+ return get_vendor_setup(SCANN_CHANNELS, 0, 0, 64)
147
+ end
148
+
149
+ result = []
150
+ (start..stop).each do |ch|
151
+ self[:channel] = ch
152
+ status = send_packet(packet)
153
+ result << ch if status && status.ack
154
+ end
155
+ return result
156
+ end
157
+
158
+ # Creates a Crazyradio object with the first USB dongle found
159
+ # @param settings [Hash] Crazyradio settings. @see #DEFAULT_SETTINGS
160
+ # @return [Crazyradio] a Crazyradio
161
+ # @raise [USBDongleException] when no USB dongle is found
162
+ def self.factory(settings={})
163
+ devs = Crazyradio.find_devices()
164
+ raise USBDongleException.new("No dongles found") if devs.empty?()
165
+ return Crazyradio.new(devs.first, settings)
166
+ end
167
+
168
+ # List crazyradio dongles
169
+ def self.find_devices
170
+ usb = LIBUSB::Context.new
171
+ usb.devices(:idVendor => CRAZYRADIO_VENDOR_ID,
172
+ :idProduct => CRAZYRADIO_PRODUCT_ID)
173
+ end
174
+
175
+ # Send a data packet and reads the response into an Ack
176
+ # @param [Array] data to be sent
177
+ def send_packet(data)
178
+ out_args = {
179
+ :endpoint => 1,
180
+ :dataOut => data.pack('C*')
181
+ }
182
+ @handle.bulk_transfer(out_args)
183
+ in_args = {
184
+ :endpoint => 0x81,
185
+ :dataIn => 64
186
+ }
187
+ response = @handle.bulk_transfer(in_args)
188
+
189
+ return nil unless response
190
+ return RadioAck.from_raw(response, @settings[:arc])
191
+ end
192
+
193
+ # Set a crazyradio setting
194
+ # @param setting [Symbol] a valid Crazyradio setting name
195
+ # @param value [Object] the setting value
196
+ def []=(setting, value)
197
+ @settings[setting] = value
198
+ apply_settings(setting)
199
+ end
200
+
201
+ # Get a crazyradio setting
202
+ # @param setting [Symbol] a valid Crazyradio setting name
203
+ # @return [Integer] the value
204
+ def [](setting)
205
+ return @settings[setting]
206
+ end
207
+
208
+ # Applies the indicated setting or all settings if not specified
209
+ # @param setting [Symbol] a valid crazyradio setting name
210
+ def apply_settings(setting=nil)
211
+ to_apply = setting.nil? ? @settings.keys() : [setting]
212
+ to_apply.each do |setting|
213
+ value = @settings[setting]
214
+ next if value.nil?
215
+
216
+ case setting
217
+ when :data_rate
218
+ set_data_rate(value)
219
+ when :channel
220
+ set_channel(value)
221
+ when :arc
222
+ set_arc(value)
223
+ when :cont_carrier
224
+ set_cont_carrier(value)
225
+ when :address
226
+ set_address(value)
227
+ when :power
228
+ set_power(value)
229
+ when :ard_bytes
230
+ set_ard_bytes(value)
231
+ else
232
+ @settings.delete(setting)
233
+ end
234
+ end
235
+ end
236
+
237
+ def send_vendor_setup(request, value, index=0, dataOut=[])
238
+ args = {
239
+ :bmRequestType => LIBUSB::REQUEST_TYPE_VENDOR,
240
+ :bRequest => request,
241
+ :wValue => value,
242
+ :wIndex => index,
243
+ :dataOut => dataOut.pack('C*')
244
+ }
245
+ @handle.control_transfer(args)
246
+ end
247
+ private :send_vendor_setup
248
+
249
+ def get_vendor_setup(request, value, index, dataIn=0)
250
+ args = {
251
+ # Why this mask?
252
+ :bmRequestType => LIBUSB::REQUEST_TYPE_VENDOR | 0x80,
253
+ :bRequest => request,
254
+ :wValue => value,
255
+ :wIndex => index,
256
+ :dataIn => dataIn
257
+ }
258
+ return @handle.control_transfer(args).unpack('C*')
259
+ end
260
+ private :get_vendor_setup
261
+
262
+ def set_channel(channel)
263
+ send_vendor_setup(SET_RADIO_CHANNEL, channel)
264
+ end
265
+ private :set_channel
266
+
267
+ def set_address(addr)
268
+ if addr.size != 5
269
+ raise USBDongleException.new("Address needs 5 bytes")
270
+ end
271
+ send_vendor_setup(SET_RADIO_ADDRESS, 0, 0, addr)
272
+ end
273
+ private :set_address
274
+
275
+ def set_data_rate(datarate)
276
+ send_vendor_setup(SET_DATA_RATE, datarate)
277
+ end
278
+ private :set_data_rate
279
+
280
+ def set_power(power)
281
+ send_vendor_setup(SET_RADIO_POWER, power)
282
+ end
283
+ private :set_power
284
+
285
+ def set_arc(arc)
286
+ send_vendor_setup(SET_RADIO_ARC, arc)
287
+ end
288
+ private :set_arc
289
+
290
+ def set_ard_bytes(nbytes)
291
+ # masking this way converts 32 to 0xA0 for example
292
+ send_vendor_setup(SET_RADIO_ARD, 0x80 | nbytes)
293
+ end
294
+ private :set_ard_bytes
295
+
296
+ def set_cont_carrier(active)
297
+ send_vendor_setup(SET_CONT_CARRIER, active ? 1 : 0)
298
+ end
299
+ private :set_cont_carrier
300
+ end
301
+ end
@@ -0,0 +1,48 @@
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
+ # An acknowlegdement packet from the Crazyflie
21
+ class RadioAck
22
+ attr_accessor :ack, :powerDet, :retry_count, :data
23
+
24
+ # Initialize a Radio Ack
25
+ # @param ack [TrueClass,FalseClass] indicates if it is an ack
26
+ # @param powerDet [TrueClass,FalseClass] powerDet
27
+ # @param retry_count [Integer] the times we retried to send the packet
28
+ # @param data [Array] the payload of the ack packet
29
+ def initialize(ack=nil, powerDet=nil, retry_count=0, data=[])
30
+ @ack = ack
31
+ @powerDet = powerDet
32
+ @retry_count = retry_count
33
+ @data = data
34
+ end
35
+
36
+ # Create from raw usb response
37
+ # @param data [String] binary data
38
+ # @return [RadioAck] a properly initialized RadioAck
39
+ def self.from_raw(data, arc=0)
40
+ response = data.unpack('C*')
41
+ header = response.shift()
42
+ ack = (header & 0x01) != 0
43
+ powerDet = (header & 0x02) != 0
44
+ retry_count = header != 0 ? header >> 4 : arc
45
+ return RadioAck.new(ack, powerDet, retry_count, response)
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,74 @@
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
+ # A simple script to list SDL axis/button numbering and read values
20
+
21
+ # This module is included where needed and offers
22
+ # easy access to the logger
23
+ module Logging
24
+ # Give me a logger
25
+ # @return [CrubyflieLogger]
26
+ def self.logger
27
+ Logging.logger
28
+ end
29
+
30
+ # Lazy initialization for a logger
31
+ # @return [CrubyflieLogger]
32
+ def logger
33
+ @logger ||= CrubyflieLogger.new()
34
+ end
35
+
36
+ # Set a logger
37
+ # @param logger [CrubyflieLogger] the new logger to use
38
+ def logger=(logger)
39
+ @logger = logger
40
+ end
41
+ end
42
+
43
+ # A simple logger to log debug messages, info, warnings and errors
44
+ class CrubyflieLogger
45
+ # Initialize a logger and enable debug logs
46
+ # @param debug [TrueClass,nil] enable output of debug messages
47
+ def initialize(debug=$debug)
48
+ @@debug = debug
49
+ end
50
+
51
+ # Logs a debug message
52
+ # @param msg [String] the message to be logged
53
+ def debug(msg)
54
+ $stderr.puts "DEBUG: #{msg}" if @@debug
55
+ end
56
+
57
+ # Logs an info message to $stdout
58
+ # @param msg [String] the message to be logged
59
+ def info(msg)
60
+ $stdout.puts "INFO: #{msg}"
61
+ end
62
+
63
+ # Logs a warning message to $stderr
64
+ # @param msg [String] the message to be logged
65
+ def warn(msg)
66
+ $stderr.puts "WARNING: #{msg}"
67
+ end
68
+
69
+ # Logs an error message to $stderr
70
+ # @param msg [String] the message to be logged
71
+ def error(msg)
72
+ $stderr.puts "ERROR: #{msg}"
73
+ end
74
+ end
@@ -0,0 +1,146 @@
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
+ module Crubyflie
21
+
22
+ # Constants related to CRTP Packets
23
+ module CRTPConstants
24
+ # The ports for the different facilities
25
+ CRTP_PORTS = {
26
+ :console => 0x00,
27
+ :param => 0x02,
28
+ :commander => 0x03,
29
+ :logging => 0x05,
30
+ :debugdriver => 0x0E,
31
+ :linkctrl => 0x0F,
32
+ :all => 0xFF
33
+ }
34
+
35
+ # How many seconds until we give up waiting for a packet to
36
+ # appear in a queue
37
+ WAIT_PACKET_TIMEOUT = 2
38
+
39
+ # TOC channel
40
+ TOC_CHANNEL = 0
41
+
42
+ # Channel to retrieve Log settings
43
+ LOG_SETTINGS_CHANNEL = 1
44
+ # Channel to retrieve Log data
45
+ LOG_DATA_CHANNEL = 2
46
+
47
+ # Channel to read parameters
48
+ PARAM_READ_CHANNEL = 1
49
+ # Channel to write parameters
50
+ PARAM_WRITE_CHANNEL = 2
51
+
52
+
53
+
54
+ # Command to request a TOC element
55
+ CMD_TOC_ELEMENT = 0
56
+ # Command to request TOC information
57
+ CMD_TOC_INFO = 1
58
+ # Create block command
59
+ CMD_CREATE_BLOCK = 0
60
+ # Append block command
61
+ CMD_APPEND_BLOCK = 1
62
+ # Delete block command
63
+ CMD_DELETE_BLOCK = 2
64
+ # Start logging command
65
+ CMD_START_LOGGING = 3
66
+ # Stop logging command
67
+ CMD_STOP_LOGGING = 4
68
+ # Reset logging command
69
+ CMD_RESET_LOGGING = 5
70
+
71
+
72
+ # These come from param.rb
73
+ # # TOC access command
74
+ # TOC_RESET = 0
75
+ # TOC_GETNEXT = 1
76
+ # TOC_GETCRC32 = 2
77
+
78
+
79
+ end
80
+
81
+
82
+ # A data packet. Raw packet data is sent to the USB driver
83
+ # Some related docs:
84
+ # http://wiki.bitcraze.se/
85
+ # projects:crazyflie:firmware:comm_protocol#serial_port
86
+ class CRTPPacket
87
+
88
+ attr_reader :size, :header, :channel, :port, :data
89
+ # Initialize a package with a header and data
90
+ # @param header [Integer] represents an 8 bit header
91
+ # @param payload [Array] @see #set_data
92
+ def initialize(header=0, payload=[])
93
+ modify_header(header)
94
+ @data = payload || []
95
+ @size = data.size #+ 1 # header. Bytes
96
+ end
97
+
98
+ # Set new data for this packet and update the size
99
+ # @param new_data [Array] the new data
100
+ def data=(new_data)
101
+ @data = new_data
102
+ @size = @data.size
103
+ end
104
+
105
+ # Modify the full header, or the channel or the port
106
+ # @param header [Integer] a new full header. Prevails over the rest
107
+ # @param port [Integer] a new port (4 bits)
108
+ # @param channel [Integer] a new channel (2 bits)
109
+ def modify_header(header=nil, port=nil, channel=nil)
110
+ if header
111
+ @header = header
112
+ @channel = header & 0b11 # lowest 2 bits of header
113
+ @port = (header >> 4) & 0b1111 # bits 4-7
114
+ return
115
+ end
116
+ if channel
117
+ @channel = channel & 0b11 # 2 bits
118
+ @header = (@header & 0b11111100) | @channel
119
+ end
120
+ if port
121
+ @port = (port & 0b1111) # 4 bits
122
+ @header = (@header & 0b00001111) | @port << 4
123
+ end
124
+ end
125
+
126
+ # Creates a packet from a raw data array
127
+ def self.unpack(data)
128
+ return CRTPPacket.new() if !data.is_a?(Array) || data.empty?()
129
+ header = data[0]
130
+ data = data[1..-1]
131
+ CRTPPacket.new(header, data)
132
+ end
133
+
134
+ # Concat the header and the data and return it
135
+ # @return [Array] header concatenated with data
136
+ def pack
137
+ [@header].concat(@data)
138
+ end
139
+
140
+ # Pack the data of the packet into unsigned chars when needed
141
+ # @return [String] binary data
142
+ def data_repack
143
+ return @data.pack('C*')
144
+ end
145
+ end
146
+ end