Generating Keyboard Events using USB Serial Commands

LabHackers’ MilliKey and USB2TTL8 devices can generate a USB Keyboard event after receiving a USB serial command from the software it is connected to.

This post explains the LabHackers’ KGEN command and illustrates how to use it in a simple Python script.

KGEN Serial Command

Both the MilliKey and USB2TTL8 support the KGEN serial command. The KGEN command tells the device to generate a key press event for the specified key and duration. The USB keyboard event is identical an event generated from an actual MilliKey button press – release.

Format

KGEN key duration [offset]\n

where:

  • key: The key to use for the event. A, B, C, …
  • duration: Number milliseconds between when device sends key press and release events.
  • offset: Optional number of microseconds device will wait before sending the key press event. 0 = send as soon as KGEN command is received / processed. Default is 0 usec.

For example, send the following serial message to your LabHackers’ device to have it generate a ‘z’ key press for 123 milliseconds:

KGEN Z 123\n

or, to press the Up arrow key for 300 milliseconds, 2.5 milliseconds after receiving the command:

KGEN UP 300 2500\n

Python Example

To keep this example as short as possible, it uses KGEN to generate keyboard events but does /not/ have code to collect them.

To ‘see’ the keyboard events generated from this example, start the example and immediately switch focus to a text editor window so that you can see the keyboard events in action.

#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import division, print_function

import serial
import time

# Serial Port of LabHackers' Device.
serial_address = 'COM138'
#serial_address = '/dev/cu.usbmodem3775821'

# press duration
press_duration = 250

# seconds to sleep between key press
iti = 0.5

# List of characters to use in KGEN.
key_list = "these key events are KGEN generated."

# Open USB Serial connection to LabHackers' device
try:
    s = serial.Serial(serial_address, baudrate=128000, timeout=0.1)
except serial.SerialException as e:
    print("Check SERIAL_PORT variable is correct for device being tested.")
    raise e

# Give user 5 seconds to switch to a text editor program.
time.sleep(5.0)

for key in key_list:
    # KGEN uses 'SPACE' to indicate ' ' key.
    if key == ' ':
        key = 'SPACE'

    # for each character in key_list, tell the LabHackers device to immidiately
    # generate a press event for that key lasting press_duration msec.
    s.write("KGEN {} {} {}\n".format(key, press_duration, 0).encode())

    # wait iti seconds before issueing next KGEN command
    time.sleep(iti)

# close serial connection
s.close()

In an upcoming post we will use the KGEN command to test keyboard event latency and event time stamping accuracy using PsychoPy.

Happy Hacking!

Share:

Detecting LabHackers’ Serial port addresses using Python

LabHackers’ MilliKey and USB2TTL8 devices have a USB Serial interface that is assigned a unique address by the operating system the first time the device is connected to a computer. The LabHackers’ Device Manager application can be used to view the serial port address assigned to a device.

However, the same LabHackers’ device will likely be assigned a different serial port address when it is connected to a different computer, so it is also useful to be able to identify LabHackers’ device serial port addresses from within your Python script.

Finding available Serial ports

If we want to connect to a LabHackers’ device USB Serial interface, but do not know the device’s serial port address, the first thing we need to do is find it.

Lets start by finding the serial port address for all serial devices connected to the computer.

import serial
import os

def get_serial_ports():
    """
    Return list of serial port addresses that have be openned.
    """
    if os.name == 'nt':  # Windows
        available = []
        for i in range(1, 512):
            try:
                sport = 'COM%d'%(i)
                s = serial.Serial(sport, baudrate=128000)
                available.append(sport)
                s.close()
            except (serial.SerialException, ValueError):
                pass
        return available
    else:  # macOS and Linux
        from serial.tools import list_ports
        return [port[0] for port in list_ports.comports()]

get_serial_ports() returns a list of all serial port addresses on the computer that have a device connected to them. For example:

serial_ports = get_serial_ports()
print(serial_ports)

run on a Windows computer with two connected serial ports, will return something like:

 ['COM2', 'COM9']

Note: get_serial_ports() returns all the serial devices connected to the computer.

Detecting LabHackers’ device Serial ports

Next we need to find which, if any, of the serial addresses are connected to a LabHackers’ MilliKey or USB2TTL8 device. Building on get_serial_ports() , we can get a list of the LabHackers’ ports by checking the responds of each serial port when “PING\n” is sent.

 def get_labhackers_ports():
    """
    Return list of connected LabHackers' device serial port addresses.
    """
    devices = []
    for p in get_serial_ports():
        s = serial.Serial(p, baudrate=128000, timeout=0.1)
        s.write(b"PING\n")
        rx = s.readline()
        if rx:
            rx = str(rx)
            if rx.find('MilliKey')>=0 or rx.find('USB2TTL8')>=0:
                devices.append(p)
        s.close()
    return devices

For example:

labhacker_ports = get_labhackers_ports()
print(labhacker_ports)

run on a Windows computer with one LabHackers’ device connected will return one serial port address:

['COM9']

Putting it together

Here is a complete version of the code.

#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import print_function

import serial
import os

def get_serial_ports():
    """
    Return list of serial port addresses that have be openned.
    """
    if os.name == 'nt':  # Windows
        available = []
        for i in range(1, 512):
            try:
                sport = 'COM%d'%(i)
                s = serial.Serial(sport, baudrate=128000)
                available.append(sport)
                s.close()
            except (serial.SerialException, ValueError):
                pass
        return available
    else:  # macOS and Linux
        from serial.tools import list_ports
        return [port[0] for port in list_ports.comports()]

def get_labhackers_ports():
    """
    Return list of connected LabHackers' device serial port addresses.
    """
    devices = []
    for p in get_serial_ports():
        s = serial.Serial(p, baudrate=128000, timeout=0.1)
        s.write(b"PING\n")
        rx = s.readline()
        if rx:
            rx = str(rx)
            if rx.find('MilliKey')>=0 or rx.find('USB2TTL8')>=0:
                devices.append(p)
        s.close()
    return devices

if __name__ == '__main__':
    print(get_labhackers_ports())

Happy Hacking!

Share: