Go Down

Topic: Using USB Keyboard (Read 20472 times) previous topic - next topic

ShapeShifter

I'm very curious to see if only the values get passed to arduino or the whole lines.
Everything you see on the output will be sent up to the sketch. That means you will be sending more data than you need (slowing things down) and the sketch will have to do a lot of parsing of the data to find the parts you are actually interested in (making more development work and slowing down the processing.) You will want to make some changes to the script so that it sends only the data you are interested in. This will minimize the amount of data transferred, which will speed things up, and it will simplify the overall development effort.

As you see from the data output, you are getting raw keystrokes from the keyboard. This pretty much translates to every key press and key release event. For example, if I recall correctly, to get a capital 'A', you will see keycodes for shift key press, 'a' key pressed, and then shift key released. You will need to map this into 'A'.

Quote
In this case, where do I put the -u? I tried putting it after event1' but I still see nothing in serial monitor.
Where you have it, it is acting as a parameter to your evdev.py script, which is not what you want. You need it to be a parameter to Python. There are two ways to do it:
  • In sonnyyu's code, he has the hashbang #!/usr/bin/python as the first line in the code. This tells Linux that when the script is run by simply typing the name of the script at the Linux command processor, it should run /usr/bin/python to run the script. So, when you run the script with a Process object by just using the name of the script (as you are doing) Linux reads this hashbang and runs #!/usr/bin/python /mnt/sdaq/evdev.py. To get this to work, you need to change the hashbang to #!/usr/bin/python -u so Linux will run #!/usr/bin/python -u /mnt/sdaq/evdev.py. In this way, the "-u" is passed to Python, so it runs the script in unbuffered mode.
  • The other way is to explicitly call Python in your command, using the "-u" option: p.begin("/usr/bin/python -u /mnt/sda1/evdev.py '/dev/input/event1'");


Quote
Sorry for the many questions, I have zero knowledge on programming, learning a bit as I go...
No problem with the questions, it's how you learn! If your questions were along the lines of "I don't want to do any work so will you give me the finished code?" that would be one thing. But it's clear you're putting in the effort, and you want to learn from it, so you will likely find lots of people willing to help.

ShapeShifter

I just noticed this post by sonnyyu:

Make USB Keyboard works with Arduino, by combine both code.

Code: [Select]
def demo():
...
    dev = DeviceGroup(sys.argv[1:])
    while 1:
        event = dev.next_event()
        if event is not None:
            print repr(event)
            if event.type == "EV_KEY" and event.value == 1:
                if event.code.startswith("KEY"):
                    print event.scanCode
                elif event.code.startswith("BTN"):
                    print event.code


Code: [Select]
#!/usr/bin/python
import sys  
sys.path.insert(0, '/usr/lib/python2.7/bridge/')
from bridgeclient import BridgeClient as bridgeclient
bc = bridgeclient()                          
bc.put('to_arduino',keyboard_code)

If this is what you meant by using sonnyyu's version of the code, I don't recommend it. This is putting values to the bridge, which the sketch then reads by using Bridge.get("to_arduino");. This adds extra overhead, but more importantly, it has a serious limitation that the sketch is not notified of new values. The sketch must continually keep reading that value and look for changes, and only react when the value has changed. As a result of that, the sketch will never know when the same key has been pressed twice in a row, as the value did not change from the previous reading.

While there are some places where Bridge.put() and Bridge.get() are useful, this is not one of them. I think you are much better off using the Process() method to send the keystrokes up to the sketch.

ShapeShifter

#32
May 25, 2017, 03:01 pm Last Edit: May 25, 2017, 03:06 pm by ShapeShifter
Here is the Python code that I'm using to read from a barcode scanner. This is a USB device that mimics the operation of a USB keyboard: when you scan a barcode, it sends the raw keystrokes as if you had typed the barcode in on a keyboard. I have the scanner configured to send a comma character at the end of the barcode, so this code collects and buffers data into the "barcode" variable until a comma is seen, and then it prints out the full barcode. For this scanner, I was only interested in the capital letters, so I ignored everything to do with the shift key and other modifiers, and assumed that every alphabetic keystroke was a capital letter. The barcode format I am using does not support many of the punctuation characters, so I left them out of the lookup table as well.

