#!/usr/bin/env python3
"""
Boilerplate script for sending control transfer payloads to a USB HID device using pyusb.
This script is released to public domain without any warranty.
Author: Santeri Pikarinen (santeri3700)
Date: 2026-09-17
"""
import time
try:
    import usb.core
    import usb.util
except ImportError:
    print(
        "pyusb is not installed! Please install it via pip or package manager!"
    )
    print(
        "E.g. `python3 -m pip install pyusb` or `sudo apt install python3-usb`"
    )
    exit(1)

#########################
# CHANGE DETAILS BELOW! #
#########################

# Device details
vid = 0xabcd             # FIXME: Change this to your device's Vendor ID!
pid = 0x1234             # FIXME: Change this to your device's Product ID!
interface = 1            # FIXME: Change this to your devices' control interface number!
data_length = 64         # FIXME: Change this to your device's data length!

# 0x21 = Direction: Host-to-device, Type: Class, Recipient: Interface
bmRequestType = 0x21     # This should remain as is
# 0x09 = SET_CONFIGURATION
bRequest = 0x09          # FIXME: Change this to your device's bRequest!
# This is the value for above setting or parameter. Usually two bytes long.
wValue = 0x020A          # FIXME: Change this to your device's wValue!
# wIndex is the interface number for the recipient interface.
wIndex = interface

# Payloads / Data fragments (will be zero-filled automatically)
# WARNING: Malformed payloads may cause the device to malfunction or brick!
# ENSURE YOU DO NOT TYPO A SINGLE BYTE! YOU HAVE BEEN WARNED!
payloads = [
    # First packet payload
    bytearray([0x00, 0x01, 0x02, 0x03]),  # FIXME: Change this!
    # Second packet payload
    bytearray([0x04, 0x05, 0x06, 0x07]),  # FIXME: Change or remove this!
    # Third packet payload
    bytearray([0x08, 0x09, 0x0A, 0x0B]),   # FIXME: Change or remove this!
    # FIXME: Add or remove payloads as needed!
]


###############################
# USB CONTROL TRANSFER LOGIC! #
###############################


# Find the USB device per the Vendor ID and Product ID
dev = usb.core.find(idVendor=vid, idProduct=pid)

# If the device is not found, raise an error
if dev is None:
    raise ValueError('Device not found!')

# Ensure the first interface is not claimed by any kernel driver
kernel_driver_detached = False
if dev.is_kernel_driver_active(interface):
    print("Detaching the current kernel driver for the device...")
    print("The device may become unresponsive for a moment.")
    try:
        dev.detach_kernel_driver(interface)
        kernel_driver_detached = True
    except usb.core.USBError as e:
        raise RuntimeError('Could not detach kernel driver!', e)
    except Exception as e:
        raise RuntimeError('Unknown error while detaching kernel driver!', e)

# Claim the interface
try:
    usb.util.claim_interface(dev, interface)
except usb.core.USBError as e:
    # Re-attach the kernel driver if it was detached before raising the error
    if kernel_driver_detached:
        try:
            dev.attach_kernel_driver(interface)
        except usb.core.USBError as e_attach:
            raise RuntimeError('Could not re-attach kernel driver after claiming interface failed!', e_attach)
        except Exception as e_attach:
            raise RuntimeError('Unknown error while re-attaching kernel driver after claiming interface failed!', e_attach)
    raise RuntimeError('Could not claim interface!', e)

# Prepare some data to send
for i, payload in enumerate(payloads):
    if len(payload) > data_length:
        raise ValueError(f'Payload #{i+1} length exceeds data length!')
    # Zero-fill each payload to match the data_length
    payloads[i] += bytearray([0x00] * (data_length - len(payload)))


# Send each payload via URB_CONTROL
for i, payload in enumerate(payloads):
    try:
        ret = dev.ctrl_transfer(
            bmRequestType=bmRequestType,
            bRequest=bRequest,
            wValue=wValue,
            wIndex=wIndex,
            data_or_wLength=payload,
            timeout=500
        )
        print(f"Payload #{i+1} sent via control transfer!")
    except usb.core.USBError as e:
        print(f"Error sending control transfer: {str(e)}")
    except Exception as e:
        print(f"Unknown error while sending control transfer: {str(e)}")

# Re-attach the kernel driver for the interface if it was detached
if kernel_driver_detached:
    try:
        print("Re-attaching the kernel driver for the device...")
        usb.util.release_interface(dev, interface)
        dev.attach_kernel_driver(interface)
    except usb.core.USBError as e:
        raise RuntimeError('Could not re-attach kernel driver!', e)
    except Exception as e:
        raise RuntimeError('Unknown error while re-attaching kernel driver!', e)

# Bye
print('All payloads sent!')
