swbt-python 0.1.1__tar.gz → 0.3.0__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.
Files changed (192) hide show
  1. {swbt_python-0.1.1 → swbt_python-0.3.0}/AGENTS.md +1 -0
  2. swbt_python-0.3.0/PKG-INFO +150 -0
  3. swbt_python-0.3.0/README.md +124 -0
  4. {swbt_python-0.1.1 → swbt_python-0.3.0}/examples/hardware_bringup.py +2 -2
  5. {swbt_python-0.1.1 → swbt_python-0.3.0}/examples/pairing_probe.py +2 -2
  6. {swbt_python-0.1.1 → swbt_python-0.3.0}/examples/tap_a.py +2 -2
  7. {swbt_python-0.1.1 → swbt_python-0.3.0}/pyproject.toml +1 -1
  8. swbt_python-0.3.0/spec/complete/unit_027/ADAPTER_DISCOVERY_API.md +268 -0
  9. swbt_python-0.3.0/spec/complete/unit_028/CONTROLLER_PROFILE_CUSTOMIZATION.md +339 -0
  10. swbt_python-0.3.0/spec/complete/unit_029/CONTROLLER_PROFILE_INJECTION.md +188 -0
  11. swbt_python-0.3.0/spec/complete/unit_030/JOYCON_PROFILE_IDENTITY_SPI.md +183 -0
  12. swbt_python-0.3.0/spec/complete/unit_031/JOYCON_INPUT_REPORT_MAPPING.md +182 -0
  13. swbt_python-0.3.0/spec/complete/unit_032/PROFILE_AWARE_SUBCOMMAND_STATE.md +182 -0
  14. swbt_python-0.3.0/spec/complete/unit_033/PROFILE_AWARE_BUMBLE_SDP.md +190 -0
  15. swbt_python-0.3.0/spec/complete/unit_034/JOYCON_CONVENIENCE_API_DOCS.md +189 -0
  16. swbt_python-0.3.0/spec/complete/unit_035/JOYCON_DEVICE_INFO_ADDRESS_WIRING.md +145 -0
  17. swbt_python-0.3.0/spec/complete/unit_036/JOYCON_SDP_IDENTITY_POLICY.md +132 -0
  18. swbt_python-0.3.0/spec/complete/unit_037/JOYCON_DEFAULT_CONTROLLER_COLORS.md +157 -0
  19. swbt_python-0.3.0/spec/complete/unit_038/REARCHITECTURE_DECISION_BOUNDARY_TESTS.md +144 -0
  20. swbt_python-0.3.0/spec/complete/unit_039/CONTROLLER_RUNTIME_EXTRACTION.md +162 -0
  21. swbt_python-0.3.0/spec/complete/unit_040/PUBLIC_CONTROLLER_API_MODEL.md +150 -0
  22. swbt_python-0.3.0/spec/complete/unit_041/CONTROLLER_CONFIG_PROFILE_OWNERSHIP.md +156 -0
  23. swbt_python-0.3.0/spec/complete/unit_042/INTERNAL_TRANSPORT_FACTORY.md +146 -0
  24. swbt_python-0.3.0/spec/complete/unit_043/PROTOCOL_PROFILE_MODULE_SPLIT.md +145 -0
  25. swbt_python-0.3.0/spec/complete/unit_044/REARCHITECTURE_DOCS_RELEASE_MATRIX.md +149 -0
  26. swbt_python-0.3.0/spec/complete/unit_045/INTERNAL_API_BOUNDARY_CLEANUP.md +165 -0
  27. swbt_python-0.3.0/spec/complete/unit_046/HARDWARE_PROFILE_TEST_SCENARIOS.md +344 -0
  28. swbt_python-0.3.0/spec/complete/unit_047/VIRTUAL_GYRO_CALIBRATION.md +272 -0
  29. swbt_python-0.3.0/spec/complete/unit_048/VIRTUAL_ACCELEROMETER_CALIBRATION.md +131 -0
  30. swbt_python-0.3.0/spec/complete/unit_049/IMU_SESSION_AND_ENCODING_REDESIGN.md +427 -0
  31. swbt_python-0.3.0/spec/dev-journal.md +3 -0
  32. {swbt_python-0.1.1 → swbt_python-0.3.0}/spec/hardware-test-log.md +724 -1
  33. {swbt_python-0.1.1 → swbt_python-0.3.0}/spec/initial/api.md +149 -11
  34. {swbt_python-0.1.1 → swbt_python-0.3.0}/spec/initial/architecture.md +13 -3
  35. {swbt_python-0.1.1 → swbt_python-0.3.0}/spec/initial/lifecycle.md +3 -1
  36. {swbt_python-0.1.1 → swbt_python-0.3.0}/spec/initial/protocol.md +43 -10
  37. {swbt_python-0.1.1 → swbt_python-0.3.0}/spec/initial/risks.md +8 -6
  38. {swbt_python-0.1.1 → swbt_python-0.3.0}/spec/initial/testing.md +14 -1
  39. {swbt_python-0.1.1 → swbt_python-0.3.0}/spec/initial/transport-bumble.md +4 -2
  40. swbt_python-0.3.0/spec/rearchitecture/01-design-change-overview.md +146 -0
  41. swbt_python-0.3.0/spec/rearchitecture/02-as-is-to-be.md +212 -0
  42. swbt_python-0.3.0/spec/rearchitecture/03-public-api-config-profile.md +368 -0
  43. swbt_python-0.3.0/spec/rearchitecture/04-runtime-profile-transport-details.md +387 -0
  44. swbt_python-0.3.0/spec/rearchitecture/05-milestones-implementation.md +359 -0
  45. swbt_python-0.3.0/spec/rearchitecture/README.md +86 -0
  46. swbt_python-0.3.0/spec/rearchitecture/mkdocs-nav-snippet.yml +10 -0
  47. {swbt_python-0.1.1 → swbt_python-0.3.0}/src/swbt/__init__.py +19 -6
  48. swbt_python-0.3.0/src/swbt/_testing/__init__.py +1 -0
  49. swbt_python-0.3.0/src/swbt/_testing/gamepad.py +74 -0
  50. swbt_python-0.3.0/src/swbt/adapter_discovery.py +231 -0
  51. {swbt_python-0.1.1 → swbt_python-0.3.0}/src/swbt/diagnostics.py +12 -2
  52. swbt_python-0.3.0/src/swbt/errors.py +100 -0
  53. {swbt_python-0.1.1 → swbt_python-0.3.0}/src/swbt/gamepad/__init__.py +10 -4
  54. swbt_python-0.3.0/src/swbt/gamepad/_config.py +101 -0
  55. {swbt_python-0.1.1 → swbt_python-0.3.0}/src/swbt/gamepad/connection.py +6 -0
  56. swbt_python-0.3.0/src/swbt/gamepad/constants.py +3 -0
  57. swbt_python-0.3.0/src/swbt/gamepad/core.py +409 -0
  58. swbt_python-0.3.0/src/swbt/gamepad/interface.py +273 -0
  59. {swbt_python-0.1.1 → swbt_python-0.3.0}/src/swbt/gamepad/output.py +38 -4
  60. swbt_python-0.1.1/src/swbt/gamepad/core.py → swbt_python-0.3.0/src/swbt/gamepad/runtime.py +131 -60
  61. swbt_python-0.3.0/src/swbt/gamepad/transport_factory.py +77 -0
  62. swbt_python-0.3.0/src/swbt/imu.py +116 -0
  63. {swbt_python-0.1.1 → swbt_python-0.3.0}/src/swbt/input.py +142 -0
  64. {swbt_python-0.1.1 → swbt_python-0.3.0}/src/swbt/probe.py +79 -4
  65. swbt_python-0.3.0/src/swbt/protocol/buttons.py +63 -0
  66. swbt_python-0.1.1/src/swbt/protocol/profile.py → swbt_python-0.3.0/src/swbt/protocol/descriptors.py +1 -13
  67. swbt_python-0.3.0/src/swbt/protocol/imu_report.py +186 -0
  68. swbt_python-0.3.0/src/swbt/protocol/input_report.py +60 -0
  69. swbt_python-0.3.0/src/swbt/protocol/profiles/__init__.py +3 -0
  70. swbt_python-0.3.0/src/swbt/protocol/profiles/base.py +250 -0
  71. swbt_python-0.3.0/src/swbt/protocol/profiles/joycon.py +84 -0
  72. swbt_python-0.3.0/src/swbt/protocol/profiles/pro_controller.py +25 -0
  73. swbt_python-0.3.0/src/swbt/protocol/session.py +112 -0
  74. swbt_python-0.3.0/src/swbt/protocol/spi.py +59 -0
  75. swbt_python-0.3.0/src/swbt/protocol/subcommand.py +200 -0
  76. {swbt_python-0.1.1 → swbt_python-0.3.0}/src/swbt/report_loop.py +19 -5
  77. {swbt_python-0.1.1 → swbt_python-0.3.0}/src/swbt/state_store.py +15 -0
  78. {swbt_python-0.1.1 → swbt_python-0.3.0}/src/swbt/transport/_bumble_sdp.py +98 -61
  79. swbt_python-0.3.0/src/swbt/transport/_bumble_usb_devices.py +33 -0
  80. swbt_python-0.3.0/src/swbt/transport/base.py +169 -0
  81. {swbt_python-0.1.1 → swbt_python-0.3.0}/src/swbt/transport/bumble.py +68 -9
  82. {swbt_python-0.1.1 → swbt_python-0.3.0}/src/swbt/transport/fake.py +8 -0
  83. {swbt_python-0.1.1 → swbt_python-0.3.0}/tests/conftest.py +30 -0
  84. {swbt_python-0.1.1 → swbt_python-0.3.0}/tests/hardware/test_close_disconnect.py +4 -4
  85. {swbt_python-0.1.1 → swbt_python-0.3.0}/tests/hardware/test_context_manager_resource_scope.py +2 -2
  86. swbt_python-0.3.0/tests/hardware/test_controller_colors.py +682 -0
  87. {swbt_python-0.1.1 → swbt_python-0.3.0}/tests/hardware/test_input_operations.py +230 -109
  88. swbt_python-0.3.0/tests/hardware/test_joycon_profile.py +1648 -0
  89. {swbt_python-0.1.1 → swbt_python-0.3.0}/tests/hardware/test_pairing_l2cap.py +4 -4
  90. {swbt_python-0.1.1 → swbt_python-0.3.0}/tests/hardware/test_reconnect_keystore.py +4 -4
  91. {swbt_python-0.1.1 → swbt_python-0.3.0}/tests/integration/test_examples.py +2 -1
  92. {swbt_python-0.1.1 → swbt_python-0.3.0}/tests/integration/test_switch_gamepad_fake_transport.py +785 -65
  93. swbt_python-0.3.0/tests/unit/fixtures/source_audit/switch_protocol_values.toml +572 -0
  94. swbt_python-0.3.0/tests/unit/test_adapter_discovery.py +315 -0
  95. swbt_python-0.3.0/tests/unit/test_bumble_sdp.py +64 -0
  96. {swbt_python-0.1.1 → swbt_python-0.3.0}/tests/unit/test_bumble_transport.py +143 -0
  97. {swbt_python-0.1.1 → swbt_python-0.3.0}/tests/unit/test_gamepad_output_dispatcher.py +7 -1
  98. swbt_python-0.3.0/tests/unit/test_gamepad_transport_factory.py +160 -0
  99. swbt_python-0.3.0/tests/unit/test_imu_report.py +149 -0
  100. swbt_python-0.3.0/tests/unit/test_input_report.py +423 -0
  101. {swbt_python-0.1.1 → swbt_python-0.3.0}/tests/unit/test_input_state.py +76 -0
  102. {swbt_python-0.1.1 → swbt_python-0.3.0}/tests/unit/test_mkdocs_site.py +2 -0
  103. swbt_python-0.3.0/tests/unit/test_package_import.py +57 -0
  104. {swbt_python-0.1.1 → swbt_python-0.3.0}/tests/unit/test_probe_cli.py +115 -10
  105. swbt_python-0.3.0/tests/unit/test_protocol_profile.py +245 -0
  106. swbt_python-0.3.0/tests/unit/test_protocol_session.py +91 -0
  107. swbt_python-0.3.0/tests/unit/test_public_api_boundary.py +695 -0
  108. swbt_python-0.3.0/tests/unit/test_public_api_docstrings.py +287 -0
  109. {swbt_python-0.1.1 → swbt_python-0.3.0}/tests/unit/test_public_docs.py +144 -19
  110. {swbt_python-0.1.1 → swbt_python-0.3.0}/tests/unit/test_publish_workflow.py +47 -0
  111. {swbt_python-0.1.1 → swbt_python-0.3.0}/tests/unit/test_readme_docs.py +40 -11
  112. swbt_python-0.3.0/tests/unit/test_release_gate_docs.py +82 -0
  113. swbt_python-0.3.0/tests/unit/test_report_loop.py +127 -0
  114. swbt_python-0.3.0/tests/unit/test_source_audit_fixtures.py +504 -0
  115. swbt_python-0.3.0/tests/unit/test_subcommand_responder.py +373 -0
  116. swbt_python-0.3.0/tests/unit/test_virtual_spi_flash.py +123 -0
  117. swbt_python-0.1.1/PKG-INFO +0 -122
  118. swbt_python-0.1.1/README.md +0 -96
  119. swbt_python-0.1.1/spec/dev-journal.md +0 -23
  120. swbt_python-0.1.1/src/swbt/errors.py +0 -33
  121. swbt_python-0.1.1/src/swbt/gamepad/transport_factory.py +0 -22
  122. swbt_python-0.1.1/src/swbt/protocol/input_report.py +0 -77
  123. swbt_python-0.1.1/src/swbt/protocol/spi.py +0 -37
  124. swbt_python-0.1.1/src/swbt/protocol/subcommand.py +0 -102
  125. swbt_python-0.1.1/src/swbt/transport/base.py +0 -81
  126. swbt_python-0.1.1/tests/unit/fixtures/source_audit/switch_protocol_values.toml +0 -312
  127. swbt_python-0.1.1/tests/unit/test_bumble_sdp.py +0 -18
  128. swbt_python-0.1.1/tests/unit/test_gamepad_transport_factory.py +0 -53
  129. swbt_python-0.1.1/tests/unit/test_input_report.py +0 -112
  130. swbt_python-0.1.1/tests/unit/test_package_import.py +0 -27
  131. swbt_python-0.1.1/tests/unit/test_protocol_profile.py +0 -21
  132. swbt_python-0.1.1/tests/unit/test_public_api_boundary.py +0 -329
  133. swbt_python-0.1.1/tests/unit/test_public_api_docstrings.py +0 -117
  134. swbt_python-0.1.1/tests/unit/test_release_gate_docs.py +0 -20
  135. swbt_python-0.1.1/tests/unit/test_report_loop.py +0 -52
  136. swbt_python-0.1.1/tests/unit/test_source_audit_fixtures.py +0 -202
  137. swbt_python-0.1.1/tests/unit/test_subcommand_responder.py +0 -79
  138. swbt_python-0.1.1/tests/unit/test_virtual_spi_flash.py +0 -29
  139. {swbt_python-0.1.1 → swbt_python-0.3.0}/LICENSE +0 -0
  140. {swbt_python-0.1.1 → swbt_python-0.3.0}/spec/complete/unit_001/M0_PROTOCOL_CORE.md +0 -0
  141. {swbt_python-0.1.1 → swbt_python-0.3.0}/spec/complete/unit_002/M1_SWITCH_GAMEPAD_FAKE_TRANSPORT.md +0 -0
  142. {swbt_python-0.1.1 → swbt_python-0.3.0}/spec/complete/unit_003/M2_BUMBLE_HID_TRANSPORT.md +0 -0
  143. {swbt_python-0.1.1 → swbt_python-0.3.0}/spec/complete/unit_004/M3_PAIRING_L2CAP.md +0 -0
  144. {swbt_python-0.1.1 → swbt_python-0.3.0}/spec/complete/unit_005/M4_SUBCOMMAND_RESPONDER_HARDWARE.md +0 -0
  145. {swbt_python-0.1.1 → swbt_python-0.3.0}/spec/complete/unit_006/M5_INPUT_OPERATION_API.md +0 -0
  146. {swbt_python-0.1.1 → swbt_python-0.3.0}/spec/complete/unit_007/M6_RECONNECT_KEYSTORE_DIAGNOSTICS.md +0 -0
  147. {swbt_python-0.1.1 → swbt_python-0.3.0}/spec/complete/unit_008/M7_PACKAGING_EXAMPLES_CLI.md +0 -0
  148. {swbt_python-0.1.1 → swbt_python-0.3.0}/spec/complete/unit_009/PORTING_SOURCE_AUDIT.md +0 -0
  149. {swbt_python-0.1.1 → swbt_python-0.3.0}/spec/complete/unit_010/DIAGNOSTICS_TRACE_SCHEMA.md +0 -0
  150. {swbt_python-0.1.1 → swbt_python-0.3.0}/spec/complete/unit_011/HARDWARE_TEST_LOG_MATRIX.md +0 -0
  151. {swbt_python-0.1.1 → swbt_python-0.3.0}/spec/complete/unit_012/INITIAL_RELEASE_GATE.md +0 -0
  152. {swbt_python-0.1.1 → swbt_python-0.3.0}/spec/complete/unit_013/POST_M5_INPUT_SEMANTIC_CHARACTERIZATION.md +0 -0
  153. {swbt_python-0.1.1 → swbt_python-0.3.0}/spec/complete/unit_014/DEVICE_CLOSE_GRACEFUL_DISCONNECT.md +0 -0
  154. {swbt_python-0.1.1 → swbt_python-0.3.0}/spec/complete/unit_015/CONTEXT_MANAGER_RESOURCE_SCOPE.md +0 -0
  155. {swbt_python-0.1.1 → swbt_python-0.3.0}/spec/complete/unit_016/JSON_KEY_STORE_CURRENT_PREVIOUS.md +0 -0
  156. {swbt_python-0.1.1 → swbt_python-0.3.0}/spec/complete/unit_017/SWITCH_GAMEPAD_API_HARDENING.md +0 -0
  157. {swbt_python-0.1.1 → swbt_python-0.3.0}/spec/complete/unit_018/KEY_STORE_TRANSPORT_BOUNDARY.md +0 -0
  158. {swbt_python-0.1.1 → swbt_python-0.3.0}/spec/complete/unit_019/TRANSPORT_RECONNECT_CONTRACT.md +0 -0
  159. {swbt_python-0.1.1 → swbt_python-0.3.0}/spec/complete/unit_020/STRUCTURAL_REFACTOR_BOUNDARIES.md +0 -0
  160. {swbt_python-0.1.1 → swbt_python-0.3.0}/spec/complete/unit_021/SWITCH_GAMEPAD_INPUT_API_CONTRACT.md +0 -0
  161. {swbt_python-0.1.1 → swbt_python-0.3.0}/spec/complete/unit_022/PUBLIC_API_USAGE_HARDWARE_DOCS.md +0 -0
  162. {swbt_python-0.1.1 → swbt_python-0.3.0}/spec/complete/unit_023/MKDOCS_DOCUMENTATION_SITE.md +0 -0
  163. {swbt_python-0.1.1 → swbt_python-0.3.0}/spec/complete/unit_024/STICK_INPUT_SHORTHAND_API.md +0 -0
  164. {swbt_python-0.1.1 → swbt_python-0.3.0}/spec/complete/unit_025/IMU_INPUT_SHORTHAND_API.md +0 -0
  165. {swbt_python-0.1.1 → swbt_python-0.3.0}/spec/complete/unit_026/LINUX_MACOS_ADAPTER_EXPERIMENTAL.md +0 -0
  166. {swbt_python-0.1.1 → swbt_python-0.3.0}/spec/initial/README.md +0 -0
  167. {swbt_python-0.1.1 → swbt_python-0.3.0}/spec/initial/naming.md +0 -0
  168. {swbt_python-0.1.1 → swbt_python-0.3.0}/spec/initial/roadmap.md +0 -0
  169. {swbt_python-0.1.1 → swbt_python-0.3.0}/src/swbt/protocol/__init__.py +0 -0
  170. {swbt_python-0.1.1 → swbt_python-0.3.0}/src/swbt/protocol/output_report.py +0 -0
  171. {swbt_python-0.1.1 → swbt_python-0.3.0}/src/swbt/protocol/rumble.py +0 -0
  172. {swbt_python-0.1.1 → swbt_python-0.3.0}/src/swbt/py.typed +0 -0
  173. {swbt_python-0.1.1 → swbt_python-0.3.0}/src/swbt/transport/__init__.py +0 -0
  174. {swbt_python-0.1.1 → swbt_python-0.3.0}/src/swbt/transport/_bumble_acl.py +0 -0
  175. {swbt_python-0.1.1 → swbt_python-0.3.0}/src/swbt/transport/_bumble_hidp.py +0 -0
  176. {swbt_python-0.1.1 → swbt_python-0.3.0}/src/swbt/transport/_bumble_key_store.py +0 -0
  177. {swbt_python-0.1.1 → swbt_python-0.3.0}/src/swbt/transport/_bumble_lifecycle.py +0 -0
  178. {swbt_python-0.1.1 → swbt_python-0.3.0}/tests/hardware/README.md +0 -0
  179. {swbt_python-0.1.1 → swbt_python-0.3.0}/tests/hardware/test_bumble_transport.py +0 -0
  180. {swbt_python-0.1.1 → swbt_python-0.3.0}/tests/unit/test_bumble_acl.py +0 -0
  181. {swbt_python-0.1.1 → swbt_python-0.3.0}/tests/unit/test_bumble_hidp.py +0 -0
  182. {swbt_python-0.1.1 → swbt_python-0.3.0}/tests/unit/test_bumble_key_store_metadata.py +0 -0
  183. {swbt_python-0.1.1 → swbt_python-0.3.0}/tests/unit/test_bumble_lifecycle.py +0 -0
  184. {swbt_python-0.1.1 → swbt_python-0.3.0}/tests/unit/test_ci_workflow.py +0 -0
  185. {swbt_python-0.1.1 → swbt_python-0.3.0}/tests/unit/test_diagnostics.py +0 -0
  186. {swbt_python-0.1.1 → swbt_python-0.3.0}/tests/unit/test_docs_workflow.py +0 -0
  187. {swbt_python-0.1.1 → swbt_python-0.3.0}/tests/unit/test_gamepad_connection_workflow.py +0 -0
  188. {swbt_python-0.1.1 → swbt_python-0.3.0}/tests/unit/test_hardware_test_log_docs.py +0 -0
  189. {swbt_python-0.1.1 → swbt_python-0.3.0}/tests/unit/test_output_report.py +0 -0
  190. {swbt_python-0.1.1 → swbt_python-0.3.0}/tests/unit/test_package_metadata.py +0 -0
  191. {swbt_python-0.1.1 → swbt_python-0.3.0}/tests/unit/test_protocol_boundary.py +0 -0
  192. {swbt_python-0.1.1 → swbt_python-0.3.0}/tests/unit/test_rumble_state.py +0 -0
