Arduino Forum

Products => Arduino Yún => Topic started by: Techniker on Dec 28, 2013, 10:32 am

Title: Using USB Keyboard
Post by: Techniker on Dec 28, 2013, 10:32 am
I need to use a keyboard for the application I'm developing. The keyboard is used to be connected to the USB-Host. Later it will be replaced by a laser scanner.
So I plugged the keyboard in, got the PnP-Event:
Code: [Select]
[  139.540000] usb 1-1.1: new low-speed USB device number 4 using ehci-platform
[  139.670000] usb 1-1.1: New USB device found, idVendor=0a81, idProduct=0103
[  139.680000] usb 1-1.1: New USB device strings: Mfr=1, Product=2, SerialNumber=0
[  139.680000] usb 1-1.1: Product: USB Keyboard
[  139.680000] usb 1-1.1: Manufacturer: CHESEN

tried to hook the input events, but there is only /dev/input/event0 available. Shouldn't the USB device be listed there? I am not very familiar with linux. So I would be happy if someone would explain how to map the keyboard to linino so it can be used.
My second question would be whats the best way to forward the input events to my sketch? I can't use the serial terminal because it conflicts with my TFT...
Title: Re: Using USB Keyboard
Post by: sonnyyu on Dec 30, 2013, 06:37 pm
THe USB HID and HID RAW  support at trunk is broken.

HID RAW might needed for Laser Scanner, Magnetic Stripe Reader...
Title: Re: Using USB Keyboard
Post by: Gernby on Dec 31, 2013, 04:49 pm
Do you have an LCD connected to the serial pins on the Yun?  If so, doesn't that interfere with the bridge between the Arduino and Linino? 

I am trying to get an LCD working with my Yun, and think the best way to do it will be to connect it to the USB host on the Linino, then use the Bridge to communicate between the LCD and Arduino sketch.  I would think you should be able to do the same thing with the keyboard.
Title: Re: Using USB Keyboard
Post by: Techniker on Jan 03, 2014, 04:05 pm

Do you have an LCD connected to the serial pins on the Yun?  If so, doesn't that interfere with the bridge between the Arduino and Linino? 

It does indeed. But the problem with the USB HID is another. It seems that OpenWRT doesn't support low speed USB devices. I'll try to connect a PS/2 keyboard next, using the PS2Keyboard lib. I hope that a future patch for LININO will fix the USB problem.

I am trying to get an LCD working with my Yun, and think the best way to do it will be to connect it to the USB host on the Linino, then use the Bridge to communicate between the LCD and Arduino sketch.  I would think you should be able to do the same thing with the keyboard.


I had the same problem with my TFT. You can't use Pin 0 and 1:
http://forum.arduino.cc/index.php?topic=206923.0
Title: Re: Using USB Keyboard
Post by: federicofissore on Jan 04, 2014, 09:22 am

THe USB HID and HID RAW  support at trunk is broken.

HID RAW might needed for Laser Scanner, Magnetic Stripe Reader...


sonnyyu, do you have a link to share or it's your direct experience?
Title: Re: Using USB Keyboard
Post by: sonnyyu on Jan 05, 2014, 07:14 am


THe USB HID and HID RAW  support at trunk is broken.

HID RAW might needed for Laser Scanner, Magnetic Stripe Reader...


sonnyyu, do you have a link to share or it's your direct experience?


I compile new module of kmod-hid-generic and recompile kmod-hid with HID RAW  support.
Here are the links.

https://drive.google.com/file/d/0B49lSur2AThlQ1NnX1hzWFV6UEE/edit?usp=sharing (https://drive.google.com/file/d/0B49lSur2AThlQ1NnX1hzWFV6UEE/edit?usp=sharing)
https://drive.google.com/file/d/0B49lSur2AThleW04WHNqRDc2S0E/edit?usp=sharing (https://drive.google.com/file/d/0B49lSur2AThleW04WHNqRDc2S0E/edit?usp=sharing)