The script starts out by looking at the input parameters. It is expecting a simple number, like 0, 1, 2, etc. It takes that number and builds an input device name from it. For example, if the parameter is 1, it uses device /dev/input/event1. The default if there is no parameter is /dev/input/event0. (I needed to support multiple barcode scanners, so I ran a copy of this script for each scanner.)

The script then sets up a lookup table. The index to the table is the keycode, and the value of a table cell is the corresponding ASCII character.

The main processing of the script is to open the device file, and keep reading events. For each event, if type is 1, and value is 1, it is a normal keypress, so the key code value is passed through the lookup table if it's in the range of the table. The translated value is added to the buffered string. If the key code is out of range, it is ignored. When a terminator character is read, the whole string is sent. If there was an error reading an event (perhaps the scanner got unplugged?) the "while event" loop drops out, the input device file is closed, and the script ends.

You will undoubtedly have to make a bunch of changes to this, and your decoding logic may have to be a bit more involved, but hopefully this gives you another way to approach this. At a minimum, you will likely want to get rid of buffering the keystrokes, and just send them out immediately when they are received.

Code: [Select]

#!/usr/bin/python -u
# Note -u parameter above, which makes this script run in ubuffered mode
import struct
import time
import sys

# This script expects one argument, the event id number.

# default values
infile_path = "/dev/input/event0"
key = "0"

# Update the values if there is an argument
if len(sys.argv) > 1:
        key = sys.argv[1]
        infile_path = "/dev/input/event" + key

#long int, long int, unsigned short, unsigned short, unsigned int
FORMAT = 'llHHI'
EVENT_SIZE = struct.calcsize(FORMAT)

KeyLookup = [
   '',       #  0   KEY_RESERVED
   '',       #  1   KEY_ESC
   '1',      #  2   KEY_1
   '2',      #  3   KEY_2
   '3',      #  4   KEY_3
   '4',      #  5   KEY_4
   '5',      #  6   KEY_5
   '6',      #  7   KEY_6
   '7',      #  8   KEY_7
   '8',      #  9   KEY_8
   '9',      #  10  KEY_9
   '0',      #  11  KEY_0
   '',       #  12  KEY_MINUS
   '',       #  13  KEY_EQUAL
   '',       #  14  KEY_BACKSPACE
   '',       #  15  KEY_TAB
   'Q',      #  16  KEY_Q
   'W',      #  17  KEY_W
   'E',      #  18  KEY_E
   'R',      #  19  KEY_R
   'T',      #  20  KEY_T
   'Y',      #  21  KEY_Y
   'U',      #  22  KEY_U
   'I',      #  23  KEY_I
   'O',      #  24  KEY_O
   'P',      #  25  KEY_P
   '[',      #  26  KEY_LEFTBRACE
   ']',      #  27  KEY_RIGHTBRACE
   '',       #  28  KEY_ENTER
   '',       #  29  KEY_LEFTCTRL
   'A',      #  30  KEY_A
   'S',      #  31  KEY_S
   'D',      #  32  KEY_D
   'F',      #  33  KEY_F
   'G',      #  34  KEY_G
   'H',      #  35  KEY_H
   'J',      #  36  KEY_J
   'K',      #  37  KEY_K
   'L',      #  38  KEY_L
   '',       #  39  KEY_SEMICOLON
   '',       #  40  KEY_APOSTROPHE
   '',       #  41  KEY_GRAVE
   '',       #  42  KEY_LEFTSHIFT
   '\\',     #  43  KEY_BACKSLASH
   'Z',      #  44  KEY_Z
   'X',      #  45  KEY_X
   'C',      #  46  KEY_C
   'V',      #  47  KEY_V
   'B',      #  48  KEY_B
   'N',      #  49  KEY_N
   'M',      #  50  KEY_M
   '',       #  51  KEY_COMMA
   '.',      #  52  KEY_DOT
   '/' ]     #  53  KEY_SLASH

