Lua USB HID
Note: Due to the wide variety of USB devices available, this functionality should be considered use at your own risk. QSC cannot guarantee compatibility with all USB HID peripherals.
Q-SYS provides two built-in Lua libraries for receiving input events from USB HID (Human Interface Device) peripherals connected to the USB-A ports on a Q-SYS Core processor:
-
UsbMonitor — Detects when USB HID devices are plugged in or removed.
-
Usb — Receives input events (key presses, axis movements, button clicks, etc.) from individual USB devices.
The UsbMonitor library monitors USB HID device connections and disconnections on the Core.
This will instantiate a new instance.
monitor = UsbMonitor.New()
This function is called when a USB Peripheral is added or removed from the core.
.EventHandler = function(device)
| Callback Parameter - Device Table | ||
| Property | Type | Description |
| devnode | string | Device node path (e.g., /dev/input/event4) |
| Added | boolean | true if the device was added, false if removed |
| sysname | string | System name (e.g., event4) |
| syspath | string | Full system path |
| subsystem | string | Subsystem identifier (typically input) |
| devpath | string | Device path in the system hierarchy |
| devtype | string | Device type (often empty for HID devices) |
| sysnum | string | System number |
| properties | table | Key-value table of device properties |
| tags | table | Device tags |
| Common Properties Fields | ||
| Key | Description | Example |
| ID_VENDOR | Vendor name | "DragonRise_Inc." |
| ID_VENDOR_ID | Vendor ID (hex) | "13ba" |
| ID_MODEL | Model name | "Generic_USB_Joystick" |
| ID_MODEL_ID | Model ID (hex) | "0001" |
| ID_SERIAL | Device serial identifier | "DragonRise_Inc._Generic_USB_Joystick" |
| ID_TYPE | Device type | "hid" |
| ID_BUS | Bus type | "usb" |
| ID_INPUT_KEYBOARD | "1" if the device is a keyboard | |
| ID_INPUT_JOYSTICK | "1" if the device is a joystick | |
| ID_INPUT_MOUSE | "1" if the device is a mouse | |
| ID_INPUT_TOUCHSCREEN | "1" if the device is a touchscreen | |
| ID_USB_DRIVER | USB driver name | "usbhid" |
| DEVNAME | Device name path | "/dev/input/event4" |
| DEVLINKS | Stable symlink paths | "/dev/input/by-id/usb-...-event-joystick" |
This Lua library deals with individual USB Devices. This is like TCP sockets that are part of a TcpSocketServer.
This will instantiate a new instance.
UsbDevice = Usb.New(dev.devnode)
| Type | Description |
| string | The device node path. Must begin with /dev/input/event (for HID devices) or /dev/tty (for serial-over-USB devices). |
This is a look-up table for USB Event types. The Names are a string such as KEY and the Value will be an integer such as 1.
https://www.kernel.org/doc/Documentation/input/event-codes.txt
| Name | Value | Description |
| SYN | 0 | Synchronization event |
| KEY | 1 | Key or button event |
| REL | 2 | Relative axis event |
| ABS | 3 | Absolute axis event |
| MSC | 4 | Miscellaneous event |
| LED | 17 | LED event |
| SND | 18 | Sound event |
| REP | 20 | Auto-repeat event |
| FF | 21 | Force feedback command |
| PWR | 22 | Power button/switch event |
| FF_STATUS | 23 | Force feedback status |
Example Code
You can look-up a code by using:
local code = Usb.Key.KEY_BACKSPACE
or you can look-up a key name:
function GetType(typeCode)
for k,v in pairs(Usb.Type) do
if typeCode == v then
return(k)
end
end
return nil
end
A look-up table for Sync Event codes.
| Name | Value | Description |
| SYN | 0 | Synchronization event |
| KEY | 1 | Key or button event |
| REL | 2 | Relative axis event |
| ABS | 3 | Absolute axis event |
A look-up table for Relative Event codes.
Content
{
"X": 0,
"Y": 1,
"Z": 2,
"RX": 3,
"RY": 4,
"RZ": 5,
"HWHEEL": 6,
"DIAL": 7,
"WHEEL": 8,
"MISC": 9
}
A look-up table for Absolute Event codes.
Content
{
"X": 0,
"Y": 1,
"Z": 2,
"RX": 3,
"RY": 4,
"RZ": 5,
"THROTTLE": 6,
"RUDDER": 7,
"WHEEL": 8,
"GAS": 9,
"BRAKE": 10,
"HAT0X": 16,
"HAT0Y": 17,
"HAT1X": 18,
"HAT1Y": 19,
"HAT2X": 20,
"HAT2Y": 21,
"HAT3X": 22,
"HAT3Y": 23,
"PRESSURE": 24,
"DISTANCE": 25,
"TILT_X": 26,
"TILT_Y": 27,
"TOOL_WIDTH": 28,
"VOLUME": 32,
"MISC": 40,
"MT_SLOT": 47,
"MT_TOUCH_MAJOR": 48,
"MT_TOUCH_MINOR": 49,
"MT_WIDTH_MAJOR": 50,
"MT_WIDTH_MINOR": 51,
"MT_ORIENTATION": 52,
"MT_POSITION_X": 53,
"MT_POSITION_Y": 54,
"MT_TOOL_TYPE": 55,
"MT_BLOB_ID": 56,
"MT_TRACKING_ID": 57,
"MT_PRESSURE": 58,
"MT_DISTANCE": 59,
"MT_TOOL_X": 60,
"MT_TOOL_Y": 61
}
Note: RFKILL_ALL and RADIO are the same. This is dictated by Linux input-event-codes.h: 
Content
{
"LID": 0,
"TABLET_MODE": 1,
"HEADPHONE_INSERT": 2,
"RFKILL_ALL": 3,
"RADIO": 3,
"MICROPHONE_INSERT": 4,
"DOCK": 5,
"LINEOUT_INSERT": 6,
"JACK_PHYSICAL_INSERT": 7,
"VIDEOOUT_INSERT": 8,
"CAMERA_LENS_COVER": 9,
"KEYPAD_SLIDE": 10,
"FRONT_PROXIMITY": 11,
"ROTATE_LOCK": 12,
"LINEIN_INSERT": 13
}
-
USB Devices should be either globally scoped or assigned to a globally scoped table to avoid issues with garbage collection.
-
The Usb.EventHandler can be called with no data, check if the parameter is nil

