usb-ruby 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.
data/lib/usb/device.rb ADDED
@@ -0,0 +1,134 @@
1
+ # frozen_string_literal: true
2
+
3
+ module USB
4
+ class Device
5
+ include Comparable
6
+
7
+ attr_reader :context
8
+
9
+ def self.finalizer(ptr)
10
+ proc do
11
+ FFIBindings.libusb_unref_device(ptr) unless ptr.nil? || ptr.null?
12
+ rescue StandardError
13
+ end
14
+ end
15
+
16
+ def initialize(context, ptr, ref: true)
17
+ raise ArgumentError, "device pointer is required" if ptr.nil? || ptr.null?
18
+
19
+ @context = context
20
+ @ptr = ptr
21
+ @owns_ref = ref
22
+
23
+ FFIBindings.libusb_ref_device(@ptr) if @owns_ref
24
+ ObjectSpace.define_finalizer(self, self.class.finalizer(@ptr)) if @owns_ref
25
+ end
26
+
27
+ def bus_number
28
+ FFIBindings.libusb_get_bus_number(@ptr)
29
+ end
30
+
31
+ def device_address
32
+ FFIBindings.libusb_get_device_address(@ptr)
33
+ end
34
+
35
+ def port_number
36
+ FFIBindings.libusb_get_port_number(@ptr)
37
+ end
38
+
39
+ def port_numbers
40
+ buffer = FFI::MemoryPointer.new(:uint8, 16)
41
+ count = Error.raise_on_error(FFIBindings.libusb_get_port_numbers(@ptr, buffer, 16))
42
+ buffer.read_array_of_uint8(count)
43
+ end
44
+
45
+ def speed
46
+ Error.raise_on_error(FFIBindings.libusb_get_device_speed(@ptr))
47
+ end
48
+
49
+ def max_packet_size(endpoint)
50
+ Error.raise_on_error(FFIBindings.libusb_get_max_packet_size(@ptr, endpoint))
51
+ end
52
+
53
+ def max_iso_packet_size(endpoint)
54
+ Error.raise_on_error(FFIBindings.libusb_get_max_iso_packet_size(@ptr, endpoint))
55
+ end
56
+
57
+ def parent
58
+ parent_ptr = FFIBindings.libusb_get_parent(@ptr)
59
+ return nil if parent_ptr.null?
60
+
61
+ Device.new(@context, parent_ptr)
62
+ end
63
+
64
+ def device_descriptor
65
+ descriptor_ptr = FFI::MemoryPointer.new(FFIBindings::DeviceDescriptorStruct)
66
+ Error.raise_on_error(FFIBindings.libusb_get_device_descriptor(@ptr, descriptor_ptr))
67
+ DeviceDescriptor.new(FFIBindings::DeviceDescriptorStruct.new(descriptor_ptr))
68
+ end
69
+
70
+ def vendor_id
71
+ device_descriptor.vendor_id
72
+ end
73
+
74
+ def product_id
75
+ device_descriptor.product_id
76
+ end
77
+
78
+ def device_class
79
+ device_descriptor.device_class
80
+ end
81
+
82
+ def config_descriptors
83
+ Array.new(device_descriptor.num_configurations) { |index| config_descriptor(index) }
84
+ end
85
+
86
+ def active_config_descriptor
87
+ fetch_config_descriptor(:libusb_get_active_config_descriptor)
88
+ end
89
+
90
+ def config_descriptor(index)
91
+ fetch_config_descriptor(:libusb_get_config_descriptor, index)
92
+ end
93
+
94
+ def config_descriptor_by_value(value)
95
+ fetch_config_descriptor(:libusb_get_config_descriptor_by_value, value)
96
+ end
97
+
98
+ def open
99
+ handle_ptr = FFI::MemoryPointer.new(:pointer)
100
+ Error.raise_on_error(FFIBindings.libusb_open(@ptr, handle_ptr))
101
+ handle = DeviceHandle.new(handle_ptr.read_pointer)
102
+ return handle unless block_given?
103
+
104
+ begin
105
+ yield handle
106
+ ensure
107
+ handle.close
108
+ end
109
+ end
110
+
111
+ def <=>(other)
112
+ [bus_number, device_address] <=> [other.bus_number, other.device_address]
113
+ end
114
+
115
+ def inspect
116
+ ids = format("%04x:%04x", vendor_id, product_id)
117
+ format("#<USB::Device %03d/%03d %s>", bus_number, device_address, ids)
118
+ rescue StandardError
119
+ format("#<USB::Device %03d/%03d>", bus_number, device_address)
120
+ end
121
+
122
+ def to_ptr
123
+ @ptr
124
+ end
125
+
126
+ private
127
+
128
+ def fetch_config_descriptor(function_name, *args)
129
+ descriptor_ptr = FFI::MemoryPointer.new(:pointer)
130
+ Error.raise_on_error(FFIBindings.public_send(function_name, @ptr, *args, descriptor_ptr))
131
+ ConfigDescriptor.new(descriptor_ptr.read_pointer)
132
+ end
133
+ end
134
+ end
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ module USB
4
+ class DeviceDescriptor
5
+ def initialize(struct)
6
+ @struct = struct
7
+ end
8
+
9
+ def bcd_usb
10
+ @struct[:bcdUSB]
11
+ end
12
+
13
+ def device_class
14
+ @struct[:bDeviceClass]
15
+ end
16
+
17
+ def device_sub_class
18
+ @struct[:bDeviceSubClass]
19
+ end
20
+
21
+ def device_protocol
22
+ @struct[:bDeviceProtocol]
23
+ end
24
+
25
+ def max_packet_size_0
26
+ @struct[:bMaxPacketSize0]
27
+ end
28
+
29
+ def vendor_id
30
+ @struct[:idVendor]
31
+ end
32
+
33
+ def product_id
34
+ @struct[:idProduct]
35
+ end
36
+
37
+ def bcd_device
38
+ @struct[:bcdDevice]
39
+ end
40
+
41
+ def manufacturer_index
42
+ @struct[:iManufacturer]
43
+ end
44
+
45
+ def product_index
46
+ @struct[:iProduct]
47
+ end
48
+
49
+ def serial_number_index
50
+ @struct[:iSerialNumber]
51
+ end
52
+
53
+ def num_configurations
54
+ @struct[:bNumConfigurations]
55
+ end
56
+
57
+ def inspect
58
+ format("#<USB::DeviceDescriptor %04x:%04x class=0x%02x>", vendor_id, product_id, device_class)
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,246 @@
1
+ # frozen_string_literal: true
2
+
3
+ module USB
4
+ class DeviceHandle
5
+ def self.finalizer(ptr)
6
+ proc do
7
+ FFIBindings.libusb_close(ptr) unless ptr.nil? || ptr.null?
8
+ rescue StandardError
9
+ end
10
+ end
11
+
12
+ def initialize(device_or_ptr)
13
+ @ptr =
14
+ case device_or_ptr
15
+ when Device
16
+ open_device(device_or_ptr)
17
+ when FFI::Pointer
18
+ device_or_ptr
19
+ else
20
+ raise ArgumentError, "expected USB::Device or FFI::Pointer"
21
+ end
22
+
23
+ raise ArgumentError, "device handle pointer is required" if @ptr.nil? || @ptr.null?
24
+
25
+ @device = device_or_ptr if device_or_ptr.is_a?(Device)
26
+ ObjectSpace.define_finalizer(self, self.class.finalizer(@ptr))
27
+ end
28
+
29
+ def close
30
+ return if closed?
31
+
32
+ ObjectSpace.undefine_finalizer(self)
33
+ FFIBindings.libusb_close(@ptr)
34
+ @ptr = FFI::Pointer::NULL
35
+ end
36
+
37
+ def closed?
38
+ @ptr.nil? || @ptr.null?
39
+ end
40
+
41
+ def device
42
+ @device ||= begin
43
+ device_ptr = FFIBindings.libusb_get_device(@ptr)
44
+ device_ptr.null? ? nil : Device.new(nil, device_ptr)
45
+ end
46
+ end
47
+
48
+ def configuration
49
+ configuration_ptr = FFI::MemoryPointer.new(:int)
50
+ Error.raise_on_error(FFIBindings.libusb_get_configuration(@ptr, configuration_ptr))
51
+ configuration_ptr.read_int
52
+ end
53
+
54
+ def configuration=(value)
55
+ Error.raise_on_error(FFIBindings.libusb_set_configuration(@ptr, value))
56
+ end
57
+
58
+ def claim_interface(number)
59
+ Error.raise_on_error(FFIBindings.libusb_claim_interface(@ptr, number))
60
+ self
61
+ end
62
+
63
+ def release_interface(number)
64
+ Error.raise_on_error(FFIBindings.libusb_release_interface(@ptr, number))
65
+ self
66
+ end
67
+
68
+ def set_interface_alt_setting(interface, alt_setting)
69
+ Error.raise_on_error(FFIBindings.libusb_set_interface_alt_setting(@ptr, interface, alt_setting))
70
+ self
71
+ end
72
+
73
+ def clear_halt(endpoint)
74
+ Error.raise_on_error(FFIBindings.libusb_clear_halt(@ptr, endpoint))
75
+ self
76
+ end
77
+
78
+ def reset_device
79
+ Error.raise_on_error(FFIBindings.libusb_reset_device(@ptr))
80
+ self
81
+ end
82
+
83
+ def kernel_driver_active?(interface)
84
+ result = FFIBindings.libusb_kernel_driver_active(@ptr, interface)
85
+ Error.raise_on_error(result) == 1
86
+ end
87
+
88
+ def detach_kernel_driver(interface)
89
+ Error.raise_on_error(FFIBindings.libusb_detach_kernel_driver(@ptr, interface))
90
+ self
91
+ end
92
+
93
+ def attach_kernel_driver(interface)
94
+ Error.raise_on_error(FFIBindings.libusb_attach_kernel_driver(@ptr, interface))
95
+ self
96
+ end
97
+
98
+ def auto_detach_kernel_driver=(enable)
99
+ Error.raise_on_error(FFIBindings.libusb_set_auto_detach_kernel_driver(@ptr, enable ? 1 : 0))
100
+ end
101
+
102
+ def with_interface(number)
103
+ claimed = false
104
+ claim_interface(number)
105
+ claimed = true
106
+ yield self
107
+ ensure
108
+ release_interface(number) if claimed && !closed?
109
+ end
110
+
111
+ def control_transfer(bm_request_type:, b_request:, w_value:, w_index:, data_or_length: nil, timeout: 1000)
112
+ if data_or_length.is_a?(Integer)
113
+ buffer = FFI::MemoryPointer.new(:uint8, [data_or_length, 1].max)
114
+ transferred = Error.raise_on_error(
115
+ FFIBindings.libusb_control_transfer(
116
+ @ptr, bm_request_type, b_request, w_value, w_index, buffer, data_or_length, timeout
117
+ )
118
+ )
119
+ buffer.read_bytes(transferred)
120
+ else
121
+ data = data_or_length.nil? ? "".b : data_or_length.to_s.b
122
+ buffer = bytes_pointer(data)
123
+ Error.raise_on_error(
124
+ FFIBindings.libusb_control_transfer(
125
+ @ptr, bm_request_type, b_request, w_value, w_index, buffer, data.bytesize, timeout
126
+ )
127
+ )
128
+ end
129
+ end
130
+
131
+ def bulk_transfer(endpoint:, data_or_length:, timeout: 1000)
132
+ transferred = FFI::MemoryPointer.new(:int)
133
+
134
+ if data_or_length.is_a?(Integer)
135
+ buffer = FFI::MemoryPointer.new(:uint8, [data_or_length, 1].max)
136
+ Error.raise_on_error(FFIBindings.libusb_bulk_transfer(@ptr, endpoint, buffer, data_or_length, transferred, timeout))
137
+ buffer.read_bytes(transferred.read_int)
138
+ else
139
+ data = data_or_length.to_s.b
140
+ buffer = bytes_pointer(data)
141
+ Error.raise_on_error(FFIBindings.libusb_bulk_transfer(@ptr, endpoint, buffer, data.bytesize, transferred, timeout))
142
+ transferred.read_int
143
+ end
144
+ end
145
+
146
+ def interrupt_transfer(endpoint:, data_or_length:, timeout: 1000)
147
+ transferred = FFI::MemoryPointer.new(:int)
148
+
149
+ if data_or_length.is_a?(Integer)
150
+ buffer = FFI::MemoryPointer.new(:uint8, [data_or_length, 1].max)
151
+ Error.raise_on_error(
152
+ FFIBindings.libusb_interrupt_transfer(@ptr, endpoint, buffer, data_or_length, transferred, timeout)
153
+ )
154
+ buffer.read_bytes(transferred.read_int)
155
+ else
156
+ data = data_or_length.to_s.b
157
+ buffer = bytes_pointer(data)
158
+ Error.raise_on_error(
159
+ FFIBindings.libusb_interrupt_transfer(@ptr, endpoint, buffer, data.bytesize, transferred, timeout)
160
+ )
161
+ transferred.read_int
162
+ end
163
+ end
164
+
165
+ def string_descriptor_ascii(index)
166
+ return nil if index.to_i.zero?
167
+
168
+ buffer = FFI::MemoryPointer.new(:uint8, 256)
169
+ length = Error.raise_on_error(FFIBindings.libusb_get_string_descriptor_ascii(@ptr, index, buffer, 256))
170
+ buffer.read_string_length(length)
171
+ end
172
+
173
+ def manufacturer
174
+ string_descriptor_ascii(device.device_descriptor.manufacturer_index)
175
+ end
176
+
177
+ def product
178
+ string_descriptor_ascii(device.device_descriptor.product_index)
179
+ end
180
+
181
+ def serial_number
182
+ string_descriptor_ascii(device.device_descriptor.serial_number_index)
183
+ end
184
+
185
+ def alloc_streams(num_streams, endpoints)
186
+ endpoint_ptr = endpoint_array_pointer(endpoints)
187
+ Error.raise_on_error(FFIBindings.libusb_alloc_streams(@ptr, num_streams, endpoint_ptr, endpoints.length))
188
+ end
189
+
190
+ def free_streams(endpoints)
191
+ endpoint_ptr = endpoint_array_pointer(endpoints)
192
+ Error.raise_on_error(FFIBindings.libusb_free_streams(@ptr, endpoint_ptr, endpoints.length))
193
+ end
194
+
195
+ def dev_mem_alloc(length)
196
+ FFIBindings.libusb_dev_mem_alloc(@ptr, length)
197
+ end
198
+
199
+ def dev_mem_free(buffer, length)
200
+ Error.raise_on_error(FFIBindings.libusb_dev_mem_free(@ptr, buffer, length))
201
+ end
202
+
203
+ def bos_descriptor
204
+ descriptor_ptr = FFI::MemoryPointer.new(:pointer)
205
+ Error.raise_on_error(FFIBindings.libusb_get_bos_descriptor(@ptr, descriptor_ptr))
206
+ BOSDescriptor.new(descriptor_ptr.read_pointer)
207
+ end
208
+
209
+ def to_ptr
210
+ @ptr
211
+ end
212
+
213
+ def inspect
214
+ descriptor = device&.device_descriptor
215
+ if descriptor
216
+ format("#<USB::DeviceHandle %04x:%04x>", descriptor.vendor_id, descriptor.product_id)
217
+ else
218
+ "#<USB::DeviceHandle>"
219
+ end
220
+ rescue StandardError
221
+ "#<USB::DeviceHandle>"
222
+ end
223
+
224
+ private
225
+
226
+ def open_device(device)
227
+ handle_ptr = FFI::MemoryPointer.new(:pointer)
228
+ Error.raise_on_error(FFIBindings.libusb_open(device.to_ptr, handle_ptr))
229
+ handle_ptr.read_pointer
230
+ end
231
+
232
+ def bytes_pointer(data)
233
+ pointer = FFI::MemoryPointer.new(:uint8, [data.bytesize, 1].max)
234
+ pointer.put_bytes(0, data) unless data.empty?
235
+ pointer
236
+ end
237
+
238
+ def endpoint_array_pointer(endpoints)
239
+ pointer = FFI::MemoryPointer.new(:uint8, endpoints.length)
240
+ endpoints.each_with_index do |endpoint, index|
241
+ pointer.put_uint8(index, endpoint)
242
+ end
243
+ pointer
244
+ end
245
+ end
246
+ end
@@ -0,0 +1,91 @@
1
+ # frozen_string_literal: true
2
+
3
+ module USB
4
+ class EndpointDescriptor
5
+ def initialize(interface_descriptor, struct)
6
+ @interface_descriptor = interface_descriptor
7
+ @struct = struct
8
+ end
9
+
10
+ def endpoint_address
11
+ @struct[:bEndpointAddress]
12
+ end
13
+
14
+ def direction
15
+ in? ? :in : :out
16
+ end
17
+
18
+ def transfer_type
19
+ case @struct[:bmAttributes] & 0x03
20
+ when TRANSFER_TYPE_CONTROL then :control
21
+ when TRANSFER_TYPE_ISOCHRONOUS then :isochronous
22
+ when TRANSFER_TYPE_BULK then :bulk
23
+ when TRANSFER_TYPE_INTERRUPT then :interrupt
24
+ else
25
+ :unknown
26
+ end
27
+ end
28
+
29
+ def max_packet_size
30
+ @struct[:wMaxPacketSize]
31
+ end
32
+
33
+ def interval
34
+ @struct[:bInterval]
35
+ end
36
+
37
+ def refresh
38
+ @struct[:bRefresh]
39
+ end
40
+
41
+ def synch_address
42
+ @struct[:bSynchAddress]
43
+ end
44
+
45
+ def extra
46
+ return "".b if @struct[:extra].null? || @struct[:extra_length].zero?
47
+
48
+ @struct[:extra].read_bytes(@struct[:extra_length])
49
+ end
50
+
51
+ def ss_endpoint_companion(context)
52
+ descriptor_ptr = FFI::MemoryPointer.new(:pointer)
53
+ Error.raise_on_error(
54
+ FFIBindings.libusb_get_ss_endpoint_companion_descriptor(context.to_ptr, @struct.pointer, descriptor_ptr)
55
+ )
56
+ SSEndpointCompanion.new(descriptor_ptr.read_pointer)
57
+ end
58
+
59
+ def in?
60
+ (endpoint_address & ENDPOINT_IN) == ENDPOINT_IN
61
+ end
62
+
63
+ def out?
64
+ !in?
65
+ end
66
+
67
+ def bulk?
68
+ transfer_type == :bulk
69
+ end
70
+
71
+ def interrupt?
72
+ transfer_type == :interrupt
73
+ end
74
+
75
+ def isochronous?
76
+ transfer_type == :isochronous
77
+ end
78
+
79
+ def control?
80
+ transfer_type == :control
81
+ end
82
+
83
+ def inspect
84
+ format("#<USB::EndpointDescriptor 0x%02x %s %s>", endpoint_address, transfer_type, direction)
85
+ end
86
+
87
+ def to_ptr
88
+ @struct.pointer
89
+ end
90
+ end
91
+ end
data/lib/usb/error.rb ADDED
@@ -0,0 +1,94 @@
1
+ # frozen_string_literal: true
2
+
3
+ module USB
4
+ class Error < StandardError
5
+ attr_reader :code
6
+
7
+ ERROR_CLASSES = {}.freeze
8
+
9
+ class << self
10
+ def raise_on_error(result)
11
+ raise class_for_code(result).new(result) if result.is_a?(Integer) && result.negative?
12
+
13
+ result
14
+ end
15
+
16
+ def class_for_code(code)
17
+ self::ERROR_CLASSES.fetch(code, self)
18
+ end
19
+
20
+ def libusb_error_name(code)
21
+ FFIBindings.libusb_error_name(code)
22
+ rescue StandardError
23
+ code.to_s
24
+ end
25
+
26
+ def libusb_error_description(code)
27
+ FFIBindings.libusb_strerror(code)
28
+ rescue StandardError
29
+ "libusb error #{code}"
30
+ end
31
+ end
32
+
33
+ def initialize(code)
34
+ @code = code
35
+ super("#{self.class.libusb_error_name(code)}: #{self.class.libusb_error_description(code)}")
36
+ end
37
+ end
38
+
39
+ class TransferError < Error
40
+ end
41
+
42
+ class IOError < Error
43
+ end
44
+
45
+ class InvalidParamError < Error
46
+ end
47
+
48
+ class AccessError < Error
49
+ end
50
+
51
+ class NoDeviceError < Error
52
+ end
53
+
54
+ class NotFoundError < Error
55
+ end
56
+
57
+ class BusyError < Error
58
+ end
59
+
60
+ class TimeoutError < Error
61
+ end
62
+
63
+ class OverflowError < Error
64
+ end
65
+
66
+ class PipeError < Error
67
+ end
68
+
69
+ class InterruptedError < Error
70
+ end
71
+
72
+ class NoMemError < Error
73
+ end
74
+
75
+ class NotSupportedError < Error
76
+ end
77
+
78
+ Error.send(:remove_const, :ERROR_CLASSES)
79
+ Error::ERROR_CLASSES = {
80
+ LIBUSB_ERROR_IO => IOError,
81
+ LIBUSB_ERROR_INVALID_PARAM => InvalidParamError,
82
+ LIBUSB_ERROR_ACCESS => AccessError,
83
+ LIBUSB_ERROR_NO_DEVICE => NoDeviceError,
84
+ LIBUSB_ERROR_NOT_FOUND => NotFoundError,
85
+ LIBUSB_ERROR_BUSY => BusyError,
86
+ LIBUSB_ERROR_TIMEOUT => TimeoutError,
87
+ LIBUSB_ERROR_OVERFLOW => OverflowError,
88
+ LIBUSB_ERROR_PIPE => PipeError,
89
+ LIBUSB_ERROR_INTERRUPTED => InterruptedError,
90
+ LIBUSB_ERROR_NO_MEM => NoMemError,
91
+ LIBUSB_ERROR_NOT_SUPPORTED => NotSupportedError,
92
+ LIBUSB_ERROR_OTHER => Error
93
+ }.freeze
94
+ end