Skip to content

Instantly share code, notes, and snippets.

@lord-carlos
Created August 28, 2025 16:53
Show Gist options
  • Select an option

  • Save lord-carlos/ec0ff8ac1d9c695a758f869f4ec2f366 to your computer and use it in GitHub Desktop.

Select an option

Save lord-carlos/ec0ff8ac1d9c695a758f869f4ec2f366 to your computer and use it in GitHub Desktop.
POC for python project that can read NI HID data. I know the x1mk3 already has midi mode, but same approach could be used to create a midi driver for the s4 mk3. Thanks to AI and OPA.
import usb.core
import usb.util
import libusb_package
import time
from datetime import datetime
# Native Instruments vendor ID
NI_VENDOR_ID = 0x17cc
# X1 MK3 product ID (from our detection)
X1_MK3_PRODUCT_ID = 0x2200
def find_x1_mk3():
"""Find the X1 MK3 device"""
device = usb.core.find(
idVendor=NI_VENDOR_ID,
idProduct=X1_MK3_PRODUCT_ID,
backend=libusb_package.get_libusb1_backend()
)
return device
def initialize_device(device):
"""Initialize the X1 MK3 device for communication"""
# Set configuration
try:
device.set_configuration()
except usb.core.USBError as e:
print(f"Could not set configuration: {e}")
return False
# On Windows, kernel driver detachment is not needed/supported
# Just try to claim the interface directly
# Claim HID interface (interface 2)
try:
usb.util.claim_interface(device, 2)
except usb.core.USBError as e:
print(f"Could not claim interface: {e}")
return False
return True
def read_device_data(device):
"""Read data from the device's HID interface"""
try:
# Endpoint 0x81 is the Interrupt IN endpoint for the HID interface
# Using a reasonable timeout (500ms)
data = device.read(0x81, 64, timeout=500)
return data
except usb.core.USBError:
# Silently ignore all USB errors including timeouts
# Timeouts are normal when no data is available from the device
return None
def format_data(data):
"""Format the data in a more readable way"""
# Convert to list of integers for easier handling
data_list = [int(x) for x in data]
# Extract button states (these seem to be in the first few bytes)
# Based on the X1 MK1 code, buttons are likely in specific positions
button_states = data_list[:8] # First 8 bytes seem to contain button info
# Format as hex string for display
hex_str = " ".join([f"{x:02x}" for x in data_list])
return hex_str, button_states
def main():
print("=" * 50)
print("X1 MK3 Button Reader")
print("=" * 50)
print("Searching for X1 MK3 device...")
device = find_x1_mk3()
if device is None:
print("X1 MK3 device not found!")
return
print("Found X1 MK3 device.")
if not initialize_device(device):
print("Failed to initialize device!")
return
print("\nDevice initialized successfully.")
print("Listening for button presses...")
print("Press Ctrl+C to exit.")
print("-" * 50)
last_data = None
press_count = 0
try:
while True:
data = read_device_data(device)
if data is not None and data != last_data:
press_count += 1
hex_str, button_states = format_data(data)
timestamp = datetime.now().strftime("%H:%M:%S.%f")[:-3]
print(f"[{timestamp}] Button press #{press_count}")
print(f" Raw data: {hex_str}")
print(f" Button states: {button_states}")
print()
last_data = data
time.sleep(0.01) # Small delay to prevent excessive CPU usage
except KeyboardInterrupt:
print(f"\nExiting... Total button presses detected: {press_count}")
except Exception as e:
print(f"\nUnexpected error: {e}")
finally:
# Release interface
try:
usb.util.release_interface(device, 2)
except:
pass
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment