HID to OSC
A Human Interface Device (HID) to Open Sound Control (OSC) converter for macOS written in Python3.
The program can send OSC to any IP and port but by default, it will send to 127.0.0.1
(localhost) on port 57120
(SuperCollider).
Here is a binary build... HIDtoOSC.zip (macOS, 64bit, 5.3MB)
To run it start Terminal.app and type...
cd ~/Downloads
./HIDtoOSC
That should list available HID devices on your system. After that, you will probably see a warning that it failed to open the default device.
So to open and start reading data from any of your own devices you will need to give the correct vendor id and product id as arguments.
usage: HIDtoOSC [-h] [-V] [--vid VID] [--pid PID] [--ip IP] [--port PORT]
[--rate RATE] [--debug]
optional arguments:
-h, --help show this help message and exit
-V, --version show program version
--vid VID set HID vendor id
--pid PID set HID product id
--ip IP set OSC destination IP
--port PORT set OSC destination port
--rate RATE update rate in milliseconds
--debug post incoming HID data
example - USB keyboard
Here is an example that shows how to connect to an external generic USB keyboard with the vendor and product id 6700, 2.

NOTE: for security reasons sudo
is needed when accessing keyboards but not for accessing most other devices (gamepads, joysticks, mice)
Some times you will need to run the program a few times before the HID is claimed. Stop the program with ctrl+c
In the example, the key A is pressed and released, but all HID will use their own data format. The --debug
flag makes the program print out the incoming data.
receiving OSC
In SuperCollider we can receive the data as an OSC message. Test it by running the line...
OSCFunc.trace;
Here is what the example above generates...
OSC Message Received:
time: 75956.941459501
address: a NetAddr(127.0.0.1, 56073)
recvPort: 57120
msg: [ /hid, 6700, 2, Int8Array[ 0, 0, 4, 0, 0, 0, 0, 0 ] ]
example - USB mouse
And here is another example connecting to an optical mouse. (no sudo
needed)

This mouse example sends OSC to port 9999 and the data format is slightly different than in the keyboard example above.
building
If you do not trust the binary above (and you should not - especially when running it with sudo) you can run the Python code directly.
Homebrew and Python3 are required.
First, install some libraries...
brew install hidapi liblo
Next, create a virtual environment...
cd ~/python3
mkdir HIDtoOSC && cd HIDtoOSC
python3 -m venv env
source env/bin/activate #to later leave the virtual environment type: deactivate
Then install some Python libraries...
pip install Cython
pip install pyliblo
pip install hid pyusb
pip install pyinstaller
Finally copy the main Python program below, put it in a file called HIDtoOSC.py
and test it with for example python HIDtoOSC.py -h
(stop with ctrl+c)
#f.olofsson 2019
# required Python libraries: pyliblo, hid, pyusb
# example: sudo python HIDtoOSC.py --vid 6700 --pid 2 --port 12002 --debug
# note: macOS must be running this as root if the device is a keyboard
import sys, argparse
import usb.core, hid, liblo
#--settings
version= 0.1
#--arguments
parser= argparse.ArgumentParser()
parser.add_argument('-V', '--version', action= 'store_true', help= 'show program version')
parser.add_argument('--vid', type= int, dest= 'vid', help= 'set HID vendor id')
parser.add_argument('--pid', type= int, dest= 'pid', help= 'set HID product id')
parser.add_argument('--ip', dest= 'ip', help= 'set OSC destination IP')
parser.add_argument('--port', type= int, dest= 'port', help= 'set OSC destination port')
parser.add_argument('--rate', type= int, dest= 'rate', help= 'update rate in milliseconds')
parser.add_argument('--debug', dest= 'debug', action= 'store_true', help= 'post incoming HID data')
parser.set_defaults(vid= 1452)
parser.set_defaults(pid= 517)
parser.set_defaults(ip= '127.0.0.1')
parser.set_defaults(port= 57120)
parser.set_defaults(rate= 100)
parser.set_defaults(debug= False)
args= parser.parse_args()
if args.version:
sys.exit('HIDtoOSC version %s'%version)
print('Available HID devices:')
for d in hid.enumerate():
print(' device: %s, %s'%(d['manufacturer_string'], d['product_string']))
print(' vid: %s, pid: %s'%(d['vendor_id'], d['product_id']))
def main():
dev= usb.core.find(idVendor= args.vid, idProduct= args.pid)
if dev is None:
sys.exit('Could not find device %s, %s'%(args.vid, args.pid))
for config in dev:
for i in range(config.bNumInterfaces):
if dev.is_kernel_driver_active(i):
dev.detach_kernel_driver(i)
try:
dev.set_configuration()
except usb.core.USBError as e:
sys.exit('Could not set configuration: %s'%str(e))
endpoint= dev[0][(0, 0)][0]
try:
dev= hid.Device(args.vid, args.pid)
print('Device %s, %s open'%(args.vid, args.pid))
except:
sys.exit('Could not open device %s, %s'%(args.vid, args.pid))
print(endpoint)
target= (args.ip, args.port)
noerror= True
while noerror:
try:
data= dev.read(endpoint.wMaxPacketSize, args.rate)
except:
noerror= False
try:
dev.close()
print('Closed the hid device')
except:
pass
if data:
if args.debug:
print([x for x in data])
try:
msg= liblo.Message('/hid', args.vid, args.pid, ('b', data))
liblo.send(target, msg)
except:
print('WARNING: Could not send OSC to %s'%(target, ))
if __name__=='__main__':
main()
Later you can build your own binary with the command...
pyinstaller HIDtoOSC.py --onefile --hidden-import=libhidapi --add-binary='/usr/local/lib/libhidapi.dylib:.'