TERMINATOR = 51 # key code for end of barcode (KEY_COMMA)

barcode = ""


#open file in binary mode
in_file = open(infile_path, "rb")

event = in_file.read(EVENT_SIZE)

while event:
    (tv_sec, tv_usec, type, code, value) = struct.unpack(FORMAT, event)

    if type == 1 and value == 1:
        if code == TERMINATOR:
            # got the end of the barcode
            print barcode
            barcode = ""
        else:
            if code < len(KeyLookup):
                barcode = barcode +  KeyLookup[code]


    event = in_file.read(EVENT_SIZE)

in_file.close()[/quote]


Added: this is an example of the Linux side trying to do as much as possible to make the work of the Arduino sketch as easy as possible. The code you are working with sends out a long string that completely describes each keystroke. While I found that code invaluable for learning how the event interface works, and what events are sent under what conditions, I feel that's too much information to send up to the sketch. Not only does that clog up the relatively slow serial port that is between the two processors, it means much more work for the sketch to decode the data. By doing all of the pre-processing in the Python script, the script sends only complete barcodes up to the sketch, which the sketch can receive by simply reading lines of text from the Process object that is running this Python script.

petros00

#33
May 25, 2017, 04:57 pm Last Edit: May 25, 2017, 05:05 pm by petros00
Wow, thanks a lot for the long replies, highly appreciated!

btw, I'm using a 18 key numpad, so I will remove all lines that deal with the rest of the characters to make the script even lighter.

Thanks again!

ShapeShifter

Just keep in mind that you can't randomly delete rows in the lookup table if a later row is needed. The comments in each row have a number that indicates the array index - these must be kept sequential. If you are only interested in the digits, you can delete the rows that follow. But if you are interested in other characters, then everything up to that character must be included in the table. For example, if your numeric keypad has a slash key (as most do) and you want to be able to receive that key, then you won't be able to delete any rows from the table as slash is the last entry.

If you are looking to translate only a small set of keys, and they cover a wide range of input values, then you could perhaps use a dictionary for the mapping rather than array.

Also, if your keypad has a numlock key, and you want it to work as intended, you will likely have to look for the numlock events and track the numlock state. Then, when a keycode comes in, you will have to modify the code you send to the sketch depending on whether numlock is currently active or not.

My scanner requirements were quite lean (limited number of characters, all upper case, no modifier keys) and the code is correspondingly lean. Even though you have a small number of keys, it's possible that the code will have to get more complex, not smaller. It all depends on how faithfully you want to handle the keypad.

petros00

#35
May 25, 2017, 08:18 pm Last Edit: May 25, 2017, 08:43 pm by petros00
I didn't remove any of the rows, thought I'd let them be, then saw your post:)

Progress so far: Removed this part, as the keypad is always event1, minor change but I'm always happy when I figure out what a piece of code does:

Code: [Select]
# Update the values if there is an argument
if len(sys.argv) > 1:
        key = sys.argv[1]
        infile_path = "/dev/input/event" + key


Removed the TERMINATOR part as I'm looking at getting a value printed every time a button is pressed.

This segment:

Code: [Select]
while event:
   (tv_sec, tv_usec, type, code, value) = struct.unpack(FORMAT, event)

   if type == 1 and value == 1:
       if code == TERMINATOR:
           # got the end of the barcode
           print barcode
           barcode = ""
       else:
           if code < len(KeyLookup):
               barcode = barcode +  KeyLookup



Became this:

Code: [Select]
while event:
    (tv_sec, tv_usec, type, code, value) = struct.unpack(FORMAT, event)

    if type == 1 and value == 1:
            print code


It took me a while to figure out what to put in place of barcode in the print argument, and code is the only one that brought results I can use. I get a double digit value for every key(74, 75, 14 etc), in between a pair of 69s if numlock is on. If numlock is off I get no values for most of the keys.

I am not planning to use the numlock, so after a bit of search I found out that this simple line before the print argument, hides the 69s:

Code: [Select]
if code != 69:

