Our story starts at the Brighton Ruby conference in 2015, where I presented a technology-themed version of the popular panel game, Just A Minute. I had put together a simple system in Pure Data to keep track of the scores, topics and the timer. Long story short, this system let me down, it crashed half way through the session and I had to re-construct the scores on the fly.
In retrospect, I found that Pure Data was not the right tool for the job, so I set about rebuilding the same system in Ruby, with the Gosu library.
One of the reasons I had chosen Pd in the first place was the simplicity of using USB HID devices (you can learn about that in my earlier tutorial). So half way through this process, I ran up against the fact that is is not quite so simple in Ruby.
Firstly, I started with libusb, a standard library for reading USB.
I'm using the controllers from the trivia game Buzz, which look like they should be a particularly simple USB device, no output, no continuous controllers, just 20 buttons, should be simple, right?
First order of business was to find out what the USB device was. There's none of the PD index-based HID identifiers, instead I had to use the linux command 'lsusb' to find them. And the output from this includes my laptops keyboard and mouse, as well as what appears to be some internal USB hubs. It looks a little like this:
ajfaraday@ajf-samsung:~$ lsusb Bus 002 Device 005: ID 0cf3:3004 Atheros Communications, Inc. Bus 002 Device 003: ID 054c:0002 Sony Corp. Standard HUB Bus 002 Device 002: ID 8087:0024 Intel Corp. Integrated Rate Matching Hub Bus 002 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub Bus 001 Device 003: ID 2232:1029 Bus 001 Device 002: ID 8087:0024 Intel Corp. Integrated Rate Matching Hub Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
To this day, I have no idea what bus 1, device 3 is.
Okay, so we can't see the word Buzz, or playstation, or HID. The only clue is that it's made by Sony. The only way I can see to confirm this is to un-plug the USB and re-run lsusb. Sure enough, the Sony Corp. Standard HUB does not appear now. So I know this is the device I'm looking for.
Bus 002 Device 006: ID 054c:0002 Sony Corp. Standard HUBIt looks like this isn't enough information to actually use it, tho. I'm going to need more. The help page for lsusb tells us we can use the -s flag to choose a specific device, and the -v device to get verbose information.
ajfaraday@ajf-samsung:~$ lsusb -h Usage: lsusb [options]... List USB devices -v, --verbose Increase verbosity (show descriptors) -s [[bus]:][devnum] Show only devices with specified device and/or bus numbers (in decimal)So we need to get the bus, and devnum (device) from our previous lsusb command. Here's the full information from my Buzz controllers.
ajfaraday@ajf-samsung:~$ lsusb -s 002:006 -v Bus 002 Device 006: ID 054c:0002 Sony Corp. Standard HUB Couldn't open device, some information will be missing Device Descriptor: bLength 18 bDescriptorType 1 bcdUSB 2.00 bDeviceClass 0 (Defined at Interface level) bDeviceSubClass 0 bDeviceProtocol 0 bMaxPacketSize0 8 idVendor 0x054c Sony Corp. idProduct 0x0002 Standard HUB bcdDevice 11.01 iManufacturer 3 iProduct 1 iSerial 0 bNumConfigurations 1 Configuration Descriptor: bLength 9 bDescriptorType 2 wTotalLength 34 bNumInterfaces 1 bConfigurationValue 1 iConfiguration 0 bmAttributes 0x80 (Bus Powered) MaxPower 100mA Interface Descriptor: bLength 9 bDescriptorType 4 bInterfaceNumber 0 bAlternateSetting 0 bNumEndpoints 1 bInterfaceClass 3 Human Interface Device bInterfaceSubClass 0 No Subclass bInterfaceProtocol 0 None iInterface 0 HID Device Descriptor: bLength 9 bDescriptorType 33 bcdHID 1.11 bCountryCode 33 US bNumDescriptors 1 bDescriptorType 34 Report wDescriptorLength 78 Report Descriptors: ** UNAVAILABLE ** Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x81 EP 1 IN bmAttributes 3 Transfer Type Interrupt Synch Type None Usage Type Data wMaxPacketSize 0x0008 1x 8 bytes bInterval 10
Okay, there's a load of extra information here that we don't need. Just to cut a bit of this out, we're going to need idVendor, idProduct and the Endpoint Descriptor.
Back to libusb, the README for the library quickly gives me a bit of example code, which I've changed to include the information for my Buzz controllers.
require "libusb" usb = LIBUSB::Context.new device = usb.devices(:idVendor => 0x054c, :idProduct => 0x0002).first device.open_interface(0) do |handle| handle.control_transfer(:bmRequestType => 0x40, :bRequest => 0xa0, :wValue => 0xe600, :wIndex => 0x0000, :dataOut => 1.chr) endBut as soon as I try running this code I see this error.
/var/lib/gems/1.9.1/gems/libusb-0.5.0/lib/libusb/constants.rb:62:in `raise_error': LIBUSB::ERROR_BUSY in libusb_claim_interface (LIBUSB::ERROR_BUSY)The stack trace points us at line 5, 'device.open_interface'. I did a lot of googling on this subject, and got a lot of confusing answers. I'm skipping a lot of trial and error here, but the answer here seems to be that something else, possibly the operating system, is reading the USB port already. So to use it with my Ruby app, I need to use detach_kernel_driver. Which looks a little like this:
def reset_device_access usb_context = LIBUSB::Context.new device = usb_context.devices( idVendor: 0x054c, idProduct: 0x0002 ).first handle = device.open handle.detach_kernel_driver(0) handle.close rescue => er puts er.message # nothing needs doing here end
There's a couple of things here, firstly, I'm finding the usb context, then finding the device by as in the example. I then need to open a device handle, run detach_kernel_driver, then close the handle. Only, if there is no kernel driver to detach, this throws an error. The rescue block here is something of a hack, I can't find out how to detect if I need to run detach_kernel_driver or not, so I simply catch the error, but don't halt the code. If it throws an error, then it didn't need to be done. This shows my ignorance of the deeper, darker parts of libusb, but it works.
I don't like this, but some times this is something we need to do, pending a better understanding of what we're working with.
So, I can actually get to my device, that's a start. Although, I want to use my device in and amongst some other code, I didn't want it to be limited to a single code block. I had to go digging around in the libusb API documentation and found that instead of using 'open_interface', which opens the interface, uses it within a block, and then closes it at the end of the block, I could instead use the 'open' method. My new code looks a bit like this:
class Controller def initialize @usb_context = LIBUSB::Context.new @device = @usb_context.devices( idVendor: 0x054c, idProduct: 0x0002 ).first reset_device_access @handle = @device.open end end
So, I've wrapped it in a class, run my reset method to ensure it's free, and saved the device handle as an instance variable, @handle. The next thing we need is a method to read that handle. For this we need the endpoint data from back in our lsusb data, specifically the attributes named bEndpointAddress, bLength and bInterval. Again, after a lot of trial and error, I found it easier to use interrupt_transfer, which is specific to input endpoints (the information coming back from a USB device). So this is also in the Controller class
def raw_data data = @handle.interrupt_transfer( :endpoint => 0x81, :dataIn => 0x0005, :timeout => 10 ) data.bytes endAgain, I had a great deal of difficulty reading the result of interrupt_transfer, and the code shows where I wound up after it. The data variable is a string, but it's not human readable, the result looks a little bit like this.
0
Each line of this is a single output from the raw_data method, while I'm pushing some buttons on the Buzz controllers. There's no understanding this. A lot of trial and error later, I discovered that this is actually an array of numbers, each one is a byte (a number made up of 8 binary bits, which translates to a number between 0 and 255. So, with the method defined above, I wrote a script to watch what happens when I push the buttons...
require 'libusb' require './controller.rb' c = Controller.new loop do begin puts c.raw_data.inspect rescue end sleep 0.01 end
Oops, there's another one of those unhandled rescue blocks. This is bad practise! Usually you would either stop the program completely on an error, and guard earlier on in the code against situations which will cause an error to be thrown. Only I can't find out if it'll work or not without just trying it. So this will have to do for now.
What I've found by probing in this way, is that when a button is pushed between one call of the raw_data method and the next, there is no error, but when no buttons are pushed, it throws the error 'error TRANSFER_TIMED_OUT'. So I just ignore the timeouts, and use the data from when there is a change. So I can now see the output from the raw data method, only when buttons are pushed. Here's what happens when I push a button at random:
[127, 127, 0, 0, 240] [127, 127, 0, 4, 240] [127, 127, 0, 0, 240] [127, 127, 0, 4, 240] [127, 127, 0, 0, 240]Okay, so the first thing I've noticed is that the first two numbers (bytes 0 and 1) don't seem to change at all. I have no idea why, but I can easily isolate the input I was looking for. The fourth item in the list (byte 2) goes up by 4 when it is pushed.
Now, 4 is a binary number, and a quick check of the other buttons proved a definite pattern. Pushing any button on the controllers will increment byte 2 or byte 3 by a binary number (1, 2, 4, 8, 16, 32, 64 or 128). The long and the short of it is this, as I mentioned before, these numbers are made up of 8 binary bits, each representing one of the numbers listed above. The number each active bit represents is added up to make that number.
If I select the index of a number in Ruby, it gives me that indexed bit of the number. For instance:
# bit 0 represents 1, so for number 1, this is active. 1[0] # => 1 # but if the number will not contain a 1 in it's binary makeup, this is 0. 2[0] # => 0 # This means that if we add up some binary numbers, the bits for these numbers is a 1: n = 4 + 32 n[2] # the bit for 4 # => 1 n[5] the bit for 32 # => 1
So, applying this to the data to
c = Controller.new loop do begin puts c.raw_data[3][0] == 1 rescue end sleep 0.01 endSo, raw_data[3] is byte 3 of our raw data and raw_data[3][0] is bit 0 of byte 3. If this bit is a 1 instead of a 0, that button is pushed. By pushing each of the buzzer buttons (the ones I'm interested in), I found this information out.
# buzzer | byte | value | bit # 1 | 2 | 1 | 0 # 2 | 2 | 32 | 5 # 3 | 3 | 4 | 2 # 4 | 3 | 128 | 7That is, for buzzer 1 we see byte 2 increment by 1, which is bit 0 (the one on the far right) etc. So to check all 4 buttons, I narrowed this down to an array with which byte and bit to look for for each button. Which I've stored as a constant on my Controller class.
class Controller BUZZ_BITS = [ [2, 0], [2, 5], [3, 2], [3, 7] ] def check data = raw_data BUZZ_BITS.each_with_index do |lookup, i| if data[lookup[0]][lookup[1]] == 1 puts "buzzer #{i + 1} pushed" end end rescue # no input, just ignore the error end ...
This is really the end-point for this tutorial. What I've done in the end is store which bit I'm looking for for each button and at each call of the check method, I grab the raw data. Then I iterate through the bits I'm looking for, and print the index of that bit (with a + 1 so people aren't confused by the zero index).
Okay, so this is pretty confusing, but it works, honest. You can see the full example code at www.github.com/ajfaraday/usb-controller-demo and see it in the wild at www.github.com/ajfaraday/ruby-jam.
Thanks for bearing with me on this one, I really hope you found it helpful. Any comments, questions? Feel free to get me on twitter at @MarmiteJunction
Thank you infinitely. This was super helpful. One thing i found while applying it to my situation was that the @handle.interrupt_transfer 2nd arg :dataIn => 0x0005 doesn't seem to make much difference for me with my device (Symbol Barcode Scanner LI4278), so i set it to 0x0008 ( makes more sense to me i think ), also i'm not sure where that number came from in your lsusb -v output. your device listed bLength as 7 not 5?
ReplyDeleteHi Erik
DeleteGlad you found the article helpful, I really wasn't sure about this one, everything in here was found my trial and error.
All the lsusb output here was copied and pasted, no changes made.
Also, I understand most barcode scanners work by identifying themselves as a keyboard. You may want to try scanning a code while you're focussed on a text editor.
Bus 001 Device 003: ID 2232:1029
ReplyDelete2232 Silicon Motion
1005 WebCam SCB-0385N
1028 WebCam SC-03FFL11939N
1029 WebCam SC-13HDL11939N
1037 WebCam SC-03FFM12339N
http://www.linux-usb.org/usb.ids
Not sure why lsusb doesn't know that though...
Great website
ReplyDeletelsusb output for my digital piano is not showing an interrupt 'transfer type'. Any thoughts?
ReplyDeleteMy first thought is that it's an instrument, so it's probably giving you MIDI data instead of HID data. This means you probably don't need to mess around with the raw data from the input stream.
DeleteI'd look for a ruby gem for handling MIDI input/output. You could try https://github.com/arirusso/unimidi
شركة تعقيم بالمدينه المنورة
ReplyDeleteشركة تعقيم بالطائف
شركة تعقيم بجدة
شركة تعقيم بمكة
I noticed you have a Twitter profile. If you want to know how to get Twitter followers you can check them out. They also helped me to buy instagram likes
ReplyDeletehave you thought about a YouTube promotion for your videos ? I normally buy targeted YouTube subscribers when promoting my blog.
ReplyDeletesuper artical
ReplyDeletezoho aptitude questions
zoho aptitude questions 2019 pdf
zoho c aptitude questions with answers
c aptitude questions for zoho
zoho aptitude questions with answers
zoho c aptitude questions with answers pdf
zoho aptitude questions 2017 pdf
zoho digital marketing aptitude questions
zoho aptitude questions 2018 pdf
zoho technical support aptitude questions
Amazing Article, Really useful information to all So, I hope you will share more information to be check and share here.
ReplyDeleteInternship near me
Inplant Training for cse
Inplant Training for IT
Inplant Training for ECE Students
Inplant Training for EEE Students
Inplant Training for MECHANICAL Students
inplant Training for CIVIL Students
Inplant Training for Aeronautical Engineering Students
Inplant Training for ICE Students
Inplant Training for BIOMEDICAL Engineering Students
Super article
ReplyDeleteWhat is Cyber Security
Types of Cyber Attacks
Types of Cyber Attackers
Cyber Security Technology
Cyber Security Tools
Cyber Security Standards
What is Google Adwords
Google Adwords tutorial
Google Keyword Planner
How to Advertise on Google
Cleanliness is from faith andit is one of the necessary things that must be taken care of. Life without hygiene becomes full of epidemics and diseases, and from here all housewives care about cleanliness and use a cleaning company to help them in all house cleaning operations
ReplyDeleteشركة تنظيف ببريدة
شركة تنظيف مجالس ببريدة
شركة مكافحة حشرات ببريدة
شركة نقل عفش ببريدة
شركة كشف تسربات المياه ببريدة