swbt-python 0.5.2__tar.gz → 0.5.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {swbt_python-0.5.2 → swbt_python-0.5.3}/PKG-INFO +2 -2
- {swbt_python-0.5.2 → swbt_python-0.5.3}/pyproject.toml +2 -2
- swbt_python-0.5.3/spec/complete/unit_083/PROFILE_SCHEMA_V2_CURRENT_KEY_ONLY.md +133 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/spec/hardware-test-log.md +4 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/spec/initial/lifecycle.md +3 -3
- swbt_python-0.5.3/spec/wip/unit_084/BUMBLE_0_0_233.md +142 -0
- swbt_python-0.5.3/src/swbt/transport/_bumble_acl.py +17 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/src/swbt/transport/_bumble_key_store.py +2 -28
- {swbt_python-0.5.2 → swbt_python-0.5.3}/src/swbt/transport/_bumble_lifecycle.py +28 -54
- {swbt_python-0.5.2 → swbt_python-0.5.3}/src/swbt/transport/_pairing_profile.py +7 -11
- {swbt_python-0.5.2 → swbt_python-0.5.3}/src/swbt/transport/bumble.py +8 -7
- {swbt_python-0.5.2 → swbt_python-0.5.3}/tests/hardware/test_pairing_profile.py +4 -4
- {swbt_python-0.5.2 → swbt_python-0.5.3}/tests/integration/test_pairing_profile.py +3 -4
- {swbt_python-0.5.2 → swbt_python-0.5.3}/tests/unit/fixtures/source_audit/switch_protocol_values.toml +9 -11
- {swbt_python-0.5.2 → swbt_python-0.5.3}/tests/unit/test_bumble_acl.py +2 -3
- {swbt_python-0.5.2 → swbt_python-0.5.3}/tests/unit/test_bumble_lifecycle.py +7 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/tests/unit/test_bumble_transport.py +75 -21
- {swbt_python-0.5.2 → swbt_python-0.5.3}/tests/unit/test_pairing_profile.py +34 -14
- {swbt_python-0.5.2 → swbt_python-0.5.3}/tests/unit/test_pairing_profile_runtime.py +15 -4
- swbt_python-0.5.2/src/swbt/transport/_bumble_acl.py +0 -33
- {swbt_python-0.5.2 → swbt_python-0.5.3}/AGENTS.md +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/LICENSE +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/README.md +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/examples/hardware_bringup.py +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/examples/pairing_probe.py +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/examples/tap_a.py +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/spec/complete/unit_001/M0_PROTOCOL_CORE.md +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/spec/complete/unit_002/M1_SWITCH_GAMEPAD_FAKE_TRANSPORT.md +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/spec/complete/unit_003/M2_BUMBLE_HID_TRANSPORT.md +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/spec/complete/unit_004/M3_PAIRING_L2CAP.md +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/spec/complete/unit_005/M4_SUBCOMMAND_RESPONDER_HARDWARE.md +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/spec/complete/unit_006/M5_INPUT_OPERATION_API.md +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/spec/complete/unit_007/M6_RECONNECT_KEYSTORE_DIAGNOSTICS.md +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/spec/complete/unit_008/M7_PACKAGING_EXAMPLES_CLI.md +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/spec/complete/unit_009/PORTING_SOURCE_AUDIT.md +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/spec/complete/unit_010/DIAGNOSTICS_TRACE_SCHEMA.md +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/spec/complete/unit_011/HARDWARE_TEST_LOG_MATRIX.md +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/spec/complete/unit_012/INITIAL_RELEASE_GATE.md +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/spec/complete/unit_013/POST_M5_INPUT_SEMANTIC_CHARACTERIZATION.md +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/spec/complete/unit_014/DEVICE_CLOSE_GRACEFUL_DISCONNECT.md +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/spec/complete/unit_015/CONTEXT_MANAGER_RESOURCE_SCOPE.md +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/spec/complete/unit_016/JSON_KEY_STORE_CURRENT_PREVIOUS.md +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/spec/complete/unit_017/SWITCH_GAMEPAD_API_HARDENING.md +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/spec/complete/unit_018/KEY_STORE_TRANSPORT_BOUNDARY.md +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/spec/complete/unit_019/TRANSPORT_RECONNECT_CONTRACT.md +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/spec/complete/unit_020/STRUCTURAL_REFACTOR_BOUNDARIES.md +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/spec/complete/unit_021/SWITCH_GAMEPAD_INPUT_API_CONTRACT.md +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/spec/complete/unit_022/PUBLIC_API_USAGE_HARDWARE_DOCS.md +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/spec/complete/unit_023/MKDOCS_DOCUMENTATION_SITE.md +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/spec/complete/unit_024/STICK_INPUT_SHORTHAND_API.md +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/spec/complete/unit_025/IMU_INPUT_SHORTHAND_API.md +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/spec/complete/unit_026/LINUX_MACOS_ADAPTER_EXPERIMENTAL.md +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/spec/complete/unit_027/ADAPTER_DISCOVERY_API.md +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/spec/complete/unit_028/CONTROLLER_PROFILE_CUSTOMIZATION.md +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/spec/complete/unit_029/CONTROLLER_PROFILE_INJECTION.md +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/spec/complete/unit_030/JOYCON_PROFILE_IDENTITY_SPI.md +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/spec/complete/unit_031/JOYCON_INPUT_REPORT_MAPPING.md +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/spec/complete/unit_032/PROFILE_AWARE_SUBCOMMAND_STATE.md +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/spec/complete/unit_033/PROFILE_AWARE_BUMBLE_SDP.md +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/spec/complete/unit_034/JOYCON_CONVENIENCE_API_DOCS.md +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/spec/complete/unit_035/JOYCON_DEVICE_INFO_ADDRESS_WIRING.md +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/spec/complete/unit_036/JOYCON_SDP_IDENTITY_POLICY.md +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/spec/complete/unit_037/JOYCON_DEFAULT_CONTROLLER_COLORS.md +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/spec/complete/unit_038/REARCHITECTURE_DECISION_BOUNDARY_TESTS.md +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/spec/complete/unit_039/CONTROLLER_RUNTIME_EXTRACTION.md +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/spec/complete/unit_040/PUBLIC_CONTROLLER_API_MODEL.md +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/spec/complete/unit_041/CONTROLLER_CONFIG_PROFILE_OWNERSHIP.md +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/spec/complete/unit_042/INTERNAL_TRANSPORT_FACTORY.md +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/spec/complete/unit_043/PROTOCOL_PROFILE_MODULE_SPLIT.md +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/spec/complete/unit_044/REARCHITECTURE_DOCS_RELEASE_MATRIX.md +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/spec/complete/unit_045/INTERNAL_API_BOUNDARY_CLEANUP.md +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/spec/complete/unit_046/HARDWARE_PROFILE_TEST_SCENARIOS.md +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/spec/complete/unit_047/VIRTUAL_GYRO_CALIBRATION.md +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/spec/complete/unit_048/VIRTUAL_ACCELEROMETER_CALIBRATION.md +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/spec/complete/unit_049/IMU_SESSION_AND_ENCODING_REDESIGN.md +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/spec/complete/unit_050/DIRECT_REPORTING_TYPES.md +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/spec/complete/unit_051/CSR_BD_ADDR_REWRITE_EXPERIMENT.md +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/spec/complete/unit_052/EXP_LOCAL_ADDRESS_PROFILE.md +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/spec/complete/unit_053/EXP_LOCAL_ADDRESS_PROFILE_JOYCON.md +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/spec/complete/unit_054/EXP_LOCAL_ADDRESS_PROFILE_DIRECT.md +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/spec/complete/unit_055/REMOVE_NATIVE_KEY_STORE_API.md +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/spec/complete/unit_056/PAIRING_PROFILE_NAMING.md +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/spec/complete/unit_057/PAIRING_PROFILE_CONTROLLER_KIND.md +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/spec/complete/unit_058/MULTI_ADDRESS_RECONNECT.md +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/spec/complete/unit_059/EXPERIMENTAL_DIRECT_SEND_TIMING_PROBE.md +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/spec/complete/unit_060/HCI_ACL_COMPLETION_CORRELATION.md +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/spec/complete/unit_061/EXPERIMENTAL_CLASSIC_LINK_POLICY_AB.md +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/spec/complete/unit_062/PERIODIC_ACL_DRAIN_CHARACTERIZATION.md +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/spec/complete/unit_063/PERIODIC_NO_DRAIN_CHARACTERIZATION.md +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/spec/complete/unit_064/BUMBLE_ENQUEUE_COMPLETION_CONTRACT.md +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/spec/complete/unit_065/PERIODIC_DEADLINE_SCHEDULER.md +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/spec/complete/unit_066/PAIRING_PROFILE_DEFAULT_ADDRESS.md +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/spec/complete/unit_067/DEFAULT_BATTERY_STATUS.md +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/spec/complete/unit_068/DIRECT_PRE_CLOSE_INPUT_REFLECTION.md +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/spec/complete/unit_069/CONTROLLER_HANDSHAKE_READINESS.md +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/spec/complete/unit_070/DIAGNOSTICS_AND_UNUSED_PATHS_CLEANUP.md +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/spec/complete/unit_071/CONTROLLER_CONSTRUCTION_PATH.md +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/spec/complete/unit_072/CONNECTION_RUNTIME_INTEGRATION.md +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/spec/complete/unit_073/TEST_CONTRACT_BOUNDARY_CLEANUP.md +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/spec/complete/unit_074/PUBLIC_GAMEPAD_IMPLEMENTATION_OWNERSHIP.md +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/spec/complete/unit_075/CONCRETE_CONTROLLER_MODULE.md +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/spec/complete/unit_076/HARDWARE_HAPPY_CASE_VALIDATION.md +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/spec/complete/unit_077/RUNTIME_INPUT_UPDATE_DISPATCH.md +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/spec/complete/unit_078/TEST_RUNTIME_FIXTURES.md +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/spec/complete/unit_079/PAIRING_PROFILE_KEY_STORE.md +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/spec/complete/unit_080/TRANSPORT_PROTOCOL_CONTRACT.md +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/spec/complete/unit_081/PROTOCOL_HANDSHAKE.md +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/spec/complete/unit_082/REPLY_HOLDOFF_VALIDATION.md +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/spec/dev-journal.md +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/spec/initial/README.md +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/spec/initial/api.md +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/spec/initial/architecture.md +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/spec/initial/naming.md +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/spec/initial/protocol.md +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/spec/initial/risks.md +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/spec/initial/roadmap.md +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/spec/initial/testing.md +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/spec/initial/transport-bumble.md +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/spec/rearchitecture/01-design-change-overview.md +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/spec/rearchitecture/02-as-is-to-be.md +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/spec/rearchitecture/03-public-api-config-profile.md +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/spec/rearchitecture/04-runtime-profile-transport-details.md +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/spec/rearchitecture/05-milestones-implementation.md +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/spec/rearchitecture/README.md +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/spec/rearchitecture/mkdocs-nav-snippet.yml +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/src/swbt/__init__.py +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/src/swbt/adapter_discovery.py +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/src/swbt/diagnostics.py +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/src/swbt/errors.py +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/src/swbt/gamepad/__init__.py +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/src/swbt/gamepad/_config.py +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/src/swbt/gamepad/connection.py +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/src/swbt/gamepad/constants.py +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/src/swbt/gamepad/controllers.py +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/src/swbt/gamepad/interface.py +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/src/swbt/gamepad/output.py +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/src/swbt/gamepad/protocol_handshake.py +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/src/swbt/gamepad/runtime.py +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/src/swbt/gamepad/transport_factory.py +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/src/swbt/imu.py +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/src/swbt/input.py +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/src/swbt/probe.py +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/src/swbt/protocol/__init__.py +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/src/swbt/protocol/buttons.py +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/src/swbt/protocol/descriptors.py +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/src/swbt/protocol/imu_report.py +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/src/swbt/protocol/input_report.py +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/src/swbt/protocol/output_report.py +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/src/swbt/protocol/profiles/__init__.py +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/src/swbt/protocol/profiles/base.py +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/src/swbt/protocol/profiles/joycon.py +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/src/swbt/protocol/profiles/pro_controller.py +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/src/swbt/protocol/rumble.py +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/src/swbt/protocol/session.py +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/src/swbt/protocol/spi.py +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/src/swbt/protocol/subcommand.py +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/src/swbt/py.typed +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/src/swbt/report_loop.py +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/src/swbt/state_store.py +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/src/swbt/transport/__init__.py +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/src/swbt/transport/_adapter_identity.py +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/src/swbt/transport/_bumble_hidp.py +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/src/swbt/transport/_bumble_sdp.py +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/src/swbt/transport/_bumble_usb_devices.py +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/src/swbt/transport/_csr_bd_addr.py +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/src/swbt/transport/_csr_bd_addr_harness.py +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/src/swbt/transport/base.py +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/src/swbt/transport/fake.py +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/tests/__init__.py +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/tests/conftest.py +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/tests/gamepad_factory.py +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/tests/hardware/README.md +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/tests/hardware/test_battery_status.py +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/tests/hardware/test_bumble_transport.py +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/tests/hardware/test_close_disconnect.py +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/tests/hardware/test_context_manager_resource_scope.py +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/tests/hardware/test_controller_colors.py +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/tests/hardware/test_multi_address_reconnect.py +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/tests/hardware/test_pairing_l2cap.py +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/tests/hardware/test_reply_holdoff.py +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/tests/integration/__init__.py +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/tests/integration/test_examples.py +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/tests/integration/test_switch_gamepad_fake_transport.py +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/tests/unit/__init__.py +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/tests/unit/test_adapter_discovery.py +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/tests/unit/test_adapter_identity.py +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/tests/unit/test_bumble_hidp.py +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/tests/unit/test_bumble_sdp.py +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/tests/unit/test_ci_workflow.py +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/tests/unit/test_csr_bd_addr_experiment.py +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/tests/unit/test_csr_bd_addr_harness.py +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/tests/unit/test_diagnostics.py +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/tests/unit/test_gamepad_config.py +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/tests/unit/test_gamepad_connection_workflow.py +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/tests/unit/test_gamepad_output_dispatcher.py +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/tests/unit/test_gamepad_transport_factory.py +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/tests/unit/test_imu_report.py +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/tests/unit/test_input_report.py +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/tests/unit/test_input_state.py +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/tests/unit/test_output_report.py +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/tests/unit/test_package_import.py +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/tests/unit/test_package_metadata.py +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/tests/unit/test_probe_cli.py +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/tests/unit/test_protocol_boundary.py +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/tests/unit/test_protocol_handshake.py +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/tests/unit/test_protocol_profile.py +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/tests/unit/test_protocol_session.py +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/tests/unit/test_public_api_boundary.py +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/tests/unit/test_publish_workflow.py +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/tests/unit/test_report_loop.py +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/tests/unit/test_rumble_state.py +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/tests/unit/test_source_audit_fixtures.py +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/tests/unit/test_subcommand_responder.py +0 -0
- {swbt_python-0.5.2 → swbt_python-0.5.3}/tests/unit/test_virtual_spi_flash.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: swbt-python
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.3
|
|
4
4
|
Summary: Python library for presenting an NX-compatible virtual Bluetooth HID input device.
|
|
5
5
|
Keywords: bluetooth,bluetooth-hid,controller,gamepad,hid,nx
|
|
6
6
|
Author: niart120
|
|
@@ -18,7 +18,7 @@ Classifier: Programming Language :: Python :: 3.12
|
|
|
18
18
|
Classifier: Programming Language :: Python :: 3.13
|
|
19
19
|
Classifier: Topic :: Software Development :: Libraries
|
|
20
20
|
Classifier: Typing :: Typed
|
|
21
|
-
Requires-Dist: bumble
|
|
21
|
+
Requires-Dist: bumble==0.0.233
|
|
22
22
|
Maintainer: niart120
|
|
23
23
|
Maintainer-email: niart120 <38847256+niart120@users.noreply.github.com>
|
|
24
24
|
Requires-Python: >=3.12
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "swbt-python"
|
|
3
|
-
version = "0.5.
|
|
3
|
+
version = "0.5.3"
|
|
4
4
|
description = "Python library for presenting an NX-compatible virtual Bluetooth HID input device."
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
authors = [
|
|
@@ -34,7 +34,7 @@ classifiers = [
|
|
|
34
34
|
"Typing :: Typed",
|
|
35
35
|
]
|
|
36
36
|
dependencies = [
|
|
37
|
-
"bumble
|
|
37
|
+
"bumble==0.0.233",
|
|
38
38
|
]
|
|
39
39
|
|
|
40
40
|
[project.scripts]
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
# Pairing profile schema v2: current key only
|
|
2
|
+
|
|
3
|
+
## 1. 概要
|
|
4
|
+
|
|
5
|
+
### 1.1 目的
|
|
6
|
+
|
|
7
|
+
pairing profile schema を v2 に上げ、現在選択中の Bluetooth address namespace だけへ pairing key を保存する。v1 profile は runtime で読み替えず、adapter を開く前に再ペアリングを案内する `InvalidProfileError` で拒否する。
|
|
8
|
+
|
|
9
|
+
### 1.2 起点 / source
|
|
10
|
+
|
|
11
|
+
| source | 内容 | path |
|
|
12
|
+
|---|---|---|
|
|
13
|
+
| GitHub Issue #143 | previous pairing key 世代の廃止、schema v2、破壊的変更の利用者案内 | `https://github.com/niart120/swbt-python/issues/143` |
|
|
14
|
+
| 実装事実 | v1 は current key 更新時に `swbt.previous::` namespace を書き込む | `src/swbt/transport/_pairing_profile.py`, `src/swbt/transport/_bumble_key_store.py` |
|
|
15
|
+
|
|
16
|
+
### 1.3 use case
|
|
17
|
+
|
|
18
|
+
| actor / boundary | 入力または状態 | 期待する観測結果 | 制約 |
|
|
19
|
+
|---|---|---|---|
|
|
20
|
+
| 新規 explicit profile 利用者 | locally administered address を指定して profile を作成 | schema v2 と当該 address の空 namespace だけを保存する | previous namespace を作らない |
|
|
21
|
+
| adapter-default profile 利用者 | profile 作成後、power-on で current address が解決する | 解決済み address の current namespace だけを key 更新する | profile 作成時は空 map |
|
|
22
|
+
| v1 profile 利用者 | `profile_path` を runtime へ渡す | adapter open 前に v2 profile の作成と再ペアリングを示す `InvalidProfileError` | compatibility load / migration を提供しない |
|
|
23
|
+
|
|
24
|
+
## 2. 対象範囲
|
|
25
|
+
|
|
26
|
+
- schema version を 2 にする。
|
|
27
|
+
- new profile、key update、diagnostics から previous 世代を除く。
|
|
28
|
+
- current namespace の key lookup、複数 peer 拒否、atomic save、identity guard を維持する。
|
|
29
|
+
- fake transport の pairing / reconnect と explicit / adapter-default variant を確認する。
|
|
30
|
+
- 公開 docs と release notes に破壊的変更と再ペアリング手順を記載する。
|
|
31
|
+
|
|
32
|
+
## 3. 対象外
|
|
33
|
+
|
|
34
|
+
- BD_ADDR 切替、identity kind、CSR identity preparation の変更。
|
|
35
|
+
- previous key fallback、復元 API / CLI、v1 互換読込、自動または in-place migration。
|
|
36
|
+
- 実機 pairing / reconnect の実行。
|
|
37
|
+
|
|
38
|
+
## 4. 関連 docs
|
|
39
|
+
|
|
40
|
+
- `spec/initial/architecture.md`
|
|
41
|
+
- `spec/initial/api.md`
|
|
42
|
+
- `spec/initial/lifecycle.md`
|
|
43
|
+
- `spec/initial/testing.md`
|
|
44
|
+
- `docs/usage.md`
|
|
45
|
+
- `docs/release-notes.md`
|
|
46
|
+
|
|
47
|
+
## 5. 根拠監査
|
|
48
|
+
|
|
49
|
+
| 項目 | 要否 | 状態 | 根拠 / 理由 |
|
|
50
|
+
|---|---|---|---|
|
|
51
|
+
| Switch HID / report bytes | not applicable | not applicable | report format、subcommand、input timing を変更しない。 |
|
|
52
|
+
| Bumble / transport | not applicable | not applicable | Bumble API / SDP / L2CAP の仮定を追加せず、既存 key-store adapter の保存世代だけを変更する。 |
|
|
53
|
+
| OS / driver / adapter | not applicable | not applicable | adapter open や driver 操作を変更しない。 |
|
|
54
|
+
|
|
55
|
+
## 6. 振る舞い仕様
|
|
56
|
+
|
|
57
|
+
| 振る舞い | 入力・状態 | 期待結果 | 備考 |
|
|
58
|
+
|---|---|---|---|
|
|
59
|
+
| v2 profile creation | explicit address | current address namespace だけを持つ schema v2 JSON | atomic create を維持 |
|
|
60
|
+
| v1 early rejection | schema v1 JSON | adapter open 前に v2 と再ペアリングを案内する `InvalidProfileError` | migration しない |
|
|
61
|
+
| current-only update | current key を新しい peer key で更新 | current namespace は新しい 1 peer だけ、previous namespace は作成しない | active lookup は current のみ |
|
|
62
|
+
| diagnostics | key update 成功 / 失敗 | status、peer_address、失敗時 error_type/message だけを記録 | key material、generation、previous_saved は出さない |
|
|
63
|
+
|
|
64
|
+
## 7. TDD Test List
|
|
65
|
+
|
|
66
|
+
| status | item | type | layer | hardware | notes |
|
|
67
|
+
|---|---|---|---|---|---|
|
|
68
|
+
| refactor-skipped | explicit address の新規 profile は schema v2 と current namespace だけを保存する | regression | unit | no | `test_pairing_profile_create_new_atomically_saves_schema_v2_current_only_pro_envelope` が red から green。構造整理なし。 |
|
|
69
|
+
| refactor-skipped | schema v1 profile は adapter open 前に再作成・再ペアリングを案内して拒否する | regression | unit | no | loader と runtime の両方で v1 を拒否し、transport creation 前の停止を確認。構造整理なし。 |
|
|
70
|
+
| refactor-skipped | current key 更新は previous namespace を作らず、current key だけを置換する | regression | integration | no | `_PairingProfileKeyStore.update()` の 2 回更新を確認。構造整理なし。 |
|
|
71
|
+
| refactor-skipped | adapter-default profile は power-on 後に解決した current namespace だけを更新する | regression | unit / integration | no | existing Bumble fake device test で遅延 namespace 選択を確認。構造整理なし。 |
|
|
72
|
+
| refactor-skipped | key_store_update diagnostics は generation / previous_saved を記録しない | regression | unit | no | 成功・失敗 event の両方を確認。構造整理なし。 |
|
|
73
|
+
| refactor-skipped | v2 profile は fake transport pairing と active reconnect で current key を利用する | regression | integration | no | integration suite の fake transport pairing / reconnect と profile key-store update を通過。構造整理なし。 |
|
|
74
|
+
|
|
75
|
+
## 8. 文書検証計画
|
|
76
|
+
|
|
77
|
+
| document | audience / task | source of truth | mechanical check | review result | unresolved |
|
|
78
|
+
|---|---|---|---|---|---|
|
|
79
|
+
| `docs/usage.md` | v1 profile の利用者が v2 profile を作り直して再ペアリングする | Issue #143 と profile loader | `uv run --group docs mkdocs build --strict` | done | none |
|
|
80
|
+
| `docs/release-notes.md` | 破壊的変更と compatibility / migration 非提供を把握する | Issue #143 | `uv run --group docs mkdocs build --strict` | done | none |
|
|
81
|
+
|
|
82
|
+
## 9. 設計メモ
|
|
83
|
+
|
|
84
|
+
これは behavior change である。green 後の構造整理は、current-only 保存を変えない範囲に分離する。v1 error は loader に置き、adapter の生成・open より前に失敗させる。
|
|
85
|
+
|
|
86
|
+
## 10. 対象ファイル
|
|
87
|
+
|
|
88
|
+
| path | change | 内容 |
|
|
89
|
+
|---|---|---|
|
|
90
|
+
| `src/swbt/transport/_pairing_profile.py` | modify | v2 envelope creation と v1 rejection message |
|
|
91
|
+
| `src/swbt/transport/_bumble_key_store.py` | modify | previous generation を除く current-only update と diagnostics |
|
|
92
|
+
| `tests/unit/test_pairing_profile.py` | modify | v2 / v1 early rejection |
|
|
93
|
+
| `tests/unit/test_bumble_transport.py` | modify | diagnostics と adapter-default current namespace |
|
|
94
|
+
| `tests/integration/test_pairing_profile.py` | modify | fake transport pairing / reconnect の保存契約 |
|
|
95
|
+
| `docs/usage.md` | modify | v1 profile の再作成・再ペアリング |
|
|
96
|
+
| `docs/release-notes.md` | modify | breaking change |
|
|
97
|
+
| `spec/initial/lifecycle.md` | modify | current-only key store と v1 非互換の設計判断 |
|
|
98
|
+
|
|
99
|
+
## 11. 検証
|
|
100
|
+
|
|
101
|
+
| command | result | notes |
|
|
102
|
+
|---|---|---|
|
|
103
|
+
| `uv run pytest tests/unit -q --basetemp tmp/issue143-unit` | passed, 448 passed | profile loader、runtime、Bumble fake device、diagnostics を含む |
|
|
104
|
+
| `uv run pytest tests/integration -q --basetemp tmp/issue143-integration-all` | passed, 154 passed | fake transport pairing / reconnect と profile key-store update を含む |
|
|
105
|
+
| `uv run ruff format --check .` | passed | 106 files formatted |
|
|
106
|
+
| `uv run ruff check .` | passed | lint passed |
|
|
107
|
+
| `uv run ty check --no-progress` | passed | type check passed |
|
|
108
|
+
| `uv run --group docs mkdocs build --strict` | passed | public docs build passed |
|
|
109
|
+
| `uv build` | passed | `swbt_python-0.5.2.tar.gz` と `swbt_python-0.5.2-py3-none-any.whl` を作成 |
|
|
110
|
+
| `pytest -m bumble` / `pytest -m hardware` | not run | adapter / Switch 操作の明示承認がない。Issue の必須条件ではない。 |
|
|
111
|
+
|
|
112
|
+
## 12. 実機実行条件
|
|
113
|
+
|
|
114
|
+
| 項目 | 内容 |
|
|
115
|
+
|---|---|
|
|
116
|
+
| 実機要否 | not required |
|
|
117
|
+
| 承認範囲 | 実機確認は Issue 完了条件に含めない。実行する場合は別途明示承認を得る。 |
|
|
118
|
+
| adapter | not applicable |
|
|
119
|
+
| 実行遮断 | 環境変数による遮断は採用しない。明示承認、対象 adapter、command、cleanup plan で管理する |
|
|
120
|
+
| log / artifact | 実行時のみ `spec/hardware-test-log.md` に記録する |
|
|
121
|
+
| cleanup | 実機未実行 |
|
|
122
|
+
|
|
123
|
+
## 13. 先送り事項
|
|
124
|
+
|
|
125
|
+
- none
|
|
126
|
+
|
|
127
|
+
## 14. チェックリスト
|
|
128
|
+
|
|
129
|
+
- [x] 対象範囲と対象外を確認した
|
|
130
|
+
- [x] TDD Test List または文書検証計画を更新した
|
|
131
|
+
- [x] 必要な根拠監査を記録した
|
|
132
|
+
- [x] 実機実行条件を記録した
|
|
133
|
+
- [x] 検証結果または未実行理由を記録した
|
|
@@ -6,6 +6,10 @@ Bumble adapter と対象機器に依存する観測を記録する正本であ
|
|
|
6
6
|
|
|
7
7
|
## Current Status
|
|
8
8
|
|
|
9
|
+
- 2026-07-25 unit_084 Bumble 0.0.233 Switch 2 fresh pairing / Button A: Windows `Windows-11-10.0.26200-SP0` / dedicated `usb:0` / CSR8510 A10 / WinUSB / Bumble 0.0.233 / Python 3.13.5 / swbt-python 0.5.2 / Switch 2 firmware 22.5.0 で、`uv run pytest "tests/hardware/test_reply_holdoff.py::test_switch_reply_holdoff_variant_fresh_pairing_characterizes_readiness_and_a_input[0.3-True]" -m hardware --swbt-bumble-adapter usb:0 --swbt-hardware-artifact-dir tmp/hardware/unit_084/fresh-a-switch2-20260725` を実行し、`1 passed in 10.34s`。承認範囲は `usb:0` の Bumble adapter open、Pro Controller fresh pairing、HID advertising、Button A 送信、neutral close、adapter release である。BD_ADDR 書換えと active reconnect は実行していない。trace は fresh pairing、key store update、control / interrupt L2CAP、protocol ready、`reason=input` の `0x30` report、disconnect terminal `closed`、transport close complete を記録した。利用者は Switch 2 の画面上で Button A が反映されたこと、および firmware と driver identity が既存実験から不変であることを確認した。driver assignment の再照会はこの run ではしていない。artifact は `tmp/hardware/unit_084/fresh-a-switch2-20260725/reply-holdoff-fresh-periodic-0_300.json` と `tmp/hardware/unit_084/fresh-a-switch2-20260725/reply-holdoff-fresh-periodic-0_300.jsonl`。
|
|
10
|
+
|
|
11
|
+
- 2026-07-25 unit_084 Bumble 0.0.233 adapter open-only: Windows `Windows-11-10.0.26200-SP0` / `usb:0` / Bumble 0.0.233 / Python 3.13.5 / swbt-python 0.5.2 で、`uv run pytest tests/hardware/test_context_manager_resource_scope.py -m bumble --swbt-bumble-adapter usb:0 --swbt-hardware-artifact-dir tmp/hardware/unit_084/bumble-open-only` を実行し `1 passed in 0.36s`。承認範囲は Bumble adapter open、transport 初期化、close / adapter release だけである。trace は `transport_open_complete`、`disconnect_request(status=unavailable, reason=channels_not_connected)`、`transport_close_complete` を記録し、`advertising_start` と `host_connection` は記録しなかった。Switch pairing、HID advertising、report loop、Switch-facing message は実行していない。driver identity はこの run で再確認していない。artifact は `tmp/hardware/unit_084/bumble-open-only/resource-open-only.jsonl`。
|
|
12
|
+
|
|
9
13
|
- 2026-07-25 issue_139 reply holdoff comparison: Windows 11 `Windows-11-10.0.26200-SP0` / dedicated `usb:0` / Bumble 0.0.230 / Python 3.13.5 / swbt-python 0.5.1。既存の unit_066 profile は current public address `0E:08:71:C0:B4:5C` と namespace が一致していたが、Switch peer `C8:48:05:F7:B5:21` の authentication failure reason 5でprotocol handshake前に失敗した。当日の fresh pairing済み unit_081 Pro profileへ差し替えると、300 ms / 100 ms の PeriodicとDirectはいずれも active reconnect、protocol ready、Button A送信、5秒観測、neutral close、transport close completeまでpassした。300 ms はPeriodicでsubcommand reply 16件、handshake report-mode automatic report 2件、Directでreply 16件、handshake report-mode automatic report 3件だった。100 msはDirectでreply 16件、handshake report-mode automatic report 2件だったが、Periodicはreply 59件、handshake report-mode automatic report 895件まで増加し、`1 passed in 34.83s`だった。0 msはPeriodic / Directともsession stateが`protocol_ready=true`へ遷移したが、`ProtocolHandshake`がoutcomeを公開できず、public `protocol_ready` event 0件のままconnection timeoutとなった。Periodicはreply 231件、handshake report-mode automatic report 6,354件で外側の60秒実行上限後もchild processが残ったため、Switch-facing送信を停止し、`tools/csr_bd_addr_probe.py --adapter usb:0 --hci-reset`でHCI / CSR address一致と`adapter_closed`を確認した。Directもreply 89件、handshake report-mode automatic report 1,432件、connection timeoutを記録したが、後続のnormal closeで`transport_close_complete`を記録した。fresh pairingでは300 msが`1 passed in 10.23s`。100 msは`1 passed in 19.99s`だったが、利用者はButton A反映を確認できなかった。0 msは20秒timeoutで、利用者はSwitchがコントローラーとして認識しなかったと観測した。0 ms fresh traceはsession stateの`protocol_ready=true`後もoutcomeを公開できず、handshake report-mode automatic reportの送信キューが621件まで増えた。testの`finally`はcontrol / interrupt channel close、disconnect terminal `closed`、`transport_close_complete`を記録した。artifactは`build/hardware/unit_082/reply-holdoff-20260725-031941/`。driverはOS権限不足で今回再確認できず。承認範囲はadapter open、advertising、fresh pairing、active reconnect、neutral automatic report、Button A、neutral close、adapter release。BD_ADDR書換えは未実行。結論は300 msをshared automatic input holdoffとして維持する。ready後の新規subcommandは観測されず、initializing限定へ変更する根拠は得られなかった。
|
|
10
14
|
|
|
11
15
|
- 2026-07-25 unit_081 Direct fresh pairing recovery: `usb:0` の認証失敗後、ユーザ承認下で新しいadapter-default Pro profileをfresh pairingで作成し、同じprofileで `test_switch_direct_adapter_default_profile_stops_automatic_reports_after_ready` を実行した。Windows 11 / CSR8510 A10 / WinUSB / Bumble 0.0.230 / Python 3.13.5 / swbt-python 0.5.1 で `2 passed in 9.87s`。Direct reconnect traceはbootstrap neutral 2件、最初の`0x02`でbootstrap停止、`0x03 30`後の`handshake_report_mode` neutral 3件、nonzero player lights reply後の`protocol_ready`、ready後1秒の自動report 0件、終了時direct neutral 1件、transport close completeを記録した。fresh pairingもroute=`pairing`でprotocol readyとtransport close completeを記録した。artifactは `tmp/hardware/unit_081/direct-fresh-20260725/`。advertising、fresh pairing、neutral-only HID report、Direct reconnect、neutral close、adapter releaseは承認範囲内。non-neutral inputとBD_ADDR書換えは未実行。
|
|
@@ -202,9 +202,9 @@ reconnect は M6 で扱う。初期実装では次だけを考慮する。
|
|
|
202
202
|
- injected transport では、`SwitchGamepad.profile_path is None` だけを根拠に reconnect 用 storage の有無を判断しない
|
|
203
203
|
- pairing 情報の保存有無を diagnostics に記録する
|
|
204
204
|
- current peer は自動 reconnect 対象の 1 件だけに正規化する
|
|
205
|
-
- pair 成功時、その peer を current
|
|
206
|
-
- previous
|
|
207
|
-
-
|
|
205
|
+
- pair 成功時、その peer を current にする。current namespace の既存 key は保持しない。
|
|
206
|
+
- pairing profile schema v2 は current key だけを保存する。previous generation、復元経路、schema v1 の互換読込は提供しない。
|
|
207
|
+
- schema v1 profile と複数 current peer を含む key store は自動移行せず、新しい profile path での再作成と再 pairing を要求する。
|
|
208
208
|
|
|
209
209
|
M6 では次を追加する。
|
|
210
210
|
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
# Bumble 0.0.233 への固定と transport 互換分岐の整理
|
|
2
|
+
|
|
3
|
+
## 1. 概要
|
|
4
|
+
|
|
5
|
+
### 1.1 目的
|
|
6
|
+
|
|
7
|
+
Bumble の正式対応を `0.0.233` だけに固定し、transport 実装を同版の API shape に合わせる。旧版を許容する動的分岐を削除し、最新 Bumble による nonblocking canary CI で将来の差分を検知する。
|
|
8
|
+
|
|
9
|
+
### 1.2 起点 / source
|
|
10
|
+
|
|
11
|
+
| source | 内容 | path |
|
|
12
|
+
|---|---|---|
|
|
13
|
+
| GitHub Issue | Bumble 0.0.233 への固定、互換分岐整理、canary CI の完了条件 | `https://github.com/niart120/swbt-python/issues/144` |
|
|
14
|
+
| 初期設計 | Bumble 依存を transport 境界内に閉じる | `spec/initial/transport-bumble.md` |
|
|
15
|
+
| 初期設計 | fake transport と Bumble / hardware test の分離 | `spec/initial/testing.md` |
|
|
16
|
+
|
|
17
|
+
### 1.3 use case
|
|
18
|
+
|
|
19
|
+
| actor / boundary | 入力または状態 | 期待する観測結果 | 制約 |
|
|
20
|
+
|---|---|---|---|
|
|
21
|
+
| ライブラリ利用者 | `swbt` を import | Bumble を import / 解決しない | lazy import 境界を維持する |
|
|
22
|
+
| CI | lock に固定された dependency | Bumble 0.0.233 で unit / integration が通る | adapter は開かない |
|
|
23
|
+
| 保守者 | schedule / 手動の canary workflow | 最新 Bumble の失敗を通常 CI と独立して観測できる | required check にしない |
|
|
24
|
+
|
|
25
|
+
## 2. 対象範囲
|
|
26
|
+
|
|
27
|
+
- `bumble==0.0.233` への dependency と lock の更新。
|
|
28
|
+
- Bumble 0.0.233 source に照合した transport の不要な互換分岐と専用 test の削除。
|
|
29
|
+
- remaining workaround の理由を本仕様へ記録。
|
|
30
|
+
- latest Bumble で unit / integration を実行する nonblocking canary workflow。
|
|
31
|
+
|
|
32
|
+
## 3. 対象外
|
|
33
|
+
|
|
34
|
+
- 複数 Bumble version の同時対応と runtime version check。
|
|
35
|
+
- 明示承認なしの Bumble adapter、Switch pairing、HID advertising、report loop の実行。
|
|
36
|
+
- HID report、subcommand、SPI、SDP 値、report timing の変更。
|
|
37
|
+
- BD_ADDR 切替機能の縮小。
|
|
38
|
+
|
|
39
|
+
## 4. 関連 docs
|
|
40
|
+
|
|
41
|
+
- `spec/initial/architecture.md`
|
|
42
|
+
- `spec/initial/transport-bumble.md`
|
|
43
|
+
- `spec/initial/lifecycle.md`
|
|
44
|
+
- `spec/initial/testing.md`
|
|
45
|
+
|
|
46
|
+
## 5. 根拠監査
|
|
47
|
+
|
|
48
|
+
| 項目 | 要否 | 状態 | 根拠 / 理由 |
|
|
49
|
+
|---|---|---|---|
|
|
50
|
+
| Switch HID / report bytes | not applicable | not applicable | protocol 値を変更しない |
|
|
51
|
+
| Bumble / transport | required | done | 0.0.230 と 0.0.233 の installed source を照合した |
|
|
52
|
+
| OS / driver / adapter | required | partial | dedicated `usb:0` / CSR8510 A10 / WinUSB と Switch 2 firmware 22.5.0 は利用者が既存実験から不変と確認した。driver assignment の再照会は今回未実行 |
|
|
53
|
+
|
|
54
|
+
### 根拠監査の結果
|
|
55
|
+
|
|
56
|
+
| 項目 | 値 / 判断 | 根拠分類 | source | status |
|
|
57
|
+
|---|---|---|---|---|
|
|
58
|
+
| HID SET_REPORT callback | `hid.Device.register_set_report_cb()` は 0.0.233 に存在するため、存在確認 fallback を削除する | source fact | `.venv/Lib/site-packages/bumble/hid.py:470-473` | done |
|
|
59
|
+
| connection event | `Connection` の diagnostics event 定数は 0.0.233 に存在するため、event 名文字列 fallback を削除する | source fact | `.venv/Lib/site-packages/bumble/device.py:1729-1760` | done |
|
|
60
|
+
| ACL queue | `connection.device.host.get_data_packet_queue(handle)` と `DataPacketQueue.drain(handle)` は 0.0.233 の正式経路である | source fact | `.venv/Lib/site-packages/bumble/host.py:182-186,882-885` | done |
|
|
61
|
+
| address | `hci.Address.to_string(False)` は 0.0.233 に存在するため、文字列化 fallback を削除する | source fact | `.venv/Lib/site-packages/bumble/hci.py:2226-2320` | done |
|
|
62
|
+
| incoming BR/EDR request | `Device.on_connection_request()` は 0.0.233 でも deprecated `host.send_command_sync()` を呼ぶ。host の一時 bridge はこの upstream 挙動だけを回避する | source fact | `.venv/Lib/site-packages/bumble/device.py:6069-6125` | retained |
|
|
63
|
+
| public accept alternative | `Device.accept()` は request より前に待機する coroutine であり、継続 accept loop と close 時の task 管理が必要になる | source fact | `.venv/Lib/site-packages/bumble/device.py:4138-4270` | deferred |
|
|
64
|
+
|
|
65
|
+
## 6. 振る舞い仕様
|
|
66
|
+
|
|
67
|
+
| 振る舞い | 入力・状態 | 期待結果 | 備考 |
|
|
68
|
+
|---|---|---|---|
|
|
69
|
+
| 正式 dependency | project install | Bumble が `0.0.233` に固定される | lock と一致する |
|
|
70
|
+
| transport callback | Bumble 0.0.233 の HID / connection object | 固定 API を使い、旧 API fallback を使わない | diagnostics 用 optional field は残せる |
|
|
71
|
+
| close | explicit disconnect | pending ACL queue だけを drain する | 通常 report では待たない |
|
|
72
|
+
| canary | schedule / 手動実行 | 最新 Bumble で unit / integration を実行する | `continue-on-error: true` |
|
|
73
|
+
|
|
74
|
+
## 7. TDD Test List
|
|
75
|
+
|
|
76
|
+
| status | item | type | layer | hardware | notes |
|
|
77
|
+
|---|---|---|---|---|---|
|
|
78
|
+
| refactor-done | 0.0.233 の固定 API で HID callback と connection listener を登録できる | characterization | unit | no | fixture を fixed API shape に更新し focused test で確認 |
|
|
79
|
+
| refactor-done | explicit disconnect で 0.0.233 の ACL queue を drain する | regression | unit | no | host 経由の正式取得経路で focused test を確認 |
|
|
80
|
+
| refactor-done | 旧 API fallback 専用の test を削除しても transport の既存契約を保つ | regression | unit / integration | no | source に残る deprecated sync workaround は維持する |
|
|
81
|
+
| refactor-done | latest Bumble canary が schedule / 手動実行で unit / integration を起動する | new | CI config | no | YAML parse により trigger、nonblocking、両 test tree を確認 |
|
|
82
|
+
| done | Switch 2 fresh pairing 後に Button A input report を送り、neutral close する | regression | hardware | yes | 0.3 s Periodic case が pytest と利用者の画面観測で成功 |
|
|
83
|
+
| deferred | Pro Controller active reconnect と normal close を Bumble adapter で確認する | regression | bumble / hardware | yes | 明示承認後にだけ実行する |
|
|
84
|
+
|
|
85
|
+
## 8. 文書検証計画
|
|
86
|
+
|
|
87
|
+
not applicable。公開利用者向け文書は変更しない。
|
|
88
|
+
|
|
89
|
+
## 9. 設計メモ
|
|
90
|
+
|
|
91
|
+
- `getattr()` を一律に削除しない。診断用 optional field と source 上で欠落し得る値だけは残す。
|
|
92
|
+
- Bumble source にない fallback は、0.0.233 固定後に互換性のため保持しない。
|
|
93
|
+
- `Device.on_connection_request()` の deprecated sync helper は 0.0.233 に残る。public `Device.accept()` への移行には accept loop と lifecycle task の設計変更が必要なため、本 unit では既存の狭い bridge を維持する。
|
|
94
|
+
|
|
95
|
+
## 10. 対象ファイル
|
|
96
|
+
|
|
97
|
+
| path | change | 内容 |
|
|
98
|
+
|---|---|---|
|
|
99
|
+
| `pyproject.toml` | modify | Bumble exact pin |
|
|
100
|
+
| `uv.lock` | modify | dependency lock |
|
|
101
|
+
| `src/swbt/transport/**` | modify | 0.0.233 API に合わせた分岐整理 |
|
|
102
|
+
| `tests/unit/**` | modify | obsolete fallback test の削除と fixed-API fixture |
|
|
103
|
+
| `.github/workflows/**` | new / modify | latest Bumble canary |
|
|
104
|
+
|
|
105
|
+
## 11. 検証
|
|
106
|
+
|
|
107
|
+
| command | result | notes |
|
|
108
|
+
|---|---|---|
|
|
109
|
+
| `uv lock --check` | passed | Bumble 0.0.233 lock と一致 |
|
|
110
|
+
| `uv run ruff format --check .` | passed | 106 files already formatted |
|
|
111
|
+
| `uv run ruff check .` | passed | All checks passed |
|
|
112
|
+
| `uv run ty check --no-progress` | passed | All checks passed |
|
|
113
|
+
| `uv run pytest tests/unit` | passed | 448 passed |
|
|
114
|
+
| `uv run pytest tests/integration` | passed | 154 passed |
|
|
115
|
+
| `uv run --group docs mkdocs build --strict` | passed | strict build succeeded |
|
|
116
|
+
| `uv build` | passed | sdist と wheel を build |
|
|
117
|
+
| `uv run python -c "...yaml.safe_load(...)..."` | passed | canary の trigger、nonblocking、unit / integration command を確認 |
|
|
118
|
+
| `uv run pytest tests/hardware/test_context_manager_resource_scope.py -m bumble --swbt-bumble-adapter usb:0 --swbt-hardware-artifact-dir tmp/hardware/unit_084/bumble-open-only` | passed | 1 passed。advertising / host connection なし、transport close complete を trace で確認 |
|
|
119
|
+
| `uv run pytest "tests/hardware/test_reply_holdoff.py::test_switch_reply_holdoff_variant_fresh_pairing_characterizes_readiness_and_a_input[0.3-True]" -m hardware --swbt-bumble-adapter usb:0 --swbt-hardware-artifact-dir tmp/hardware/unit_084/fresh-a-switch2-20260725` | passed | 1 passed in 10.34s。fresh pairing、Button A の input report、neutral close を trace で確認し、利用者が Switch 2 UI への反映を確認 |
|
|
120
|
+
|
|
121
|
+
## 12. 実機実行条件
|
|
122
|
+
|
|
123
|
+
| 項目 | 内容 |
|
|
124
|
+
|---|---|
|
|
125
|
+
| 実機要否 | Bumble adapter open-only と Switch 2 fresh pairing / Button A は完了。active reconnect と normal close は未実行 |
|
|
126
|
+
| 承認範囲 | `usb:0` の open-only Bumble marker に加え、Switch 2 の fresh pairing、HID advertising、Button A、neutral close、adapter release を実行済み。active reconnect は未承認 |
|
|
127
|
+
| adapter | `usb:0` |
|
|
128
|
+
| 実行遮断 | 環境変数による遮断は採用しない。明示承認、対象 adapter、command、cleanup plan で管理する |
|
|
129
|
+
| log / artifact | `spec/hardware-test-log.md`、`tmp/hardware/unit_084/bumble-open-only/resource-open-only.jsonl`、`tmp/hardware/unit_084/fresh-a-switch2-20260725/reply-holdoff-fresh-periodic-0_300.jsonl` |
|
|
130
|
+
| cleanup | open-only と Switch 2 fresh pairing の両方で `transport_close_complete` を確認。Switch 2 run では disconnect terminal `closed` と neutral close も記録 |
|
|
131
|
+
|
|
132
|
+
## 13. 先送り事項
|
|
133
|
+
|
|
134
|
+
- adapter を使う active reconnect と normal close の実機確認は、利用者の明示承認後に実行する。
|
|
135
|
+
|
|
136
|
+
## 14. チェックリスト
|
|
137
|
+
|
|
138
|
+
- [x] 対象範囲と対象外を確認した
|
|
139
|
+
- [x] TDD Test List または文書検証計画を更新した
|
|
140
|
+
- [x] 必要な根拠監査を記録した
|
|
141
|
+
- [x] 実機実行条件を記録した
|
|
142
|
+
- [x] 検証結果または未実行理由を記録した
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""ACL queue drain helper for the pinned Bumble HID transport."""
|
|
2
|
+
|
|
3
|
+
from typing import Any, cast
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
async def drain_bumble_acl_queue(l2cap_channel: object) -> None:
|
|
7
|
+
"""Wait for the pinned Bumble connection's pending ACL packets to complete."""
|
|
8
|
+
channel = cast("Any", l2cap_channel)
|
|
9
|
+
connection = channel.connection
|
|
10
|
+
acl_packet_queue = connection.device.host.get_data_packet_queue(connection.handle)
|
|
11
|
+
if acl_packet_queue is None:
|
|
12
|
+
return
|
|
13
|
+
try:
|
|
14
|
+
await acl_packet_queue.drain(connection.handle)
|
|
15
|
+
except ValueError:
|
|
16
|
+
# Bumble raises when the queue has no state for a channel that was already drained.
|
|
17
|
+
return
|
|
@@ -9,11 +9,9 @@ from swbt.diagnostics import DiagnosticsRecorder
|
|
|
9
9
|
from swbt.errors import InvalidKeyStoreError
|
|
10
10
|
from swbt.transport._pairing_profile import KeyStoreNamespaces, PairingProfile
|
|
11
11
|
|
|
12
|
-
PREVIOUS_NAMESPACE_PREFIX = "swbt.previous::"
|
|
13
|
-
|
|
14
12
|
|
|
15
13
|
class _PairingProfileKeyStore:
|
|
16
|
-
"""Bumble-compatible profile key store
|
|
14
|
+
"""Bumble-compatible profile key store for the current namespace."""
|
|
17
15
|
|
|
18
16
|
def __init__(
|
|
19
17
|
self,
|
|
@@ -24,7 +22,6 @@ class _PairingProfileKeyStore:
|
|
|
24
22
|
self._profile_path = Path(profile_path)
|
|
25
23
|
self._namespace = namespace if isinstance(namespace, str) else None
|
|
26
24
|
self._namespace_resolver = None if isinstance(namespace, str) else namespace
|
|
27
|
-
self.last_update_previous_saved = False
|
|
28
25
|
|
|
29
26
|
def _resolve_namespace(self) -> str:
|
|
30
27
|
namespace = (
|
|
@@ -36,16 +33,9 @@ class _PairingProfileKeyStore:
|
|
|
36
33
|
return namespace
|
|
37
34
|
|
|
38
35
|
async def update(self, name: str, keys: object) -> None:
|
|
39
|
-
"""
|
|
36
|
+
"""Replace the current namespace with one peer's keys."""
|
|
40
37
|
current_store = self._current_store()
|
|
41
38
|
db, current_key_map = await current_store.load()
|
|
42
|
-
previous_namespace = self._previous_namespace(current_store)
|
|
43
|
-
previous_key_map = copy.deepcopy(current_key_map)
|
|
44
|
-
self.last_update_previous_saved = bool(previous_key_map)
|
|
45
|
-
if previous_key_map:
|
|
46
|
-
db[previous_namespace] = previous_key_map
|
|
47
|
-
else:
|
|
48
|
-
db.pop(previous_namespace, None)
|
|
49
39
|
current_key_map.clear()
|
|
50
40
|
current_key_map[name] = cast("Any", keys).to_dict()
|
|
51
41
|
await current_store.save(db)
|
|
@@ -84,10 +74,6 @@ class _PairingProfileKeyStore:
|
|
|
84
74
|
adapter_default=self._namespace_resolver is not None,
|
|
85
75
|
)
|
|
86
76
|
|
|
87
|
-
@staticmethod
|
|
88
|
-
def _previous_namespace(current_store: "_PairingProfileNamespaceStore") -> str:
|
|
89
|
-
return f"{PREVIOUS_NAMESPACE_PREFIX}{current_store.namespace}"
|
|
90
|
-
|
|
91
77
|
|
|
92
78
|
class _PairingProfileNamespaceStore:
|
|
93
79
|
"""JsonKeyStore-compatible view over one profile namespace map."""
|
|
@@ -169,20 +155,16 @@ class _DiagnosticKeyStore:
|
|
|
169
155
|
try:
|
|
170
156
|
await cast("Any", self._key_store).update(name, keys)
|
|
171
157
|
except Exception as error:
|
|
172
|
-
fields = self._generation_fields()
|
|
173
158
|
self._diagnostics.record_event(
|
|
174
159
|
"key_store_update",
|
|
175
|
-
**fields,
|
|
176
160
|
error_type=type(error).__name__,
|
|
177
161
|
message=str(error),
|
|
178
162
|
peer_address=name,
|
|
179
163
|
status="failed",
|
|
180
164
|
)
|
|
181
165
|
raise
|
|
182
|
-
fields = self._generation_fields()
|
|
183
166
|
self._diagnostics.record_event(
|
|
184
167
|
"key_store_update",
|
|
185
|
-
**fields,
|
|
186
168
|
peer_address=name,
|
|
187
169
|
status="succeeded",
|
|
188
170
|
)
|
|
@@ -209,11 +191,3 @@ class _DiagnosticKeyStore:
|
|
|
209
191
|
|
|
210
192
|
def __getattr__(self, name: str) -> object:
|
|
211
193
|
return getattr(self._key_store, name)
|
|
212
|
-
|
|
213
|
-
def _generation_fields(self) -> dict[str, object]:
|
|
214
|
-
if not isinstance(self._key_store, _PairingProfileKeyStore):
|
|
215
|
-
return {}
|
|
216
|
-
return {
|
|
217
|
-
"generation": "current",
|
|
218
|
-
"previous_saved": self._key_store.last_update_previous_saved,
|
|
219
|
-
}
|
|
@@ -25,20 +25,19 @@ class ConnectionDiagnostics:
|
|
|
25
25
|
self._record_event = record_event
|
|
26
26
|
|
|
27
27
|
def register(self, connection: object) -> None:
|
|
28
|
-
"""Register all
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
return
|
|
28
|
+
"""Register all diagnostics callbacks provided by Bumble 0.0.233."""
|
|
29
|
+
connection_with_attrs = cast("Any", connection)
|
|
30
|
+
on_event = connection_with_attrs.on
|
|
32
31
|
on_event(
|
|
33
|
-
|
|
32
|
+
connection_with_attrs.EVENT_DISCONNECTION,
|
|
34
33
|
self._handle_disconnection,
|
|
35
34
|
)
|
|
36
35
|
on_event(
|
|
37
|
-
|
|
36
|
+
connection_with_attrs.EVENT_CLASSIC_PAIRING,
|
|
38
37
|
lambda *_args: self._record_event("classic_pairing", adapter=self._adapter),
|
|
39
38
|
)
|
|
40
39
|
on_event(
|
|
41
|
-
|
|
40
|
+
connection_with_attrs.EVENT_CLASSIC_PAIRING_FAILURE,
|
|
42
41
|
lambda reason=None, *_args: self._record_event(
|
|
43
42
|
"classic_pairing_failure",
|
|
44
43
|
adapter=self._adapter,
|
|
@@ -46,15 +45,15 @@ class ConnectionDiagnostics:
|
|
|
46
45
|
),
|
|
47
46
|
)
|
|
48
47
|
on_event(
|
|
49
|
-
|
|
48
|
+
connection_with_attrs.EVENT_PAIRING_START,
|
|
50
49
|
lambda *_args: self._record_event("pairing_start", adapter=self._adapter),
|
|
51
50
|
)
|
|
52
51
|
on_event(
|
|
53
|
-
|
|
52
|
+
connection_with_attrs.EVENT_PAIRING,
|
|
54
53
|
lambda keys=None, *_args: self._record_pairing_complete(connection, keys),
|
|
55
54
|
)
|
|
56
55
|
on_event(
|
|
57
|
-
|
|
56
|
+
connection_with_attrs.EVENT_PAIRING_FAILURE,
|
|
58
57
|
lambda reason=None, *_args: self._record_event(
|
|
59
58
|
"pairing_failure",
|
|
60
59
|
adapter=self._adapter,
|
|
@@ -62,15 +61,11 @@ class ConnectionDiagnostics:
|
|
|
62
61
|
),
|
|
63
62
|
)
|
|
64
63
|
on_event(
|
|
65
|
-
|
|
64
|
+
connection_with_attrs.EVENT_CONNECTION_AUTHENTICATION,
|
|
66
65
|
lambda *_args: self._record_connection_authentication(connection),
|
|
67
66
|
)
|
|
68
67
|
on_event(
|
|
69
|
-
|
|
70
|
-
connection,
|
|
71
|
-
"EVENT_CONNECTION_AUTHENTICATION_FAILURE",
|
|
72
|
-
"connection_authentication_failure",
|
|
73
|
-
),
|
|
68
|
+
connection_with_attrs.EVENT_CONNECTION_AUTHENTICATION_FAILURE,
|
|
74
69
|
lambda error=None, *_args: self._record_event(
|
|
75
70
|
"connection_authentication_failure",
|
|
76
71
|
adapter=self._adapter,
|
|
@@ -78,19 +73,11 @@ class ConnectionDiagnostics:
|
|
|
78
73
|
),
|
|
79
74
|
)
|
|
80
75
|
on_event(
|
|
81
|
-
|
|
82
|
-
connection,
|
|
83
|
-
"EVENT_CONNECTION_ENCRYPTION_CHANGE",
|
|
84
|
-
"connection_encryption_change",
|
|
85
|
-
),
|
|
76
|
+
connection_with_attrs.EVENT_CONNECTION_ENCRYPTION_CHANGE,
|
|
86
77
|
lambda *_args: self._record_connection_encryption_change(connection),
|
|
87
78
|
)
|
|
88
79
|
on_event(
|
|
89
|
-
|
|
90
|
-
connection,
|
|
91
|
-
"EVENT_CONNECTION_ENCRYPTION_FAILURE",
|
|
92
|
-
"connection_encryption_failure",
|
|
93
|
-
),
|
|
80
|
+
connection_with_attrs.EVENT_CONNECTION_ENCRYPTION_FAILURE,
|
|
94
81
|
lambda error=None, *_args: self._record_event(
|
|
95
82
|
"connection_encryption_failure",
|
|
96
83
|
adapter=self._adapter,
|
|
@@ -98,23 +85,19 @@ class ConnectionDiagnostics:
|
|
|
98
85
|
),
|
|
99
86
|
)
|
|
100
87
|
on_event(
|
|
101
|
-
|
|
102
|
-
connection,
|
|
103
|
-
"EVENT_CONNECTION_ENCRYPTION_KEY_REFRESH",
|
|
104
|
-
"connection_encryption_key_refresh",
|
|
105
|
-
),
|
|
88
|
+
connection_with_attrs.EVENT_CONNECTION_ENCRYPTION_KEY_REFRESH,
|
|
106
89
|
lambda *_args: self._record_connection_encryption_refresh(connection),
|
|
107
90
|
)
|
|
108
91
|
on_event(
|
|
109
|
-
|
|
92
|
+
connection_with_attrs.EVENT_LINK_KEY,
|
|
110
93
|
lambda *_args: self._record_event("link_key_available", adapter=self._adapter),
|
|
111
94
|
)
|
|
112
95
|
on_event(
|
|
113
|
-
|
|
96
|
+
connection_with_attrs.EVENT_MODE_CHANGE,
|
|
114
97
|
lambda *_args: self._record_classic_mode_change(connection),
|
|
115
98
|
)
|
|
116
99
|
on_event(
|
|
117
|
-
|
|
100
|
+
connection_with_attrs.EVENT_MODE_CHANGE_FAILURE,
|
|
118
101
|
lambda status=None, *_args: self._record_event(
|
|
119
102
|
"classic_mode_change_failure",
|
|
120
103
|
adapter=self._adapter,
|
|
@@ -179,7 +162,8 @@ def register_connection_request_bridge(
|
|
|
179
162
|
record_event: EventRecorder,
|
|
180
163
|
) -> None:
|
|
181
164
|
"""Wrap Bumble's connection request callback while avoiding deprecated sync APIs."""
|
|
182
|
-
|
|
165
|
+
device_with_attrs = cast("Any", device)
|
|
166
|
+
original_connection_request = device_with_attrs.on_connection_request
|
|
183
167
|
|
|
184
168
|
def on_connection_request(
|
|
185
169
|
bd_addr: object,
|
|
@@ -201,7 +185,6 @@ def register_connection_request_bridge(
|
|
|
201
185
|
link_type,
|
|
202
186
|
)
|
|
203
187
|
|
|
204
|
-
device_with_attrs = cast("Any", device)
|
|
205
188
|
device_with_attrs.on_connection_request = on_connection_request
|
|
206
189
|
replace_host_connection_request_listener(
|
|
207
190
|
device,
|
|
@@ -267,24 +250,18 @@ def call_connection_request_without_deprecated_sync_command(
|
|
|
267
250
|
link_type: int,
|
|
268
251
|
) -> None:
|
|
269
252
|
"""Run Bumble's connection request handler without its deprecated sync helper."""
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
if host is None or not callable(send_async_command):
|
|
273
|
-
connection_request(bd_addr, class_of_device, link_type)
|
|
274
|
-
return
|
|
253
|
+
device_with_attrs = cast("Any", device)
|
|
254
|
+
host = device_with_attrs.host
|
|
275
255
|
|
|
276
256
|
from bumble import utils # noqa: PLC0415
|
|
277
257
|
|
|
278
258
|
missing = object()
|
|
279
|
-
|
|
280
|
-
previous_instance_attr = (
|
|
281
|
-
host_dict.get("send_command_sync", missing) if isinstance(host_dict, dict) else missing
|
|
282
|
-
)
|
|
259
|
+
previous_instance_attr = host.__dict__.get("send_command_sync", missing)
|
|
283
260
|
|
|
284
261
|
def send_command_sync(command: object) -> None:
|
|
285
|
-
utils.AsyncRunner.spawn(send_async_command(command))
|
|
262
|
+
utils.AsyncRunner.spawn(host.send_async_command(command))
|
|
286
263
|
|
|
287
|
-
host_with_attrs =
|
|
264
|
+
host_with_attrs = host
|
|
288
265
|
host_with_attrs.send_command_sync = send_command_sync
|
|
289
266
|
try:
|
|
290
267
|
connection_request(bd_addr, class_of_device, link_type)
|
|
@@ -300,12 +277,9 @@ def replace_host_connection_request_listener(
|
|
|
300
277
|
original_connection_request: Callable[[object, int, int], None],
|
|
301
278
|
replacement_connection_request: Callable[[object, int, int], None],
|
|
302
279
|
) -> None:
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
on = getattr(host, "on", None)
|
|
306
|
-
if not callable(remove_listener) or not callable(on):
|
|
307
|
-
return
|
|
280
|
+
device_with_attrs = cast("Any", device)
|
|
281
|
+
host = device_with_attrs.host
|
|
308
282
|
|
|
309
283
|
with suppress(KeyError, ValueError):
|
|
310
|
-
remove_listener("connection_request", original_connection_request)
|
|
311
|
-
on("connection_request", replacement_connection_request)
|
|
284
|
+
host.remove_listener("connection_request", original_connection_request)
|
|
285
|
+
host.on("connection_request", replacement_connection_request)
|