crubyflie 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +18 -0
  3. data/Gemfile +4 -0
  4. data/LICENSE.txt +674 -0
  5. data/README.md +99 -0
  6. data/Rakefile +15 -0
  7. data/bin/crubyflie +85 -0
  8. data/configs/joystick_default.yaml +48 -0
  9. data/crubyflie.gemspec +50 -0
  10. data/examples/params_and_logging.rb +87 -0
  11. data/lib/crubyflie/crazyflie/commander.rb +54 -0
  12. data/lib/crubyflie/crazyflie/console.rb +67 -0
  13. data/lib/crubyflie/crazyflie/log.rb +383 -0
  14. data/lib/crubyflie/crazyflie/log_conf.rb +57 -0
  15. data/lib/crubyflie/crazyflie/param.rb +220 -0
  16. data/lib/crubyflie/crazyflie/toc.rb +239 -0
  17. data/lib/crubyflie/crazyflie/toc_cache.rb +87 -0
  18. data/lib/crubyflie/crazyflie.rb +282 -0
  19. data/lib/crubyflie/crazyradio/crazyradio.rb +301 -0
  20. data/lib/crubyflie/crazyradio/radio_ack.rb +48 -0
  21. data/lib/crubyflie/crubyflie_logger.rb +74 -0
  22. data/lib/crubyflie/driver/crtp_packet.rb +146 -0
  23. data/lib/crubyflie/driver/radio_driver.rb +333 -0
  24. data/lib/crubyflie/exceptions.rb +36 -0
  25. data/lib/crubyflie/input/input_reader.rb +168 -0
  26. data/lib/crubyflie/input/joystick_input_reader.rb +280 -0
  27. data/lib/crubyflie/version.rb +22 -0
  28. data/lib/crubyflie.rb +31 -0
  29. data/spec/commander_spec.rb +67 -0
  30. data/spec/console_spec.rb +76 -0
  31. data/spec/crazyflie_spec.rb +176 -0
  32. data/spec/crazyradio_spec.rb +226 -0
  33. data/spec/crtp_packet_spec.rb +79 -0
  34. data/spec/crubyflie_logger_spec.rb +39 -0
  35. data/spec/crubyflie_spec.rb +20 -0
  36. data/spec/input_reader_spec.rb +136 -0
  37. data/spec/joystick_cfg.yaml +48 -0
  38. data/spec/joystick_input_reader_spec.rb +238 -0
  39. data/spec/log_spec.rb +266 -0
  40. data/spec/param_spec.rb +166 -0
  41. data/spec/radio_ack_spec.rb +43 -0
  42. data/spec/radio_driver_spec.rb +227 -0
  43. data/spec/spec_helper.rb +51 -0
  44. data/spec/toc_cache_spec.rb +87 -0
  45. data/spec/toc_spec.rb +187 -0
  46. data/tools/sdl-joystick-axis.rb +69 -0
  47. metadata +222 -0
@@ -0,0 +1,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