|
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() |