diff --git a/README.md b/README.md index 4ea511b..55f2482 100644 --- a/README.md +++ b/README.md @@ -269,14 +269,14 @@ with SwitchUARTClient("/dev/cu.usbserial-0001") as client: ## IMU / Motion Controls -The bridge supports gyroscope and accelerometer passthrough from controllers that have motion sensors (e.g. the Nintendo Switch Pro Controller, DualSense). Motion data is forwarded to the Pico which injects it into the emulated Switch Pro Controller's HID reports. +The bridge supports gyroscope and accelerometer passthrough from controllers that have motion sensors (e.g. the Nintendo Switch Pro Controller and DualSense). Motion data is forwarded to the Pico as a rolling three-sample window; the Pico emits standard 0x30 reports at 15 ms intervals and supports both raw IMU mode 1 and packed quaternion mode 2. ### Requirements -- A controller with gyro/accelerometer support (SDL2 must be able to enable sensors on it). +- A controller with gyro/accelerometer support that SDL3 can enable. - The Switch will automatically use motion data once the controller is recognised as a Pro Controller. ### Gyro bias calibration -On startup, the bridge collects the first 200 gyro readings while the controller is stationary and averages them to compute a per-axis bias (zero-rate offset). Gyro output is zeroed during this ~1 second calibration window, then bias is subtracted from all subsequent readings. Keep the controller still when starting the bridge for best results. Use `--no-gyro-bias` to skip calibration and use raw values directly. +On startup, the bridge collects the first 200 gyro readings while the controller is stationary and averages them to compute a per-axis bias (zero-rate offset). The bias is subtracted from subsequent readings. Keep the controller still during startup for best results. ### CLI flags - `--debug-imu`: Print raw sensor values (m/s² and rad/s) and converted Switch integer counts every ~200ms. Useful for verifying the sensor is detected and producing sensible data. @@ -284,10 +284,9 @@ On startup, the bridge collects the first 200 gyro readings while the controller - `--gyro-scale FLOAT` (default 1.0): Multiply all gyro values by this factor before sending. Reduce below 1.0 if the camera moves too fast; increase above 1.0 for more sensitivity. ### Troubleshooting -- **Gyro not detected**: Run with `--debug-imu`. If no IMU readings appear, SDL2 cannot see sensors on your controller (may not be supported or driver issue). On Linux, the `hid-nintendo` kernel driver routes Pro Controller IMU to a separate evdev device that SDL2 cannot read; use Windows or macOS for gyro passthrough. -- **Wild camera swinging**: Start with `--gyro-scale 0.3` and increase gradually. Ensure the controller is still during the first second of startup (bias calibration). -- **Verifying Pico output**: Use `python tools/read_pro_imu.py --vid 0x057E --pid 0x2009` to read raw IMU bytes directly from the Pico's USB HID output and confirm non-zero values appear. -- **SDL2 accuracy**: SDL2 (version < 2.32.7) has a known inaccuracy bug with Switch Pro Controller gyro data. Updating the SDL2 shared library to 2.32.7 or later improves accuracy. +- **Gyro not detected**: Run with `--debug-imu`. If no IMU readings appear, SDL3 cannot see sensors on the controller. On Linux, the `hid-nintendo` kernel driver may expose Nintendo controller motion differently; DualSense motion is supported by SDL3's PlayStation HID driver. +- **Wild camera swinging**: Rebuild and flash the current Pico firmware. Older builds acknowledged quaternion IMU mode 2 but emitted raw mode-1 bytes, which Zelda interpreted as random quaternion data. Keep the controller still during startup, then use `--gyro-scale` only for deliberate sensitivity adjustment. +- **Verifying Pico output**: Use `uv run python tools/read_pro_imu.py --vid 0x057E --pid 0x2009` to read raw IMU bytes directly from the Pico's USB HID output. A stationary controller should show gyro values near zero and three non-empty, non-duplicated samples per report. ## References - GP2040-CE (controller firmware ecosystem): https://github.com/OpenStickCommunity/GP2040-CE diff --git a/pyproject.toml b/pyproject.toml index 9f8a3df..79a0508 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,7 +5,7 @@ build-backend = "setuptools.build_meta" [project] name = "switch-pico-bridge" version = "0.1.0" -description = "SDL2-to-UART host bridge and helpers for the switch-pico firmware." +description = "SDL3-to-UART host bridge and helpers for the switch-pico firmware." readme = "README.md" requires-python = ">=3.9" authors = [{ name = "Switch Pico Maintainers" }] @@ -13,6 +13,7 @@ dependencies = [ "pyserial", "PySDL3", "rich", + "hidapi", ] [project.scripts] diff --git a/src/switch_pico_bridge/controller_uart_bridge.py b/src/switch_pico_bridge/controller_uart_bridge.py index c1db5b1..7f07c93 100644 --- a/src/switch_pico_bridge/controller_uart_bridge.py +++ b/src/switch_pico_bridge/controller_uart_bridge.py @@ -61,7 +61,6 @@ CONTROLLER_DB_URL_DEFAULT = "https://raw.githubusercontent.com/mdqinc/SDL_GameCo SDL_TRUE = True SDL_EVENT_GAMEPAD_SENSOR_UPDATE = getattr(sdl3, "SDL_EVENT_GAMEPAD_SENSOR_UPDATE", 0x658) GYRO_BIAS_SAMPLES = 200 -IMU_BUFFER_SIZE = 32 def parse_mapping(value: str) -> Tuple[int, str]: @@ -1368,8 +1367,8 @@ def handle_sensor_update( ) ctx.imu_samples.append(sample) - if len(ctx.imu_samples) > IMU_BUFFER_SIZE: - ctx.imu_samples = ctx.imu_samples[-IMU_BUFFER_SIZE:] + if len(ctx.imu_samples) > IMU_SAMPLES_PER_REPORT: + del ctx.imu_samples[:-IMU_SAMPLES_PER_REPORT] if config.debug_imu: now = time.monotonic() @@ -1567,12 +1566,9 @@ def service_contexts( try: if now - ctx.last_send >= config.interval: if ctx.sensors_enabled and not config.no_imu: - count = min(len(ctx.imu_samples), IMU_SAMPLES_PER_REPORT) - if count > 0: - ctx.report.imu_samples = ctx.imu_samples[:count] - ctx.imu_samples = ctx.imu_samples[count:] - else: - ctx.report.imu_samples = [] + # Keep publishing the latest complete sensor window. Draining + # this at the faster UART rate leaves most USB reports empty. + ctx.report.imu_samples = ctx.imu_samples else: ctx.report.imu_samples = [] ctx.uart.send_report(ctx.report) diff --git a/switch_pro_driver.cpp b/switch_pro_driver.cpp index ee68f68..4d26922 100644 --- a/switch_pro_driver.cpp +++ b/switch_pro_driver.cpp @@ -1,6 +1,7 @@ #include "switch_pro_driver.h" #include +#include #include #include #include @@ -16,6 +17,10 @@ // force a report to be sent every X ms #define SWITCH_PRO_KEEPALIVE_TIMER 5 +// Real Pro Controller cadence: 3 IMU frames per report, 5ms/frame => 15ms/report +// (~66.7Hz). Emitting the 3-frame 0x30 faster makes the console over-integrate +// gyro (3 frames assumed 5ms apart delivered too often) => wild camera swing. +#define SWITCH_PRO_IMU_REPORT_TIMER 15 static SwitchInputState g_input_state{ false, false, false, false, @@ -41,7 +46,13 @@ static uint8_t handshake_counter = 0; static SwitchDeviceInfo device_info{}; static uint8_t player_id = 0; static uint8_t input_mode = 0x30; -static bool is_imu_enabled = false; +enum class SwitchImuMode : uint8_t { + Off = 0, + Raw = 1, + Quaternion = 2, +}; + +static SwitchImuMode imu_mode = SwitchImuMode::Off; static bool is_vibration_enabled = false; // Optional compile-time colour override (body/buttons/grips). @@ -98,9 +109,11 @@ static const uint8_t factory_config_data[0xEFF] = { 0xFF, 0xFF, 0xFF, 0xFF, // config & calibration 1 - 0xE3, 0xFF, 0x39, 0xFF, 0xED, 0x01, 0x00, 0x40, - 0x00, 0x40, 0x00, 0x40, 0x09, 0x00, 0xEA, 0xFF, - 0xA1, 0xFF, 0x3B, 0x34, 0x3B, 0x34, 0x3B, 0x34, + // IMU calibration @ 0x6020: zero offsets, 4096 LSB/g, and + // 818.5 LSB/(rad/s), matching the bridge's raw-value conversion. + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, + 0x00, 0x40, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x3B, 0x34, 0x3B, 0x34, 0x3B, 0x34, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, @@ -184,32 +197,164 @@ static std::map spi_flash_data = { static inline uint16_t scale16To12(uint16_t pos) { return pos >> 4; } -static void fill_imu_report_data(const SwitchInputState& state) { +struct MotionQuaternion { + float x; + float y; + float z; + float w; + int16_t accel_x; + int16_t accel_y; + int16_t accel_z; +}; + +static MotionQuaternion motion_quaternion{0.0f, 0.0f, 0.0f, 1.0f, 0, 0, 0}; + +static void reset_motion_quaternion() { + motion_quaternion = {0.0f, 0.0f, 0.0f, 1.0f, 0, 0, 0}; +} + +static void write_int16_le(uint8_t* dst, int16_t value) { + dst[0] = static_cast(value & 0xFF); + dst[1] = static_cast((value >> 8) & 0xFF); +} + +static void write_bits_le(uint8_t* dst, uint16_t bit_offset, uint32_t value, uint8_t width) { + for (uint8_t bit = 0; bit < width; ++bit) { + if ((value & (1u << bit)) != 0) { + uint16_t output_bit = static_cast(bit_offset + bit); + dst[output_bit >> 3] |= static_cast(1u << (output_bit & 7u)); + } + } +} + +static void integrate_motion_sample(const SwitchImuSample& sample) { + constexpr float sample_dt = 0.005f; + constexpr float gyro_rad_per_lsb = 1.0f / 818.5f; + + // Nintendo mode 2 uses Y, X, Z sensor order for quaternion axes. + float angle_x = static_cast(sample.gyro_y) * gyro_rad_per_lsb * sample_dt; + float angle_y = static_cast(sample.gyro_x) * gyro_rad_per_lsb * sample_dt; + float angle_z = static_cast(sample.gyro_z) * gyro_rad_per_lsb * sample_dt; + float norm = sqrtf(angle_x * angle_x + angle_y * angle_y + angle_z * angle_z); + float half = 0.5f * norm; + float vector_scale = norm > 1e-12f ? sinf(half) / norm : 0.5f; + float scalar = norm > 1e-12f ? cosf(half) : 1.0f; + + float dx = angle_x * vector_scale; + float dy = angle_y * vector_scale; + float dz = angle_z * vector_scale; + float dw = scalar; + + float x = motion_quaternion.w * dx + motion_quaternion.x * dw + + motion_quaternion.y * dz - motion_quaternion.z * dy; + float y = motion_quaternion.w * dy - motion_quaternion.x * dz + + motion_quaternion.y * dw + motion_quaternion.z * dx; + float z = motion_quaternion.w * dz + motion_quaternion.x * dy + - motion_quaternion.y * dx + motion_quaternion.z * dw; + float w = motion_quaternion.w * dw - motion_quaternion.x * dx + - motion_quaternion.y * dy - motion_quaternion.z * dz; + float magnitude = sqrtf(x * x + y * y + z * z + w * w); + if (magnitude > 1e-12f) { + float inverse = 1.0f / magnitude; + motion_quaternion.x = x * inverse; + motion_quaternion.y = y * inverse; + motion_quaternion.z = z * inverse; + motion_quaternion.w = w * inverse; + } else { + reset_motion_quaternion(); + } + + motion_quaternion.accel_x = sample.accel_x; + motion_quaternion.accel_y = sample.accel_y; + motion_quaternion.accel_z = sample.accel_z; +} + +static void fill_raw_imu_report_data(const SwitchInputState& state) { if (state.imu_sample_count == 0) { memset(switch_report.imuData, 0x00, sizeof(switch_report.imuData)); return; } uint8_t sample_count = state.imu_sample_count > 3 ? 3 : state.imu_sample_count; - // If fewer than 3 samples, duplicate the last one to fill all 3 slots uint8_t* dst = switch_report.imuData; for (uint8_t i = 0; i < 3; ++i) { - const SwitchImuSample& s = (i < sample_count) ? state.imu_samples[i] : state.imu_samples[sample_count - 1]; - dst[0] = static_cast(s.accel_x & 0xFF); - dst[1] = static_cast((s.accel_x >> 8) & 0xFF); - dst[2] = static_cast(s.accel_y & 0xFF); - dst[3] = static_cast((s.accel_y >> 8) & 0xFF); - dst[4] = static_cast(s.accel_z & 0xFF); - dst[5] = static_cast((s.accel_z >> 8) & 0xFF); - dst[6] = static_cast(s.gyro_x & 0xFF); - dst[7] = static_cast((s.gyro_x >> 8) & 0xFF); - dst[8] = static_cast(s.gyro_y & 0xFF); - dst[9] = static_cast((s.gyro_y >> 8) & 0xFF); - dst[10] = static_cast(s.gyro_z & 0xFF); - dst[11] = static_cast((s.gyro_z >> 8) & 0xFF); + const SwitchImuSample& sample = + (i < sample_count) ? state.imu_samples[i] : state.imu_samples[sample_count - 1]; + write_int16_le(dst + 0, sample.accel_x); + write_int16_le(dst + 2, sample.accel_y); + write_int16_le(dst + 4, sample.accel_z); + write_int16_le(dst + 6, sample.gyro_x); + write_int16_le(dst + 8, sample.gyro_y); + write_int16_le(dst + 10, sample.gyro_z); dst += 12; } } +static void fill_quaternion_imu_report_data(const SwitchInputState& state, uint32_t now_ms) { + if (state.imu_sample_count > 0) { + uint8_t sample_count = state.imu_sample_count > 3 ? 3 : state.imu_sample_count; + for (uint8_t i = 0; i < 3; ++i) { + const SwitchImuSample& sample = + (i < sample_count) ? state.imu_samples[i] : state.imu_samples[sample_count - 1]; + integrate_motion_sample(sample); + } + } + + uint8_t* dst = switch_report.imuData; + memset(dst, 0x00, sizeof(switch_report.imuData)); + + // Mode 2 accelerometer vectors are encoded in Y, X, Z order. + write_int16_le(dst + 0, motion_quaternion.accel_y); + write_int16_le(dst + 2, motion_quaternion.accel_x); + write_int16_le(dst + 4, motion_quaternion.accel_z); + + float quaternion[4] = { + motion_quaternion.x, + motion_quaternion.y, + motion_quaternion.z, + motion_quaternion.w, + }; + uint8_t max_index = 0; + for (uint8_t i = 1; i < 4; ++i) { + if (fabsf(quaternion[i]) > fabsf(quaternion[max_index])) { + max_index = i; + } + } + + uint32_t packed_component[3]{}; + float sign = quaternion[max_index] < 0.0f ? -1.0f : 1.0f; + for (uint8_t i = 0; i < 3; ++i) { + int32_t component = static_cast( + quaternion[(max_index + i + 1) & 3] * 1073741824.0f * sign); + packed_component[i] = static_cast(component >> 10) & 0x1FFFFFu; + } + + write_bits_le(dst, 48, 2, 2); + write_bits_le(dst, 50, max_index, 2); + write_bits_le(dst, 52, packed_component[0], 21); + write_bits_le(dst, 73, packed_component[1], 21); + write_bits_le(dst, 94, packed_component[2] & 0x3u, 2); + write_bits_le(dst, 144, packed_component[2] >> 2, 19); + + uint16_t timestamp = static_cast(now_ms & 0x7FFu); + write_bits_le(dst, 271, timestamp, 11); + write_bits_le(dst, 282, 3, 6); +} + +static void fill_imu_report_data(const SwitchInputState& state, uint32_t now_ms) { + switch (imu_mode) { + case SwitchImuMode::Raw: + fill_raw_imu_report_data(state); + break; + case SwitchImuMode::Quaternion: + fill_quaternion_imu_report_data(state, now_ms); + break; + case SwitchImuMode::Off: + default: + memset(switch_report.imuData, 0x00, sizeof(switch_report.imuData)); + break; + } +} + static SwitchInputState make_neutral_state() { SwitchInputState s{}; s.lx = SWITCH_PRO_JOYSTICK_MID; @@ -425,14 +570,24 @@ static void handle_feature_report(uint8_t switchReportID, uint8_t switchReportSu canSend = true; LOG_PRINTF("[HID] FEATURE SET_HOME_LIGHT\n"); break; - case TOGGLE_IMU: - is_imu_enabled = reportData[11]; + case TOGGLE_IMU: { + SwitchImuMode requested_mode = SwitchImuMode::Off; + if (reportData[11] == static_cast(SwitchImuMode::Raw)) { + requested_mode = SwitchImuMode::Raw; + } else if (reportData[11] == static_cast(SwitchImuMode::Quaternion)) { + requested_mode = SwitchImuMode::Quaternion; + } + if (requested_mode == SwitchImuMode::Quaternion && imu_mode != requested_mode) { + reset_motion_quaternion(); + } + imu_mode = requested_mode; report_buffer[13] = 0x80; report_buffer[14] = commandID; report_buffer[15] = 0x00; canSend = true; - LOG_PRINTF("[HID] FEATURE TOGGLE_IMU %u\n", is_imu_enabled); + LOG_PRINTF("[HID] FEATURE TOGGLE_IMU %u\n", static_cast(imu_mode)); break; + } case IMU_SENSITIVITY: report_buffer[13] = 0x80; report_buffer[14] = commandID; @@ -512,11 +667,12 @@ static void update_switch_report_from_state() { switch_report.inputs.rightStick.setX(std::min(std::max(scaleRightStickX,rightMinX), rightMaxX)); switch_report.inputs.rightStick.setY(-std::min(std::max(scaleRightStickY,rightMinY), rightMaxY)); - fill_imu_report_data(g_input_state); switch_report.rumbleReport = 0x09; } void switch_pro_init() { + imu_mode = SwitchImuMode::Off; + reset_motion_quaternion(); player_id = 0; last_report_counter = 0; handshake_counter = 0; @@ -531,8 +687,8 @@ void switch_pro_init() { last_report_timer = 0; device_info = { - .majorVersion = 0x04, - .minorVersion = 0x91, + .majorVersion = 0x03, + .minorVersion = 0x48, .controllerType = SWITCH_TYPE_PRO_CONTROLLER, .unknown00 = 0x02, .macAddress = {0x7c, 0xbb, 0x8a, static_cast(get_rand_32() % 0xff), static_cast(get_rand_32() % 0xff), static_cast(get_rand_32() % 0xff)}, @@ -545,8 +701,8 @@ void switch_pro_init() { .timestamp = 0, .inputs { - .connectionInfo = 0x08, // wired connection - .batteryLevel = 0x0F, // full battery + .connectionInfo = 0x01, // Pro Controller powered by the console + .batteryLevel = 0x08, // full battery .buttonY = 0, .buttonX = 0, @@ -618,8 +774,10 @@ void switch_pro_task() { } if (is_ready && !report_sent) { - if ((now - last_report_timer) > SWITCH_PRO_KEEPALIVE_TIMER) { - switch_report.timestamp = last_report_counter; + if ((now - last_report_timer) >= SWITCH_PRO_IMU_REPORT_TIMER) { + // One timer tick per 5ms IMU frame; three frames per report. + fill_imu_report_data(g_input_state, now); + switch_report.timestamp += 3; void * inputReport = &switch_report; uint16_t report_size = sizeof(switch_report); if (tud_hid_ready() && send_report(0, inputReport, report_size) == true ) { diff --git a/tests/test_controller_uart_bridge.py b/tests/test_controller_uart_bridge.py new file mode 100644 index 0000000..cb14973 --- /dev/null +++ b/tests/test_controller_uart_bridge.py @@ -0,0 +1,130 @@ +"""Regression tests for SDL sensor buffering in the UART bridge.""" + +from argparse import Namespace +from io import StringIO +from types import SimpleNamespace +from typing import Protocol, cast + +import sdl3 +from rich.console import Console + +import switch_pico_bridge.controller_uart_bridge as bridge +from switch_pico_bridge.switch_pico_uart import ( + IMUSample, + PicoUART, + SENSOR_ACCEL, + SENSOR_GYRO, + SwitchButton, + SwitchReport, + UART_BAUD, +) + + +class MonkeyPatch(Protocol): + def setattr(self, target: object, name: str, value: object) -> None: ... + + +class RecordingUART: + def __init__(self) -> None: + self.sent_imu: list[tuple[IMUSample, ...]] = [] + + def send_report(self, report: SwitchReport) -> None: + self.sent_imu.append(tuple(report.imu_samples)) + + def read_rumble_payload(self) -> bytes | None: + return None + + +def make_config() -> bridge.BridgeConfig: + return bridge.BridgeConfig( + interval=0.002, + deadzone_raw=0, + trigger_threshold=0, + zero_sticks=False, + zero_hotkey="", + swap_hotkey="", + button_map_default={}, + button_map_swapped={}, + swap_abxy_indices=set(), + swap_abxy_ids=set(), + swap_abxy_global=False, + ) + + +def test_sensor_buffer_retains_latest_three_samples() -> None: + controller = cast(sdl3.SDL_Gamepad, object()) + ctx = bridge.ControllerContext(controller, 7, 0, "dualsense", None, None) + ctx.sensors_enabled = True + ctx.gyro_bias_locked = True + contexts = {ctx.instance_id: ctx} + config = make_config() + + accel_event = cast( + sdl3.SDL_Event, + cast( + object, + SimpleNamespace( + gsensor=SimpleNamespace( + which=ctx.instance_id, + sensor=SENSOR_ACCEL, + data=(0.0, 9.80665, 0.0), + ) + ), + ), + ) + bridge.handle_sensor_update(accel_event, contexts, config) + + for gyro_x in (1.0, 2.0, 3.0, 4.0): + gyro_event = cast( + sdl3.SDL_Event, + cast( + object, + SimpleNamespace( + gsensor=SimpleNamespace( + which=ctx.instance_id, + sensor=SENSOR_GYRO, + data=(gyro_x, 0.0, 0.0), + ) + ), + ), + ) + bridge.handle_sensor_update(gyro_event, contexts, config) + + assert len(ctx.imu_samples) == 3 + assert [sample.gyro_y for sample in ctx.imu_samples] == [ + bridge.convert_gyro_to_raw(-2.0), + bridge.convert_gyro_to_raw(-3.0), + bridge.convert_gyro_to_raw(-4.0), + ] + + +def test_service_republishes_latest_imu_window(monkeypatch: MonkeyPatch) -> None: + uart = RecordingUART() + controller = cast(sdl3.SDL_Gamepad, object()) + ctx = bridge.ControllerContext( + controller, 7, 0, "dualsense", "/dev/null", cast(PicoUART, cast(object, uart)) + ) + ctx.sensors_enabled = True + samples = [ + IMUSample(1, 2, 3, 4, 5, 6), + IMUSample(7, 8, 9, 10, 11, 12), + IMUSample(13, 14, 15, 16, 17, 18), + ] + ctx.imu_samples = samples.copy() + contexts = {ctx.instance_id: ctx} + config = make_config() + + def ignore_poll( + _ctx: bridge.ControllerContext, _button_map: dict[int, SwitchButton] + ) -> None: + return None + + monkeypatch.setattr(bridge, "poll_controller_buttons", ignore_poll) + + args = Namespace(baud=UART_BAUD) + console = Console(file=StringIO()) + bridge.service_contexts(1.0, args, config, contexts, [], console) + bridge.service_contexts(2.0, args, config, contexts, [], console) + + assert uart.sent_imu == [tuple(samples), tuple(samples)] + assert ctx.imu_samples == samples diff --git a/uv.lock b/uv.lock index ec7899d..d400c30 100644 --- a/uv.lock +++ b/uv.lock @@ -451,6 +451,84 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/9a/9a/e35b4a917281c0b8419d4207f4334c8e8c5dbf4f3f5f9ada73958d937dcc/frozenlist-1.8.0-py3-none-any.whl", hash = "sha256:0c18a16eab41e82c295618a77502e17b195883241c563b00f0aa5106fc4eaa0d", size = 13409, upload-time = "2025-10-06T05:38:16.721Z" }, ] +[[package]] +name = "hidapi" +version = "0.15.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/74/f6/caad9ed701fbb9223eb9e0b41a5514390769b4cb3084a2704ab69e9df0fe/hidapi-0.15.0.tar.gz", hash = "sha256:ecbc265cbe8b7b88755f421e0ba25f084091ec550c2b90ff9e8ddd4fcd540311", size = 184995, upload-time = "2025-12-09T09:48:54.129Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bd/5a/46620fc194f3fa728dce1966ce977334b080fc33b8b525018ad0e0324b91/hidapi-0.15.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b0e1781f7fb8b4015e318d839d66fa79e98900d53900e31d04edb336e0103846", size = 70517, upload-time = "2025-12-09T09:44:47.751Z" }, + { url = "https://files.pythonhosted.org/packages/2d/97/bcbcb89f9461c29d3b12dd32affd29e6312fd521154ee7f394496d0039a9/hidapi-0.15.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1fa3e792987d4b7ed66d785491307e23d4f09d3636f8a23665a9694c43e92409", size = 70181, upload-time = "2025-12-09T09:44:49.321Z" }, + { url = "https://files.pythonhosted.org/packages/cc/19/b9f1cd2bce226ed43afa7e7649caeee5552e7ea844e997521d747f0da184/hidapi-0.15.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:75c2d3b83a8300953653ddd7f5190d5ee6072d4a6ee5944db47e92d97344ed92", size = 1039531, upload-time = "2025-12-09T09:44:55.735Z" }, + { url = "https://files.pythonhosted.org/packages/3c/b9/5a6a1a4219ada2da251225c706d450076fd2b3d624000699ff4d329326ab/hidapi-0.15.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:91b43099b123363c8935a264acffb4c7e5c8dba3390f9b2bf19ff76597677a31", size = 1573936, upload-time = "2025-12-09T09:44:51.193Z" }, + { url = "https://files.pythonhosted.org/packages/42/7c/a2df1c993db58f9337d5ee64d3c58f5b9c5127f3ef68907d0c50070d1e50/hidapi-0.15.0-cp310-cp310-manylinux_2_28_i686.whl", hash = "sha256:cea09ccc3efa5b92ab18d3ae8636836edce2c421d67ed4409761da090e230e5a", size = 1654515, upload-time = "2025-12-09T09:44:54.166Z" }, + { url = "https://files.pythonhosted.org/packages/54/ca/341b3f5c713f72a7e61a942859b003a507f4a7d7cf5135a20da519bbcf05/hidapi-0.15.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b2de66142cf780ee7c797b7c35e488d67bccdf2de98178ac12a4038c490c44ea", size = 669173, upload-time = "2025-12-09T09:44:57.545Z" }, + { url = "https://files.pythonhosted.org/packages/f8/55/e5922d48b59959820d976c8e232b14c1f57d65742e416831ce53fda32f6b/hidapi-0.15.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e283c3e9255850d66152adbf580744d38c840d399141b98d64d359cb97cbc128", size = 664906, upload-time = "2025-12-09T09:44:58.893Z" }, + { url = "https://files.pythonhosted.org/packages/50/b2/87683617901cbc5232a1d99cb8e51a967792aae48d462a8e2a842d63ec25/hidapi-0.15.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5b9f6d2f3bc15c718d8e2ce349d2d019a0fcc343ed1a705a91d0f1c7e792d6ea", size = 671123, upload-time = "2025-12-09T09:45:00.828Z" }, + { url = "https://files.pythonhosted.org/packages/26/cc/03f7d56b82a9dc2abcfcd8d55915504e156b6558fb66926629836e551595/hidapi-0.15.0-cp310-cp310-win32.whl", hash = "sha256:81de6b5fcb4fbbbfc71c6d201a2ac6914d1d86e51930ffde5f96faf50e922473", size = 60160, upload-time = "2025-12-09T09:45:04.472Z" }, + { url = "https://files.pythonhosted.org/packages/35/20/a39e33a9088d76f98f80d9320f8413bdaeaa0458b42470e63a47ac4ccf94/hidapi-0.15.0-cp310-cp310-win_amd64.whl", hash = "sha256:c36895abaef3a4004af6c5020ca214fcdacb2a491e4cde5576afbb1dad903548", size = 67063, upload-time = "2025-12-09T09:45:02.852Z" }, + { url = "https://files.pythonhosted.org/packages/3b/c5/3cfa157e7d1fd6af5ad52ea6ea031b1c8da141c61a2506ad5cb3420afa7f/hidapi-0.15.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:53d018e6ac639a217c019c6dcd79a1d30c2401ac7a8147eedd11f2aa29307661", size = 70578, upload-time = "2025-12-09T09:45:05.497Z" }, + { url = "https://files.pythonhosted.org/packages/a5/7e/a43efc66b3b0a68058e76ad0cb2267ce9b30d9006dce10824f3c8b314b08/hidapi-0.15.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a11ef4b5ab0a2f36e35f44af394bff41cb645b03ce36c5b2f2c00873f112661f", size = 70233, upload-time = "2025-12-09T09:45:06.826Z" }, + { url = "https://files.pythonhosted.org/packages/6c/30/d8551d99528e0b7b2962cc76fb9a2b5990ea16aeb1457d1c3c61015d020f/hidapi-0.15.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e47d4fe13ddfc63fd1961974e10f3c7d50a57ae0bb73974e465cfe95dad5da18", size = 1070472, upload-time = "2025-12-09T09:45:16.322Z" }, + { url = "https://files.pythonhosted.org/packages/7c/26/433fc4e9efdfb9d1beb52e39ccdec265ae02003b1d2f8395aba3022b665b/hidapi-0.15.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:f39917c9e9bea895384b1a8869159d808b4a53561af9b4f7c3148bdf6bfd97a0", size = 1602092, upload-time = "2025-12-09T09:45:08.274Z" }, + { url = "https://files.pythonhosted.org/packages/7e/13/911b757f6847a197ab5c978fe8dd55181035096a5659cf7183e013ed1ea2/hidapi-0.15.0-cp311-cp311-manylinux_2_28_i686.whl", hash = "sha256:f4d294fcd2551fb5b41d5f478a08e8945382e1d86d7d40320d02594922d6b667", size = 1677973, upload-time = "2025-12-09T09:45:10.357Z" }, + { url = "https://files.pythonhosted.org/packages/53/3f/cd92833886a15b34225bbd765ed1eb0270bc78b4cf6385e302c0e4bae494/hidapi-0.15.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:68cb85467151a3d4aa6ab4e11ca89a87c2404a778dbccb8fb5ae51e998133bb3", size = 699789, upload-time = "2025-12-09T09:45:18.506Z" }, + { url = "https://files.pythonhosted.org/packages/e6/0e/9cdad5783bf1b2aea6667da37f2435b286750d650071dcc50b42834c8810/hidapi-0.15.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:06fbcd7696ad768d7e9500b64e2ae742b5d90a2531b5aa8d355c0f049516a09d", size = 695983, upload-time = "2025-12-09T09:45:20.192Z" }, + { url = "https://files.pythonhosted.org/packages/86/ac/a57bc6e28d0dc1a0aed3dcc24302262ecbf2b723f9c4ba1af4ea51add5b4/hidapi-0.15.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:221e3a0c181503061bfc4a672eccd9f86b1da1167b31914be00d919edfd6e1bf", size = 697710, upload-time = "2025-12-09T09:45:21.807Z" }, + { url = "https://files.pythonhosted.org/packages/53/40/6a45375a52027d8142e7acdaeab182a44ff6b818df41384019662eb4f351/hidapi-0.15.0-cp311-cp311-win32.whl", hash = "sha256:2c35bd9cc62227ec91047e36b260f75bdbf50814f3cf3c3b28648ac3ffabd9d7", size = 60070, upload-time = "2025-12-09T09:45:24.745Z" }, + { url = "https://files.pythonhosted.org/packages/4a/a9/8ad4e6423c7416eb8dd765327f3be67f083c985b41b9b48ef3061a64c5f2/hidapi-0.15.0-cp311-cp311-win_amd64.whl", hash = "sha256:c5f7ee9ce8e3373fdb7002497f16bc652d9d4acf20c91275877f55165caf6f0c", size = 67296, upload-time = "2025-12-09T09:45:23.313Z" }, + { url = "https://files.pythonhosted.org/packages/b3/fd/9076aba0736f339b71bda5ee26cb678c02420bf96c7ea5bd9ebcbaf0aa3c/hidapi-0.15.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:0b7eb950214191c936b5bdabaeaf1cab99c0dfc3ae3edc220e3c6d4547296053", size = 70157, upload-time = "2025-12-09T09:45:25.77Z" }, + { url = "https://files.pythonhosted.org/packages/4e/9c/9335957cb7e1ca1be997e97afe850f62a9ff0708015048b3871b553eed5c/hidapi-0.15.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:27933e7f3da7007e5e8d0b25bcf48a79a74e802555e496bba2c7d87f8a77cd61", size = 69597, upload-time = "2025-12-09T09:45:26.707Z" }, + { url = "https://files.pythonhosted.org/packages/de/bc/0eb92f4f238adc8edc3decb859662cf87e302b0a6ecdabe0e4d0560eb5fd/hidapi-0.15.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:cfdbc74d5c4b5fdeef58a246d80f7dbf4ca33fe741c889505a4a350dd2eb54fb", size = 1082139, upload-time = "2025-12-09T09:45:32.799Z" }, + { url = "https://files.pythonhosted.org/packages/03/04/856827cfecaa89091ed470f697e3f9cfb172fc852960e6b902e765d7d650/hidapi-0.15.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:4b7b6207c1c7df3524e9d4019c7196547327636e9924010b9b0bf0c40029e329", size = 1620074, upload-time = "2025-12-09T09:45:28.573Z" }, + { url = "https://files.pythonhosted.org/packages/fa/63/1338cf0e273d95e07202e391b17198f27bd2cfef71348707eac98cd8db2b/hidapi-0.15.0-cp312-cp312-manylinux_2_28_i686.whl", hash = "sha256:908e4b8c35041aa800b51094fd9fbaa5d6161bfb71e55821f43c6433f54e2865", size = 1689689, upload-time = "2025-12-09T09:45:31.195Z" }, + { url = "https://files.pythonhosted.org/packages/84/08/28ce99698e14bb6bdf9d0dcf7ab45ab86d8bade894885aa15bb3fa43bcc1/hidapi-0.15.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f9356fffebf723ccfbf0f9370d3725de37b3e2f1f6bffc40c3466f854542861c", size = 713596, upload-time = "2025-12-09T09:45:34.564Z" }, + { url = "https://files.pythonhosted.org/packages/a5/16/23e446e2d0ad0d75de149d94f8e6313329aec4d7c18d027e56f5a386779e/hidapi-0.15.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:fd082e13ca92bbd7a4ec65d9fbd9ef06ecd71798ce9f036b7144b3d498623a7e", size = 697243, upload-time = "2025-12-09T09:45:36.537Z" }, + { url = "https://files.pythonhosted.org/packages/af/ef/e2fa4d795235eb09951f806bbfdbd766d1be805df448d67918e828b7cb31/hidapi-0.15.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:35eb5797d86306bd67aee80ad8973ab0a0b4e6d54018d3efb328767d0e8c9373", size = 717599, upload-time = "2025-12-09T09:45:37.825Z" }, + { url = "https://files.pythonhosted.org/packages/e7/a7/9b7fe9acd09ed90d702f8cc01190ab53e01d5022c21808b079144bc9017d/hidapi-0.15.0-cp312-cp312-win32.whl", hash = "sha256:ce6f99554dae15c48cd89a12d5aede77a92a8bd184b45d0b257a17c97e053cdb", size = 60136, upload-time = "2025-12-09T09:45:40.612Z" }, + { url = "https://files.pythonhosted.org/packages/7f/35/f9e2d3ead60b573140546b041dd41c78a48c0e6b573d61a03130adccd32e/hidapi-0.15.0-cp312-cp312-win_amd64.whl", hash = "sha256:9abc71a62bf8a8f1d70a6fd3613f86feb37ddb67e05ed934e734df79e27bf4f6", size = 67073, upload-time = "2025-12-09T09:45:39.261Z" }, + { url = "https://files.pythonhosted.org/packages/d1/19/bda2dce4af8b8028e6d611d5caf60e223a4d32a638d1155eefdc6d8f2462/hidapi-0.15.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f336dd2dc308928d54a6fdce137814a941a3633ad6722919d7a3142dd99005c2", size = 69045, upload-time = "2025-12-09T09:45:41.534Z" }, + { url = "https://files.pythonhosted.org/packages/1b/18/9b7d6b6a7561654b9b0d7b22fba9c5c971b36f5fc541b04e02d71928349d/hidapi-0.15.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8017f2213810c3e57d9ab64c2328c593a1129b25804240108a6451fe35cde193", size = 68755, upload-time = "2025-12-09T09:45:42.464Z" }, + { url = "https://files.pythonhosted.org/packages/f1/b0/f144cee5ce9e45518d234fd07c7a4f9a66de3a78ed42bf9f3f4083165e10/hidapi-0.15.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:208c7fc89f78c3448d55957c3e12893778aa91218ed7dc22697e554f091fe4fe", size = 1072286, upload-time = "2025-12-09T09:45:47.236Z" }, + { url = "https://files.pythonhosted.org/packages/47/bf/7fee015cc48b719bd40c6416f65084a900e7889f0cf52fcfedb9be107b10/hidapi-0.15.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:90f8bd963ae43a44036af5d689d090fab0e3eb52861186e4d2b863dd910a2726", size = 1609789, upload-time = "2025-12-09T09:45:44.112Z" }, + { url = "https://files.pythonhosted.org/packages/d3/b6/ac33532711c2ecda5a22d8b2db3223c8a13f16db6d2793ee73a809cca5b6/hidapi-0.15.0-cp313-cp313-manylinux_2_28_i686.whl", hash = "sha256:cd6edc7af885755163f4ffb29639c14f8a4ad5cca13ed641e47aa11f9646d8b6", size = 1680636, upload-time = "2025-12-09T09:45:45.67Z" }, + { url = "https://files.pythonhosted.org/packages/a3/e1/5b4426b8a77f9efbef70c0dbc1d64602d36ea8878f76c3607959901c978c/hidapi-0.15.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6ef0f80b2ff32ba446c9a2d4bfca6623ab5f0c05d742a0161ea1f7af2dc33b12", size = 708617, upload-time = "2025-12-09T09:45:48.874Z" }, + { url = "https://files.pythonhosted.org/packages/bb/a6/1ecdadc3e19f6755eed40f2111f447ec071e3d4f5f4f99b95d26a6d67219/hidapi-0.15.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:6106d10873c1e465a2764e979fb96ec0d16bb926eedac829310830f90816cc76", size = 689589, upload-time = "2025-12-09T09:45:50.328Z" }, + { url = "https://files.pythonhosted.org/packages/d6/02/579af4220acdde5ab76d091edaa92f62e110f89131a56b11856f77779607/hidapi-0.15.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4f7a710df215ea3b53a81029488f78e7303b83f01a2e8478db20d7c9756588dd", size = 710213, upload-time = "2025-12-09T09:45:51.747Z" }, + { url = "https://files.pythonhosted.org/packages/f9/c7/52f2d903a607f024086829349383dcc7f0c22533fc242f26f9754eba2f06/hidapi-0.15.0-cp313-cp313-win32.whl", hash = "sha256:e4ddb57e71e2b8aca2c685b94b8587dc7d7cfae3c529a7c4076ba0775eb28d4a", size = 59749, upload-time = "2025-12-09T09:45:53.882Z" }, + { url = "https://files.pythonhosted.org/packages/d9/00/7dd3f866a748b0c9eb4adedaa025ec8c533ed1946e9e918661c948a5c0f4/hidapi-0.15.0-cp313-cp313-win_amd64.whl", hash = "sha256:04e0c1ed6d742bc6e1d00599394bec6b2afb8ee2fc5a0a183d3c4c2d4e315c34", size = 66362, upload-time = "2025-12-09T09:45:52.797Z" }, + { url = "https://files.pythonhosted.org/packages/0a/65/e9c70deb396f5baea92cd2ea7803914a55d455c3982367bae4390483e6cc/hidapi-0.15.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:0a6bb079bcd5152dd8968893aecdcbde3954b849896078f54df2254c0d1f301e", size = 69532, upload-time = "2025-12-09T09:45:55.269Z" }, + { url = "https://files.pythonhosted.org/packages/05/eb/d006a7e28ec63b5db76340e4aca6ff077ad336f8fbe64ac804b968927011/hidapi-0.15.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:d80c745d285dfc9889e856226daa8eee6f9e83025ef2cb97e5fe0b1396f7818a", size = 69350, upload-time = "2025-12-09T09:45:56.695Z" }, + { url = "https://files.pythonhosted.org/packages/52/2b/95e7595eb61982c651ed2b3ecd9bae05a0298b668c801a992e4e814b22f2/hidapi-0.15.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b0f4411fd479345ca86742d9dda77200476a902749d8e47ed438f31ec98af418", size = 1065210, upload-time = "2025-12-09T09:46:02.484Z" }, + { url = "https://files.pythonhosted.org/packages/c7/a5/0344a7c223c3610510ec7f91f7220b0e5852c92d52aff23377f1c2888880/hidapi-0.15.0-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:d93f7173dcbec61b928f4cf800bf26535f81ef3b7bd89199237207f6ca7e4f43", size = 1610362, upload-time = "2025-12-09T09:45:58.533Z" }, + { url = "https://files.pythonhosted.org/packages/d9/96/781212cf852705bc1db26edd4340b01b9ce04cadd5d327c00b64f7324978/hidapi-0.15.0-cp314-cp314-manylinux_2_28_i686.whl", hash = "sha256:dacf0b9e5d776fde69b92d68c09ab878a09486629e1fe8e6ae851252b0f33ce5", size = 1680942, upload-time = "2025-12-09T09:46:00.971Z" }, + { url = "https://files.pythonhosted.org/packages/d1/48/6d51a37d3a81355740a82cd265d4fc8485a0a279e4b545cbf790bd106f51/hidapi-0.15.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:bda6ce04a77662e77a4b6b2598211935ca281e1cf663248d54bce75bd684fed8", size = 707550, upload-time = "2025-12-09T09:46:03.993Z" }, + { url = "https://files.pythonhosted.org/packages/86/77/175efa84ecea161c70948dc67b412b7444c2a196e4def8b305cb756077c6/hidapi-0.15.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:74900b5c7e884e0e4316d4deb746e5dd294b5aaaf90eac056c4f9f19cf2c8097", size = 689865, upload-time = "2025-12-09T09:46:05.976Z" }, + { url = "https://files.pythonhosted.org/packages/7e/29/0e513390a8d686448672fabd30385f9cda6e01663765851d0cef88e6f0b1/hidapi-0.15.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:76236468509dfcb256fa0fbd5a73708c598b3819695b7d08499ae489c82116b6", size = 706809, upload-time = "2025-12-09T09:46:07.309Z" }, + { url = "https://files.pythonhosted.org/packages/a2/d6/c0b9d8568fdb38eebb9caaa56643a81d92cccb5d59a5b44e0d975a0f3808/hidapi-0.15.0-cp314-cp314-win32.whl", hash = "sha256:901c03817306eac7edd589f09e0e07467871b45f665949cc4caa645b54abc97b", size = 61089, upload-time = "2025-12-09T09:46:09.59Z" }, + { url = "https://files.pythonhosted.org/packages/cb/14/3b27516b2867e53545164ebd8ef45d0c36857d2a062036e788e56e2e34ce/hidapi-0.15.0-cp314-cp314-win_amd64.whl", hash = "sha256:df0dd435562654e27ad0c7d045074eb1e1e016b09167dd48a258dec1ce4b9127", size = 67710, upload-time = "2025-12-09T09:46:08.677Z" }, + { url = "https://files.pythonhosted.org/packages/97/8f/ff034661faf99acaabe8954fb7db87c5242c79bf763dd972b23b1be5f104/hidapi-0.15.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:2f16e9ea8a1d24ac7a6bf316905823eafd58f78ec815a93118d4710faf95a224", size = 70893, upload-time = "2025-12-09T09:46:10.625Z" }, + { url = "https://files.pythonhosted.org/packages/d8/44/617666a0919d06521d732ef07998306b8ddcbd96038e19348c831acd76e8/hidapi-0.15.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:77f0965ca81a9be44694e84aa18e501d5cda49e372202a38a52d22acc38deadd", size = 71430, upload-time = "2025-12-09T09:46:11.648Z" }, + { url = "https://files.pythonhosted.org/packages/64/64/129aed49fa45b6a5e2fd955618eebe93ff7e3889f05237da6b45051c77bb/hidapi-0.15.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:541c85b8225ce19b38ceacd404283ca1857345ccfa7ca12f54131203ff2d7f75", size = 1094257, upload-time = "2025-12-09T09:46:17.529Z" }, + { url = "https://files.pythonhosted.org/packages/17/ca/266ca521ff643957823a7f04a67030f6d2917e86e31c60c3d4878cd6b913/hidapi-0.15.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:89f909a6783307c2f067e8e7948f38ec43386f15d04759d11e5b32e3b2fdf3d8", size = 1663756, upload-time = "2025-12-09T09:46:13.403Z" }, + { url = "https://files.pythonhosted.org/packages/38/de/eb9d0fe6d9ccd2645e993930cc7fdeb6e12ddcdec086f3ef7dec947286ca/hidapi-0.15.0-cp314-cp314t-manylinux_2_28_i686.whl", hash = "sha256:fd32f311633fad21a52e750775cd4ea23da912199c63d1331f4d8ae00ab0874b", size = 1710998, upload-time = "2025-12-09T09:46:15.68Z" }, + { url = "https://files.pythonhosted.org/packages/07/43/e5c0cd80df4a65a468b7a9fc6891ea77435a75e64bb273104c37f12e05b3/hidapi-0.15.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8f7ca38699557ab057333f1628a346073ae6088c09897b2cd9fbc68f4896780c", size = 751383, upload-time = "2025-12-09T09:46:19.183Z" }, + { url = "https://files.pythonhosted.org/packages/8c/8f/033e19857bcc3ad1b9880b5c0dfbaa7c945e577b7dee18919c1b2478484d/hidapi-0.15.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:1c4e707c82629637a311fa1a7adc1ae1386de7b539e84b4e37125e52e449318d", size = 722837, upload-time = "2025-12-09T09:46:20.707Z" }, + { url = "https://files.pythonhosted.org/packages/ff/f0/2b4e7ff42cdc6a6bf7def0591dfd3854af81038a1925ac1ed7a96578e834/hidapi-0.15.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:cc99bb16b359023190bf857297f6492a1c8441ccacff66dac881c9e278f5c262", size = 750923, upload-time = "2025-12-09T09:46:23.161Z" }, + { url = "https://files.pythonhosted.org/packages/bd/eb/c0676ff1f7e9ec9d99eaa428c66745cdab5b1742808f0c16061c85bd0b18/hidapi-0.15.0-cp314-cp314t-win32.whl", hash = "sha256:005c84c74c940a314b211674edc763a931ace0b63685ace88148496f563e25f9", size = 66288, upload-time = "2025-12-09T09:46:25.246Z" }, + { url = "https://files.pythonhosted.org/packages/cd/5b/f22dbf886824559d3e66d84710c5fb48c09856e6816885da97f2f51700eb/hidapi-0.15.0-cp314-cp314t-win_amd64.whl", hash = "sha256:e384430363d400c4ffb33fe65f22a61c9f288a870158dc5a9f5c9d917b7250c7", size = 74723, upload-time = "2025-12-09T09:46:24.36Z" }, + { url = "https://files.pythonhosted.org/packages/f2/c0/dc8d3fa85a5a4feb6ed8d79da9f5fefdd3b1607ce9220e1b54faa95927e5/hidapi-0.15.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3d220314975b99a083f4ee21abfd796a9ceeb99cbf3487a41c5eb80269102a4a", size = 71079, upload-time = "2025-12-09T09:46:40.112Z" }, + { url = "https://files.pythonhosted.org/packages/ff/c0/73d0b576332467e1ff84c51bef3eb21f07125835a7a580f4047919a67d1d/hidapi-0.15.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:bc61ab7f45dddbb2df2fca93565c575c0b035337d1e13ce2868c092ff24e9c47", size = 70800, upload-time = "2025-12-09T09:46:41.021Z" }, + { url = "https://files.pythonhosted.org/packages/03/a7/9819036af3a3f6ab58570c4292c28bfd42ac9898e04070f598e92939ac51/hidapi-0.15.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:aefc2afd146733e3ec8b33474bfb832c9281dd106309a216a20bade19dc0e95f", size = 1039182, upload-time = "2025-12-09T09:46:49.149Z" }, + { url = "https://files.pythonhosted.org/packages/fe/c4/f696f62702b75eccc4dbcf619e82cf773b0d892d2bcc7e9cf85aefb3d9f2/hidapi-0.15.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:ef3b43b1a4d4df8ea542487cda888138eaac4707db46bbca0f7bb78d97a06323", size = 1572449, upload-time = "2025-12-09T09:46:43.048Z" }, + { url = "https://files.pythonhosted.org/packages/2d/7c/e58643470288fc6132933475ba810482616ad08c333770f23e9d4c9f31ca/hidapi-0.15.0-cp39-cp39-manylinux_2_28_i686.whl", hash = "sha256:0608a882f06d7f37e0f1786e255e9a8b1e2386fdb8f9fb9d209b82682551f4b8", size = 1654806, upload-time = "2025-12-09T09:46:46.53Z" }, + { url = "https://files.pythonhosted.org/packages/32/a0/021d8f40ebed1f21a90c0f6c794657e3e9e9fe013788e2467bc5ce3b42f2/hidapi-0.15.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:788e16711a4530a2a7a8a4cd942f7250fa7f907a3be892b11d8eb51ec697739a", size = 667407, upload-time = "2025-12-09T09:46:50.51Z" }, + { url = "https://files.pythonhosted.org/packages/d9/a5/4c7355b133be152d1e085190e37880230a47575a853752a06a824cd37951/hidapi-0.15.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:48e79274384c23b250bc95016c8aa6497cf0a23b3a112d05376cbf5ace266886", size = 665330, upload-time = "2025-12-09T09:46:51.848Z" }, + { url = "https://files.pythonhosted.org/packages/cf/02/24656533d5b7d0047ca70f9abaeae8fd9fe694c32e91a88a5885652aa605/hidapi-0.15.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:c49aa659ecfe46a93a7057bca42208bfd123c2fcbdfd4a28eb1f9b8189260b68", size = 669911, upload-time = "2025-12-09T09:46:53.202Z" }, + { url = "https://files.pythonhosted.org/packages/dd/4e/7dd283c34d85898b4a259b8ea1116800de2b603256b20d19fad31bf88be4/hidapi-0.15.0-cp39-cp39-win32.whl", hash = "sha256:27d288d405bd4abedd047eefb9c4ab797d232454b35e17e7b61a03edf70c64e8", size = 60498, upload-time = "2025-12-09T09:46:55.587Z" }, + { url = "https://files.pythonhosted.org/packages/bb/8c/b51934ac6dd24409d94b949c143bb55207a3a8213a520671cbdd95f24f42/hidapi-0.15.0-cp39-cp39-win_amd64.whl", hash = "sha256:cb835b81624a72830e5ebebde26b93ab5ee812de648386a47d13dfbf64b348c7", size = 67432, upload-time = "2025-12-09T09:46:54.653Z" }, +] + [[package]] name = "idna" version = "3.11" @@ -859,6 +937,7 @@ name = "switch-pico-bridge" version = "0.1.0" source = { editable = "." } dependencies = [ + { name = "hidapi" }, { name = "pysdl3" }, { name = "pyserial" }, { name = "rich" }, @@ -866,6 +945,7 @@ dependencies = [ [package.metadata] requires-dist = [ + { name = "hidapi" }, { name = "pysdl3" }, { name = "pyserial" }, { name = "rich" },