All perfect on this side I guess. Do I need to look at anything else before I move to the arduino side? So far I don't get anything, just need to know if the problem is in my sketch or need to add something here.

Thanks again!

ShapeShifter

Good progress!

If you are happy with just sending the code values, then you don't need the KeyLookup array. It's function is to translate that key code value (which could theoretically be up to 3 digits) into an ASCII character. It's these lines that do it:

Code: [Select]
           if code < len(KeyLookup):
                barcode = barcode +  KeyLookup[code]


This checks to see if the "code" variable has a value that is less than the number of rows in the lookup table. If it's out of range, the value is simply ignored and nothing is sent (the check prevents an error from trying to access an array with an index that is too big.) If it is in the range of the lookup table, it takes the code value and uses it as an index into the lookup table: if the code value is 0, it takes the first row, which is nothing; if 1 it takes the value in the second row which is also nothing; if 2 it takes the value in the third row which is the character '0', and so on up until a value of 53 takes the value in row 52 which is a slash character. (Arrays are zero based, so the index of the first row is zero, not one.)

You could use logic something like this:

Code: [Select]
   if type == 1 and value == 1:
        if code < len(KeyLookup):
            print KeyLookup[code]


This looks to see if the code is in the array. If not, it is thrown away. If it is in range, the corresponding code is translated to an ASCII character and printed.

There are some significant advantages to the translation:
  • It's got to be decoded at some point, and the Linux side has more speed and resources than the sketch to do it
  • It's less data to send over the relatively slow serial link (one character instead of one, two, or three)
  • Because it's a single character, as opposed to one through three digits, there is no chance of things getting out of sync - you don't have to worry about losing a character or getting an extra noise character and getting the wrong data. And you don't have to worry if a code is more or less than the two digits you are expecting

To get a better understanding of the key codes, the USB HID spec >LINK< lists the scan code in chapter 10, page 53. This is how you translate the code numbers to the key meanings (74=keypad home, 75=keypad PageUp, 14='K'.) It's interesting that you're getting 69s with numlock on, that's the code for F12. I would've expected you to get key codes saying the numlock key is pressed, and you need to keep track of the state.

If you are getting meaningful codes up in the 75 range, then you will need a much longer lookup table. But if you only have 18 keys, that's a lot of wasted slots in the table. Instead of an array, you might do well using a dictionary (look it up on your favorite Python reference source) to translate the code into a decoded character.[/code]
[/code]

petros00

#37
May 26, 2017, 09:00 am Last Edit: May 26, 2017, 10:07 am by petros00
So, instead of going for a dictionary, I went for a dirty, manual method of if statements. This is the finished version of the code:

Code: [Select]
#!/usr/bin/python -u
# Note -u parameter above, which makes this script run in ubuffered mode
import struct
import time
import sys

# This script expects one argument, the event id number.

# default values
infile_path = "/dev/input/event1"


#long int, long int, unsigned short, unsigned short, unsigned int
FORMAT = 'llHHI'
EVENT_SIZE = struct.calcsize(FORMAT)


#open file in binary mode
in_file = open(infile_path, "rb")

event = in_file.read(EVENT_SIZE)

while event:
    (tv_sec, tv_usec, type, code, value) = struct.unpack(FORMAT, event)

    if type == 1 and value == 1:

#if type == 1 and value == 1:
          if code == 71:
                print 7
        if code == 72:
                print 8   
        if code == 73:
                print 9   
          if code == 75:
                print 4
          if code == 76:
                print 5
          if code == 77:
                print 6
          if code == 79:
                print 1
          if code == 80:
                print 2
          if code == 81:
                print 3
          if code == 82:
                print 0
          if code == 83:
                print 'a'
          if code == 96:
                print 'b'
          if code == 14:
                print 'c'
          if code == 78:
                print 'd'
          if code == 74:
                print 'e'
          if code == 55:
                print 'f'
          if code == 98:
                print 'g' 



    event = in_file.read(EVENT_SIZE)

in_file.close()


