hybridgroup-crubyflie 0.1.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +18 -0
  3. data/.travis.yml +8 -0
  4. data/Gemfile +5 -0
  5. data/LICENSE.txt +674 -0
  6. data/README.md +127 -0
  7. data/Rakefile +15 -0
  8. data/bin/crubyflie +94 -0
  9. data/configs/joystick_default.yaml +106 -0
  10. data/crubyflie.gemspec +50 -0
  11. data/examples/params_and_logging.rb +87 -0
  12. data/lib/crubyflie/crazyflie/commander.rb +54 -0
  13. data/lib/crubyflie/crazyflie/console.rb +67 -0
  14. data/lib/crubyflie/crazyflie/log.rb +383 -0
  15. data/lib/crubyflie/crazyflie/log_conf.rb +57 -0
  16. data/lib/crubyflie/crazyflie/param.rb +220 -0
  17. data/lib/crubyflie/crazyflie/toc.rb +239 -0
  18. data/lib/crubyflie/crazyflie/toc_cache.rb +87 -0
  19. data/lib/crubyflie/crazyflie.rb +282 -0
  20. data/lib/crubyflie/crazyradio/crazyradio.rb +301 -0
  21. data/lib/crubyflie/crazyradio/radio_ack.rb +48 -0
  22. data/lib/crubyflie/crubyflie_logger.rb +74 -0
  23. data/lib/crubyflie/driver/crtp_packet.rb +146 -0
  24. data/lib/crubyflie/driver/radio_driver.rb +363 -0
  25. data/lib/crubyflie/exceptions.rb +36 -0
  26. data/lib/crubyflie/input/input_reader.rb +190 -0
  27. data/lib/crubyflie/input/joystick_input_reader.rb +328 -0
  28. data/lib/crubyflie/version.rb +22 -0
  29. data/lib/crubyflie.rb +36 -0
  30. data/spec/commander_spec.rb +67 -0
  31. data/spec/console_spec.rb +76 -0
  32. data/spec/crazyflie_spec.rb +176 -0
  33. data/spec/crazyradio_spec.rb +228 -0
  34. data/spec/crtp_packet_spec.rb +79 -0
  35. data/spec/crubyflie_logger_spec.rb +39 -0
  36. data/spec/crubyflie_spec.rb +21 -0
  37. data/spec/input_reader_spec.rb +136 -0
  38. data/spec/joystick_cfg.yaml +44 -0
  39. data/spec/joystick_input_reader_spec.rb +323 -0
  40. data/spec/log_spec.rb +266 -0
  41. data/spec/param_spec.rb +166 -0
  42. data/spec/radio_ack_spec.rb +43 -0
  43. data/spec/radio_driver_spec.rb +227 -0
  44. data/spec/spec_helper.rb +53 -0
  45. data/spec/toc_cache_spec.rb +87 -0
  46. data/spec/toc_spec.rb +187 -0
  47. data/tools/sdl-joystick-axis.rb +69 -0
  48. 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