Class: HIDAPI::Device
- Inherits:
-
Object
- Object
- HIDAPI::Device
- Defined in:
- lib/hidapi/device.rb
Overview
This class is the interface to a HID device.
Each instance can connect to a single interface on an HID device. If you have more than one interface, you will need to have more than one instance of this class to work with all of them.
When open, the device is polled continuously for incoming data. It will build up a cache of up to 32 packets. If you are not reading from the device, it will silently discard the oldest packets and continue storing the newest packets.
The read method can block. This is controlled by the blocking attribute. The default value is true. If you want the read method to be non-blocking, set this attribute to false.
Instance Attribute Summary collapse
-
#blocking ⇒ Object
Gets or sets the blocking nature for
read. -
#interface ⇒ Object
readonly
Gets the interface this HID device uses on the USB device.
-
#path ⇒ Object
readonly
Gets the path for this device that can be used by HIDAPI::Engine#get_device_by_path.
-
#usb_device ⇒ Object
readonly
Gets the USB device this HID device uses.
Class Method Summary collapse
-
.make_path(usb_dev, interface = 0) ⇒ Object
Generates a path for a device.
-
.validate_path(path) ⇒ Object
Validates a device path.
Instance Method Summary collapse
-
#blocking? ⇒ Boolean
Is this device in blocking mode (for reading)?.
-
#close ⇒ Object
Closes the device (if open).
-
#get_feature_report(report_number, buffer_size = nil) ⇒ Object
Gets a feature report from the device.
-
#initialize(usb_device, interface = 0) ⇒ Device
constructor
Initializes an HID device.
-
#inspect ⇒ Object
:nodoc:.
-
#manufacturer ⇒ Object
Gets the manufacturer of the device.
-
#open ⇒ Object
Opens the device.
-
#open? ⇒ Boolean
Is the device currently open?.
-
#product ⇒ Object
Gets the product/model of the device.
-
#product_id ⇒ Object
Gets the product ID.
-
#read ⇒ Object
Reads the next report from the device.
-
#read_string(index, on_failure = '') ⇒ Object
Reads a string descriptor from the USB device.
-
#read_timeout(milliseconds) ⇒ Object
Attempts to read from the device, waiting up to
millisecondsbefore returning. -
#send_feature_report(data) ⇒ Object
Sends a feature report to the device.
-
#serial_number ⇒ Object
Gets the serial number of the device.
-
#to_s ⇒ Object
:nodoc:.
-
#vendor_id ⇒ Object
Gets the vendor ID.
-
#write(*data) ⇒ Object
Writes data to the device.
Constructor Details
#initialize(usb_device, interface = 0) ⇒ Device
Initializes an HID device.
94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 |
# File 'lib/hidapi/device.rb', line 94 def initialize(usb_device, interface = 0) raise HIDAPI::InvalidDevice, "invalid object (#{usb_device.class.name})" unless usb_device.is_a?(LIBUSB::Device) self.usb_device = usb_device self.blocking = true self.mutex = Mutex.new self.interface = interface self.path = HIDAPI::Device.make_path(usb_device, interface) self.input_endpoint = self.output_endpoint = nil self.thread = nil self.thread_initialized = false self.input_reports = [] self.shutdown_thread = false self.transfer_cancelled = LIBUSB::Context::CompletionFlag.new self.open_count = 0 self.class.init_hook.each do |proc| proc.call self end end |
Instance Attribute Details
#blocking ⇒ Object
Gets or sets the blocking nature for read.
Defaults to true. Set to false to have read be non-blocking.
60 61 62 |
# File 'lib/hidapi/device.rb', line 60 def blocking @blocking end |
#interface ⇒ Object
Gets the interface this HID device uses on the USB device.
53 54 55 |
# File 'lib/hidapi/device.rb', line 53 def interface @interface end |
#path ⇒ Object
Gets the path for this device that can be used by HIDAPI::Engine#get_device_by_path
85 86 87 |
# File 'lib/hidapi/device.rb', line 85 def path @path end |
#usb_device ⇒ Object
Gets the USB device this HID device uses.
24 25 26 |
# File 'lib/hidapi/device.rb', line 24 def usb_device @usb_device end |
Class Method Details
.make_path(usb_dev, interface = 0) ⇒ Object
Generates a path for a device.
416 417 418 419 420 421 422 423 424 425 |
# File 'lib/hidapi/device.rb', line 416 def self.make_path(usb_dev, interface = 0) if usb_dev.is_a?(Hash) bus = usb_dev[:bus] || usb_dev['bus'] address = usb_dev[:device_address] || usb_dev['device_address'] else bus = usb_dev.bus_number address = usb_dev.device_address end "#{bus.to_hex(4)}:#{address.to_hex(4)}:#{interface.to_hex(2)}" end |
.validate_path(path) ⇒ Object
Validates a device path.
429 430 431 432 433 434 435 436 437 438 439 |
# File 'lib/hidapi/device.rb', line 429 def self.validate_path(path) match = /(?<BUS>\d+):(?<ADDR>\d+):(?<IFACE>\d+)/.match(path) return nil unless match make_path( { bus: match['BUS'].to_i(16), device_address: match['ADDR'].to_i(16) }, match['IFACE'].to_i(16) ) end |
Instance Method Details
#blocking? ⇒ Boolean
Is this device in blocking mode (for reading)?
364 365 366 |
# File 'lib/hidapi/device.rb', line 364 def blocking? !!blocking end |
#close ⇒ Object
Closes the device (if open).
Returns the device.
158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 |
# File 'lib/hidapi/device.rb', line 158 def close self.open_count = open_count - 1 if open_count <= 0 HIDAPI.debug("open_count for device #{path} is #{open_count}") if open_count < 0 if handle begin self.shutdown_thread = true transfer.cancel! rescue nil if transfer thread.join rescue =>e HIDAPI.debug "failed to kill read thread on device #{path}: #{e.inspect}" end begin handle.release_interface(interface) rescue =>e HIDAPI.debug "failed to release interface on device #{path}: #{e.inspect}" end begin handle.close rescue =>e HIDAPI.debug "failed to close device #{path}: #{e.inspect}" end HIDAPI.debug "closed device #{path}" end self.handle = nil mutex.synchronize { self.input_reports = [] } self.open_count = 0 end self end |
#get_feature_report(report_number, buffer_size = nil) ⇒ Object
Gets a feature report from the device.
389 390 391 392 393 394 395 396 397 398 399 400 401 |
# File 'lib/hidapi/device.rb', line 389 def get_feature_report(report_number, buffer_size = nil) buffer_size ||= input_ep_max_packet_size handle.control_transfer( bmRequestType: LIBUSB::REQUEST_TYPE_CLASS | LIBUSB::RECIPIENT_INTERFACE | LIBUSB::ENDPOINT_IN, bRequest: 0x01, # HID Get_Report wValue: (3 << 8) | report_number, wIndex: interface, dataIn: buffer_size ) end |
#inspect ⇒ Object
:nodoc:
404 405 406 |
# File 'lib/hidapi/device.rb', line 404 def inspect # :nodoc: "#<#{self.class.name}:0x#{self.object_id.to_hex(16)} #{vendor_id.to_hex(4)}:#{product_id.to_hex(4)} #{manufacturer} #{product} #{serial_number} (#{open? ? 'OPEN' : 'CLOSED'})>" end |
#manufacturer ⇒ Object
Gets the manufacturer of the device.
120 121 122 |
# File 'lib/hidapi/device.rb', line 120 def manufacturer @manufacturer ||= read_string(usb_device.iManufacturer, "VENDOR(0x#{vendor_id.to_hex(4)})").strip end |
#open ⇒ Object
Opens the device.
Returns the device.
193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 |
# File 'lib/hidapi/device.rb', line 193 def open if open? self.open_count = open_count + 1 if open_count < 1 HIDAPI.debug "open_count for open device #{path} is #{open_count}" self.open_count = 1 end return self end self.open_count = 0 begin self.handle = usb_device.open raise 'no handle returned' unless handle begin if handle.kernel_driver_active?(interface) handle.detach_kernel_driver(interface) end rescue LIBUSB::ERROR_NOT_SUPPORTED HIDAPI.debug 'cannot determine kernel driver status, continuing to open device' end handle.claim_interface(interface) self.input_endpoint = self.output_endpoint = nil # now we need to find the endpoints. usb_device.settings .keep_if {|item| item.bInterfaceNumber == interface} .each do |intf_desc| intf_desc.endpoints.each do |ep| if ep.transfer_type == :interrupt if input_endpoint.nil? && ep.direction == :in self.input_endpoint = ep.bEndpointAddress self.input_ep_max_packet_size = ep.wMaxPacketSize end if output_endpoint.nil? && ep.direction == :out self.output_endpoint = ep.bEndpointAddress end end break if input_endpoint && output_endpoint end end # output_ep is optional, input_ep is required raise 'failed to locate input endpoint' unless input_endpoint # start the read thread self.input_reports = [] self.thread_initialized = false self.shutdown_thread = false self.thread = Thread.start(self) { |dev| dev.send(:execute_read_thread) } sleep 0 until thread_initialized rescue =>e handle.close rescue nil self.handle = nil HIDAPI.debug "failed to open device #{path}: #{e.inspect}" raise DeviceOpenFailed, e.inspect end HIDAPI.debug "opened device #{path}" self.open_count = 1 self end |
#open? ⇒ Boolean
Is the device currently open?
150 151 152 |
# File 'lib/hidapi/device.rb', line 150 def open? !!handle end |
#product ⇒ Object
Gets the product/model of the device.
126 127 128 |
# File 'lib/hidapi/device.rb', line 126 def product @product ||= read_string(usb_device.iProduct, "PRODUCT(0x#{product_id.to_hex(4)})").strip end |
#product_id ⇒ Object
Gets the product ID.
144 145 146 |
# File 'lib/hidapi/device.rb', line 144 def product_id @product_id ||= usb_device.idProduct end |
#read ⇒ Object
Reads the next report from the device.
In blocking mode, it will wait for a report. In non-blocking mode, it will return immediately with an empty string if there is no report.
Returns nil on error.
358 359 360 |
# File 'lib/hidapi/device.rb', line 358 def read read_timeout blocking? ? -1 : 0 end |
#read_string(index, on_failure = '') ⇒ Object
Reads a string descriptor from the USB device.
444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 |
# File 'lib/hidapi/device.rb', line 444 def read_string(index, on_failure = '') begin # does not require an interface, so open from the usb_dev instead of using our open method. data = if open? handle.string_descriptor_ascii(index) else usb_device.open { |handle| handle.string_descriptor_ascii(index) } end HIDAPI.debug("read string at index #{index} for device #{path}: #{data.inspect}") data rescue =>e HIDAPI.debug("failed to read string at index #{index} for device #{path}: #{e.inspect}") on_failure || '' end end |
#read_timeout(milliseconds) ⇒ Object
Attempts to read from the device, waiting up to milliseconds before returning.
If milliseconds is less than 1, it will wait forever. If milliseconds is 0, then it will return immediately.
Returns the next report on success. If no report is available and it is not waiting forever, it will return an empty string.
Returns nil on error.
297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 |
# File 'lib/hidapi/device.rb', line 297 def read_timeout(milliseconds) raise DeviceNotOpen unless open? mutex.synchronize do if input_reports.count > 0 data = input_reports.delete_at(0) HIDAPI.debug "read data from device #{path}: #{data.inspect}" return data end if shutdown_thread HIDAPI.debug "read thread for device #{path} is not running" return nil end end # no data to return, do not block. return '' if milliseconds == 0 if milliseconds < 0 # wait forever (as long as the read thread doesn't die) until shutdown_thread mutex.synchronize do if input_reports.count > 0 data = input_reports.delete_at(0) HIDAPI.debug "read data from device #{path}: #{data.inspect}" return data end end sleep 0 end # error, return nil HIDAPI.debug "read thread ended while waiting on device #{path}" nil else # wait up to so many milliseconds for input. stop_at = Time.now + (milliseconds * 0.001) while Time.now < stop_at mutex.synchronize do if input_reports.count > 0 data = input_reports.delete_at(0) HIDAPI.debug "read data from device #{path}: #{data.inspect}" return data end end sleep 0 end # no input, return empty. '' end end |
#send_feature_report(data) ⇒ Object
Sends a feature report to the device.
370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 |
# File 'lib/hidapi/device.rb', line 370 def send_feature_report(data) raise ArgumentError, 'data must not be blank' if data.nil? || data.length < 1 raise HIDAPI::DeviceNotOpen unless open? data, report_number, skipped_report_id = clean_output_data(data) handle.control_transfer( bmRequestType: LIBUSB::REQUEST_TYPE_CLASS | LIBUSB::RECIPIENT_INTERFACE | LIBUSB::ENDPOINT_OUT, bRequest: 0x09, # HID Set_Report wValue: (3 << 8) | report_number, # HID feature = 3 wIndex: interface, dataOut: data ) data.length + (skipped_report_id ? 1 : 0) end |
#serial_number ⇒ Object
Gets the serial number of the device.
132 133 134 |
# File 'lib/hidapi/device.rb', line 132 def serial_number @serial_number ||= read_string(usb_device.iSerialNumber, '?').strip end |
#to_s ⇒ Object
:nodoc:
409 410 411 |
# File 'lib/hidapi/device.rb', line 409 def to_s # :nodoc: "#{manufacturer} #{product} (#{serial_number})" end |
#vendor_id ⇒ Object
Gets the vendor ID.
138 139 140 |
# File 'lib/hidapi/device.rb', line 138 def vendor_id @vendor_id ||= usb_device.idVendor end |
#write(*data) ⇒ Object
Writes data to the device.
The data to be written can be individual byte values, an array of byte values, or a string packed with data.
262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 |
# File 'lib/hidapi/device.rb', line 262 def write(*data) raise ArgumentError, 'data must not be blank' if data.nil? || data.length < 1 raise HIDAPI::DeviceNotOpen unless open? data, report_number, skipped_report_id = clean_output_data(data) if output_endpoint.nil? # No interrupt out endpoint, use the control endpoint. handle.control_transfer( bmRequestType: LIBUSB::REQUEST_TYPE_CLASS | LIBUSB::RECIPIENT_INTERFACE | LIBUSB::ENDPOINT_OUT, bRequest: 0x09, # HID Set_Report wValue: (2 << 8) | report_number, # HID output = 2 wIndex: interface, dataOut: data ) data.length + (skipped_report_id ? 1 : 0) else # Use the interrupt out endpoint. handle.interrupt_transfer( endpoint: output_endpoint, dataOut: data ) end end |