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.
- checksums.yaml +7 -0
- data/.gitignore +18 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +674 -0
- data/README.md +99 -0
- data/Rakefile +15 -0
- data/bin/crubyflie +85 -0
- data/configs/joystick_default.yaml +48 -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 +333 -0
- data/lib/crubyflie/exceptions.rb +36 -0
- data/lib/crubyflie/input/input_reader.rb +168 -0
- data/lib/crubyflie/input/joystick_input_reader.rb +280 -0
- data/lib/crubyflie/version.rb +22 -0
- data/lib/crubyflie.rb +31 -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 +226 -0
- data/spec/crtp_packet_spec.rb +79 -0
- data/spec/crubyflie_logger_spec.rb +39 -0
- data/spec/crubyflie_spec.rb +20 -0
- data/spec/input_reader_spec.rb +136 -0
- data/spec/joystick_cfg.yaml +48 -0
- data/spec/joystick_input_reader_spec.rb +238 -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 +51 -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 +222 -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
|