usbkit 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.
@@ -0,0 +1,193 @@
1
+ # frozen_string_literal: true
2
+
3
+ module UsbKit
4
+ module TransferResultValidation
5
+ private
6
+
7
+ def validate_status(status)
8
+ return status if TransferStatus.valid?(status)
9
+
10
+ raise ArgumentError, "Invalid transfer status: #{status.inspect}"
11
+ end
12
+ end
13
+
14
+ ##
15
+ # Result for control and bulk IN transfers.
16
+ #
17
+ class InTransferResult
18
+ include TransferResultValidation
19
+
20
+ # @return [String, nil] received data
21
+ # @return [Symbol] transfer status
22
+ attr_reader :data, :status
23
+
24
+ # @param attrs [Hash]
25
+ # @return [void]
26
+ def initialize(attrs)
27
+ @data = attrs[:data]
28
+ @status = validate_status(attrs.fetch(:status))
29
+ freeze
30
+ end
31
+
32
+ # @return [Hash]
33
+ def to_h
34
+ { data: data, status: status }
35
+ end
36
+
37
+ # @return [String]
38
+ def inspect
39
+ "#<#{self.class} status=#{status.inspect} bytes=#{data&.bytesize || 0}>"
40
+ end
41
+ end
42
+
43
+ ##
44
+ # Result for control and bulk OUT transfers.
45
+ #
46
+ class OutTransferResult
47
+ include TransferResultValidation
48
+
49
+ # @return [Integer] number of bytes written
50
+ # @return [Symbol] transfer status
51
+ attr_reader :bytes_written, :status
52
+
53
+ # @param attrs [Hash]
54
+ # @return [void]
55
+ def initialize(attrs)
56
+ @bytes_written = attrs.fetch(:bytes_written)
57
+ @status = validate_status(attrs.fetch(:status))
58
+ freeze
59
+ end
60
+
61
+ # @return [Hash]
62
+ def to_h
63
+ { bytes_written: bytes_written, status: status }
64
+ end
65
+
66
+ # @return [String]
67
+ def inspect
68
+ "#<#{self.class} status=#{status.inspect} bytes_written=#{bytes_written.inspect}>"
69
+ end
70
+ end
71
+
72
+ ##
73
+ # Per-packet result for isochronous IN transfers.
74
+ #
75
+ class IsochronousInTransferPacket
76
+ include TransferResultValidation
77
+
78
+ # @return [String, nil] packet payload
79
+ # @return [Symbol] packet status
80
+ attr_reader :data, :status
81
+
82
+ # @param attrs [Hash]
83
+ # @return [void]
84
+ def initialize(attrs)
85
+ @data = attrs[:data]
86
+ @status = validate_status(attrs.fetch(:status))
87
+ freeze
88
+ end
89
+
90
+ # @return [Hash]
91
+ def to_h
92
+ { data: data, status: status }
93
+ end
94
+
95
+ # @return [String]
96
+ def inspect
97
+ "#<#{self.class} status=#{status.inspect} bytes=#{data&.bytesize || 0}>"
98
+ end
99
+ end
100
+
101
+ ##
102
+ # Result for isochronous IN transfers.
103
+ #
104
+ class IsochronousInTransferResult
105
+ # @return [String, nil] concatenated transfer data
106
+ # @return [Array<IsochronousInTransferPacket>] packet-level results
107
+ attr_reader :data, :packets
108
+
109
+ # @param attrs [Hash]
110
+ # @return [void]
111
+ def initialize(attrs)
112
+ @packets = Array(attrs.fetch(:packets, [])).map { |packet| build_packet(packet) }.freeze
113
+ @data = attrs.key?(:data) ? attrs[:data] : packets.filter_map(&:data).join
114
+ freeze
115
+ end
116
+
117
+ # @return [Hash]
118
+ def to_h
119
+ { data: data, packets: packets.map(&:to_h) }
120
+ end
121
+
122
+ # @return [String]
123
+ def inspect
124
+ "#<#{self.class} packets=#{packets.size} bytes=#{data&.bytesize || 0}>"
125
+ end
126
+
127
+ private
128
+
129
+ def build_packet(packet)
130
+ packet.is_a?(IsochronousInTransferPacket) ? packet : IsochronousInTransferPacket.new(packet)
131
+ end
132
+ end
133
+
134
+ ##
135
+ # Per-packet result for isochronous OUT transfers.
136
+ #
137
+ class IsochronousOutTransferPacket
138
+ include TransferResultValidation
139
+
140
+ # @return [Integer] number of bytes written for the packet
141
+ # @return [Symbol] packet status
142
+ attr_reader :bytes_written, :status
143
+
144
+ # @param attrs [Hash]
145
+ # @return [void]
146
+ def initialize(attrs)
147
+ @bytes_written = attrs.fetch(:bytes_written)
148
+ @status = validate_status(attrs.fetch(:status))
149
+ freeze
150
+ end
151
+
152
+ # @return [Hash]
153
+ def to_h
154
+ { bytes_written: bytes_written, status: status }
155
+ end
156
+
157
+ # @return [String]
158
+ def inspect
159
+ "#<#{self.class} status=#{status.inspect} bytes_written=#{bytes_written.inspect}>"
160
+ end
161
+ end
162
+
163
+ ##
164
+ # Result for isochronous OUT transfers.
165
+ #
166
+ class IsochronousOutTransferResult
167
+ # @return [Array<IsochronousOutTransferPacket>] packet-level results
168
+ attr_reader :packets
169
+
170
+ # @param attrs [Hash]
171
+ # @return [void]
172
+ def initialize(attrs)
173
+ @packets = Array(attrs.fetch(:packets, [])).map { |packet| build_packet(packet) }.freeze
174
+ freeze
175
+ end
176
+
177
+ # @return [Hash]
178
+ def to_h
179
+ { packets: packets.map(&:to_h) }
180
+ end
181
+
182
+ # @return [String]
183
+ def inspect
184
+ "#<#{self.class} packets=#{packets.size}>"
185
+ end
186
+
187
+ private
188
+
189
+ def build_packet(packet)
190
+ packet.is_a?(IsochronousOutTransferPacket) ? packet : IsochronousOutTransferPacket.new(packet)
191
+ end
192
+ end
193
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module UsbKit
4
+ ##
5
+ # Immutable USB configuration descriptor.
6
+ #
7
+ class Configuration
8
+ # @return [Device, nil] parent device
9
+ # @return [Integer] USB configuration value
10
+ # @return [String, nil] configuration name
11
+ # @return [Array<Interface>] interfaces for the configuration
12
+ attr_reader :device, :configuration_value, :configuration_name, :interfaces
13
+
14
+ # @param attrs [Hash]
15
+ # @param device [Device, nil]
16
+ # @return [void]
17
+ def initialize(attrs, device: nil)
18
+ @device = device
19
+ @configuration_value = attrs.fetch(:configuration_value)
20
+ @configuration_name = attrs[:configuration_name]
21
+ @interfaces = Array(attrs.fetch(:interfaces, [])).map { |interface| build_interface(interface) }.freeze
22
+ freeze
23
+ end
24
+
25
+ # @return [Hash]
26
+ def to_h
27
+ {
28
+ configuration_value: configuration_value,
29
+ configuration_name: configuration_name,
30
+ interfaces: interfaces.map(&:to_h)
31
+ }
32
+ end
33
+
34
+ # @return [String]
35
+ def inspect
36
+ "#<#{self.class} configuration_value=#{configuration_value.inspect} interfaces=#{interfaces.size}>"
37
+ end
38
+
39
+ private
40
+
41
+ def build_interface(interface)
42
+ interface.is_a?(Interface) ? interface : Interface.new(interface)
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module UsbKit
4
+ VERSION = "0.1.0"
5
+ end
data/lib/usbkit.rb ADDED
@@ -0,0 +1,87 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "usbkit/version"
4
+ require_relative "usbkit/errors"
5
+ require_relative "usbkit/constants"
6
+ require_relative "usbkit/configuration"
7
+ require_relative "usbkit/endpoint"
8
+ require_relative "usbkit/alternate_interface"
9
+ require_relative "usbkit/interface"
10
+ require_relative "usbkit/usb_configuration"
11
+ require_relative "usbkit/transfer_results"
12
+ require_relative "usbkit/connection_event"
13
+ require_relative "usbkit/filter"
14
+ require_relative "usbkit/device"
15
+ require_relative "usbkit/context"
16
+ require_relative "usbkit/backend/base"
17
+
18
+ ##
19
+ # WebUSB-inspired USB access toolkit for Ruby.
20
+ #
21
+ module UsbKit
22
+ class << self
23
+ attr_writer :config
24
+
25
+ # @return [GemConfiguration] current gem-level configuration
26
+ def config
27
+ @config ||= GemConfiguration.new
28
+ end
29
+
30
+ # Configure UsbKit.
31
+ #
32
+ # @yieldparam config [GemConfiguration]
33
+ # @return [GemConfiguration, Enumerator]
34
+ def configure
35
+ return enum_for(__method__) unless block_given?
36
+
37
+ yield(config)
38
+ end
39
+
40
+ # @return [Backend::Base] resolved backend implementation
41
+ # @raise [BackendNotAvailableError] when no backend can be resolved
42
+ def backend
43
+ @backend ||= resolve_backend
44
+ end
45
+
46
+ # @return [void]
47
+ def reset_backend!
48
+ @backend = nil
49
+ end
50
+
51
+ # @return [Context]
52
+ def context
53
+ Context.new
54
+ end
55
+
56
+ private
57
+
58
+ def resolve_backend
59
+ case config.backend
60
+ when :auto
61
+ try_usb_ruby || try_usbfs || raise(BackendNotAvailableError, "No USB backend available")
62
+ when :usb_ruby
63
+ try_usb_ruby || raise(BackendNotAvailableError, "usb-ruby backend not available")
64
+ when :usbfs
65
+ try_usbfs || raise(BackendNotAvailableError, "usbfs backend not available")
66
+ else
67
+ raise ArgumentError, "Unknown backend: #{config.backend.inspect}"
68
+ end
69
+ end
70
+
71
+ def try_usb_ruby
72
+ require_relative "usbkit/backend/usb_ruby"
73
+ Backend::UsbRuby.new
74
+ rescue LoadError, BackendNotAvailableError
75
+ nil
76
+ end
77
+
78
+ def try_usbfs
79
+ return nil unless RUBY_PLATFORM.match?(/linux/i)
80
+
81
+ require_relative "usbkit/backend/usbfs"
82
+ Backend::Usbfs.new
83
+ rescue LoadError, BackendNotAvailableError
84
+ nil
85
+ end
86
+ end
87
+ end
metadata ADDED
@@ -0,0 +1,79 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: usbkit
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - UsbKit Contributors
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: usb-ruby
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: '0'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - ">="
24
+ - !ruby/object:Gem::Version
25
+ version: '0'
26
+ description: UsbKit provides a WebUSB API-compatible interface for communicating with
27
+ USB devices from Ruby using usb-ruby with a Linux usbfs fallback.
28
+ executables: []
29
+ extensions: []
30
+ extra_rdoc_files: []
31
+ files:
32
+ - CHANGELOG.md
33
+ - LICENSE.txt
34
+ - README.md
35
+ - lib/usbkit.rb
36
+ - lib/usbkit/alternate_interface.rb
37
+ - lib/usbkit/async.rb
38
+ - lib/usbkit/backend/base.rb
39
+ - lib/usbkit/backend/usb_ruby.rb
40
+ - lib/usbkit/backend/usb_ruby/device_wrapper.rb
41
+ - lib/usbkit/backend/usbfs.rb
42
+ - lib/usbkit/backend/usbfs/ioctl_commands.rb
43
+ - lib/usbkit/backend/usbfs/sysfs_reader.rb
44
+ - lib/usbkit/configuration.rb
45
+ - lib/usbkit/connection_event.rb
46
+ - lib/usbkit/constants.rb
47
+ - lib/usbkit/context.rb
48
+ - lib/usbkit/device.rb
49
+ - lib/usbkit/endpoint.rb
50
+ - lib/usbkit/errors.rb
51
+ - lib/usbkit/filter.rb
52
+ - lib/usbkit/interface.rb
53
+ - lib/usbkit/transfer_results.rb
54
+ - lib/usbkit/usb_configuration.rb
55
+ - lib/usbkit/version.rb
56
+ homepage: https://github.com/usbkit-rb/usbkit
57
+ licenses:
58
+ - MIT
59
+ metadata:
60
+ homepage_uri: https://github.com/usbkit-rb/usbkit
61
+ source_code_uri: https://github.com/usbkit-rb/usbkit
62
+ rdoc_options: []
63
+ require_paths:
64
+ - lib
65
+ required_ruby_version: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - ">="
68
+ - !ruby/object:Gem::Version
69
+ version: '3.1'
70
+ required_rubygems_version: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ version: '0'
75
+ requirements: []
76
+ rubygems_version: 4.0.6
77
+ specification_version: 4
78
+ summary: Ruby implementation of the WebUSB API for cross-platform USB access
79
+ test_files: []