@@ -91,6 +91,7 @@ repo-local skill は `.agents/skills` を正本として管理する。`.github/
91
91
  - `agentic-self-review`: 仕様変更、実装、PR 前に gate 結果と未検証リスクを整理する。
92
92
  - `spec-format`: `spec/wip` / `spec/complete` の作業仕様を作成、更新、完了移動する。
93
93
  - `dev-journal`: 小さい設計観測や先送り事項を `spec/dev-journal.md` に記録する。
94
+ - `docs-wording`: README、docs、release notes の文言整理で、swbt-python 固有の訳語と残す英語表記をそろえる。
94
95
  - `source-audit`: Switch HID、Bumble、既存実装、実機ログ由来の値を根拠分類して記録する。
95
96
  - `hardware-harness`: Bumble adapter、Bluetooth dongle、Switch 実機を使う検証の承認境界と記録項目を確認する。
96
97
  - `tdd-workflow`: TDD Test List から red / green / refactor を進める。
@@ -0,0 +1,150 @@
1
+ Metadata-Version: 2.4
2
+ Name: swbt-python
3
+ Version: 0.3.0
4
+ Summary: Python library for presenting an NX-compatible virtual Bluetooth HID input device.
5
+ Keywords: bluetooth,bluetooth-hid,controller,gamepad,hid,nx
6
+ Author: niart120
7
+ Author-email: niart120 <38847256+niart120@users.noreply.github.com>
8
+ License-Expression: MIT
9
+ License-File: LICENSE
10
+ Classifier: Development Status :: 2 - Pre-Alpha
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Operating System :: MacOS
14
+ Classifier: Operating System :: Microsoft :: Windows
15
+ Classifier: Operating System :: POSIX :: Linux
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Topic :: Software Development :: Libraries
20
+ Classifier: Typing :: Typed
21
+ Requires-Dist: bumble>=0.0.230,<0.0.231
22
+ Maintainer: niart120
23
+ Maintainer-email: niart120 <38847256+niart120@users.noreply.github.com>
24
+ Requires-Python: >=3.12
25
+ Description-Content-Type: text/markdown
26
+
27
+ # swbt-python
28
+
29
+ NX 向けの仮想 Bluetooth HID 入力デバイスを Python から扱うためのライブラリです。
30
+
31
+ 本ライブラリは pre-alpha 版です。実機での動作は Bluetooth ドングル、ドライバー、対象機器の FW バージョンに依存します。
32
+
33
+ ## 必要なもの
34
+
35
+ - Python 3.12 以降
36
+ - uv
37
+ - Bumble が利用可能な専用 USB Bluetooth ドングル
38
+
39
+ ## インストール
40
+
41
+ ```powershell
42
+ pip install swbt-python
43
+ ```
44
+
45
+ ソースから動かす場合は次を使います。
46
+
47
+ ```powershell
48
+ uv sync --dev
49
+ ```
50
+
51
+ ## ドキュメント
52
+
53
+ [公開ドキュメント](https://niart120.github.io/swbt-python/) には API、利用例、実機準備手順、AI エージェント向け要約があります。
54
+
55
+ - API 仕様: [API Reference](https://niart120.github.io/swbt-python/api/)
56
+ - 利用例: [Usage Guide](https://niart120.github.io/swbt-python/usage/)
57
+ - 実機準備手順とトラブルシューティング: [Hardware Guide](https://niart120.github.io/swbt-python/hardware/)
58
+ - AI エージェント向け要約: [Agent Brief](https://niart120.github.io/swbt-python/agent-brief/)
59
+
60
+ 同じ内容は `docs/` 配下でも確認できます。
61
+
62
+ ## 利用例
63
+ ### Pro Controller
64
+ ```python
65
+ import asyncio
66
+ from swbt import Button, ProController
67
+
68
+
69
+ async def main() -> None:
70
+ async with ProController(
71
+ adapter="usb:0",
72
+ key_store_path="switch-bond.json",
73
+ ) as pad:
74
+ await pad.connect(
75
+ timeout=30.0,
76
+ allow_pairing=True,
77
+ )
78
+ await pad.tap(Button.A)
79
+ await pad.neutral()
80
+
81
+
82
+ asyncio.run(main())
83
+ ```
84
+
85
+ Pro Controller 相当の仮想デバイスを作成し、ペアリング後に A ボタン入力を送信するコードの例です。
86
+
87
+ ### Joy-Con L/R
88
+
89
+ Joy-Con 相当の仮想デバイスは `JoyConL(...)` または `JoyConR(...)` で作成します。接続と入力の扱い方は `ProController` と同じです。
90
+
91
+ ```python
92
+ import asyncio
93
+ from swbt import Button, JoyConL, Stick
94
+
95
+
96
+ async def main() -> None:
97
+ async with JoyConL(
98
+ adapter="usb:0",
99
+ key_store_path="switch-left-joycon-bond.json",
100
+ ) as left:
101
+ await left.connect(timeout=30.0, allow_pairing=True)
102
+ await left.tap(Button.SR, Button.SL)
103
+ await left.tap(Button.L)
104
+ await left.lstick(Stick.left())
105
+ await left.neutral()
106
+
107
+
108
+ asyncio.run(main())
109
+ ```
110
+
111
+ "持ち方/順番を変える" 画面で Joy-Con としてペアリングする場合は、接続後に `await left.tap(Button.SR, Button.SL)` のように SR+SL を送信する必要があります。
112
+
113
+ Pro Controller、Joy-Con L、Joy-Con R では `key_store_path` を分けてください。Joy-Con L で右スティックや A/B/X/Y、Joy-Con R で左スティックや十字キーを入力すると `UnsupportedInputError` が送出されます。`JoyConPair` は未実装です。
114
+
115
+ ## 接続方法
116
+
117
+ 実機接続には、PC の通常 Bluetooth 機能と共有しない専用 USB Bluetooth ドングルと、OS ごとのドライバー準備が必要です。Windows では、[Zadig](https://zadig.akeo.ie/) などで専用ドングルに WinUSB / libwdi ドライバーを入れてから アダプタ名を確認します。
118
+
119
+ ドライバー準備、アダプタ名の確認、トラブルシューティングの詳細は [Hardware Guide](https://niart120.github.io/swbt-python/hardware/) にあります。
120
+
121
+ ### 確認済み構成
122
+
123
+ 2026-07-07 時点では、Windows 11 / CSR8510 A10 / WinUSB / `usb:0` / Switch 2 firmware 22.1.0 で、Pro Controller のペアリング、保存済みペアリング情報を使う再接続、主要なボタン / スティック入力、ニュートラル復帰を確認済みです。
124
+
125
+ 同じ Windows 構成で、Joy-Con L/R も部分的に動作確認済みです。確認済み範囲と未確認範囲の詳細は Hardware Guide にあります。
126
+
127
+ macOS 15.7.7 / CSR8510 A10 では、Pro Controller のペアリング、保存済みペアリング情報を使う再接続、ボタン入力、ニュートラル復帰を記録しています。
128
+
129
+ ### 実験的構成
130
+
131
+ Linux は experimental です。手順は Hardware Guide に整備されていますが、専用 USB Bluetooth ドングルにアクセスできるか、ペアリングできるか、入力が反映されるかは未確認です。macOS は Pro Controller の一部挙動のみ検証済みです。Joy-Con、別ドングル、別ファームウェアでの互換性は未確認です。
132
+
133
+ ## 開発
134
+
135
+ ```powershell
136
+ uv sync --dev
137
+ uv run ruff format --check .
138
+ uv run ruff check .
139
+ uv run ty check --no-progress
140
+ uv run pytest tests/unit
141
+ uv run pytest tests/integration
142
+ ```
143
+
144
+ ## ライセンス
145
+
146
+ MIT ライセンスです。全文は [LICENSE](https://github.com/niart120/swbt-python/blob/main/LICENSE) にあります。
147
+
148
+ ## 注記
149
+
150
+ このプロジェクトは、対象機器や関連商標の権利者から承認、後援、提携を受けたものではありません。
@@ -0,0 +1,124 @@
1
+ # swbt-python
2
+
3
+ NX 向けの仮想 Bluetooth HID 入力デバイスを Python から扱うためのライブラリです。
4
+
5
+ 本ライブラリは pre-alpha 版です。実機での動作は Bluetooth ドングル、ドライバー、対象機器の FW バージョンに依存します。
6
+
7
+ ## 必要なもの
8
+
9
+ - Python 3.12 以降
10
+ - uv
11
+ - Bumble が利用可能な専用 USB Bluetooth ドングル
12
+
13
+ ## インストール
14
+
15
+ ```powershell
16
+ pip install swbt-python
17
+ ```
18
+
19
+ ソースから動かす場合は次を使います。
20
+
21
+ ```powershell
22
+ uv sync --dev
23
+ ```
24
+
25
+ ## ドキュメント
26
+
27
+ [公開ドキュメント](https://niart120.github.io/swbt-python/) には API、利用例、実機準備手順、AI エージェント向け要約があります。
28
+
29
+ - API 仕様: [API Reference](https://niart120.github.io/swbt-python/api/)
30
+ - 利用例: [Usage Guide](https://niart120.github.io/swbt-python/usage/)
31
+ - 実機準備手順とトラブルシューティング: [Hardware Guide](https://niart120.github.io/swbt-python/hardware/)
32
+ - AI エージェント向け要約: [Agent Brief](https://niart120.github.io/swbt-python/agent-brief/)
33
+
34
+ 同じ内容は `docs/` 配下でも確認できます。
35
+
36
+ ## 利用例
37
+ ### Pro Controller
38
+ ```python
39
+ import asyncio
40
+ from swbt import Button, ProController
41
+
42
+
43
+ async def main() -> None:
44
+ async with ProController(
45
+ adapter="usb:0",
46
+ key_store_path="switch-bond.json",
47
+ ) as pad:
48
+ await pad.connect(
49
+ timeout=30.0,
50
+ allow_pairing=True,
51
+ )
52
+ await pad.tap(Button.A)
53
+ await pad.neutral()
54
+
55
+
56
+ asyncio.run(main())
57
+ ```
58
+
59
+ Pro Controller 相当の仮想デバイスを作成し、ペアリング後に A ボタン入力を送信するコードの例です。
60
+
61
+ ### Joy-Con L/R
62
+
63
+ Joy-Con 相当の仮想デバイスは `JoyConL(...)` または `JoyConR(...)` で作成します。接続と入力の扱い方は `ProController` と同じです。
64
+
65
+ ```python
66
+ import asyncio
67
+ from swbt import Button, JoyConL, Stick
68
+
69
+
70
+ async def main() -> None:
71
+ async with JoyConL(
72
+ adapter="usb:0",
73
+ key_store_path="switch-left-joycon-bond.json",
74
+ ) as left:
75
+ await left.connect(timeout=30.0, allow_pairing=True)
76
+ await left.tap(Button.SR, Button.SL)
77
+ await left.tap(Button.L)
78
+ await left.lstick(Stick.left())
79
+ await left.neutral()
80
+
81
+
82
+ asyncio.run(main())
83
+ ```
84
+
85
+ "持ち方/順番を変える" 画面で Joy-Con としてペアリングする場合は、接続後に `await left.tap(Button.SR, Button.SL)` のように SR+SL を送信する必要があります。
86
+
87
+ Pro Controller、Joy-Con L、Joy-Con R では `key_store_path` を分けてください。Joy-Con L で右スティックや A/B/X/Y、Joy-Con R で左スティックや十字キーを入力すると `UnsupportedInputError` が送出されます。`JoyConPair` は未実装です。
88
+
89
+ ## 接続方法
90
+
91
+ 実機接続には、PC の通常 Bluetooth 機能と共有しない専用 USB Bluetooth ドングルと、OS ごとのドライバー準備が必要です。Windows では、[Zadig](https://zadig.akeo.ie/) などで専用ドングルに WinUSB / libwdi ドライバーを入れてから アダプタ名を確認します。
92
+
93
+ ドライバー準備、アダプタ名の確認、トラブルシューティングの詳細は [Hardware Guide](https://niart120.github.io/swbt-python/hardware/) にあります。
94
+
95
+ ### 確認済み構成
96
+
97
+ 2026-07-07 時点では、Windows 11 / CSR8510 A10 / WinUSB / `usb:0` / Switch 2 firmware 22.1.0 で、Pro Controller のペアリング、保存済みペアリング情報を使う再接続、主要なボタン / スティック入力、ニュートラル復帰を確認済みです。
98
+
99
+ 同じ Windows 構成で、Joy-Con L/R も部分的に動作確認済みです。確認済み範囲と未確認範囲の詳細は Hardware Guide にあります。
100
+
101
+ macOS 15.7.7 / CSR8510 A10 では、Pro Controller のペアリング、保存済みペアリング情報を使う再接続、ボタン入力、ニュートラル復帰を記録しています。
102
+
103
+ ### 実験的構成
104
+
105
+ Linux は experimental です。手順は Hardware Guide に整備されていますが、専用 USB Bluetooth ドングルにアクセスできるか、ペアリングできるか、入力が反映されるかは未確認です。macOS は Pro Controller の一部挙動のみ検証済みです。Joy-Con、別ドングル、別ファームウェアでの互換性は未確認です。
106
+
107
+ ## 開発
108
+
109
+ ```powershell
110
+ uv sync --dev
111
+ uv run ruff format --check .
112
+ uv run ruff check .
113
+ uv run ty check --no-progress
114
+ uv run pytest tests/unit
115
+ uv run pytest tests/integration
116
+ ```
117
+
118
+ ## ライセンス
119
+
120
+ MIT ライセンスです。全文は [LICENSE](https://github.com/niart120/swbt-python/blob/main/LICENSE) にあります。
121
+
122
+ ## 注記
123
+
124
+ このプロジェクトは、対象機器や関連商標の権利者から承認、後援、提携を受けたものではありません。
@@ -11,7 +11,7 @@ from collections.abc import Sequence
11
11
  from pathlib import Path
12
12
  from typing import TextIO
13
13
 
14
- from swbt import Button, DiagnosticsConfig, SwitchGamepad
14
+ from swbt import Button, DiagnosticsConfig, ProController
15
15
 
16
16
 
17
17
  async def run(
@@ -51,7 +51,7 @@ async def _bring_up_with_trace(
51
51
  trace_writer: TextIO,
52
52
  ) -> None:
53
53
  diagnostics = DiagnosticsConfig(trace_writer=trace_writer)
54
- async with SwitchGamepad(
54
+ async with ProController(
55
55
  adapter=adapter,
56
56
  key_store_path=key_store_path,
57
57
  diagnostics=diagnostics,
@@ -11,7 +11,7 @@ from collections.abc import Sequence
11
11
  from pathlib import Path
12
12
  from typing import TextIO
13
13
 
14
- from swbt import DiagnosticsConfig, SwitchGamepad
14
+ from swbt import DiagnosticsConfig, ProController
15
15
 
16
16
 
17
17
  async def run(
@@ -47,7 +47,7 @@ async def _pair_with_trace(
47
47
  trace_writer: TextIO,
48
48
  ) -> None:
49
49
  diagnostics = DiagnosticsConfig(trace_writer=trace_writer)
50
- async with SwitchGamepad(
50
+ async with ProController(
51
51
  adapter=adapter,
52
52
  key_store_path=key_store_path,
53
53
  diagnostics=diagnostics,
@@ -9,7 +9,7 @@ import argparse
9
9
  import asyncio
10
10
  from collections.abc import Sequence
11
11
 
12
- from swbt import Button, SwitchGamepad
12
+ from swbt import Button, ProController, SwitchGamepad
13
13
 
14
14
 
15
15
  async def tap_a_once(pad: SwitchGamepad, *, duration: float = 0.08) -> None:
@@ -43,7 +43,7 @@ async def run(
43
43
  duration: Seconds to keep Button A pressed.
44
44
  allow_pairing: If ``True``, allow first-time pairing when no bond exists.
45
45
  """
46
- async with SwitchGamepad(adapter=adapter, key_store_path=key_store_path) as pad:
46
+ async with ProController(adapter=adapter, key_store_path=key_store_path) as pad:
47
47
  await pad.connect(
48
48
  timeout=connect_timeout,
49
49
  allow_pairing=allow_pairing,
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "swbt-python"
3
- version = "0.1.1"
3
+ version = "0.3.0"
4
4
  description = "Python library for presenting an NX-compatible virtual Bluetooth HID input device."
5
5
  readme = "README.md"
6
6
  authors = [
@@ -0,0 +1,268 @@
1
+ # Adapter Discovery API 仕様書
2
+
3
+ ## 1. 概要
4
+
5
+ ### 1.1 目的
6
+
7
+ Python 利用者が `SwitchGamepad(adapter=...)` に渡せる USB Bluetooth adapter 候補を、adapter open なしで確認できる公開 API を追加する。
8
+
9
+ API 名は `list_adapters()`、戻り値の要素は `AdapterInfo` とする。この API が列挙するのは PC 側の専用 USB Bluetooth dongle 候補であり、Nintendo Switch 本体や周辺の Bluetooth host を探索する API ではない。
10
+
11
+ ### 1.2 起点 / source
12
+
13
+ | source | 内容 | path |
14
+ |---|---|---|
15
+ | user request | 接続可能なデバイスを列挙する API を検討する | conversation |
16
+ | user scope | `list_adapters()` / `AdapterInfo` を仮名とし、Switch 本体ではなく `SwitchGamepad(adapter=...)` に渡す USB Bluetooth adapter 候補の列挙とする | conversation |
17
+ | public API design | 公開 API は `swbt` module root から import でき、Bumble の詳細を public API に露出しない | `spec/initial/api.md` |
18
+ | public error policy | no-open discovery の列挙不能は adapter open 失敗ではないため、public 例外 `AdapterDiscoveryError` として分ける | conversation |
19
+ | Bumble transport design | `adapter` は Bumble transport に渡す adapter moniker。既存例は `usb:0` | `spec/initial/api.md`, `spec/initial/transport-bumble.md` |
20
+ | no-open CLI | `swbt-probe adapters --json` は `opens_adapter=false` を返し、adapter を開かない診断境界として存在する | `src/swbt/probe.py`, `tests/unit/test_probe_cli.py` |
21
+ | hardware guide | adapter 名確認は `swbt-probe adapters --json`。この command は pairing、HID advertising、report loop を開始しない | `docs/hardware.md` |
22
+ | local Bumble implementation | `usb_probe.py` は libusb の device iterator から候補名、VID/PID、serial、manufacturer、product を表示する | `.venv/Lib/site-packages/bumble/apps/usb_probe.py` |
23
+ | local Bumble implementation | `open_usb_transport()` は endpoint 探索後に `found.open()` へ進むため、adapter open 境界である | `.venv/Lib/site-packages/bumble/transport/usb.py` |
24
+ | previous work unit | Linux/macOS experimental 対応では `swbt-probe adapters --json` の no-open 境界を診断入口として扱った | `spec/complete/unit_026/LINUX_MACOS_ADAPTER_EXPERIMENTAL.md` |
25
+
26
+ ### 1.3 use case
27
+
28
+ | actor / boundary | 入力または状態 | 期待する観測結果 | 制約 |
29
+ |---|---|---|---|
30
+ | Python user | `from swbt import list_adapters` して呼び出す | `SwitchGamepad(adapter=info.name)` に渡せる候補を取得できる | Switch 本体は列挙しない |
31
+ | Python user | adapter が 0 件 | 空 tuple を受け取る | 「adapter なし」と「列挙不能」は区別する |
32
+ | Python user | 同じ VID/PID の dongle が複数ある | `usb:N` と VID/PID alias、可能なら serial alias で候補を区別できる | `usb:N` の順序は USB 接続状態で変わり得る |
33
+ | CLI user | `swbt-probe adapters --json` | API と同じ候補情報を機械処理できる JSON で確認できる | adapter open、power on、advertising、pairing、report loop は行わない |
34
+ | maintainer | unit test | 実 USB device なしで no-open 契約と JSON 形状を固定できる | 実USB列挙は必須 gate にしない |
35
+ | hardware tester | 承認済みの手動確認 | 実 USB device の列挙結果を characterization として確認できる | adapter open や Switch-facing 動作へ進まない |
36
+
37
+ ## 2. 対象範囲
38
+
39
+ - `swbt` module root から import する公開 API として、`list_adapters()` と `AdapterInfo` を追加する仕様。
40
+ - `AdapterInfo.name` を `SwitchGamepad(adapter=...)` に渡せる adapter moniker として扱う仕様。
41
+ - Bumble / libusb の object 型や callback 型を公開 API に出さない境界。
42
+ - 実 USB device を開かずに、USB descriptor と Bumble transport name 候補を取得する no-open 契約。
43
+ - 既存 `swbt-probe adapters --json` を `list_adapters()` の CLI 表面として再利用する方向性。
44
+ - 単体テストと CLI regression を中心にした TDD Test List。
45
+ - 実 USB device を使う列挙確認を、承認が必要な `bumble` / `hardware` 境界として分ける方針。
46
+
47
+ ## 3. 対象外
48
+
49
+ - Nintendo Switch 本体、pairing 待ちの host、周辺 Bluetooth 機器の探索。
50
+ - USB Bluetooth dongle を Bumble transport として開くこと。
51
+ - `open_transport()`、`open_usb_transport()`、`BumbleHidTransport.open()`、`SwitchGamepad.open()`、`connect()`、`pair()`、`reconnect()` の呼び出し。
52
+ - Bluetooth controller の `power_on`。
53
+ - HID Device advertising、pairing、HID control / interrupt channel open。
54
+ - periodic input report loop。
55
+ - Switch-facing output report / subcommand handling。
56
+ - `@pytest.mark.bumble` / `@pytest.mark.hardware` を標準 gate に入れること。
57
+ - OS 別の driver 修復、udev rule 作成、Zadig 操作、macOS NVRAM 設定。
58
+ - 接続成功や Switch 互換性を `list_adapters()` の戻り値だけで保証すること。
59
+
60
+ ## 4. 関連 docs
61
+
62
+ - `spec/initial/README.md`
63
+ - `spec/initial/api.md`
64
+ - `spec/initial/transport-bumble.md`
65
+ - `spec/initial/testing.md`
66
+ - `spec/initial/risks.md`
67
+ - `spec/complete/unit_008/M7_PACKAGING_EXAMPLES_CLI.md`
68
+ - `spec/complete/unit_026/LINUX_MACOS_ADAPTER_EXPERIMENTAL.md`
69
+ - `docs/hardware.md`
70
+ - `src/swbt/probe.py`
71
+ - `tests/unit/test_probe_cli.py`
72
+ - `.venv/Lib/site-packages/bumble/apps/usb_probe.py`
73
+ - `.venv/Lib/site-packages/bumble/transport/usb.py`
74
+
75
+ ## 5. 根拠監査
76
+
77
+ | 項目 | 要否 | 状態 | 根拠 / 理由 |
78
+ |---|---|---|---|
79
+ | Switch HID / report bytes | not applicable | not applicable | この unit は Switch HID report、subcommand、SPI、rumble、report period を変更しない |
80
+ | Bumble / transport | required | reviewed-for-spec | adapter moniker と USB 列挙、transport open の境界をローカル Bumble 実装で確認した |
81
+ | OS / driver / adapter | required | reviewed-for-spec | public docs と既存 hardware observation を確認した。ただしこの turn では実 USB device を列挙していない |
82
+
83
+ ### 5.1 監査結果
84
+
85
+ | 項目 | 値 / 判断 | 根拠分類 | source | status |
86
+ |---|---|---|---|---|
87
+ | 公開 API 境界 | `swbt` module root から import でき、Bumble object 型を public API に出さない | source fact | `spec/initial/api.md` | done |
88
+ | adapter 引数 | `SwitchGamepad(adapter="usb:0")` は Bumble transport に渡す adapter moniker | source fact | `spec/initial/api.md`, `spec/initial/transport-bumble.md` | done |
89
+ | CLI no-open | `_run_adapters()` は `list_adapters()` 由来の `adapters` 配列と `opens_adapter=false` を返し、`SwitchGamepad` を作らない | implementation fact | `src/swbt/probe.py` | done |
90
+ | CLI regression | `test_swbt_probe_adapters_json_reports_no_open_environment` は `adapters` 配列、VID/PID の int / hex 併記、`opens_adapter=false` を確認する | implementation fact | `tests/unit/test_probe_cli.py` | done |
91
+ | Hardware Guide の no-open 説明 | `swbt-probe adapters --json` は pairing、HID advertising、report loop を開始しない | source fact | `docs/hardware.md` | done |
92
+ | Bumble USB 列挙 | `usb_probe.py` は `load_libusb()`、`usb1.USBContext()`、`getDeviceIterator(skip_on_error=True)`、descriptor getter で候補情報を作る | source fact | `.venv/Lib/site-packages/bumble/apps/usb_probe.py` | done |
93
+ | Bumble transport name | HCI device には `usb:N`、全 USB device には `usb:VID:PID`、重複時は `#index`、serial があれば `/serial` の alias を作る | source fact | `.venv/Lib/site-packages/bumble/apps/usb_probe.py` | done |
94
+ | Bumble HCI 判定 | device class または interface class が Wireless Controller / RF Controller / Bluetooth primary controller の tuple に一致するかを見る | source fact | `.venv/Lib/site-packages/bumble/apps/usb_probe.py` | done |
95
+ | Bumble open 境界 | `open_usb_transport()` は `context.open()`、endpoint 探索、`found.open()`、kernel driver auto-detach、configuration 設定へ進む | source fact | `.venv/Lib/site-packages/bumble/transport/usb.py` | done |
96
+ | Windows 確認済み構成 | Windows 11 / CSR8510 A10 / WinUSB / Switch 2 firmware 22.1.0 で pairing、reconnect、入力反映が確認済み | hardware observation | `docs/hardware.md` | done |
97
+ | macOS 確認済み構成 | macOS 15.7.7 / CSR8510 A10 / Homebrew libusb / Bumble 0.0.230 / Python 3.12.13 / `usb:0` で pairing、active reconnect、入力反映が確認済み | hardware observation | `docs/hardware.md` | done |
98
+ | Linux 状態 | Linux の adapter listing、adapter open、HID advertising、pairing、reconnect、input reflection は未確認 | hardware observation | `docs/hardware.md` | done |
99
+ | no-open API の意味 | libusb による USB device enumeration は行うが、Bumble transport open、controller power on、HID advertising へ進まない | inference | Bumble `usb_probe.py` と `open_usb_transport()` の差分 | spec decision |
100
+ | `AdapterInfo` の候補性 | HCI class の USB device でも driver、権限、OS stack 競合、Switch 互換性は別問題 | inference | `docs/hardware.md`, `spec/initial/transport-bumble.md` | spec decision |
101
+ | 列挙結果が接続成功を保証する | adapter 候補が返れば Switch と接続できる | unverified hypothesis | none | not a contract |
102
+ | descriptor getter の OS 差 | manufacturer、product、serial の取得可否は OS、driver、permission、device に依存する | unverified hypothesis | local source inspection only | keep-optional |
103
+
104
+ ### 5.2 未解決事項
105
+
106
+ - `usb1.USBContext()` と descriptor getter が各 OS でどの程度の権限を要求するかは、Windows / CSR8510 A10 / WinUSB / `usb:0` では no-open discovery として確認した。macOS / Linux はこの unit では実機確認しない。
107
+ - `usb_probe.py` と同じ HCI 判定で、swbt-python が実際に扱える adapter 候補を過不足なく列挙できるかは未検証である。
108
+ - serial number を読めない device では、VID/PID duplicate の識別が `#index` に寄る。この順序は接続状態で変わり得る。
109
+ - macOS / Linux の実USB列挙結果は、この unit の必須検証には含めない。
110
+
111
+ ## 6. 振る舞い仕様
112
+
113
+ | 振る舞い | 入力・状態 | 期待結果 | 備考 |
114
+ |---|---|---|---|
115
+ | API import | `from swbt import list_adapters, AdapterInfo` | import できる | `swbt` module root の公開 API とする |
116
+ | public error import | `from swbt import AdapterDiscoveryError` | import できる | no-open discovery 専用の public 例外とする |
117
+ | API result shape | `list_adapters()` | `tuple[AdapterInfo, ...]` を返す | 呼び出し側が immutable snapshot として扱える |
118
+ | adapter target | 返却された `AdapterInfo.name` | `SwitchGamepad(adapter=info.name)` に渡せる文字列 | Switch 本体の列挙ではない |
119
+ | no-open contract | `list_adapters()` | adapter open、power on、advertising、pairing、report loop を行わない | libusb enumeration は行う |
120
+ | no devices | HCI 候補が 0 件 | 空 tuple を返す | 例外にしない |
121
+ | discovery failure | libusb load または USBContext 作成、iteration が開始できない | `AdapterDiscoveryError` を投げる | adapter 0 件と区別する |
122
+ | descriptor failure | serial、manufacturer、product の取得だけ失敗 | 該当 field を `None` にして候補全体は残す | Bumble `usb_probe.py` と同じく descriptor USBError を候補落ちにしない |
123
+ | HCI filter | default call | Bluetooth HCI class と判定できる device だけを返す | unknown class の forced mode は初期対象外 |
124
+ | aliases | HCI device | `usb:N`、VID/PID、重複 index、serial alias を表現する | alias は `AdapterInfo.aliases` に保持する |
125
+ | unstable index | USB 接続状態が変わる | `usb:N` の安定性を保証しない | docs と docstring に書く |
126
+ | CLI JSON | `swbt-probe adapters --json` | `opens_adapter=false` と adapters 配列を出す | `candidate_adapters` は維持しない。`AdapterInfo` 相当の構造化 JSON を正本にする |
127
+ | CLI text | `swbt-probe adapters` | no-open 境界、候補名、主要 metadata を表示する | 実 USB 列挙が失敗した場合は setup 問題として表示する |
128
+
129
+ ## 7. API 案
130
+
131
+ ```python
132
+ @dataclass(frozen=True, slots=True)
133
+ class AdapterInfo:
134
+ name: str
135
+ aliases: tuple[str, ...]
136
+ vendor_id: int | None
137
+ product_id: int | None
138
+ manufacturer: str | None
139
+ product: str | None
140
+ serial_number: str | None
141
+ bus_number: int | None
142
+ device_address: int | None
143
+ port_numbers: tuple[int, ...]
144
+ is_bluetooth_hci: bool
145
+
146
+
147
+ class AdapterDiscoveryError(SwbtError):
148
+ ...
149
+
150
+
151
+ def list_adapters() -> tuple[AdapterInfo, ...]: ...
152
+ ```
153
+
154
+ `name` は最初に利用者へ提示する adapter moniker である。HCI class device では `usb:0` のような index moniker を優先する。`aliases` には `usb:VID:PID`、`usb:VID:PID#index`、`usb:VID:PID/serial` のような別指定を入れる。`aliases` に `name` を重複して入れない。
155
+
156
+ `vendor_id` と `product_id` は Python API では `int` とする。CLI JSON でも同じ `int` field を正とし、人間が USB ID と照合しやすいように `vendor_id_hex` / `product_id_hex` を 4 桁大文字 hex 文字列で併記する。
157
+
158
+ `AdapterDiscoveryError` は public 例外として `swbt` module root から公開する。候補 0 件ではなく、libusb load、USB context 作成、device iteration などの列挙処理自体が成立しない場合に使う。`TransportOpenError` は adapter open の失敗用なので、no-open API の列挙失敗には流用しない。
159
+
160
+ `AdapterDiscoveryError` は `raise ... from exc` で元例外を保持する。追加 field は `platform`、`backend="bumble-usb"`、`libusb_available: bool | None`、`bumble_version: str | None` に限定し、libusb や Bumble の生 object は public API に出さない。
161
+
162
+ ## 8. TDD Test List
163
+
164
+ | status | item | type | layer | hardware | notes |
165
+ |---|---|---|---|---|---|
166
+ | green | `list_adapters()` と `AdapterInfo` を `swbt` module root から import できる | new | unit | no | `uv run pytest tests/unit/test_adapter_discovery.py -q` で確認。Bumble object 型を公開しない |
167
+ | green | `AdapterDiscoveryError` を `swbt` module root から import でき、`SwbtError` の派生として扱える | new | unit | no | `uv run pytest tests/unit/test_adapter_discovery.py -q` で確認。no-open discovery の public 例外として固定する |
168
+ | green | fake USB enumerator が 1 件の Bluetooth HCI device を返すと、`list_adapters()` が `AdapterInfo(name="usb:0")` を返す | new | unit | no | `uv run pytest tests/unit/test_adapter_discovery.py -q` で確認。実USB列挙を使わない |
169
+ | green | fake USB enumerator の HCI device から VID/PID、manufacturer、product、serial、bus/device 情報を `AdapterInfo` へ写す | new | unit | no | `uv run pytest tests/unit/test_adapter_discovery.py -q` で確認。descriptor getter は fake で観測する |
170
+ | green | 同じ VID/PID の HCI device が複数ある場合、`aliases` に duplicate index または serial alias が入る | edge | unit | no | `uv run pytest tests/unit/test_adapter_discovery.py -q` で確認。`usb_probe.py` の重複 moniker 生成を仕様化する |
171
+ | green | serial、manufacturer、product の取得だけが失敗しても、該当 field を `None` にして候補を返す | edge | unit | no | `uv run pytest tests/unit/test_adapter_discovery.py -q` で確認。descriptor access 失敗を列挙失敗にしない |
172
+ | green | HCI class ではない USB device は default の `list_adapters()` に含めない | new | unit | no | `uv run pytest tests/unit/test_adapter_discovery.py -q` で確認。forced mode や unknown USB device は初期対象外 |
173
+ | green | HCI 候補が 0 件の場合、`list_adapters()` は空 tuple を返す | edge | unit | no | `uv run pytest tests/unit/test_adapter_discovery.py -q` で確認。no devices と discovery failure を分ける |
174
+ | green | libusb load または USBContext 初期化が失敗した場合、`AdapterDiscoveryError` を投げる | edge | unit | no | `uv run pytest tests/unit/test_adapter_discovery.py -q` で確認。元例外と public metadata を保持し、`TransportOpenError` にしない |
175
+ | green | `list_adapters()` は `open_transport()`、`open_usb_transport()`、`device.open()`、`SwitchGamepad` を呼ばない | regression | unit | no | `uv run pytest tests/unit/test_adapter_discovery.py -q` で spy 確認。monkeypatch / spy で no-open 契約を固定する |
176
+ | green | `swbt-probe adapters --json` は `list_adapters()` の結果を `adapters` 配列に出し、`opens_adapter=false` を維持する | regression | unit | no | `uv run pytest tests/unit/test_probe_cli.py -q` で確認。`candidate_adapters` は出さない。VID/PID は int と 4 桁大文字 hex を併記する |
177
+ | green | `swbt-probe adapters --json` は discovery failure を adapter open 失敗として報告しない | edge | unit | no | `uv run pytest tests/unit/test_probe_cli.py -q` で確認。exit code `1`、`status="discovery_error"`、`error.type="AdapterDiscoveryError"` を返す |
178
+ | green | `swbt-probe adapters` の human output が no-open 境界と候補 adapter name を表示する | regression | unit | no | `uv run pytest tests/unit/test_probe_cli.py -q` で確認。help text の no-open 文言も維持する |
179
+ | green | public API docs と docstring が `AdapterDiscoveryError` を候補 0 件ではなく列挙不能の例外として説明する | new | unit | no | `uv run pytest tests/unit/test_public_api_docstrings.py tests/unit/test_public_docs.py::test_api_doc_covers_top_level_public_exports_and_methods tests/unit/test_package_import.py -q` で確認 |
180
+ | green | Windows で `uv run swbt-probe adapters --json` を実 USB dongle ありで実行する | characterization | bumble | yes | 2026-07-05 に承認済み範囲で実行。CSR8510 A10 / `usb:0` を検出し、`opens_adapter=false`、`manufacturer=null`、`serial_number=null` を確認。adapter open はしていない |
181
+ | deferred | macOS で `uv run swbt-probe adapters --json` を実 USB dongle ありで実行する | characterization | bumble | yes | 実行する場合は libusb path と OS Bluetooth stack 状態を記録する |
182
+ | deferred | Linux で `uv run swbt-probe adapters --json` を実 USB dongle ありで実行する | characterization | bumble | yes | Linux は adapter listing も未確認。udev / BlueZ 状態を記録する |
183
+ | green | `list_adapters()` で返った adapter を `SwitchGamepad(adapter=...)` で open できるか確認する | characterization | bumble | yes | 2026-07-05 に承認済みの open-only smoke で `usb:0` を確認。`transport_open_complete` と `transport_close_complete` を記録し、`advertising_start` と `host_connection` は記録しなかった。HID advertising、pairing、report loop、入力送信はしていない |
184
+
185
+ ## 9. 設計メモ
186
+
187
+ - API 名に `device` を入れない。`device` は Switch 本体、仮想 controller、USB dongle のどれにも読めるため、公開名は `adapter` を使う。
188
+ - `list_adapters()` は「接続可能な Switch」を探す関数ではない。`SwitchGamepad(adapter=...)` の入力候補を返す関数である。
189
+ - `list_adapters()` の no-open 契約は「USB に一切触れない」ではない。libusb の列挙と descriptor 読み取りは行うが、Bumble transport として device handle を開かない。
190
+ - `swbt-probe adapters --json` は `list_adapters()` 由来の `adapters` 配列と環境情報を返す。unit 着手前の固定候補 `candidate_adapters=["usb:0"]` は維持しない。`opens_adapter=false` は no-open 契約として維持する。
191
+ - `usb:N` は短く便利だが、接続順や他の dongle に影響される。利用者向け docs では、`serial_number` が取れる場合に serial alias を永続的な指定として推奨する。serial を取れない同一 VID/PID 複数台では、`usb:N` と duplicate index は接続状態で変わる診断用識別子として扱う。
192
+ - `AdapterInfo` は Bumble の device object を保持しない。戻り値は文字列、数値、文字列 tuple だけにする。
193
+ - `AdapterDiscoveryError` は public API とする。docstring、root export、CLI のエラー表示を同じ unit で固定する。
194
+ - 実 USB 列挙は adapter open ではないが、OS driver と libusb permission に依存する。標準 gate では fake enumerator の unit test を先に固定し、実 USB は characterization に分ける。
195
+ - `usb_probe.py` は HCI class ではない USB device にも transport name を出すが、swbt-python の初期 API は HCI class と判定できる adapter だけを返す。unknown class の forced mode は、必要性が出た時点で別 unit に分ける。
196
+ - `docs/hardware.md` の troubleshooting では、`adapters=[]`、`AdapterDiscoveryError`、候補は出るが `SwitchGamepad(adapter=...)` で open できない状態を別症状として説明する。
197
+
198
+ ## 10. 対象ファイル
199
+
200
+ ### 10.1 この unit で編集したファイル
201
+
202
+ | path | change | 内容 |
203
+ |---|---|---|
204
+ | `src/swbt/__init__.py` | modify | `list_adapters`、`AdapterInfo`、`AdapterDiscoveryError` を公開する |
205
+ | `src/swbt/adapter_discovery.py` | new | no-open adapter 列挙 API の実装 |
206
+ | `src/swbt/transport/_bumble_usb_devices.py` | new | Bumble / usb1 による no-open USB device enumeration 境界 |
207
+ | `src/swbt/errors.py` | modify | `AdapterDiscoveryError` を public 例外として追加する |
208
+ | `src/swbt/probe.py` | modify | `swbt-probe adapters` を `list_adapters()` ベースへ寄せる |
209
+ | `tests/unit/test_adapter_discovery.py` | new | fake USB enumerator による API unit tests |
210
+ | `tests/unit/test_probe_cli.py` | modify | `swbt-probe adapters --json` の no-open regression を更新する |
211
+ | `tests/unit/test_package_import.py` | modify | `swbt.__all__` の public export を更新する |
212
+ | `tests/unit/test_public_api_docstrings.py` | modify | `AdapterInfo` / `AdapterDiscoveryError` / `list_adapters()` の docstring contract を固定する |
213
+ | `tests/unit/test_public_docs.py` | modify | hardware docs の adapter discovery 境界を固定する |
214
+ | `spec/initial/api.md` | modify | adapter discovery API を正本へ反映する |
215
+ | `spec/initial/transport-bumble.md` | modify | adapter discovery と adapter open 境界を追記する |
216
+ | `docs/api.md` | modify | public API docs へ adapter discovery を追加する |
217
+ | `docs/hardware.md` | modify | `list_adapters()` と `swbt-probe adapters` の使い分けを追記する |
218
+ | `spec/complete/unit_027/ADAPTER_DISCOVERY_API.md` | moved | TDD 結果、検証、完了境界を更新して complete へ移動する |
219
+
220
+ ## 11. 検証
221
+
222
+ | command | result | notes |
223
+ |---|---|---|
224
+ | `uv run pytest tests/unit/test_adapter_discovery.py -q` | pass | public import、fake USB enumerator、metadata、alias、descriptor fallback、HCI filter、0 件時の空 tuple、discovery failure、no-open spy を確認。10 passed |
225
+ | `uv run pytest tests/unit/test_probe_cli.py -q` | pass | `swbt-probe adapters` の JSON success / discovery error、human output、既存 help / pair 境界を確認。7 passed |
226
+ | `uv run pytest tests/unit/test_public_api_docstrings.py tests/unit/test_public_docs.py::test_api_doc_covers_top_level_public_exports_and_methods tests/unit/test_package_import.py -q` | pass | adapter discovery の public export、API docs、docstring を確認。4 passed |
227
+ | `uv run ruff format --check .` | pass | 76 files already formatted |
228
+ | `uv run ruff check .` | pass | All checks passed |
229
+ | `uv run ty check --no-progress` | pass | All checks passed |
230
+ | `uv run pytest tests/unit -q` | pass | 243 passed |
231
+ | `uv run pytest tests/integration -q` | pass | 69 passed |
232
+ | `uv build` | pass | `dist\swbt_python-0.1.1.tar.gz` と `dist\swbt_python-0.1.1-py3-none-any.whl` を生成。`dist/` は gitignore 対象 |
233
+ | `uv run swbt-probe adapters --json` | pass | 2026-07-05 に Windows / CSR8510 A10 / `usb:0` で実行。`opens_adapter=false`、`aliases=["usb:0A12:0001"]`、`manufacturer=null`、`product="CSR8510 A10"`、`serial_number=null` を確認 |
234
+ | `uv run pytest tests\hardware\test_context_manager_resource_scope.py::test_switch_gamepad_open_only_does_not_start_advertising_on_bumble -m bumble --swbt-bumble-adapter usb:0 --swbt-hardware-artifact-dir .pytest_cache\hardware\unit_027\20260705-open-only -q -s` | pass | 2026-07-05 に承認済み範囲で実行。`1 passed in 0.30s`。trace は `transport_open_complete` と `transport_close_complete` を記録し、`advertising_start` と `host_connection` は記録しなかった |
235
+ | `uv run pytest -m bumble` | not run | 承認範囲を no-open discovery と open-only smoke に限定したため、bumble marker 全体は実行しない |
236
+ | `uv run pytest -m hardware` | not run | Switch-facing 動作をこの unit の必須条件にしない。HID advertising、pairing、report loop、入力送信は未実行 |
237
+
238
+ ## 12. 実機実行条件
239
+
240
+ | 項目 | 内容 |
241
+ |---|---|
242
+ | 実機要否 | Python 実装の単体テストでは不要。実 USB 列挙と open-only smoke の characterization では必要 |
243
+ | 承認範囲 | 2026-07-05 にユーザが `usb:0`、no-open discovery、open-only smoke を承認した。範囲は USB descriptor listing と Bumble adapter open / close まで。HID advertising、Switch pairing、report loop、入力送信は対象外 |
244
+ | adapter | 2026-07-05 の実行では `usb:0`。文書例としての `usb:0` は固定値扱いしない |
245
+ | 実行遮断 | 環境変数による遮断は採用しない。明示承認、対象 adapter、command、cleanup plan で管理する |
246
+ | log / artifact | `spec/hardware-test-log.md` に記録済み。open-only smoke artifact は `.pytest_cache\hardware\unit_027\20260705-open-only\resource-open-only.jsonl` |
247
+ | cleanup | open-only smoke は `pad.close(neutral=True)` を `finally` で実行し、trace に `transport_close_complete` を記録した。no-open discovery は adapter open、advertising、pairing、report loop を発生させていない |
248
+
249
+ ## 13. 先送り事項
250
+
251
+ - 実 USB dongle ありの macOS / Linux 列挙結果。
252
+ - Windows 以外で `list_adapters()` の結果を `SwitchGamepad(adapter=...)` で open できるかの characterization。
253
+ - Unit 027 の範囲を超える HID advertising、pairing、report loop、入力送信の実機確認。
254
+
255
+ ## 14. チェックリスト
256
+
257
+ - [x] 対象範囲と対象外を確認した
258
+ - [x] `Switch` 本体の列挙ではなく USB Bluetooth adapter 候補の列挙であることを明記した
259
+ - [x] no-open 契約を adapter open、power on、HID advertising、pairing、report loop と分けて記録した
260
+ - [x] Bumble / libusb / OS driver の根拠を分類して記録した
261
+ - [x] TDD Test List を観測可能な振る舞いで作成した
262
+ - [x] `list_adapters()`、`AdapterInfo`、`AdapterDiscoveryError` を public API として実装した
263
+ - [x] `swbt-probe adapters` を `list_adapters()` ベースへ更新した
264
+ - [x] public docs と initial docs へ adapter discovery 境界を反映した
265
+ - [x] unit / integration / static gate を実行した
266
+ - [x] 実 USB 列挙を `bumble` / `hardware` 承認境界として分けた
267
+ - [x] Windows / CSR8510 A10 / `usb:0` で承認済み no-open discovery と open-only smoke を記録した
268
+ - [x] 検証結果または未実行理由を記録した