Code: [Select]
opkg update
opkg install kmod-input-core
opkg install  kmod-input-evdev
rm /tmp/opkg-lists/*; opkg install kmod-hid_3.8.3-1_ar71xx.ipk
opkg install kmod-hid-generic_3.8.3-1_ar71xx.ipk
opkg update
opkg install kmod-usb-hid


Code: [Select]
insmod  hid-generic
echo "hid-generic" >>/etc/modules.d/62-hid-generic
cat /dev/input/event1  | hexdump


Code: [Select]
0000000 52c8 f605 0002 118a 0004 0004 0007 0004
0000010 52c8 f605 0002 118a 0001 001e 0000 0001
0000020 52c8 f605 0002 118a 0000 0000 0000 0000
0000030 52c8 f605 0003 e636 0004 0004 0007 0004
0000040 52c8 f605 0003 e636 0001 001e 0000 0000
0000050 52c8 f605 0003 e636 0000 0000 0000 0000
0000060 52c8 f606 000c 140f 0004 0004 0007 0005
0000070 52c8 f606 000c 140f 0001 0030 0000 0001
0000080 52c8 f606 000c 140f 0000 0000 0000 0000
0000090 52c8 f606 000d 8b0e 0004 0004 0007 0005
00000a0 52c8 f606 000d 8b0e 0001 0030 0000 0000
00000b0 52c8 f606 000d 8b0e 0000 0000 0000 0000
00000c0 52c8 f608 000e 468b 0004 0004 0007 0006
00000d0 52c8 f608 000e 468b 0001 002e 0000 0001
00000e0 52c8 f608 000e 468b 0000 0000 0000 0000
00000f0 52c8 f609 0000 b9ca 0004 0004 0007 0006
0000100 52c8 f609 0000 b9ca 0001 002e 0000 0000
0000110 52c8 f609 0000 b9ca 0000 0000 0000 0000


Above is keyboard input "abc"
Title: Re: Using USB Keyboard
Post by: federicofissore on Jan 05, 2014, 09:51 am
I googled around but I couldn't find neither openwrt forum topics or anything else related. Do you have some links to share?
Title: Re: Using USB Keyboard
Post by: sonnyyu on Jan 05, 2014, 04:49 pm
I couldn't find neither at openwrt, I did hardway use up stream OS of Openwrt , at Debian I setup working case.
lsmod
Code: [Select]
hid_generic            12540  0
usbhid                 47346  0
hid                   105826  2 hid_generic,usbhid


Now I know I need hid_generic, then read through openwrt source and linino source code. fix the bug and recompile.



Title: Re: Using USB Keyboard
Post by: thomas-c on Jan 05, 2014, 05:17 pm
Thank you sonnyyu.  I was working towards compiling this and this was the info I had come across:
https://dev.openwrt.org/ticket/12631  (to add hid-generic - see patchwork url at the end)
https://dev.openwrt.org/ticket/11145  (to add hid raw)
http://h-wrt.com/en/doc/kb  (general usb keyboard info for openwrt)

Not sure if this is the correct way, but I was modifying these files:
~/linino/trunk/package/kernel/modules/001-depends.mk  (to add +kmod-hid-generic)
~/linino/trunk/package/kernel/modules/input.mk  (to add CONFIG_HIDRAW=y)

Thanks again.
Title: Re: Using USB Keyboard
Post by: snooops on Jan 20, 2014, 06:25 pm
Hi thomas-c is it possible for you to supply your new kernel/firmware here?
Title: Re: Using USB Keyboard
Post by: sonnyyu on Jan 20, 2014, 07:02 pm
I am second one need new kernel/firmware source as well.
Title: Re: Using USB Keyboard
Post by: thomas-c on Jan 22, 2014, 12:42 am
Thanks again sonnyyu for providing the kmod-hid-generic and kmod-hid module install packages above.  My USB keyboard (/dev/input/eventX) and HID RAW (/dev/hidrawX) devices are now visible for use.

Sorry, I only have access to the same public precompiled binaries/firmware that are located at download.linino.org where our Yun's web panel OPKG-Configuration (/etc/opkg.conf) is configured to point to.
Title: Re: Using USB Keyboard
Post by: Tunell on Jan 28, 2014, 09:37 pm
Anybody have any code to access the USB HID on the Yun for a keyboard?
Not sure how to expose the keystrokes to my sketch.
Title: Re: Using USB Keyboard
Post by: snooops on Jan 29, 2014, 10:38 am
Im compiling right now a new kernel with usb hid for /dev/input/eventx support. After its finished i post here the results.
Title: Re: Using USB Keyboard
Post by: snooops on Jan 29, 2014, 02:25 pm
Update:
im running to an error during compile i dont know how to fix, maybe someone can help:
Code: [Select]
/home/dennis/storage/kernelbuilder/linino/trunk/scripts/download.pl "/home/dennis/storage/kernelbuilder/linino/trunk/dl" "cpu-mcu-bridge-1.1.0.tar.bz2" "afa194cf16a3d2b1ca5180f630054ed2" "http://downloads.arduino.cc/"
--2014-01-21 14:43:48--  http://downloads.arduino.cc/cpu-mcu-bridge-1.1.0.tar.bz2
Resolving downloads.arduino.cc (downloads.arduino.cc)... 64.90.32.4, 2607:f298:4:143:acce:55:0:1
Connecting to downloads.arduino.cc (downloads.arduino.cc)|64.90.32.4|:80... connected.
HTTP request sent, awaiting response... 200 OK
Length: 10843 (11K) [application/x-tar]
Saving to: `STDOUT'

100%[=======================================================================================================>] 10,843      --.-K/s   in 0s      

2014-01-21 14:43:49 (69.3 MB/s) - written to stdout [10843/10843]

MD5 sum of the downloaded file does not match (file: 0b877889de3c28601a599f7222ebb1c3, requested: afa194cf16a3d2b1ca5180f630054ed2) - deleting download.
Title: Re: Using USB Keyboard
Post by: snooops on Jan 29, 2014, 02:35 pm
here is a reported issue ...
https://github.com/arduino/linino/issues/22
Title: Re: Using USB Keyboard
Post by: Tunell on Feb 28, 2014, 12:11 am
For anybody who's a noob like me, I've outlined the exact steps of what to do to get the keystrokes to echo properly

1) Download sonnyyu's custom .ipk files on SD card in \arduino\www
https://drive.google.com/file/d/0B49lSur2AThlQ1NnX1hzWFV6UEE/edit?usp=sharing
https://drive.google.com/file/d/0B49lSur2AThleW04WHNqRDc2S0E/edit?usp=sharing

2) Do general package updates
Code: [Select]

  opkg update
  opkg install kmod-input-core
  opkg install kmod-input-evdev


3) Install USB updates kmod-hid***.ipk from your sd card
Code: [Select]

  opkg install /mnt/sda1/arduino/www/kmod-hid_3.8.3-1_ar71xx.ipk
  opkg install /mnt/sda1/arduino/www/kmod-hid-generic_3.8.3-1_ar71xx.ipk


4) Install necessary HID driver  
Code: [Select]

  opkg update
  opkg install kmod-usb-hid


5) Put hid in kernel
Code: [Select]

  insmod  hid-generic
  echo "hid-generic" >>/etc/modules.d/62-hid-generic


6) See the data on the terminal
Code: [Select]

  cat /dev/input/event1  | hexdump     #continuous dumping of raw (useless) hex data
 

OR
Code: [Select]

  hexdump -n 96 /dev/input/event1 | head -n 2 | cut -d' ' -f9  #single keystroke's code and value


7) write a script to continuously monitor and decode /dev/input/event1 stream and echo the characters to the bridge
(still in the works. I'm still very new at this)

here's a link for the USB keycode to ascii lookup:
https://chromium.googlesource.com/chromium/chromium/+/master/ui/base/keycodes/usb_keycode_map.h
Title: Re: Using USB Keyboard
Post by: sonnyyu on Feb 28, 2014, 01:00 am
Nice!
Title: Re: Using USB Keyboard
Post by: Tunell on Mar 18, 2014, 09:40 pm

Nice!


Sonnyyu, do you know the best way to have the actual characters dump into the console rather than the keycodes? I've written a script that listens to the events, parses them, and translates them, but it is not looping fast enough to keep up with user input. Keys are being dropped and I'm having to manage the shift and control keys manually. This can't be right...

It seems like there should be a simple solution to this. I'm not super-familiar with linux, but I have a feeling that it is a simple answer that I am just not aware of.
Title: Re: Using USB Keyboard
Post by: sonnyyu on Mar 19, 2014, 08:10 pm
Plan A: C's evtest
At evtest is include evtest-capture,

Code: [Select]
evtest-capture "/dev/input/eventX" [evtest-capture.xml]

evtest-capture captures the information and events from the input device specified on the command line and writes it to the xml file given. If no filename is given for the output file, evtest-capture.xml is chosen as default.

Cross compile is needed.


Plan B: Python evdev (c-api)

http://packages.python.org/evdev/ (http://packages.python.org/evdev/)
Even name is python but it is c-api python, Cross compile is needed.
Title: Re: Using USB Keyboard
Post by: sonnyyu on Mar 19, 2014, 08:16 pm
Plan C: Python evdev (ctypes)

Micah Dowty wrote very nice pure python evdev.

Install hid driver:

Code: [Select]
opkg update
opkg install kmod-input-core
opkg install     kmod-input-evdev
opkg install wget
cd /tmp
wget https://www.dropbox.com/s/whnf5huyk39krqx/kmod-hid_3.8.3-1_ar71xx.ipk --no-check-certificate
rm /tmp/opkg-lists/*; opkg install kmod-hid_3.8.3-1_ar71xx.ipk
wget https://www.dropbox.com/s/2yegmen4rdy6uwm/kmod-hid-generic_3.8.3-1_ar71xx.ipk --no-check-certificate
opkg install kmod-hid-generic_3.8.3-1_ar71xx.ipk
opkg update
opkg install kmod-usb-hid


Code: [Select]
insmod  hid-generic
echo "hid-generic" >>/etc/modules.d/62-hid-generic


test keyboard driver:  

Code: [Select]
cat /dev/input/event1  | hexdump

download Micah's code ( I clean a little):

Code: [Select]
cd /mnt/sda1
wget https://www.dropbox.com/s/uwy1d0lgawljf3b/evdev.py --no-check-certificate
chmod 755 evdev.py


Test code:

Code: [Select]
./evdev.py '/dev/input/event1'
<Event timestamp=1395255517.851952 type='EV_MSC' code=4 value=458756>
<Event timestamp=1395255517.851952 type='EV_KEY' code='KEY_A' value=1>
30
Title: Re: Using USB Keyboard
Post by: sonnyyu on Mar 19, 2014, 08:21 pm
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)
Title: Re: Using USB Keyboard
Post by: Tunell on Mar 20, 2014, 07:19 pm
nice, I'll have to see what I can do with this!
Title: Re: Using USB Keyboard
Post by: tdicola on Mar 23, 2014, 02:56 am

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)



Thanks for these steps--unfortunately I'm hitting this error when attempting to install the custom kmod-hid:

Code: [Select]

root@Arduino:/tmp# opkg install kmod-hid_3.8.3-1_ar71xx.ipk
Installing kmod-hid (3.8.3-1) to root...
Collected errors:
* satisfy_dependencies_for: Cannot satisfy the following dependencies for kmod-hid:
* kernel (= 3.8.3-1-cf85e4dc55fb28677fb803cf0f9ad60a) *
* opkg_install_cmd: Cannot install package kmod-hid.
root@Arduino:/tmp#


Any idea what might be wrong?  I had just updated my linino OS before finding this thread in the hope that it would fix access to keyboards / mice (it didn't).  Could I have a newer kernel that isn't compatible?

edit: Actually, running uname -r it looks like my kernel is version 3.3.8--that seems kind of weird for it to be lower.  I installed the latest linino image using the steps here: http://linino.org/doku.php?id=wiki:upgradeimage  Are those wrong and did I actually downgrade?  Any pointer on how to get version 3.8.3 ?

edit2: Just reverted to the 1.0 image from http://arduino.cc/en/Tutorial/YunSysupgrade#.Uy5FdnWx2OW and now uname -r returns 3.8.3, phew.  I have no idea what that linino.org page is pointing to but strongly suggest everyone just forget it exists because it seems quite out of date.
Title: Re: Using USB Keyboard
Post by: tdicola on Mar 23, 2014, 03:47 am
Got everything working with sunnyyu's instructions.  Can access the events generated from a mouse attached to the Yun.  Thanks again for posting those steps!

What needs to be done to get this fix into the next version of Linino?  This would really be great to resolve as it's quite an interesting scenario to attach USB HID devices to the Yun and use them in sketches through some bridge communication.
Title: Re: Using USB Keyboard
Post by: sonnyyu on Mar 23, 2014, 04:25 pm

...
edit: Actually, running uname -r it looks like my kernel is version 3.3.8--that seems kind of weird for it to be lower.  I installed the latest linino image using the steps here: http://linino.org/doku.php?id=wiki:upgradeimage  Are those wrong and did I actually downgrade?  Any pointer on how to get version 3.8.3 ?

edit2: Just reverted to the 1.0 image from http://arduino.cc/en/Tutorial/YunSysupgrade#.Uy5FdnWx2OW and now uname -r returns 3.8.3, phew.  I have no idea what that linino.org page is pointing to but strongly suggest everyone just forget it exists because it seems quite out of date.


Thanks a lot for sharing.  ( sunnyyu is typo, it is sonnyyu)
Title: Re: Using USB Keyboard
Post by: federicofissore on Apr 23, 2014, 10:13 pm
USB keyboard support should be fixed in the latest image we just published. See http://forum.arduino.cc/index.php?topic=235360.0
Title: Re: Using USB Keyboard
Post by: petros00 on May 24, 2017, 08:32 pm
Sorry to resurrect such an old thread, but I'm struggling to send keyboard strokes from the linen side to my sketch and can't figure out what to do. I'm running the python script that sonnyyu posted, but then what should I run on the arduino side in order to read the keystrokes?

Thanks
Title: Re: Using USB Keyboard
Post by: ShapeShifter on May 25, 2017, 01:49 pm
I've not specifically tried to talk to a USB keyboard, but I have used a USB scanner that acts like a keyboard. The code should be the same.

My approach was to write a Python script (with a lot of input from sonnyyu) that I could run on the Linux side. This script reads the keyboard keycodes, translates them into ASCII characters, and prints out those characters as they were typed. I'd test it by running that script in a Linux SSH shell, and verify that it displayed the data as expected. The output from the script should be the minimum amount of data to represent what's going on, for example it should just output the letter typed and not a full debug string like "The letter typed is X"

With that working on the Linux side, I then moved on to the sketch. In there, I use a Process object to start the Python script on the Linux side. That automatically starts the Python code, and any output it generates can be read from the Process object.

When setting up the Process object, you want it to be defined up at the file level so it never gets destroyed (don't define it inside loop().) In setup(), start the Process to run asynchronously. Make sure to include "-u" in the Python command line that starts the script, or the output will be buffered and you won't see anything until a large amount of data has been typed, and then you will see it all at once. With the Process started in setup(), you can then look for output from the Process in loop() using Process.available() to see if there is data to read.

I usually take it one step further, and inside of loop I use Process.running() to see if the script is still running. If that returns false, the Python script has crashed, so I restart it with another runAsynchronously() call.
Title: Re: Using USB Keyboard
Post by: petros00 on May 25, 2017, 02:05 pm
I'm using sonnyyu's version of Micah's code(found further up in this thread) which when run in ssh produces this type of results if keys are pressed:

<Event timestamp=1495713324.5426631 type='EV_RST' code=0 value=0>
<Event timestamp=1495713324.662632 type='EV_MSC' code=4 value=458850>
<Event timestamp=1495713324.6626431 type='EV_KEY' code='KEY_KP0' value=0>
<Event timestamp=1495713324.6626478 type='EV_RST' code=0 value=0>
<Event timestamp=1495713324.6866268 type='EV_MSC' code=4 value=458835>
<Event timestamp=1495713324.6866381 type='EV_KEY' code='KEY_NUMLOCK' value=1>

I'm very curious to see if only the values get passed to arduino or the whole lines.

In order to get the script going, I do this: python /mnt/sda1/evdev.py '/dev/input/event1'

On the arduino side, should it be the same, e.g., this?

p.begin("/mnt/sda1/evdev.py '/dev/input/event1'");
p.runAsynchronously();

In this case, where do I put the -u? I tried putting it after event1' but I still see nothing in serial monitor. The code I use on 32u4 side is this:

Code: [Select]
#include "Bridge.h"
#include "Process.h"

Process p;

void setup() {
  Serial.begin(19200);
  Bridge.begin();
  
  p.begin("/mnt/sda1/evdev.py '/dev/input/event1' -u");
  p.runAsynchronously();
}

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




Also, sonny suggests using this code to get the data over to 32u4, where does it need to go?

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)


Sorry for the many questions, I have zero knowledge on programming, learning a bit as I go...
Title: Re: Using USB Keyboard
Post by: ShapeShifter on May 25, 2017, 02:32 pm
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:


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.
Title: Re: Using USB Keyboard
Post by: ShapeShifter on May 25, 2017, 02:39 pm
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.
Title: Re: Using USB Keyboard
Post by: ShapeShifter on May 25, 2017, 03:01 pm
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.
Title: Re: Using USB Keyboard
Post by: petros00 on May 25, 2017, 04:57 pm
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!
Title: Re: Using USB Keyboard
Post by: ShapeShifter on May 25, 2017, 05:09 pm
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.
Title: Re: Using USB Keyboard
Post by: petros00 on May 25, 2017, 08:18 pm
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!
Title: Re: Using USB Keyboard
Post by: ShapeShifter on May 25, 2017, 09:35 pm
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:

To get a better understanding of the key codes, the USB HID spec >LINK< (http://www.usb.org/developers/hidpage/Hut1_12v2.pdf) 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]
Title: Re: Using USB Keyboard
Post by: petros00 on May 26, 2017, 09:00 am
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?
Title: Re: Using USB Keyboard
Post by: ShapeShifter on May 26, 2017, 03:29 pm
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.
Title: Re: Using USB Keyboard
Post by: petros00 on May 30, 2017, 02:13 am
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!