file
local UsbHelpers = {}
UsbHelpers.GetType = function(code)
for k, v in pairs(Usb.Type) do
if code == v then
return (k)
end
end
return nil
end
UsbHelpers.GetSync = function(code)
for k, v in pairs(Usb.Sync) do
if code == v then
return (k)
end
end
return nil
end
UsbHelpers.GetRelative = function(code)
for k, v in pairs(Usb.Relative) do
if code == v then
return (k)
end
end
return nil
end
UsbHelpers.GetAbsolute = function(code)
for k, v in pairs(Usb.Absolute) do
if code == v then
return (k)
end
end
return nil
end
UsbHelpers.GetSwitch = function(code)
for k, v in pairs(Usb.Switch) do
if code == v then
return (k)
end
end
return nil
end
UsbHelpers.GetMisc = function(code)
for k, v in pairs(Usb.Misc) do
if code == v then
return (k)
end
end
return nil
end
UsbHelpers.GetLed = function(code)
for k, v in pairs(Usb.Led) do
if code == v then
return (k)
end
end
return nil
end
UsbHelpers.GetRepeat = function(code)
for k, v in pairs(Usb.Repeat) do
if code == v then
return (k)
end
end
return nil
end
UsbHelpers.GetSound = function(code)
for k, v in pairs(Usb.Sound) do
if code == v then
return (k)
end
end
return nil
end
UsbHelpers.GetForceFeedback = function(code)
for k, v in pairs(Usb.FF) do
if code == v then
return (k)
end
end
return nil
end
UsbHelpers.GetKey = function(code)
for k, v in pairs(Usb.Key) do
if code == v then
return (k)
end
end
return nil
end
UsbHelpers.GetButton = function(code)
for k, v in pairs(Usb.Button) do
if code == v then
return (k)
end
end
return nil
end
UsbHelpers.GetKeyButton = function(code)
for k, v in pairs(Usb.Key) do
if code == v then
return (k)
end
end
for k, v in pairs(Usb.Button) do
if code == v then
return (k)
end
end
return nil
end
UsbHelpers.Actions = {
[0] = "Released",
[1] = "Pressed",
[2] = "Held"
}
return UsbHelpers
This example uses the Lua Helper module shown above
Local=UTC + bias
The bias is time zone offset plus the daylight savings if in effect. The bias is retrieve using the Lua function os.date and os.time. It assumes that the Lua function os.time returns the number of seconds since the start time (called "epoch").
If the time value is outside the allowable range of times, usually Jan 01 1970 00:00:00 to Jan 19 2038 03:14:07 the bias will be retrieved using the equivalent year inside the allowable range.
Note: Two years are considered equivalent if they have the same leap year and starting weekday.
The function that needs local time support are date(true) to get the current UTC time; date(false) to get the current local time; date(num_time), dateObj:getbias(), dateObject:fmt(str) when str has a '%Z' or '%z'.
Parsable date value is a lua value that can be converted to a dateObject. This value must be num_time, or tbl_date, or str_date or bool_now argument described in the date Lua USB HID method.
If a function needs a month value it must be a string or a number. If the value is a string, it must be the name of the month full or abbreviated. If the value is a number, that number must be 1-12 (January-December).
| Index | Abbreviation | Full Name |
|---|---|---|
|
1 |
Jan |
January |
|
2 |
Feb |
February |
|
3 |
Mar |
March |
|
4 |
Apr |
April |
|
5 |
May |
May |
|
6 |
Jun |
June |
|
7 |
Jul |
July |
|
8 |
Aug |
August |
|
9 |
Sep |
September |
|
10 |
Oct |
October |
|
11 |
Nov |
November |
|
12 |
Dec |
December |
If the value does not represent month, that is equivalent to passing a nil value.
dateObject is a table containing date and time value. It has a metatable for manipulation and retrieval of dates and times. Use the __call method of date to construct a dateObject.
How Date and Time are Stored in dateObject
Time is stored in dateObject as Ticks or Day Fraction. Date is stored in dateObject as Day Number. Ticks is time unit per seconds. For example, if the tick unit is 1000000. 0.25 seconds is equivalent to 250000 ticks (0.25*1000000). Day number, is the number days since the epoch, which is January 1, 0001 AD. Example.
dobj = date("15:49:59.3669")
If the tick unit is 1000000, dobj store this time as 56999366900 ticks and 0 days.
((((15*60)+49)*60)+59.3669)*1000000 == 56999366900
dobj = date("Jan-5-0001 10:30:15")
If the tick unit is 1000000, dobj stores this date and time as 37815000000 ticks and 4 days.
| Day # | Date |
|---|---|
|
0 |
Jan 1 0001 |
|
1 |
Jan 2 0001 |
|
2 |
Jan 3 0001 |
|
3 |
Jan 4 0001 |
|
4 |
Jan 5 0001 |
|
5 |
Jan 6 0001 |
|
... |
... |
Tip: The default tick unit is 1000000 (micro-second-ticks).
The dateObject module provides many different functions. dateObject: adddays, addhours, addminutes, addmonths, addseconds, addticks, addyears, copy, fmt, getbias, getclockhour, getdate, getday, getfracsec, gethours, getisoweekday, getisoweeknumber, getisoyear, getminutes, getmonth, getseconds, getticks, gettime, getweekday, getweeknumber, getyear, getyearday, setday, sethours, setisoweekday, setisoweeknumber, setisoyear, setminutes, setmonth, setseconds, setticks, setyear, spandays, spanhours, spanminutes, spanseconds, spanticks, tolocal, toutc.
Tip: To learn more about dateObject, see dateObject Methods.
The date module provides three different functions. .diff, .epoch, and .isleapyear. Additionally, there is a metamethod of date, which is the Lua USB HID function.
Subtracts the date and time value of two dateObject and returns the resulting dateObject.
Syntax
date.diff(var_date1, var_date2)
Arguments
var_date1: Requires a dateObject or
var_date2: Requires a parsable date value
Returns
The resulting dateObject if successful.
Returns nil in case of error.
Example
-- get the days between two dates
d = date.diff("Jan 7 1563", date(1563, 1, 2))
assert(d:spandays()==5)
Returns dateObject whose date and time value is the Operating System epoch.
Syntax
date.ephoch()
Arguments
var_date1: Requires a dateObject.
var_date2: Requires a parsable date value.
Returns
The resulting dateObject if successful.
Returns nil in case of error.
Example
assert(date.epoch()==("Jan 1 1920"))
Checks if a number or dateObject is a leapyear.
Syntax
date.isleapyear(var_year)
Arguments
var_year: A number, dateObject, or parsable date value.
Returns
Returns true if var_year is a leap year.
Returns false if var_year is not leap year.
Example
d = date(1776, 1, 1)
asert(date.isleapyear(d))
assert(date.isleapyear(d:getyear()))
assert(date.isleapyear(1776))
Constructs a dateObject. This is a metamethod of date. Remember, date:_call() is the same as date().
Syntax
date.(num_time)
date.(tbl_date)
date.(str_date)
date.(bool_now)
date.(int_year, var_month, int_day, [num_hour], [num_min], [num_sec], [int_ticks]).
Arguments
num_time: Required number value. Represents the number of seconds in Universal Coordinated Time between the specified value and the System epoch.
tbl_date: Required table value. Time (hour,,min, sec, or msec) must be supplied if date (year, month, and day) is not given, vice versa. The constructor will look for the value of this key:
year- an integer, the full year, for example, 1969. Required if month and day is given.month- a parsable month value. Required if year and day is given.day- an integer, the day of month from 1 to 31. Required if year and month is given.hour- a number, hours value, from 0 to 23, indicating the number of hours since midnight. (default = 0).min- a number, minutes value, from 0 to 59. (default = 0).sec- a number, seconds value, from 0 to 59. (default = 0).
str_date: Required string value. It must have number / words representing date and / or time. Use commas and spaces as delimiters. Strings enclosed by parenthesis is treated as a comment and is ignored. The stated day of the week is ignored whether its correct or not. A string containing an invalid date is an error. For example, a string containing two years or two months is an error. Time must be supplied if date is not given, vice versa.
- Time Format: Hours, minutes, and seconds are separated by colons, although all need not be specified. "10:", "10:11", and "10:11:12" are all valid. If the 24-hour clock is used, it is an error to specify "PM" for times later than 12 noon. For example, "23:15 PM" is an error.
- Time Zone Format: First character is a sign "+" (east of UTC) or "-" (west of UTC). Hours and minutes offset are separated by colons.
- Example:
assert( date("Jul 27 2006 03:56:28 +2:00") == date(2006,07,27,1,56,28))
- Another format is [sign][number] If [number] is less than 24, it is the offset in hours e.g. "-10" = -10 hours. Otherwise it is the offset in hundred hours e.g. "+75" = "+115" = +1.25 hours.
- Examples:
assert(date("Jul 27 2006 -75 ") == date(2006,07,27,1,15,0)), Orassert(date("Jul 27 2006 -115") == date(2006,07,27,1,15,0)).
bool_now: Required boolean value. if bool_now is false it returns the current local date and time. If bool_now is true it returns the current UTC date and time.
int_year: Required integer value. The year value.
var_month: Required. A parsable month value.
int_day: Required integer value. The day of month.
num_hour: Optional number value. Equal to the hours value. The default value is 0.
num_min: Optional number value. Equal to the minutes value. The default value is 0.
num_sec: Optional number value. Equal to the seconds value. The default value is 0.
int_ticks: Optional integer value. Equal to the ticks value. The default value is 0.
Returns
The resulting dateObject if successful.
Returns nil in case of error.
Example
a = date(2006, 8, 13) assert(a == date("Sun Aug 13 2006"))
b = date("Jun 13 1999") assert(b == date(1999, 6, 13))
c = date(1234483200) assert(c == date("Feb 13 2009"))
d = date({year=2009, month=11, day=13, min=6})
assert(d == date("Nov 13 2009 00:06:00"))
e = date() assert(e)
Portions of this topic are reprinted under permission of the LuaDate license.
