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,239 @@
|
|
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
|
+
require 'crazyflie/toc_cache'
|
21
|
+
|
22
|
+
module Crubyflie
|
23
|
+
|
24
|
+
# Base class for a TocElement. To be extended by specific classes
|
25
|
+
class TOCElement
|
26
|
+
# Initializes a TOC element
|
27
|
+
# @param element_h [Hash] indicates :ident, :group, :name, :ctype,
|
28
|
+
# :rtype, :access
|
29
|
+
attr_reader :ident, :group, :name, :ctype, :type_id, :directive, :access
|
30
|
+
def initialize(element_h)
|
31
|
+
@ident = element_h.delete(:ident) || 0
|
32
|
+
@group = element_h.delete(:group) || ""
|
33
|
+
@name = element_h.delete(:name) || ""
|
34
|
+
@ctype = element_h.delete(:ctype) || ""
|
35
|
+
@type_id = element_h.delete(:type_id) || -1
|
36
|
+
@directive = element_h.delete(:directive) || ""
|
37
|
+
@access = element_h.delete(:access) || 0
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# A Table Of Contents
|
42
|
+
# It is a hash that stores a group index.
|
43
|
+
# Each group is a Hash indexed by element ID that stores TOC element
|
44
|
+
class TOC
|
45
|
+
include Logging
|
46
|
+
include CRTPConstants
|
47
|
+
attr_reader :toc
|
48
|
+
# Initializes the hash
|
49
|
+
# @param cache_folder [String] where is the cache for this toc stored
|
50
|
+
# @param element_class [Class] the class type we should instantiate
|
51
|
+
# TOC elements to, when fetching them from
|
52
|
+
# the crazyflie
|
53
|
+
def initialize(cache_folder=nil, element_class=TOCElement)
|
54
|
+
@toc = {}
|
55
|
+
@cache = TOCCache.new(cache_folder)
|
56
|
+
@element_class = element_class
|
57
|
+
end
|
58
|
+
|
59
|
+
# Get a TOC element
|
60
|
+
# @param name_or_id [String,Symbol] name or ident of the element
|
61
|
+
# @param mode [Symbol] get it :by_name, :by_id, :both
|
62
|
+
# @return [TocElement, nil] the element or nil if not found
|
63
|
+
def [](name_or_id, mode=:both)
|
64
|
+
by_name_element = nil
|
65
|
+
by_id_element = nil
|
66
|
+
# Find by name
|
67
|
+
if [:both, :by_name].include?(mode)
|
68
|
+
group = name = nil
|
69
|
+
if name_or_id.is_a?(String)
|
70
|
+
group, name = name_or_id.split(".", 2)
|
71
|
+
end
|
72
|
+
|
73
|
+
if name.nil?
|
74
|
+
name = group
|
75
|
+
group = nil
|
76
|
+
end
|
77
|
+
|
78
|
+
if group
|
79
|
+
gr = @toc[group]
|
80
|
+
by_name_element = gr[name] if gr
|
81
|
+
else
|
82
|
+
@toc.each do |group_name, group|
|
83
|
+
candidate = group[name]
|
84
|
+
by_name_element = candidate if candidate
|
85
|
+
break if candidate
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
if [:both, :by_id].include?(mode)
|
91
|
+
@toc.each do |group_name, group|
|
92
|
+
group.each do |name, element|
|
93
|
+
by_id_element = element if element.ident == name_or_id
|
94
|
+
break if by_id_element
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
return by_name_element || by_id_element
|
100
|
+
end
|
101
|
+
|
102
|
+
# Insert a TOC element in the TOC
|
103
|
+
# @param element [TocElement] the name of the element group
|
104
|
+
def insert(element)
|
105
|
+
group = element.group
|
106
|
+
name = element.name
|
107
|
+
@toc[group] = {} if @toc[group].nil?
|
108
|
+
@toc[group][name] = element
|
109
|
+
end
|
110
|
+
alias_method :<<, :insert
|
111
|
+
|
112
|
+
# Saves this TOC into cache
|
113
|
+
def export_to_cache(crc)
|
114
|
+
@cache.insert(crc, @toc)
|
115
|
+
end
|
116
|
+
|
117
|
+
# Retrieves this TOC from the cache
|
118
|
+
def import_from_cache(crc)
|
119
|
+
@toc = @cache.fetch(crc)
|
120
|
+
end
|
121
|
+
|
122
|
+
# Produce a simple string representation of the TOC
|
123
|
+
# @return [String] a pretty string
|
124
|
+
def to_s
|
125
|
+
s = ""
|
126
|
+
@toc.each do |group,v|
|
127
|
+
s << "- #{group}\n"
|
128
|
+
v.each do |name, elem|
|
129
|
+
s << " * #{name} (#{elem.ctype}/##{elem.type_id})\n"
|
130
|
+
end
|
131
|
+
end
|
132
|
+
return s
|
133
|
+
end
|
134
|
+
|
135
|
+
# Fetches a TOC from crazyflie in a synchronous way
|
136
|
+
# Instead of a TOCFetcher (as in the python library), we just found
|
137
|
+
# easier to take advantage of the facility queues and read them
|
138
|
+
# (and block when there is nothing to read) to initialize the TOC.
|
139
|
+
# Doing it this way not only saves to have to register and chain
|
140
|
+
# callbacks unrelated places (as Crazyflie class), but also to
|
141
|
+
# reorder incoming TOCElement packages if they come unordered (we
|
142
|
+
# just requeue them). This might happen if a package needs to be resent
|
143
|
+
# and the answer for the next one comes earlier.
|
144
|
+
#
|
145
|
+
# This function should be preferably called before starting any
|
146
|
+
# other activities (send/receive threads) in the relevant facilities.
|
147
|
+
# @param crazyflie [Crazyflie] used to send packages
|
148
|
+
# @param port [Integer] the port to send the packages
|
149
|
+
# @param in_queue [Integer] a queue on which the responses to the
|
150
|
+
# sent packages are queued
|
151
|
+
def fetch_from_crazyflie(crazyflie, port, in_queue)
|
152
|
+
# http://wiki.bitcraze.se/projects:crazyflie:firmware:comm_protocol
|
153
|
+
# #table_of_content_access
|
154
|
+
packet = CRTPPacket.new(0, [CMD_TOC_INFO])
|
155
|
+
packet.modify_header(nil, port, TOC_CHANNEL)
|
156
|
+
in_queue.clear()
|
157
|
+
|
158
|
+
crazyflie.send_packet(packet, true)
|
159
|
+
response = in_queue.pop() # we block here if none :)
|
160
|
+
while response.channel != TOC_CHANNEL do
|
161
|
+
in_queue << response
|
162
|
+
logger.debug "Got a non-TOC packet. Requeueing..."
|
163
|
+
Thread.pass
|
164
|
+
# @todo timeout
|
165
|
+
response = in_queue.pop()
|
166
|
+
end
|
167
|
+
data = response.data
|
168
|
+
# command = data[0]
|
169
|
+
# Repack the payload
|
170
|
+
payload = response.data_repack[1..-1] # get rid of byte 0
|
171
|
+
# The crc comes in an unsigned int (L) in little endian (<)
|
172
|
+
total_toc_items, crc = payload[0..5].unpack('CL<')
|
173
|
+
hex_crc = crc.to_s(16)
|
174
|
+
|
175
|
+
logger.debug "TOC crc #{hex_crc}, #{total_toc_items} items"
|
176
|
+
import_from_cache(hex_crc)
|
177
|
+
|
178
|
+
if !@toc.nil? # in cache so we can stop here
|
179
|
+
logger.debug "TOC found in cache"
|
180
|
+
return
|
181
|
+
end
|
182
|
+
|
183
|
+
logger.debug "Not in cache"
|
184
|
+
@toc = {}
|
185
|
+
# We proceed to request all the TOC elements
|
186
|
+
requested_item = 0
|
187
|
+
while requested_item < total_toc_items do
|
188
|
+
wait = WAIT_PACKET_TIMEOUT
|
189
|
+
request_toc_element(crazyflie, requested_item, port)
|
190
|
+
|
191
|
+
begin
|
192
|
+
response = timeout(wait, WaitTimeoutException) do
|
193
|
+
response = in_queue.pop() # block here
|
194
|
+
end
|
195
|
+
rescue
|
196
|
+
retries ||= 0
|
197
|
+
retries += 1
|
198
|
+
retry if retries < 2
|
199
|
+
logger.error("Timeout reached waiting for TOC element")
|
200
|
+
raise $!
|
201
|
+
end
|
202
|
+
if response.channel != TOC_CHANNEL
|
203
|
+
# Requeue
|
204
|
+
in_queue << response
|
205
|
+
mesg = "Got a non-TOC packet on #{response.channel}."
|
206
|
+
mesg << " Requeueing..."
|
207
|
+
logger.debug(mesg)
|
208
|
+
Thread.pass
|
209
|
+
next
|
210
|
+
end
|
211
|
+
payload = response.data_repack()[1..-1] # leave byte 0 out
|
212
|
+
toc_elem = @element_class.new(payload)
|
213
|
+
if (a = requested_item) != (b = toc_elem.ident())
|
214
|
+
logger.debug "#{port}: Expected #{a}, but got #{b}"
|
215
|
+
if b > a
|
216
|
+
logger.debug "Requeing"
|
217
|
+
# this way we are ordering items
|
218
|
+
in_queue << response
|
219
|
+
end
|
220
|
+
next
|
221
|
+
end
|
222
|
+
|
223
|
+
insert(toc_elem)
|
224
|
+
logger.debug("Added #{toc_elem.ident} to TOC")
|
225
|
+
requested_item += 1
|
226
|
+
end
|
227
|
+
export_to_cache(hex_crc)
|
228
|
+
end
|
229
|
+
|
230
|
+
|
231
|
+
def request_toc_element(crazyflie, index, port)
|
232
|
+
packet = CRTPPacket.new(0, [CMD_TOC_ELEMENT, index])
|
233
|
+
packet.modify_header(nil, port, TOC_CHANNEL)
|
234
|
+
crazyflie.send_packet(packet, true)
|
235
|
+
end
|
236
|
+
private :request_toc_element
|
237
|
+
|
238
|
+
end
|
239
|
+
end
|
@@ -0,0 +1,87 @@
|
|
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
|
+
# Keeps a serialized version of a TOC in disk so there is no need to
|
21
|
+
# query the Crazyflie. The files are named with the TOC CRC.
|
22
|
+
|
23
|
+
require 'fileutils'
|
24
|
+
|
25
|
+
module Crubyflie
|
26
|
+
|
27
|
+
# Table of contents can be saved to disk and re-read from there
|
28
|
+
# based on the CRC that they have attached. This class
|
29
|
+
# is used for that
|
30
|
+
class TOCCache
|
31
|
+
|
32
|
+
# Initializes the cache directory
|
33
|
+
# @param folder [String] a path to the folder
|
34
|
+
def initialize(folder=nil)
|
35
|
+
@folder = folder
|
36
|
+
return if !@folder
|
37
|
+
if !File.exist?(folder)
|
38
|
+
begin
|
39
|
+
FileUtils.mkdir_p(folder)
|
40
|
+
rescue Errno::EACCES
|
41
|
+
warn "Deactivating cache. Cannot create folder"
|
42
|
+
@folder = nil
|
43
|
+
end
|
44
|
+
elsif !File.directory?(folder)
|
45
|
+
@folder = nil
|
46
|
+
warn "Deactivating cache. Folder is not a directory"
|
47
|
+
return
|
48
|
+
else
|
49
|
+
begin
|
50
|
+
test_f = File.join(folder, 'test')
|
51
|
+
FileUtils.touch(test_f)
|
52
|
+
FileUtils.rm(test_f)
|
53
|
+
rescue Errno::EACCES
|
54
|
+
@folder = nil
|
55
|
+
warn "Deactivating cache. Cannot write to folder"
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
# Fetches a record from the cache
|
61
|
+
# @param crc [String] the CRC of the TOC
|
62
|
+
# @return [TOC,nil] A TOC if found
|
63
|
+
def fetch(crc)
|
64
|
+
return nil if !@folder
|
65
|
+
begin
|
66
|
+
File.open(File.join(@folder, crc), 'r') do |f|
|
67
|
+
Marshal.load(f.read)
|
68
|
+
end
|
69
|
+
rescue Errno::ENOENT, Errno::EACCES
|
70
|
+
nil
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
# Saves a record to the cache
|
75
|
+
# @param crc [String] the CRC of the TOC
|
76
|
+
# @param toc [TOC] A TOC
|
77
|
+
def insert(crc, toc)
|
78
|
+
return if !@folder
|
79
|
+
begin
|
80
|
+
File.open(File.join(@folder, crc), 'w') do |f|
|
81
|
+
f.write(Marshal.dump(toc))
|
82
|
+
end
|
83
|
+
rescue Errno::ENOENT, Errno::EACCES
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,282 @@
|
|
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 'thread'
|
20
|
+
|
21
|
+
require 'driver/radio_driver'
|
22
|
+
require 'crazyflie/toc'
|
23
|
+
require 'crazyflie/console'
|
24
|
+
require 'crazyflie/commander'
|
25
|
+
require 'crazyflie/log'
|
26
|
+
require 'crazyflie/param'
|
27
|
+
|
28
|
+
module Crubyflie
|
29
|
+
|
30
|
+
# This is the main entry point for interacting with a Crazyflie
|
31
|
+
# It allows to connect to the Crazyflie on the specified radio URL
|
32
|
+
# and make use of the different facilities: log, param, commander and
|
33
|
+
# console. Facilities are instantiated and packages delivered to their
|
34
|
+
# queues from here. For example, you can use
|
35
|
+
# the @crazyflie.commander.set_sendpoint() to send a new setpoint
|
36
|
+
# to a Crazyflie
|
37
|
+
class Crazyflie
|
38
|
+
include Logging
|
39
|
+
include CRTPConstants
|
40
|
+
|
41
|
+
# Groups of callbacks available
|
42
|
+
CALLBACKS = [
|
43
|
+
:received_packet,
|
44
|
+
:disconnected,
|
45
|
+
:connection_failed,
|
46
|
+
:connection_initiated,
|
47
|
+
:connection_setup_finished,
|
48
|
+
:link
|
49
|
+
]
|
50
|
+
|
51
|
+
attr_accessor :callbacks
|
52
|
+
attr_reader :cache_folder, :commander, :console, :param, :log
|
53
|
+
attr_reader :crtp_queues, :link
|
54
|
+
# Initialize a Crazyflie by registering default received-packet
|
55
|
+
# callbacks and intializing Queues for every facility.
|
56
|
+
# Packets will be queued for each facility depending on their port
|
57
|
+
# @param cache_folder [String] folder path to store logging TOC cache
|
58
|
+
def initialize(cache_folder=nil)
|
59
|
+
@cache_folder = cache_folder
|
60
|
+
# Callbacks will fire in some specific situations
|
61
|
+
# Specially when receiving packages
|
62
|
+
@callbacks = {}
|
63
|
+
CALLBACKS.each do |cb|
|
64
|
+
@callbacks[cb] = {}
|
65
|
+
end
|
66
|
+
register_default_callbacks()
|
67
|
+
|
68
|
+
@crtp_queues = {}
|
69
|
+
CRTP_PORTS.keys().each do |k|
|
70
|
+
@crtp_queues[k] = Queue.new
|
71
|
+
end
|
72
|
+
|
73
|
+
# Thread that regularly checks for packages
|
74
|
+
@receive_packet_thread = nil
|
75
|
+
@retry_packets_thread = nil
|
76
|
+
|
77
|
+
# A hash with keys "port_<portnumber>"
|
78
|
+
@retry_packets = {}
|
79
|
+
|
80
|
+
@commander = Commander.new(self)
|
81
|
+
@console = Console.new(self)
|
82
|
+
@param = Param.new(self)
|
83
|
+
@log = Log.new(self)
|
84
|
+
|
85
|
+
@link = nil
|
86
|
+
end
|
87
|
+
|
88
|
+
|
89
|
+
# Connect to a Crazyflie using the radio driver
|
90
|
+
# @param uri [String] radio uri to connect to
|
91
|
+
def open_link(uri)
|
92
|
+
call_cb(:connection_initiated, uri)
|
93
|
+
begin
|
94
|
+
@link = RadioDriver.new()
|
95
|
+
link_cbs = {
|
96
|
+
:link_quality_cb => @callbacks[:link][:quality],
|
97
|
+
:link_error_cb => @callbacks[:link][:error]
|
98
|
+
}
|
99
|
+
|
100
|
+
@link.connect(uri, link_cbs)
|
101
|
+
@callbacks[:received_packet][:connected] = Proc.new do |packet|
|
102
|
+
logger.info "Connected!"
|
103
|
+
@callbacks[:received_packet].delete(:connected)
|
104
|
+
end
|
105
|
+
receive_packet_thread()
|
106
|
+
sleep 0.5 # Allow setup and failures
|
107
|
+
setup_connection() if @link
|
108
|
+
rescue Exception
|
109
|
+
#logger.warn $!.backtrace.join("\n")
|
110
|
+
call_cb(:connection_failed, $!.message)
|
111
|
+
close_link()
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
# Close the link and clean up
|
116
|
+
# Attemps to disconnect from the crazyflie.
|
117
|
+
def close_link
|
118
|
+
@commander.send_setpoint(0,0,0,0) if @link
|
119
|
+
sleep 0.05
|
120
|
+
uri = @link ? @link.uri.to_s : "nowhere"
|
121
|
+
@link.disconnect(force=true) if @link
|
122
|
+
@link = nil
|
123
|
+
@receive_packet_thread.kill() if @receive_packet_thread
|
124
|
+
@receive_packet_thread = nil
|
125
|
+
@retry_packets_thread.kill() if @retry_packets_thread
|
126
|
+
@log.stop_packet_reader_thread()
|
127
|
+
@retry_packets.clear()
|
128
|
+
@crtp_queues.each do |k,q|
|
129
|
+
q.clear()
|
130
|
+
end
|
131
|
+
call_cb(:disconnected, uri)
|
132
|
+
end
|
133
|
+
|
134
|
+
# Checks if there is an open link
|
135
|
+
# @return [TrueClass, FalseClass] true if there is an open link
|
136
|
+
def active?
|
137
|
+
return !@link.nil?
|
138
|
+
end
|
139
|
+
|
140
|
+
# Calls #RadioDriver::scan_interface()
|
141
|
+
# @return [Array] List of radio URIs where a crazyflie was found
|
142
|
+
def scan_interface
|
143
|
+
if @link
|
144
|
+
logger.error "Cannot scan when link is open. Disconnect first"
|
145
|
+
return []
|
146
|
+
end
|
147
|
+
begin
|
148
|
+
RadioDriver.new().scan_interface()
|
149
|
+
rescue Exception
|
150
|
+
logger.error("Cannot scan interfaces: #{$!}")
|
151
|
+
return []
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
# Send a packet
|
156
|
+
# @param packet [CRTPPacket] packet to send
|
157
|
+
# @param expect_answer [TrueClass, FalseClass] if set to true, a timer
|
158
|
+
# will be set up to
|
159
|
+
# resend the package if
|
160
|
+
# no response has been
|
161
|
+
# received in 0.1secs
|
162
|
+
def send_packet(packet, expect_answer=false)
|
163
|
+
return if @link.nil?
|
164
|
+
@link.send_packet(packet)
|
165
|
+
setup_retry(packet) if expect_answer
|
166
|
+
end
|
167
|
+
|
168
|
+
|
169
|
+
def receive_packet
|
170
|
+
packet = @link.receive_packet(false) # block here
|
171
|
+
return if packet.nil?
|
172
|
+
call_cb(:received_packet, packet)
|
173
|
+
port = packet.port
|
174
|
+
facility = CRTP_PORTS.invert[port]
|
175
|
+
queue = @crtp_queues[facility]
|
176
|
+
if queue then queue << packet
|
177
|
+
else logger.warn "No queue for packet on port #{port}" end
|
178
|
+
end
|
179
|
+
private :receive_packet
|
180
|
+
|
181
|
+
def setup_connection
|
182
|
+
# Logging will send other packets such RESET_LOGGING
|
183
|
+
# We make sure to handle them by running the thread
|
184
|
+
# while we refresh the Log TOC
|
185
|
+
#logger.debug("Setup connection: Log TOC refresh")
|
186
|
+
@log.start_packet_reader_thread()
|
187
|
+
@log.refresh_toc()
|
188
|
+
@log.stop_packet_reader_thread()
|
189
|
+
#logger.debug("Setup connection: Param TOC refresh")
|
190
|
+
@param.refresh_toc()
|
191
|
+
call_cb(:connection_setup_finished, @link.uri.to_s)
|
192
|
+
end
|
193
|
+
private :setup_connection
|
194
|
+
|
195
|
+
def receive_packet_thread
|
196
|
+
@receive_packet_thread = Thread.new do
|
197
|
+
Thread.current.priority = -2
|
198
|
+
loop do
|
199
|
+
if @link.nil? then sleep 1
|
200
|
+
else receive_packet(); Thread.pass() end
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
# This threads resends packets for which no answer has been
|
205
|
+
# received.
|
206
|
+
@retry_packets_thread = Thread.new do
|
207
|
+
Thread.current.priority = -5
|
208
|
+
|
209
|
+
loop do
|
210
|
+
@retry_packets.each do |k,v|
|
211
|
+
now = Time.now.to_f
|
212
|
+
ts = v[:timestamp]
|
213
|
+
if now - ts >= 0.2
|
214
|
+
pk = v[:packet]
|
215
|
+
logger.debug("Replay on #{pk.port}:#{pk.channel}")
|
216
|
+
send_packet(v[:packet], true)
|
217
|
+
end
|
218
|
+
end
|
219
|
+
sleep 0.2
|
220
|
+
end
|
221
|
+
end
|
222
|
+
end
|
223
|
+
private :receive_packet_thread
|
224
|
+
|
225
|
+
def call_cb(cb, *args)
|
226
|
+
@callbacks[cb].each do |name, proc|
|
227
|
+
proc.call(*args)
|
228
|
+
end
|
229
|
+
end
|
230
|
+
private :call_cb
|
231
|
+
|
232
|
+
# Register some callbacks which are default
|
233
|
+
def register_default_callbacks
|
234
|
+
@callbacks[:received_packet][:delete_timer] = Proc.new do |pk|
|
235
|
+
sym = "port_#{pk.port}".to_sym
|
236
|
+
@retry_packets.delete(sym)
|
237
|
+
#logger.debug("Packet: #{pk.port}: #{pk.channel}")
|
238
|
+
end
|
239
|
+
|
240
|
+
@callbacks[:disconnected][:log] = Proc.new do |uri|
|
241
|
+
logger.info "Disconnected from #{uri}"
|
242
|
+
end
|
243
|
+
|
244
|
+
@callbacks[:connection_failed][:log] = Proc.new do |m|
|
245
|
+
logger.error "Connection failed: #{m}"
|
246
|
+
end
|
247
|
+
|
248
|
+
@callbacks[:connection_initiated][:log] = Proc.new do |uri|
|
249
|
+
logger.info "Connection initiated to #{uri}"
|
250
|
+
end
|
251
|
+
|
252
|
+
@callbacks[:connection_setup_finished][:log] = Proc.new do |uri|
|
253
|
+
logger.debug "TOCs extracted from #{uri}"
|
254
|
+
logger.info "Connection ready!"
|
255
|
+
end
|
256
|
+
|
257
|
+
# The message is an integer from 0 - 100
|
258
|
+
@callbacks[:link][:quality] = Proc.new do |m|
|
259
|
+
#logger.debug "Link quality #{m}"
|
260
|
+
end
|
261
|
+
|
262
|
+
@callbacks[:link][:error] = Proc.new do |m|
|
263
|
+
logger.error "Link error: #{m}"
|
264
|
+
close_link()
|
265
|
+
end
|
266
|
+
|
267
|
+
end
|
268
|
+
private :register_default_callbacks
|
269
|
+
|
270
|
+
# A retry will fire for this request
|
271
|
+
def setup_retry(packet)
|
272
|
+
retry_sym = "port_#{packet.port}".to_sym
|
273
|
+
@retry_packets[retry_sym] = {
|
274
|
+
:packet => packet,
|
275
|
+
:timestamp => Time.now.to_f
|
276
|
+
}
|
277
|
+
|
278
|
+
end
|
279
|
+
private :setup_retry
|
280
|
+
|
281
|
+
end
|
282
|
+
end
|