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,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
|