The process is very responsive, and since I will not be pressing more than one key every now and then(I'm building a midi controller) I don't see a reason to go different route, even though I would learn how to use a dictionary and-I guess-have a more streamlined code.

In the arduino side everything looks good as well, this is the test code that will be incorporated-with the necessary changes in my main sketch(minus there serial.print, it's here only for testing purposes):

Code: [Select]
#include <Bridge.h>
#include <Process.h>

Process p;

void setup()
{
   Bridge.begin();
   Serial.begin(19200);
   
   p.begin("/mnt/sda1/numpad.py");
   p.runAsynchronously();
}

void loop()
{
     
   
   while (p.available()){
     if ((char)p.read() == '1'){
       //Serial.print((char)p.read());
       Serial.println("hello");

      }

  }   

}


You will notice that right now only key 1 produces results, that's because I was testing how to assign each button to execute a specific part of the code, and-guess what- again I'm gonna use ifs for every case. I might try to find out how to setup an array(I guess?) to do this instead of 18 if statements(btw, are they more taxing for the processor?).


Is there any way to get values from numeric keys when numlock is off? Now I don't get anything nor could I find a way.

One last question. One of the keys in the numpad is a triple 0, that is, if pressed, it will send 3 zeros one after the other. I'd like to be able to use it as well, without delaying the loop. My guess is I'll have to do it on linux side, something like, if code == 0, wait for 5ms, if within those 5 ms you didn't get more zeros, print 0, if you did, print 'h'. Is it a sound way to do it?

ShapeShifter

More great progress! Thanks for keeping us up to date.

So, instead of going for a dictionary, I went for a dirty, manual method of if statements.
A dictionary would be a bit cleaner and perhaps easier to maintain, but I don't think it's necessarily faster unless it is performing a binary search behind the scenes (I don't know how it's implemented internally.) It would look something like this: (I've not tried to compile this, so it could have some silly mistakes)

Create the dictionary: (I didn't type in the whole mapping, I'll leave that to you  ;D )
Code: [Select]
# Define a dictionary to map scan codes to the character to be sent to the sketch
lookup = {71:'7', 72:'8', 73:'9', <and so on...>}


Use the dictionary:
Code: [Select]
   # If this is a key press, try to look it up in the dictionary. If there is a match, send the character
    if type == 1 and value == 1:            # Check for a normal key code
        if (code in lookup):                # Is the code contained in the dictionary?
            print lookup[ code ]            # Map the key code to a character and send to the sketch


The dictionary defines a list of key/value pairs. The value to the left of the colon character is the key. If the index value being used to access the dictionary matches a key, the result of the access is the value to the right of the colon.

If you do want to still use a series of IF statements, you should consider using if-else clauses. In your existing code, let's say the code is 75: It executes the first three IF statements, and they are all false. The fourth IF evaluates true, and you have your result. But then, it still has to go through and evaluate all of the remaining IF statements - you know there's no point to it since it can't match more than one statement, but the computer doesn't necessarily know that. It would be more efficient if you re-wrote it to look something like this:

Code: [Select]
    if code == 71:
        print '7'
    elif code == 72:
        print '8'
    elif code == 73:
        print '9'
    and so on...


The advantage here is that once a match has been found, the elif (else if) is an instruction to the computer that there is no need to check any other alternatives. So, for the case of 75, it will only do four comparisons instead of all of them. For 71, it will only do one. The only time it will do all comparisons is if the code is 98.

Quote
and-guess what- again I'm gonna use ifs for every case. I might try to find out how to setup an array(I guess?) to do this instead of 18 if statements(btw, are they more taxing for the processor?).
Using a lookup table is faster than a series of IF statements, because all it needs to do is multiply the index value by the size of an array element, and then add it to the starting address of the array. But that really only works if there is a relatively small range of values and there aren't a lot of gaps in the table. With a larger range of values or with large gaps, it gets less efficient. Still, it may be more efficient than a long a string of IF statements where each comparison needs to be performed.

If you go with a series of IF statements, using the same if/else scheme as above would be more efficient. (Note that with C++ as used in the sketch, the syntax of if/else is different than it is in Python.) Be aware that if you go this way, you are setting yourself up for trouble with the way you are reading a character from the Process and then testing it in the same statement. You will be in trouble if you add more IF statement of the same type, like this:

Code: [Select]
   while (p.available()){
     if ((char)p.read() == '1'){
       Serial.println("hello");}
     if ((char)p.read() == '2'){
       Serial.println("goodbye");}

      }


This will not work as you expect. What will happen is that you will check to see if at least one character is available in the first line. Then, in the second line you will read a character and compare it to '1', and print a string if it matches. Then, in the fourth line, you will attempt to read another character from the process and compare it to 2. If a second key hasn't yet been pressed, it will wait for one. I don't really know your application, but I'm positive this is not what you want. You need to perform only one read() call, save that in a variable, and then do all of your tests against that variable:

Code: [Select]
   while (p.available()){
     char ch = (char)p.read();
     if (ch == '1'){
       Serial.println("hello");}
     else if (ch == '2'){
       Serial.println("goodbye");}

      }


BTW, The Arduino IDE loves to put the opening brace on the same line, but I think it makes it harder to read. While it's a rather personal choice, and everyone has their own style, I think this is much easier to read and the structure is more apparent, even though it takes up more lines:

Code: [Select]
   while (p.available())
   {
      char ch = (char)p.read();

      if (ch == '1')
      {
         Serial.println("hello");
      }
      else if (ch == '2')
      {
         Serial.println("goodbye");
      }
   }


An alternative between the lookup and IF statements, and what I would be inclined to use here, is a switch statement. It works well when the value being tested is a simple value (like a char) and the values being compared against are constant values. Operationally, it's much like a series of if/else statements, but it's more concise:

Code: [Select]
   while (p.available())
   {
      char ch - (char)p.read();

      switch (ch)
      {
         case '1':
            Serial.println("hello");
            break;

         case '2':
            Serial.println("goodbye");
            break;
      }
   }


Now, because the ch variable is only evaluated once, you can go back to reading and evaluating it in one step, and not need the variable. And since the statements being executed in each case are so simple, it can be reformatted quite concisely:

Code: [Select]
   while (p.available())
   {
      switch ((char)p.read())
      {
         case '1':   Serial.println("hello");      break;
         case '2':   Serial.println("goodbye");    break;
      }
   }


There are so many ways to accomplish the same thing. That's one of the things I love about programming - it gives you a chance to think about the problem, and come up with an elegant solution. There is no single correct answer. (Although there are an infinite number of wrong answers!)

Quote
Is there any way to get values from numeric keys when numlock is off? Now I don't get anything nor could I find a way.
I'm not familiar with your keypad, so I can't say. I would go back to the original script you had that printed out the full information for each key code (the type, value, etc) and see if there is a pattern you can recognize and decode. It will take a little investigative and design work.

Quote
One last question. One of the keys in the numpad is a triple 0, that is, if pressed, it will send 3 zeros one after the other. I'd like to be able to use it as well, without delaying the loop. My guess is I'll have to do it on linux side, something like, if code == 0, wait for 5ms, if within those 5 ms you didn't get more zeros, print 0, if you did, print 'h'. Is it a sound way to do it?
Are you saying that when you press this key, the keypad sends the keycode sequence for '0' three times? If so, then your plan sounds like a reasonable one. Again, I would use the old version of the script that prints out all of the keystroke information, and see if there is anything in that sequence that would make it easier to recognize that situation.

petros00

Dictionary worked like a charm, didn't need to change anything, way cleaner and prettier! Good to know about elif too, I'm sure it will be useful in the near future.

My doh! moment was the switch since I already use it in a different part of the code, should have thought of it instead of the if statements! Out of curiosity I tried using only if and not else if, and of course you were right, it didn't work as I wanted, it would only display the first keypress and then nothing.

Numlock didn't lead anywhere, seems that this particular keypad doesn't send any characters when numlock is on, so I'm not using it.

All good in software side, only thing missing is implementing the 000 button, I believe I will figure it out if I study one of the numerous tutorials on push buttons, if not I will post the part I'm stuck for guidance.

Thanks!



Go Up