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,383 @@
|
|
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 'crazyflie/log_conf.rb'
|
21
|
+
|
22
|
+
module Crubyflie
|
23
|
+
|
24
|
+
# An element in the Logging Table of Contents
|
25
|
+
# A LogTOCElement knows what the type of data that comes in a #LogBlock
|
26
|
+
# and is able to initialize the #TOCElement from a TOC Logging packet
|
27
|
+
class LogTOCElement < TOCElement
|
28
|
+
|
29
|
+
# A map between crazyflie C types and ruby directives to
|
30
|
+
# interpret them. This will help parsing the logging data
|
31
|
+
C_RUBY_TYPE_MAP = {
|
32
|
+
1 => {
|
33
|
+
:ctype => "uint8_t",
|
34
|
+
:directive => 'C',
|
35
|
+
:size => 1
|
36
|
+
},
|
37
|
+
2 => {
|
38
|
+
:ctype => "uint16_t",
|
39
|
+
:directive => 'S<',
|
40
|
+
:size => 2
|
41
|
+
},
|
42
|
+
3 => {
|
43
|
+
:ctype => "uint32_t",
|
44
|
+
:directive => 'L<',
|
45
|
+
:size => 4
|
46
|
+
},
|
47
|
+
4 => {
|
48
|
+
:ctype => "int8_t",
|
49
|
+
:directive => 'c',
|
50
|
+
:size => '1'
|
51
|
+
},
|
52
|
+
5 => {
|
53
|
+
:ctype => "int16_t",
|
54
|
+
:directive => 's<',
|
55
|
+
:size => 2
|
56
|
+
},
|
57
|
+
6 => {
|
58
|
+
:ctype => "int32_t",
|
59
|
+
:directive => 'l<',
|
60
|
+
:size => 4
|
61
|
+
},
|
62
|
+
7 => {
|
63
|
+
:ctype => "float",
|
64
|
+
:directive => 'e',
|
65
|
+
:size => 4
|
66
|
+
},
|
67
|
+
# Unsupported
|
68
|
+
# 8 => {
|
69
|
+
# :ctype => "FP16",
|
70
|
+
# :directive => '',
|
71
|
+
# :size => 2
|
72
|
+
# }
|
73
|
+
}
|
74
|
+
|
75
|
+
# Initializes a Log TOC element, which means interpreting the
|
76
|
+
# data in the packet and calling the parent class
|
77
|
+
# @param data [String] a binary payload
|
78
|
+
def initialize(data)
|
79
|
+
# unpack two null padded strings
|
80
|
+
group, name = data[2..-1].unpack('Z*Z*')
|
81
|
+
ident = data[0].ord()
|
82
|
+
ctype_id = data[1].ord() & 0b1111 # go from 0 to 15
|
83
|
+
ctype = C_RUBY_TYPE_MAP[ctype_id][:ctype]
|
84
|
+
directive = C_RUBY_TYPE_MAP[ctype_id][:directive]
|
85
|
+
access = data[1].ord & 0b00010000 # 0x10, the 5th bit
|
86
|
+
|
87
|
+
super({
|
88
|
+
:ident => ident,
|
89
|
+
:group => group,
|
90
|
+
:name => name,
|
91
|
+
:ctype => ctype,
|
92
|
+
:type_id => ctype_id,
|
93
|
+
:directive => directive,
|
94
|
+
:access => access
|
95
|
+
})
|
96
|
+
end
|
97
|
+
|
98
|
+
|
99
|
+
end
|
100
|
+
|
101
|
+
# A LogBlock represents a piece of logging information that is
|
102
|
+
# received periodically from the Crazyflie after having set the
|
103
|
+
# START_LOGGING command. Each LogBlock will trigger a callback when
|
104
|
+
# a piece of data is received for it.
|
105
|
+
#
|
106
|
+
# Note log blocks are added/removed by the Logging class through the
|
107
|
+
# interface provided. So you should not need to use them directly
|
108
|
+
class LogBlock
|
109
|
+
|
110
|
+
@@block_id_counter = 0
|
111
|
+
attr_reader :ident, :period
|
112
|
+
attr_writer :data_callback
|
113
|
+
|
114
|
+
# Initialize a LogBlock
|
115
|
+
# @param variables [Array] a set of LogConfVariables
|
116
|
+
# @param opts [Hash] current options:
|
117
|
+
# :period, in centiseconds (100 = 1s)
|
118
|
+
def initialize(variables, opts={})
|
119
|
+
@variables = variables || []
|
120
|
+
@ident = @@block_id_counter
|
121
|
+
@@block_id_counter += 1
|
122
|
+
|
123
|
+
@period = opts.delete(:period) || 10
|
124
|
+
|
125
|
+
@data_callback = nil
|
126
|
+
end
|
127
|
+
|
128
|
+
# Finds out the binary data by unpacking each of the variables
|
129
|
+
# depending on the number of bites for the declared size
|
130
|
+
# @param data [String] Binary data string
|
131
|
+
def unpack_log_data(data)
|
132
|
+
unpacked_data = {}
|
133
|
+
position = 0
|
134
|
+
@variables.each do |var|
|
135
|
+
fetch_as = var.fetch_as
|
136
|
+
map = LogTOCElement::C_RUBY_TYPE_MAP
|
137
|
+
size = map[fetch_as][:size]
|
138
|
+
directive = map[fetch_as][:directive]
|
139
|
+
name = var.name
|
140
|
+
data_to_unpack = data[position..position + size - 1]
|
141
|
+
value = data_to_unpack.unpack(directive).first
|
142
|
+
unpacked_data[name] = value
|
143
|
+
position += size
|
144
|
+
end
|
145
|
+
@data_callback.call(unpacked_data) if @data_callback
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
# The logging facility class
|
150
|
+
#
|
151
|
+
# This class is used to read packages received in the Logging port.
|
152
|
+
# It maintains a list of log blocks, which are conveniently added,
|
153
|
+
# or removed and for which logging is started or stopped.
|
154
|
+
# When a packet with new information for log block comes in,
|
155
|
+
# the block in question unpacks the data and triggers a callback.
|
156
|
+
#
|
157
|
+
# In Crubyflie, the Log class includes all the functionality
|
158
|
+
# which is to be found in the Python library LogEntry class (start logging,
|
159
|
+
# add block etc) and the Crazyflie class (callbacks for intialization), so
|
160
|
+
# interfacing with Log should be done through this class primarily.
|
161
|
+
#
|
162
|
+
# Unlike the original Pyhton library, there are no callbacks registered
|
163
|
+
# somewhere else or anything and functions being called from them. In turn,
|
164
|
+
# the Crazyflie class will queue all the logging requests in the @in_queue
|
165
|
+
# while a thread in the Logging class takes care of processing them
|
166
|
+
# and doing the appropiate. This saves us from registering callbacks
|
167
|
+
# in other places and from selecting which data we are to use here.
|
168
|
+
class Log
|
169
|
+
include Logging
|
170
|
+
include CRTPConstants
|
171
|
+
|
172
|
+
attr_reader :log_blocks, :toc
|
173
|
+
# Store the crazyflie, find the incoming packet queue for this
|
174
|
+
# facility and initialize a new TOC
|
175
|
+
# @param crazyflie [Crazyflie]
|
176
|
+
def initialize(crazyflie)
|
177
|
+
@log_blocks = {}
|
178
|
+
@crazyflie = crazyflie
|
179
|
+
@in_queue = crazyflie.crtp_queues[:logging]
|
180
|
+
@toc = TOC.new(@crazyflie.cache_folder, LogTOCElement)
|
181
|
+
@packet_reader_thread = nil
|
182
|
+
end
|
183
|
+
|
184
|
+
# Refreshes the TOC. TOC class implement this step synchronously
|
185
|
+
# so there is no need to provide callbacks or anything
|
186
|
+
def refresh_toc
|
187
|
+
reset_packet = packet_factory()
|
188
|
+
reset_packet.data = [CMD_RESET_LOGGING]
|
189
|
+
port = Crazyflie::CRTP_PORTS[:logging]
|
190
|
+
channel = TOC_CHANNEL
|
191
|
+
@crazyflie.send_packet(reset_packet)
|
192
|
+
@toc.fetch_from_crazyflie(@crazyflie, port, @in_queue)
|
193
|
+
end
|
194
|
+
|
195
|
+
# Creates a log block with the information from a configuration
|
196
|
+
# object.
|
197
|
+
# @param log_conf [LogConf] Configuration for this block
|
198
|
+
# @return [Integer] block ID if things went well,
|
199
|
+
def create_log_block(log_conf)
|
200
|
+
start_packet_reader_thread() if !@packet_reader_thread
|
201
|
+
block = LogBlock.new(log_conf.variables,
|
202
|
+
{:period => log_conf.period})
|
203
|
+
block_id = block.ident
|
204
|
+
@log_blocks[block_id] = block
|
205
|
+
packet = packet_factory()
|
206
|
+
packet.data = [CMD_CREATE_BLOCK, block_id]
|
207
|
+
log_conf.variables.each do |var|
|
208
|
+
if var.is_toc_variable?
|
209
|
+
packet.data << var.stored_fetch_as
|
210
|
+
packet.data << @toc[var.name].ident
|
211
|
+
else
|
212
|
+
bin_stored_fetch_as = [var.stored_fetch_as].pack('C')
|
213
|
+
bin_address = [var.address].pack('L<')
|
214
|
+
packet.data += bin_stored_fetch_as.unpack('C*')
|
215
|
+
packet.data += bin_address.unpack('C*')
|
216
|
+
end
|
217
|
+
end
|
218
|
+
logger.debug "Adding block #{block_id}"
|
219
|
+
@crazyflie.send_packet(packet)
|
220
|
+
return block_id
|
221
|
+
end
|
222
|
+
|
223
|
+
# Sends the START_LOGGING command for a given block.
|
224
|
+
# It should be called after #create_toc_log_block. This call
|
225
|
+
# will return immediately, but the provided block will be called
|
226
|
+
# regularly as logging data is received, until #stop_logging is
|
227
|
+
# issued for the same log
|
228
|
+
# Crazyflie. It fails silently if the block does not exist.
|
229
|
+
#
|
230
|
+
# @param block_id [Integer]
|
231
|
+
# @param data_callback [Proc] a block to be called everytime
|
232
|
+
# the log data is received.
|
233
|
+
def start_logging(block_id, &data_callback)
|
234
|
+
block = @log_blocks[block_id]
|
235
|
+
block.data_callback = data_callback
|
236
|
+
return if !block
|
237
|
+
start_packet_reader_thread() if !@packet_reader_thread
|
238
|
+
packet = packet_factory()
|
239
|
+
period = block.period
|
240
|
+
packet.data = [CMD_START_LOGGING, block_id, period]
|
241
|
+
logger.debug("Start logging on #{block_id} every #{period*10} ms")
|
242
|
+
@crazyflie.send_packet(packet)
|
243
|
+
end
|
244
|
+
|
245
|
+
# Sends the STOP_LOGGING command to the crazyflie for a given block.
|
246
|
+
# It fails silently if the block does not exist.
|
247
|
+
# @param block_id [Integer]
|
248
|
+
def stop_logging(block_id)
|
249
|
+
block = @log_blocks[block_id]
|
250
|
+
return if !block
|
251
|
+
packet = packet_factory()
|
252
|
+
packet.data = [CMD_STOP_LOGGING, block_id]
|
253
|
+
logger.debug("Stop logging on #{block_id}")
|
254
|
+
@crazyflie.send_packet(packet)
|
255
|
+
end
|
256
|
+
|
257
|
+
# Sends the DELETE_BLOCK command to the Crazyflie for a given block.
|
258
|
+
# It fails silently if the block does not exist.
|
259
|
+
# To be called after #stop_logging.
|
260
|
+
def delete_block(block_id)
|
261
|
+
block = @log_blocks.delete(block_id)
|
262
|
+
return if !block
|
263
|
+
packet = packet_factory()
|
264
|
+
packet.data = [CMD_DELETE_BLOCK, block_id]
|
265
|
+
@crazyflie.send_packet(packet)
|
266
|
+
end
|
267
|
+
|
268
|
+
# A thread that processes the queue of packets intended for this
|
269
|
+
# facility. Recommended to start it after TOC has been refreshed.
|
270
|
+
def start_packet_reader_thread
|
271
|
+
stop_packet_reader_thread()
|
272
|
+
@packet_reader_thread = Thread.new do
|
273
|
+
Thread.current.priority = -4
|
274
|
+
loop do
|
275
|
+
packet = @in_queue.pop() # block here if nothing is up
|
276
|
+
# @todo align these two
|
277
|
+
case packet.channel()
|
278
|
+
when LOG_SETTINGS_CHANNEL
|
279
|
+
handle_settings_packet(packet)
|
280
|
+
when LOG_DATA_CHANNEL
|
281
|
+
handle_logdata_packet(packet)
|
282
|
+
when TOC_CHANNEL
|
283
|
+
# We are refreshing TOC probably
|
284
|
+
@in_queue << packet
|
285
|
+
sleep 0.2
|
286
|
+
else
|
287
|
+
logger.debug("Log on #{packet.channel}. Cannot handle")
|
288
|
+
## in_queue << packet
|
289
|
+
end
|
290
|
+
end
|
291
|
+
end
|
292
|
+
end
|
293
|
+
|
294
|
+
# Stop the facility's packet processing
|
295
|
+
def stop_packet_reader_thread
|
296
|
+
@packet_reader_thread.kill() if @packet_reader_thread
|
297
|
+
@packet_reader_thread = nil
|
298
|
+
end
|
299
|
+
|
300
|
+
# Finds a log block by id
|
301
|
+
# @param block_id [Integer] the block ID
|
302
|
+
# @return p
|
303
|
+
def [](block_id)
|
304
|
+
@log_blocks[block_id]
|
305
|
+
end
|
306
|
+
|
307
|
+
# Processes an incoming settings packet. Sort of a callback
|
308
|
+
# @param packet [CRTPPacket] the packet on the settings channel
|
309
|
+
def handle_settings_packet(packet)
|
310
|
+
cmd = packet.data[0] # byte 0 of data
|
311
|
+
payload = packet.data_repack()[1..-1] # the rest of data
|
312
|
+
|
313
|
+
block_id = payload[0].ord()
|
314
|
+
#See projects:crazyflie:firmware:comm_protocol#list_of_return_codes
|
315
|
+
# @todo write down error codes in some constant
|
316
|
+
error_st = payload[1].ord() # 0 is no error
|
317
|
+
|
318
|
+
case cmd
|
319
|
+
when CMD_CREATE_BLOCK
|
320
|
+
if !@log_blocks[block_id]
|
321
|
+
logger.error "No log entry for #{block_id}"
|
322
|
+
return
|
323
|
+
end
|
324
|
+
if error_st != 0
|
325
|
+
hex_error = error_st.to_s(16)
|
326
|
+
mesg = "Error creating block #{block_id}: #{hex_error}"
|
327
|
+
logger.error(mesg)
|
328
|
+
return
|
329
|
+
end
|
330
|
+
# Block was created, let's start logging
|
331
|
+
logger.debug "Log block #{block_id} created"
|
332
|
+
# We do not start logging right away do we?
|
333
|
+
when CMD_APPEND_BLOCK
|
334
|
+
logger.debug "Received log settings with APPEND_LOG"
|
335
|
+
when CMD_DELETE_BLOCK
|
336
|
+
logger.debug "Received log settings with DELETE_LOG"
|
337
|
+
when CMD_START_LOGGING
|
338
|
+
if error_st != 0
|
339
|
+
hex_error = error_st.to_s(16)
|
340
|
+
mesg = "Error starting to log #{block_id}: #{hex_error}"
|
341
|
+
logger.error(mesg)
|
342
|
+
else
|
343
|
+
logger.debug "Logging started for #{block_id}"
|
344
|
+
end
|
345
|
+
when CMD_STOP_LOGGING
|
346
|
+
# @todo
|
347
|
+
logger.debug "Received log settings with STOP_LOGGING"
|
348
|
+
when CMD_RESET_LOGGING
|
349
|
+
# @todo
|
350
|
+
logger.debug "Received log settings with RESET_LOGGING"
|
351
|
+
else
|
352
|
+
mesg = "Received log settings with #{cmd}. Dont now what to do"
|
353
|
+
logger.warn(mesg)
|
354
|
+
end
|
355
|
+
|
356
|
+
end
|
357
|
+
private :handle_settings_packet
|
358
|
+
|
359
|
+
def handle_logdata_packet(packet)
|
360
|
+
block_id = packet.data[0]
|
361
|
+
#logger.debug("Handling log data for block #{block_id}")
|
362
|
+
#timestamp = packet.data[1..3] . pack and re unpack as 4 bytes
|
363
|
+
logdata = packet.data_repack()[4..-1]
|
364
|
+
block = @log_blocks[block_id]
|
365
|
+
if block
|
366
|
+
block.unpack_log_data(logdata)
|
367
|
+
else
|
368
|
+
logger.error "No entry for logdata for block #{block_id}"
|
369
|
+
end
|
370
|
+
end
|
371
|
+
private :handle_logdata_packet
|
372
|
+
|
373
|
+
def packet_factory
|
374
|
+
packet = CRTPPacket.new()
|
375
|
+
packet.modify_header(nil,
|
376
|
+
CRTP_PORTS[:logging],
|
377
|
+
LOG_SETTINGS_CHANNEL)
|
378
|
+
return packet
|
379
|
+
end
|
380
|
+
private :packet_factory
|
381
|
+
|
382
|
+
end
|
383
|
+
end
|
@@ -0,0 +1,57 @@
|
|
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
|
+
# Interface for Logging configuration objects
|
22
|
+
class LogConf
|
23
|
+
attr_reader :variables, :data_callback, :period
|
24
|
+
def initialize(variables, data_callback, opts={})
|
25
|
+
@variables = variables
|
26
|
+
@data_callback = data_callback
|
27
|
+
@period = opts[:period] || 20
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
# Interface for Logging variable configuration objects
|
32
|
+
# this class lists methods to be implemented
|
33
|
+
# Python implementation is in cfclient/utils/logconfigreader.py
|
34
|
+
class LogConfVariable
|
35
|
+
|
36
|
+
attr_reader :name, :stored_as, :fetch_as, :address
|
37
|
+
|
38
|
+
def initialize(name, is_toc, stored_as, fetch_as, address=0)
|
39
|
+
@name = name
|
40
|
+
@is_toc = is_toc
|
41
|
+
@stored_as = stored_as
|
42
|
+
@fetch_as = fetch_as
|
43
|
+
@address = address
|
44
|
+
end
|
45
|
+
# @return [Integer] a byte where the upper 4 bits are the
|
46
|
+
# type indentifier of how the variable is stored and the lower
|
47
|
+
# 4 bits are the type the variable should be fetched as
|
48
|
+
def stored_fetch_as
|
49
|
+
return @stored_as << 4 | (0x0F & @fetch_as)
|
50
|
+
end
|
51
|
+
|
52
|
+
# @return [TrueClass,FalseClass] true if it is stored in the TOC
|
53
|
+
def is_toc_variable?
|
54
|
+
return @is_toc == true
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,220 @@
|
|
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 'timeout'
|
20
|
+
|
21
|
+
module Crubyflie
|
22
|
+
# An element in the Parameters Table of Contents
|
23
|
+
class ParamTOCElement < TOCElement
|
24
|
+
# A map between crazyflie C types and ruby directives to
|
25
|
+
# interpret them. This will help parsing the parameter data
|
26
|
+
C_RUBY_TYPE_MAP = {
|
27
|
+
0 => {
|
28
|
+
:ctype => "int8_t",
|
29
|
+
:directive => 'c',
|
30
|
+
:size => 1
|
31
|
+
},
|
32
|
+
1 => {
|
33
|
+
:ctype => "int16_t",
|
34
|
+
:directive => 's<',
|
35
|
+
:size => 2
|
36
|
+
},
|
37
|
+
2 => {
|
38
|
+
:ctype => "int32_t",
|
39
|
+
:directive => 'l<',
|
40
|
+
:size => 4
|
41
|
+
},
|
42
|
+
3 => {
|
43
|
+
:ctype => "int64_t",
|
44
|
+
:directive => 'q<',
|
45
|
+
:size => 8
|
46
|
+
},
|
47
|
+
5 => {
|
48
|
+
:ctype => "FP16",
|
49
|
+
:directive => 'e',
|
50
|
+
:size => 2
|
51
|
+
},
|
52
|
+
6 => {
|
53
|
+
:ctype => "float",
|
54
|
+
:directive => 'e',
|
55
|
+
:size => 4
|
56
|
+
},
|
57
|
+
7 => {
|
58
|
+
:ctype => "double",
|
59
|
+
:directive => 'E',
|
60
|
+
:size => 8
|
61
|
+
},
|
62
|
+
8 => {
|
63
|
+
:ctype => "uint8_t",
|
64
|
+
:directive => 'C',
|
65
|
+
:size => 1
|
66
|
+
},
|
67
|
+
9 => {
|
68
|
+
:ctype => "uint16_t",
|
69
|
+
:directive => 'S<',
|
70
|
+
:size => 2
|
71
|
+
},
|
72
|
+
10 => {
|
73
|
+
:ctype => "uint32_t",
|
74
|
+
:directive => 'L<',
|
75
|
+
:size => 4
|
76
|
+
},
|
77
|
+
11 => {
|
78
|
+
:ctype => "int64_t",
|
79
|
+
:directive => 'Q<',
|
80
|
+
:size => '8'
|
81
|
+
}
|
82
|
+
}
|
83
|
+
|
84
|
+
# Initializes a Param TOC element, which means interpreting the
|
85
|
+
# data in the packet and calling the parent class
|
86
|
+
# @param data [String] a binary payload
|
87
|
+
# @todo It turns out this is the same as Log. Only the type conversion
|
88
|
+
# changes
|
89
|
+
def initialize(data)
|
90
|
+
# The group and name are zero-terminated strings from the 3rd byte
|
91
|
+
group, name = data[2..-1].unpack('Z*Z*')
|
92
|
+
ident = data[0].ord()
|
93
|
+
ctype_id = data[1].ord() & 0b1111 #from 0 to 15
|
94
|
+
ctype = C_RUBY_TYPE_MAP[ctype_id][:ctype]
|
95
|
+
directive = C_RUBY_TYPE_MAP[ctype_id][:directive]
|
96
|
+
access = data[1].ord() & 0b00010000 # 5th bit
|
97
|
+
|
98
|
+
super({
|
99
|
+
:ident => ident,
|
100
|
+
:group => group,
|
101
|
+
:name => name,
|
102
|
+
:ctype => ctype,
|
103
|
+
:type_id => ctype_id,
|
104
|
+
:directive => directive,
|
105
|
+
:access => access
|
106
|
+
})
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
# The parameter facility. Used to retrieve the table of contents,
|
111
|
+
# set the value of a parameter and read the value of a parameter
|
112
|
+
class Param
|
113
|
+
include Logging
|
114
|
+
include CRTPConstants
|
115
|
+
|
116
|
+
attr_reader :toc
|
117
|
+
# Initialize the parameter facility
|
118
|
+
# @param crazyflie [Crazyflie]
|
119
|
+
def initialize(crazyflie)
|
120
|
+
@crazyflie = crazyflie
|
121
|
+
@in_queue = crazyflie.crtp_queues[:param]
|
122
|
+
@toc = TOC.new(@crazyflie.cache_folder, ParamTOCElement)
|
123
|
+
end
|
124
|
+
|
125
|
+
# Refreshes the TOC. It only returns when it is finished
|
126
|
+
def refresh_toc
|
127
|
+
channel = TOC_CHANNEL
|
128
|
+
port = Crazyflie::CRTP_PORTS[:param]
|
129
|
+
@toc.fetch_from_crazyflie(@crazyflie, port, @in_queue)
|
130
|
+
end
|
131
|
+
|
132
|
+
# Set the value of a paremeter. This call will only return after
|
133
|
+
# a ack response has been received, or will timeout if
|
134
|
+
# #CRTPConstants::WAIT_PACKET_TIMEOUT is reached.
|
135
|
+
# @param name [String] parameter group.name
|
136
|
+
# @param value [Numeric] a value. It must be packable as binary data,
|
137
|
+
# the type being set in the params TOC
|
138
|
+
# @param block [Proc] an optional block that will be called with the
|
139
|
+
# response CRTPPacket received. Otherwise will
|
140
|
+
# log to debug
|
141
|
+
def set_value(name, value, &block)
|
142
|
+
element = @toc[name]
|
143
|
+
if element.nil?
|
144
|
+
logger.error "Param #{name} not in TOC!"
|
145
|
+
return
|
146
|
+
end
|
147
|
+
|
148
|
+
ident = element.ident
|
149
|
+
packet = CRTPPacket.new()
|
150
|
+
packet.modify_header(nil, CRTP_PORTS[:param],
|
151
|
+
PARAM_WRITE_CHANNEL)
|
152
|
+
packet.data = [ident]
|
153
|
+
packet.data += [value].pack(element.directive()).unpack('C*')
|
154
|
+
@in_queue.clear()
|
155
|
+
@crazyflie.send_packet(packet, true) # expect answer
|
156
|
+
|
157
|
+
response = wait_for_response()
|
158
|
+
return if response.nil?
|
159
|
+
|
160
|
+
if block_given?
|
161
|
+
yield response
|
162
|
+
else
|
163
|
+
mesg = "Got answer to setting param '#{name}' with '#{value}'"
|
164
|
+
logger.debug(mesg)
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
# Request an update for a parameter and call the provided block with
|
169
|
+
# the value of the parameter. This call will block until the response
|
170
|
+
# has been received or the #CRTPConstants::WAIT_PACKET_TIMEOUT is
|
171
|
+
# reached.
|
172
|
+
# @param name [String] a name in the form group.name
|
173
|
+
# @param block [Proc] a block to be called with the value as argument
|
174
|
+
def get_value(name, &block)
|
175
|
+
element = @toc[name]
|
176
|
+
if element.nil?
|
177
|
+
logger.error "Cannot update #{name}, not in TOC"
|
178
|
+
return
|
179
|
+
end
|
180
|
+
packet = CRTPPacket.new()
|
181
|
+
packet.modify_header(nil, CRTP_PORTS[:param],
|
182
|
+
PARAM_READ_CHANNEL)
|
183
|
+
packet.data = [element.ident]
|
184
|
+
@in_queue.clear()
|
185
|
+
@crazyflie.send_packet(packet, true)
|
186
|
+
|
187
|
+
# Pop packages until mine comes up
|
188
|
+
begin
|
189
|
+
response = wait_for_response()
|
190
|
+
return if response.nil?
|
191
|
+
|
192
|
+
ident = response.data()[0]
|
193
|
+
if ident != element.ident()
|
194
|
+
mesg = "Value expected for element with ID #{element.ident}"
|
195
|
+
mesg << " but got for element with ID #{ident}. Requeing"
|
196
|
+
logger.debug(mesg)
|
197
|
+
end
|
198
|
+
end until ident == element.ident()
|
199
|
+
|
200
|
+
value = response.data_repack()[1..-1]
|
201
|
+
value = value.unpack(element.directive).first
|
202
|
+
yield(value)
|
203
|
+
end
|
204
|
+
alias_method :request_param_update, :get_value
|
205
|
+
|
206
|
+
def wait_for_response
|
207
|
+
begin
|
208
|
+
wait = WAIT_PACKET_TIMEOUT
|
209
|
+
response = timeout(wait, WaitTimeoutException) do
|
210
|
+
@in_queue.pop()
|
211
|
+
end
|
212
|
+
return response
|
213
|
+
rescue WaitTimeoutException
|
214
|
+
logger.error("Param: waited too long to get response")
|
215
|
+
return nil
|
216
|
+
end
|
217
|
+
end
|
218
|
+
private :wait_for_response
|
219
|
+
end
|
220
|
+
end
|