USB Keyboard issue

Attached python code.

evdev.doc (59 KB)

Mr.Sonnyyu and Mr.Shapeshifter, last time you both helped a lot for me to establish USB Keyboard communication with Arduino Yun.

Kindly go through this and pls share your ideas to solve this.

Thanks.

@sridevi,
in your sketch you have the serial line set to 9600

void setup()
{
  mySerial.begin(9600); // begin Serial
:::

This is not very fast. Your LCD may NOT be able to work faster. If so, you must go slower or buffer your keyboard (in the python or the sketch). Try that, if you can.

Jesse

If you run the python code from a ssh shell, do you lose any character?

sridevi:
Mr.Sonnyyu and Mr.Shapeshifter, last time you both helped a lot for me to establish USB Keyboard communication with Arduino Yun.

Kindly go through this and pls share your ideas to solve this.

I saw this thread yesterday, but I'm sorry that I don't think I have any help to offer, that's why I didn't respond.

It's not really my area of expertise, but I'm willing to take a look at the Python code. But I'm not willing to download a .doc file from an unknown source. If you post the python code again as a straight text file, I'll take a look at it.

jessemonroy650:
in your sketch you have the serial line set to 9600

No, that's not very fast, but it should be fast enough to echo typed characters. Even the fastest typist in the world types at less than 20 characters per second. Being able to only send 960 characters per second to the screen should not be the limiting factor.


I'm more concerned about the 10ms delay at the end of loop(). That limits the loop rate to no more than 100 times per second. The actual loop repetition rate will be less, since all of the other code in the loop takes some time. But even if the loop rate were much slower, that is made up by the while (p.available() > 0) loop that will take all available characters in one shot. So, the delay() statement and loop processing overhead may add some latency, but it shouldn't directly cause any dropped characters since the Process class does do some buffering of the data stream. I don't think the sketch loop speed is the issue.


sridevi:
for num in range(1,500):
print '*'

  1. Actually I do not need these two lines for my task. But without these lines program did not respond me.

This sounds to me like a buffering issue. To increase throughput and reduce overhead, there is likely some buffering going on (by Python, by the process pipe, by the Bridge communications class, or...) Instead of sending each character one at a time, they are likely being buffered up until a sufficient number of characters are queued up, then they are all sent at once. This gives a higher overall throughput since the communications overhead doesn't need to be applied to each character, just each block of characters.

By putting a bunch of "*" characters in the stream (and ignoring them in the sketch) you are filling up the buffer enough that it is being sent. But of course, you are adding a lot of overhead that then limits your bandwidth.

Two possible solutions are to disable that buffering scheme, or to explicitly flush the output to force it to be sent earlier. Unfortunately, I don't know how to do either in this environment. (But maybe this statement will trigger an idea from someone else?)

Did you try putting a carriage return or newline character after the keystroke? Sometimes, that will flush a buffer early.

Hmmm... now that I've thought it through, and responded, maybe something here is helpful after all?

Install Python/C evdev module

http://forum.arduino.cc/index.php?topic=305926.msg2148554#msg2148554

cd /usr/lib/python2.7/
wget https://www.dropbox.com/s/n6pafjual1dfcmg/evdev.tar.gz?dl=0 -O evdev.tar.gz --no-check-certificate
tar -zxvf evdev.tar.gz
rm evdev.tar.gz
nano /mnt/sda1/keyinput.py
#!/usr/bin/python
from evdev import InputDevice, categorize, ecodes  # import * is evil :)
dev = InputDevice('/dev/input/event1')

# Provided as an example taken from my own keyboard attached to a Centos 6 box:
scancodes = {
    # Scancode: ASCIICode
    0: None, 1: u'ESC', 2: u'1', 3: u'2', 4: u'3', 5: u'4', 6: u'5', 7: u'6', 8: u'7', 9: u'8',
    10: u'9', 11: u'0', 12: u'-', 13: u'=', 14: u'BKSP', 15: u'TAB', 16: u'q', 17: u'w', 18: u'e', 19: u'r',
    20: u't', 21: u'y', 22: u'u', 23: u'i', 24: u'o', 25: u'p', 26: u'[', 27: u']', 28: u'CRLF', 29: u'LCTRL',
    30: u'a', 31: u's', 32: u'd', 33: u'f', 34: u'g', 35: u'h', 36: u'j', 37: u'k', 38: u'l', 39: u';',
    40: u'"', 41: u'`', 42: u'LSHFT', 43: u'\\', 44: u'z', 45: u'x', 46: u'c', 47: u'v', 48: u'b', 49: u'n',
    50: u'm', 51: u',', 52: u'.', 53: u'/', 54: u'RSHFT', 56: u'LALT', 100: u'RALT'
}

capscodes = {
    0: None, 1: u'ESC', 2: u'!', 3: u'@', 4: u'#', 5: u'
chmod 755 /mnt/sda1/keyinput.py
/mnt/sda1/keyinput.py

Start typing test., 6: u'%', 7: u'^', 8: u'&', 9: u'*',
   10: u'(', 11: u')', 12: u'_', 13: u'+', 14: u'BKSP', 15: u'TAB', 16: u'Q', 17: u'W', 18: u'E', 19: u'R',
   20: u'T', 21: u'Y', 22: u'U', 23: u'I', 24: u'O', 25: u'P', 26: u'{', 27: u'}', 28: u'CRLF', 29: u'LCTRL',
   30: u'A', 31: u'S', 32: u'D', 33: u'F', 34: u'G', 35: u'H', 36: u'J', 37: u'K', 38: u'L', 39: u':',
   40: u''', 41: u'~', 42: u'LSHFT', 43: u'|', 44: u'Z', 45: u'X', 46: u'C', 47: u'V', 48: u'B', 49: u'N',
   50: u'M', 51: u'<', 52: u'>', 53: u'?', 54: u'RSHFT', 56: u'LALT', 100: u'RALT'
}
#setup vars
x = ''
caps = False

#grab that shit
dev.grab()

#loop
for event in dev.read_loop():
   if event.type == ecodes.EV_KEY:
       data = categorize(event)  # Save the event temporarily to introspect it
       if data.scancode == 42:
           if data.keystate == 1:
               caps = True
           if data.keystate == 0:
               caps = False
       if data.keystate == 1:  # Down events only
           if caps:
               key_lookup = u'{}'.format(capscodes.get(data.scancode)) or u'UNKNOWN:[{}]'.format(data.scancode)  # Lookup or return UNKNOWN:XX
           else:
               key_lookup = u'{}'.format(scancodes.get(data.scancode)) or u'UNKNOWN:[{}]'.format(data.scancode)  # Lookup or return UNKNOWN:XX
           if (data.scancode != 42) and (data.scancode != 28):
               x += key_lookup  # Print it all out!
               print  key_lookup
           if(data.scancode == 28):
               print x
               x = ''
[/code]

§_DISCOURSE_HOISTED_CODE_5_§
§_DISCOURSE_HOISTED_CODE_6_§

Start typing test.

Software requirements for Yun:

opkg update
opkg install kmod-input-core
opkg install kmod-input-evde
opkg install kmod-usb-hid

Hardware requirements for Yun:

Powered USB hub.

Testing:

cat /dev/input/event1  | hexdump
0000000 550b b360 000e 8ae8 0004 0004 0007 000b
0000010 550b b360 000e 8af3 0001 0023 0000 0001
0000020 550b b360 000e 8afa 0000 0000 0000 0000
0000030 550b b361 0000 8126 0004 0004 0007 0016

ATmega32u4 code:

#include <Bridge.h>
char data[2];
void setup() {
  Bridge.begin();
  Serial.begin(9600);
  while (!Serial); // wait for a serial connection
}
void loop() {
  Bridge.get("key", data, 2); //get latest parsed data
  Serial.print(data);
  delay(100);
}

Bridge code:

...
import sys    
sys.path.insert(0, '/usr/lib/python2.7/bridge/')                                              
from bridgeclient import BridgeClient as bridgeclient                                                    
value = bridgeclient() 
...
... 
          print key_lookup
          value.put('key', key_lookup)
          value.put('key', '')
          if(data.scancode == 28):
          ...

Regarding the last two posts: using Bridge.put/Bridge.get is a valid way of passing data from Python to the sketch, and should work around the buffering issue with sending data back through the Process object. But I don't feel it's a very good solution for this particular task.

A potential issue is timing and latency: it can probably keep up with average typing rates, but my gut feeling is that it's getting close enough that it may be a concern. But the biggest issue I have with it is the way that it works like a shared variable: there is no notification that the other side has written to that key. The only thing that the sketch will see is that the value has changed. (The posted example sketch doesn't do that, it will just keep displaying the last key typed, even if no more keys are typed.) If the typist types the same key twice, the sketch will have no way of knowing that.

I've used this method when passing strings read from a barcode reader. In my sketch, it continually reads the key, and looks for a non-blank value. If found, the received string is processed and Bridge.put() is called to clear that value. This both lets the sketch know that the value has already been processed, and it lets the sketch detect the same value twice in a row (because it will always be different from the blank empty string.)

But the problem with that here is that writing that blank value adds more time to the overall detection process, and in the case of fast typing could overwrite an incoming keystroke before it is read (if that keystroke happens to come in during that window of time after reading the previous keystroke and clearing it.) In my barcode reader sketch, that wasn't really a problem, since the reader can't process bar codes faster about once per second. But a typist can type faster than that, and it might become an issue.

In my mind, the first thing to try is to figure out a way to disable the buffering between Python and the Process object, or figure out a way to flush that stream. I think that falling back to bridge put/get is a "Plan B" scenario.

Speed is definitely issue, since bridge only support max speed at 250ms. Typing faster then code might break.

The work around solution A:

if(data.scancode == 28):
                print x
                value.put('key', x)
                x = ''

send complete string until return key presss.

The work around solution B:
Wrote buffering at code, smoothing the peak.

Work solution (Hardware):

Bypass ATmega32u4 as well as Bridge, use lcd2usb LCD module connect directly to USB port.

I think I have a very simple solution.

Going back to the sketch and Python code referenced in the original post, edit the Python script and:

  • Near the top of the script add [color=red]import sys[/color]
  • After printing the keystroke, replace the problematic loop that prints 1500 asterisks with [color=red]sys.stdout.flush()[/color]

That should be it. Now, instead of waiting for the output buffer to fill up before it gets sent to the Process object in the sketch, the output is flushed so it is sent right away.

I tested it with this Python script:

#!/usr/bin/python

import time
import sys

while 1:
  for i in range(10):
    print i,
    sys.stdout.flush()
    time.sleep(0.1)

The idea is that it (slowly, because of the sleep) prints out a series of digits. With the buffering, it would take a VERY long time before the sketch saw anything, and then it would display a large amount of data at once.

With the statement to flush the output, the sketch sees the output right away.

This is the corresponding sketch, which simply launches the Python code and echos everything to the serial port:

#include "Bridge.h"
#include "Process.h"

Process p;

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

void loop() {
  while (p.available() )
    Serial.print((char)p.read());
}

ShapeShifter:
This sounds to me like a buffering issue. To increase throughput and reduce overhead, there is likely some buffering going on (by Python, by the process pipe, by the Bridge communications class, or...)

BTW, it looks like the buffering is being done by Python and is not part of the Bridge or the Process class. Or more correctly, Python has opened stdout in buffered mode. I say this because running the above script directly from the command line outputs immediately with the flush statement in place, and takes a very long time before it blasts out a lot of data if the flush statement is not there.

sys.stdout.flush()

seem helpful

Python code:

print key_lookup
          value.put('key', key_lookup)
          value.put('key', '')
          sys.stdout.flush()
          if(data.scancode == 28):

ATmega32u4 code:

void loop() {
  Bridge.get("key", data, 2); //get latest parsed data
  Serial.print(data);
  delay(200);
}

I'm taking another look at what you just posted, and posted earlier. I guess I didn't read it closely enough the first time.

sonnyyu:

          print key_lookup

value.put('key', key_lookup)
         value.put('key', '')
         sys.stdout.flush()
         if(data.scancode == 28):

I'm confused by this. You're printing the key_lookup value the stdout, and now flushing it, but your sketch code is not looking for incoming characters from the process object.

And I just noticed that the Python code puts the key_lookup value, and then immediately clears it out by using a blank value. How is the sketch supposed to see the key_lookup value? It would have to make the corresponding Bridge.get() call in that small window of opportunity between the two puts on the Python side, otherwise wouldn't it just see nothing?

When I used a similar manner of communications for my barcode scanner, the Python code put the string to the Bridge, and then when the sketch read the value it was the sketch that put the blank value to the key as a way of acknowledging the data. This made it easy for the sketch to detect new data, even if it was the same data twice in a row.

I don't see how the code posted immediately above would work. What is your intention with it?

remove

#!/usr/bin/python

and

sys.stdout.flush()

start with

python -u keyinput.py

disabled script output buffering.

value.put('key', '')

send out terminator for per character typing.

Hmmm... I just made what I thought was a useful discovery, and I came back to post it in this thread which was very similar. And now I re-read Sonny's post which I didn't understand last week, and I see that it's pretty much the same thing. :frowning:

I guess I shouldn't be surprised that sonnyyu was a step or three ahead of me. :wink:

But, at least, there is one small saving grace for my pride in that I think my idea is slightly simpler. The idea is to run the Python script with the "-u" option to run it in ubuffered mode, which eliminates the need to explicitly flush stdout. But that means you have to launch it with a special command line to include the "-u" option.

sonnyyu suggests to get rid of the hash-bang at the beginning of the script, but if it is kept and the "-u" option is added there, the unbuffered mode will automatically be always applied to the script, even if it is run by just typing the name of the script.

No other changes are needed to the script or the way it is called. Just change the first line this:

#!/usr/bin/python -u

See my other post for details.

Even though I installed evade correctly, when I try to run the code I get this error:

from evdev import InputDevice, categorize, ecodes # import * is evil :slight_smile:
ImportError: cannot import name InputDevice

Any idea how to proceed?