swbt-python 0.3.0__tar.gz → 0.4.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.
- {swbt_python-0.3.0 → swbt_python-0.4.0}/AGENTS.md +10 -1
- {swbt_python-0.3.0 → swbt_python-0.4.0}/PKG-INFO +1 -1
- {swbt_python-0.3.0 → swbt_python-0.4.0}/pyproject.toml +1 -1
- swbt_python-0.4.0/spec/complete/unit_050/DIRECT_REPORTING_TYPES.md +260 -0
- {swbt_python-0.3.0 → swbt_python-0.4.0}/spec/initial/api.md +73 -60
- {swbt_python-0.3.0 → swbt_python-0.4.0}/spec/initial/architecture.md +22 -15
- {swbt_python-0.3.0 → swbt_python-0.4.0}/spec/initial/lifecycle.md +19 -15
- {swbt_python-0.3.0 → swbt_python-0.4.0}/spec/initial/testing.md +31 -8
- {swbt_python-0.3.0 → swbt_python-0.4.0}/src/swbt/__init__.py +10 -0
- {swbt_python-0.3.0 → swbt_python-0.4.0}/src/swbt/_testing/gamepad.py +72 -1
- {swbt_python-0.3.0 → swbt_python-0.4.0}/src/swbt/gamepad/__init__.py +12 -0
- {swbt_python-0.3.0 → swbt_python-0.4.0}/src/swbt/gamepad/core.py +105 -28
- {swbt_python-0.3.0 → swbt_python-0.4.0}/src/swbt/gamepad/interface.py +55 -26
- {swbt_python-0.3.0 → swbt_python-0.4.0}/src/swbt/gamepad/output.py +7 -5
- {swbt_python-0.3.0 → swbt_python-0.4.0}/src/swbt/gamepad/runtime.py +141 -19
- {swbt_python-0.3.0 → swbt_python-0.4.0}/src/swbt/report_loop.py +123 -52
- {swbt_python-0.3.0 → swbt_python-0.4.0}/tests/integration/test_switch_gamepad_fake_transport.py +468 -2
- {swbt_python-0.3.0 → swbt_python-0.4.0}/tests/unit/test_gamepad_output_dispatcher.py +6 -3
- {swbt_python-0.3.0 → swbt_python-0.4.0}/tests/unit/test_package_import.py +10 -0
- {swbt_python-0.3.0 → swbt_python-0.4.0}/tests/unit/test_public_api_boundary.py +56 -2
- {swbt_python-0.3.0 → swbt_python-0.4.0}/tests/unit/test_publish_workflow.py +0 -27
- swbt_python-0.3.0/tests/unit/test_docs_workflow.py +0 -68
- swbt_python-0.3.0/tests/unit/test_hardware_test_log_docs.py +0 -73
- swbt_python-0.3.0/tests/unit/test_mkdocs_site.py +0 -46
- swbt_python-0.3.0/tests/unit/test_public_api_docstrings.py +0 -287
- swbt_python-0.3.0/tests/unit/test_public_docs.py +0 -376
- swbt_python-0.3.0/tests/unit/test_readme_docs.py +0 -133
- swbt_python-0.3.0/tests/unit/test_release_gate_docs.py +0 -82
- {swbt_python-0.3.0 → swbt_python-0.4.0}/LICENSE +0 -0
- {swbt_python-0.3.0 → swbt_python-0.4.0}/README.md +0 -0
- {swbt_python-0.3.0 → swbt_python-0.4.0}/examples/hardware_bringup.py +0 -0
- {swbt_python-0.3.0 → swbt_python-0.4.0}/examples/pairing_probe.py +0 -0
- {swbt_python-0.3.0 → swbt_python-0.4.0}/examples/tap_a.py +0 -0
- {swbt_python-0.3.0 → swbt_python-0.4.0}/spec/complete/unit_001/M0_PROTOCOL_CORE.md +0 -0
- {swbt_python-0.3.0 → swbt_python-0.4.0}/spec/complete/unit_002/M1_SWITCH_GAMEPAD_FAKE_TRANSPORT.md +0 -0
- {swbt_python-0.3.0 → swbt_python-0.4.0}/spec/complete/unit_003/M2_BUMBLE_HID_TRANSPORT.md +0 -0
- {swbt_python-0.3.0 → swbt_python-0.4.0}/spec/complete/unit_004/M3_PAIRING_L2CAP.md +0 -0
- {swbt_python-0.3.0 → swbt_python-0.4.0}/spec/complete/unit_005/M4_SUBCOMMAND_RESPONDER_HARDWARE.md +0 -0
- {swbt_python-0.3.0 → swbt_python-0.4.0}/spec/complete/unit_006/M5_INPUT_OPERATION_API.md +0 -0
- {swbt_python-0.3.0 → swbt_python-0.4.0}/spec/complete/unit_007/M6_RECONNECT_KEYSTORE_DIAGNOSTICS.md +0 -0
- {swbt_python-0.3.0 → swbt_python-0.4.0}/spec/complete/unit_008/M7_PACKAGING_EXAMPLES_CLI.md +0 -0
- {swbt_python-0.3.0 → swbt_python-0.4.0}/spec/complete/unit_009/PORTING_SOURCE_AUDIT.md +0 -0
- {swbt_python-0.3.0 → swbt_python-0.4.0}/spec/complete/unit_010/DIAGNOSTICS_TRACE_SCHEMA.md +0 -0
- {swbt_python-0.3.0 → swbt_python-0.4.0}/spec/complete/unit_011/HARDWARE_TEST_LOG_MATRIX.md +0 -0
- {swbt_python-0.3.0 → swbt_python-0.4.0}/spec/complete/unit_012/INITIAL_RELEASE_GATE.md +0 -0
- {swbt_python-0.3.0 → swbt_python-0.4.0}/spec/complete/unit_013/POST_M5_INPUT_SEMANTIC_CHARACTERIZATION.md +0 -0
- {swbt_python-0.3.0 → swbt_python-0.4.0}/spec/complete/unit_014/DEVICE_CLOSE_GRACEFUL_DISCONNECT.md +0 -0
- {swbt_python-0.3.0 → swbt_python-0.4.0}/spec/complete/unit_015/CONTEXT_MANAGER_RESOURCE_SCOPE.md +0 -0
- {swbt_python-0.3.0 → swbt_python-0.4.0}/spec/complete/unit_016/JSON_KEY_STORE_CURRENT_PREVIOUS.md +0 -0
- {swbt_python-0.3.0 → swbt_python-0.4.0}/spec/complete/unit_017/SWITCH_GAMEPAD_API_HARDENING.md +0 -0
- {swbt_python-0.3.0 → swbt_python-0.4.0}/spec/complete/unit_018/KEY_STORE_TRANSPORT_BOUNDARY.md +0 -0
- {swbt_python-0.3.0 → swbt_python-0.4.0}/spec/complete/unit_019/TRANSPORT_RECONNECT_CONTRACT.md +0 -0
- {swbt_python-0.3.0 → swbt_python-0.4.0}/spec/complete/unit_020/STRUCTURAL_REFACTOR_BOUNDARIES.md +0 -0
- {swbt_python-0.3.0 → swbt_python-0.4.0}/spec/complete/unit_021/SWITCH_GAMEPAD_INPUT_API_CONTRACT.md +0 -0
- {swbt_python-0.3.0 → swbt_python-0.4.0}/spec/complete/unit_022/PUBLIC_API_USAGE_HARDWARE_DOCS.md +0 -0
- {swbt_python-0.3.0 → swbt_python-0.4.0}/spec/complete/unit_023/MKDOCS_DOCUMENTATION_SITE.md +0 -0
- {swbt_python-0.3.0 → swbt_python-0.4.0}/spec/complete/unit_024/STICK_INPUT_SHORTHAND_API.md +0 -0
- {swbt_python-0.3.0 → swbt_python-0.4.0}/spec/complete/unit_025/IMU_INPUT_SHORTHAND_API.md +0 -0
- {swbt_python-0.3.0 → swbt_python-0.4.0}/spec/complete/unit_026/LINUX_MACOS_ADAPTER_EXPERIMENTAL.md +0 -0
- {swbt_python-0.3.0 → swbt_python-0.4.0}/spec/complete/unit_027/ADAPTER_DISCOVERY_API.md +0 -0
- {swbt_python-0.3.0 → swbt_python-0.4.0}/spec/complete/unit_028/CONTROLLER_PROFILE_CUSTOMIZATION.md +0 -0
- {swbt_python-0.3.0 → swbt_python-0.4.0}/spec/complete/unit_029/CONTROLLER_PROFILE_INJECTION.md +0 -0
- {swbt_python-0.3.0 → swbt_python-0.4.0}/spec/complete/unit_030/JOYCON_PROFILE_IDENTITY_SPI.md +0 -0
- {swbt_python-0.3.0 → swbt_python-0.4.0}/spec/complete/unit_031/JOYCON_INPUT_REPORT_MAPPING.md +0 -0
- {swbt_python-0.3.0 → swbt_python-0.4.0}/spec/complete/unit_032/PROFILE_AWARE_SUBCOMMAND_STATE.md +0 -0
- {swbt_python-0.3.0 → swbt_python-0.4.0}/spec/complete/unit_033/PROFILE_AWARE_BUMBLE_SDP.md +0 -0
- {swbt_python-0.3.0 → swbt_python-0.4.0}/spec/complete/unit_034/JOYCON_CONVENIENCE_API_DOCS.md +0 -0
- {swbt_python-0.3.0 → swbt_python-0.4.0}/spec/complete/unit_035/JOYCON_DEVICE_INFO_ADDRESS_WIRING.md +0 -0
- {swbt_python-0.3.0 → swbt_python-0.4.0}/spec/complete/unit_036/JOYCON_SDP_IDENTITY_POLICY.md +0 -0
- {swbt_python-0.3.0 → swbt_python-0.4.0}/spec/complete/unit_037/JOYCON_DEFAULT_CONTROLLER_COLORS.md +0 -0
- {swbt_python-0.3.0 → swbt_python-0.4.0}/spec/complete/unit_038/REARCHITECTURE_DECISION_BOUNDARY_TESTS.md +0 -0
- {swbt_python-0.3.0 → swbt_python-0.4.0}/spec/complete/unit_039/CONTROLLER_RUNTIME_EXTRACTION.md +0 -0
- {swbt_python-0.3.0 → swbt_python-0.4.0}/spec/complete/unit_040/PUBLIC_CONTROLLER_API_MODEL.md +0 -0
- {swbt_python-0.3.0 → swbt_python-0.4.0}/spec/complete/unit_041/CONTROLLER_CONFIG_PROFILE_OWNERSHIP.md +0 -0
- {swbt_python-0.3.0 → swbt_python-0.4.0}/spec/complete/unit_042/INTERNAL_TRANSPORT_FACTORY.md +0 -0
- {swbt_python-0.3.0 → swbt_python-0.4.0}/spec/complete/unit_043/PROTOCOL_PROFILE_MODULE_SPLIT.md +0 -0
- {swbt_python-0.3.0 → swbt_python-0.4.0}/spec/complete/unit_044/REARCHITECTURE_DOCS_RELEASE_MATRIX.md +0 -0
- {swbt_python-0.3.0 → swbt_python-0.4.0}/spec/complete/unit_045/INTERNAL_API_BOUNDARY_CLEANUP.md +0 -0
- {swbt_python-0.3.0 → swbt_python-0.4.0}/spec/complete/unit_046/HARDWARE_PROFILE_TEST_SCENARIOS.md +0 -0
- {swbt_python-0.3.0 → swbt_python-0.4.0}/spec/complete/unit_047/VIRTUAL_GYRO_CALIBRATION.md +0 -0
- {swbt_python-0.3.0 → swbt_python-0.4.0}/spec/complete/unit_048/VIRTUAL_ACCELEROMETER_CALIBRATION.md +0 -0
- {swbt_python-0.3.0 → swbt_python-0.4.0}/spec/complete/unit_049/IMU_SESSION_AND_ENCODING_REDESIGN.md +0 -0
- {swbt_python-0.3.0 → swbt_python-0.4.0}/spec/dev-journal.md +0 -0
- {swbt_python-0.3.0 → swbt_python-0.4.0}/spec/hardware-test-log.md +0 -0
- {swbt_python-0.3.0 → swbt_python-0.4.0}/spec/initial/README.md +0 -0
- {swbt_python-0.3.0 → swbt_python-0.4.0}/spec/initial/naming.md +0 -0
- {swbt_python-0.3.0 → swbt_python-0.4.0}/spec/initial/protocol.md +0 -0
- {swbt_python-0.3.0 → swbt_python-0.4.0}/spec/initial/risks.md +0 -0
- {swbt_python-0.3.0 → swbt_python-0.4.0}/spec/initial/roadmap.md +0 -0
- {swbt_python-0.3.0 → swbt_python-0.4.0}/spec/initial/transport-bumble.md +0 -0
- {swbt_python-0.3.0 → swbt_python-0.4.0}/spec/rearchitecture/01-design-change-overview.md +0 -0
- {swbt_python-0.3.0 → swbt_python-0.4.0}/spec/rearchitecture/02-as-is-to-be.md +0 -0
- {swbt_python-0.3.0 → swbt_python-0.4.0}/spec/rearchitecture/03-public-api-config-profile.md +0 -0
- {swbt_python-0.3.0 → swbt_python-0.4.0}/spec/rearchitecture/04-runtime-profile-transport-details.md +0 -0
- {swbt_python-0.3.0 → swbt_python-0.4.0}/spec/rearchitecture/05-milestones-implementation.md +0 -0
- {swbt_python-0.3.0 → swbt_python-0.4.0}/spec/rearchitecture/README.md +0 -0
- {swbt_python-0.3.0 → swbt_python-0.4.0}/spec/rearchitecture/mkdocs-nav-snippet.yml +0 -0
- {swbt_python-0.3.0 → swbt_python-0.4.0}/src/swbt/_testing/__init__.py +0 -0
- {swbt_python-0.3.0 → swbt_python-0.4.0}/src/swbt/adapter_discovery.py +0 -0
- {swbt_python-0.3.0 → swbt_python-0.4.0}/src/swbt/diagnostics.py +0 -0
- {swbt_python-0.3.0 → swbt_python-0.4.0}/src/swbt/errors.py +0 -0
- {swbt_python-0.3.0 → swbt_python-0.4.0}/src/swbt/gamepad/_config.py +0 -0
- {swbt_python-0.3.0 → swbt_python-0.4.0}/src/swbt/gamepad/connection.py +0 -0
- {swbt_python-0.3.0 → swbt_python-0.4.0}/src/swbt/gamepad/constants.py +0 -0
- {swbt_python-0.3.0 → swbt_python-0.4.0}/src/swbt/gamepad/transport_factory.py +0 -0
- {swbt_python-0.3.0 → swbt_python-0.4.0}/src/swbt/imu.py +0 -0
- {swbt_python-0.3.0 → swbt_python-0.4.0}/src/swbt/input.py +0 -0
- {swbt_python-0.3.0 → swbt_python-0.4.0}/src/swbt/probe.py +0 -0
- {swbt_python-0.3.0 → swbt_python-0.4.0}/src/swbt/protocol/__init__.py +0 -0
- {swbt_python-0.3.0 → swbt_python-0.4.0}/src/swbt/protocol/buttons.py +0 -0
- {swbt_python-0.3.0 → swbt_python-0.4.0}/src/swbt/protocol/descriptors.py +0 -0
- {swbt_python-0.3.0 → swbt_python-0.4.0}/src/swbt/protocol/imu_report.py +0 -0
- {swbt_python-0.3.0 → swbt_python-0.4.0}/src/swbt/protocol/input_report.py +0 -0
- {swbt_python-0.3.0 → swbt_python-0.4.0}/src/swbt/protocol/output_report.py +0 -0
- {swbt_python-0.3.0 → swbt_python-0.4.0}/src/swbt/protocol/profiles/__init__.py +0 -0
- {swbt_python-0.3.0 → swbt_python-0.4.0}/src/swbt/protocol/profiles/base.py +0 -0
- {swbt_python-0.3.0 → swbt_python-0.4.0}/src/swbt/protocol/profiles/joycon.py +0 -0
- {swbt_python-0.3.0 → swbt_python-0.4.0}/src/swbt/protocol/profiles/pro_controller.py +0 -0
- {swbt_python-0.3.0 → swbt_python-0.4.0}/src/swbt/protocol/rumble.py +0 -0
- {swbt_python-0.3.0 → swbt_python-0.4.0}/src/swbt/protocol/session.py +0 -0
- {swbt_python-0.3.0 → swbt_python-0.4.0}/src/swbt/protocol/spi.py +0 -0
- {swbt_python-0.3.0 → swbt_python-0.4.0}/src/swbt/protocol/subcommand.py +0 -0
- {swbt_python-0.3.0 → swbt_python-0.4.0}/src/swbt/py.typed +0 -0
- {swbt_python-0.3.0 → swbt_python-0.4.0}/src/swbt/state_store.py +0 -0
- {swbt_python-0.3.0 → swbt_python-0.4.0}/src/swbt/transport/__init__.py +0 -0
- {swbt_python-0.3.0 → swbt_python-0.4.0}/src/swbt/transport/_bumble_acl.py +0 -0
- {swbt_python-0.3.0 → swbt_python-0.4.0}/src/swbt/transport/_bumble_hidp.py +0 -0
- {swbt_python-0.3.0 → swbt_python-0.4.0}/src/swbt/transport/_bumble_key_store.py +0 -0
- {swbt_python-0.3.0 → swbt_python-0.4.0}/src/swbt/transport/_bumble_lifecycle.py +0 -0
- {swbt_python-0.3.0 → swbt_python-0.4.0}/src/swbt/transport/_bumble_sdp.py +0 -0
- {swbt_python-0.3.0 → swbt_python-0.4.0}/src/swbt/transport/_bumble_usb_devices.py +0 -0
- {swbt_python-0.3.0 → swbt_python-0.4.0}/src/swbt/transport/base.py +0 -0
- {swbt_python-0.3.0 → swbt_python-0.4.0}/src/swbt/transport/bumble.py +0 -0
- {swbt_python-0.3.0 → swbt_python-0.4.0}/src/swbt/transport/fake.py +0 -0
- {swbt_python-0.3.0 → swbt_python-0.4.0}/tests/conftest.py +0 -0
- {swbt_python-0.3.0 → swbt_python-0.4.0}/tests/hardware/README.md +0 -0
- {swbt_python-0.3.0 → swbt_python-0.4.0}/tests/hardware/test_bumble_transport.py +0 -0
- {swbt_python-0.3.0 → swbt_python-0.4.0}/tests/hardware/test_close_disconnect.py +0 -0
- {swbt_python-0.3.0 → swbt_python-0.4.0}/tests/hardware/test_context_manager_resource_scope.py +0 -0
- {swbt_python-0.3.0 → swbt_python-0.4.0}/tests/hardware/test_controller_colors.py +0 -0
- {swbt_python-0.3.0 → swbt_python-0.4.0}/tests/hardware/test_input_operations.py +0 -0
- {swbt_python-0.3.0 → swbt_python-0.4.0}/tests/hardware/test_joycon_profile.py +0 -0
- {swbt_python-0.3.0 → swbt_python-0.4.0}/tests/hardware/test_pairing_l2cap.py +0 -0
- {swbt_python-0.3.0 → swbt_python-0.4.0}/tests/hardware/test_reconnect_keystore.py +0 -0
- {swbt_python-0.3.0 → swbt_python-0.4.0}/tests/integration/test_examples.py +0 -0
- {swbt_python-0.3.0 → swbt_python-0.4.0}/tests/unit/fixtures/source_audit/switch_protocol_values.toml +0 -0
- {swbt_python-0.3.0 → swbt_python-0.4.0}/tests/unit/test_adapter_discovery.py +0 -0
- {swbt_python-0.3.0 → swbt_python-0.4.0}/tests/unit/test_bumble_acl.py +0 -0
- {swbt_python-0.3.0 → swbt_python-0.4.0}/tests/unit/test_bumble_hidp.py +0 -0
- {swbt_python-0.3.0 → swbt_python-0.4.0}/tests/unit/test_bumble_key_store_metadata.py +0 -0
- {swbt_python-0.3.0 → swbt_python-0.4.0}/tests/unit/test_bumble_lifecycle.py +0 -0
- {swbt_python-0.3.0 → swbt_python-0.4.0}/tests/unit/test_bumble_sdp.py +0 -0
- {swbt_python-0.3.0 → swbt_python-0.4.0}/tests/unit/test_bumble_transport.py +0 -0
- {swbt_python-0.3.0 → swbt_python-0.4.0}/tests/unit/test_ci_workflow.py +0 -0
- {swbt_python-0.3.0 → swbt_python-0.4.0}/tests/unit/test_diagnostics.py +0 -0
- {swbt_python-0.3.0 → swbt_python-0.4.0}/tests/unit/test_gamepad_connection_workflow.py +0 -0
- {swbt_python-0.3.0 → swbt_python-0.4.0}/tests/unit/test_gamepad_transport_factory.py +0 -0
- {swbt_python-0.3.0 → swbt_python-0.4.0}/tests/unit/test_imu_report.py +0 -0
- {swbt_python-0.3.0 → swbt_python-0.4.0}/tests/unit/test_input_report.py +0 -0
- {swbt_python-0.3.0 → swbt_python-0.4.0}/tests/unit/test_input_state.py +0 -0
- {swbt_python-0.3.0 → swbt_python-0.4.0}/tests/unit/test_output_report.py +0 -0
- {swbt_python-0.3.0 → swbt_python-0.4.0}/tests/unit/test_package_metadata.py +0 -0
- {swbt_python-0.3.0 → swbt_python-0.4.0}/tests/unit/test_probe_cli.py +0 -0
- {swbt_python-0.3.0 → swbt_python-0.4.0}/tests/unit/test_protocol_boundary.py +0 -0
- {swbt_python-0.3.0 → swbt_python-0.4.0}/tests/unit/test_protocol_profile.py +0 -0
- {swbt_python-0.3.0 → swbt_python-0.4.0}/tests/unit/test_protocol_session.py +0 -0
- {swbt_python-0.3.0 → swbt_python-0.4.0}/tests/unit/test_report_loop.py +0 -0
- {swbt_python-0.3.0 → swbt_python-0.4.0}/tests/unit/test_rumble_state.py +0 -0
- {swbt_python-0.3.0 → swbt_python-0.4.0}/tests/unit/test_source_audit_fixtures.py +0 -0
- {swbt_python-0.3.0 → swbt_python-0.4.0}/tests/unit/test_subcommand_responder.py +0 -0
- {swbt_python-0.3.0 → swbt_python-0.4.0}/tests/unit/test_virtual_spi_flash.py +0 -0
|
@@ -77,10 +77,18 @@
|
|
|
77
77
|
| 実機観測 | `spec/hardware-test-log.md` |
|
|
78
78
|
| 初期設計 | `spec/initial/` |
|
|
79
79
|
|
|
80
|
-
作業仕様には、対象範囲、対象外、関連 docs、根拠監査、TDD Test List
|
|
80
|
+
作業仕様には、対象範囲、対象外、関連 docs、根拠監査、TDD Test List または文書検証計画、検証、実機実行条件、先送り事項、チェックリストを含める。
|
|
81
81
|
|
|
82
82
|
複数の作業仕様から参照する安定した判断は、既存の `spec/initial/` を更新するか、必要に応じて `spec/` 配下へ分ける。未確定の観測は `spec/dev-journal.md` に置く。
|
|
83
83
|
|
|
84
|
+
## 文書の検証
|
|
85
|
+
|
|
86
|
+
- 自然言語の意味要件を、固定語句の存在・不在 assertion に置き換えない。正しい言い換えを落とす検査、または誤った説明を通す検査は追加しない。
|
|
87
|
+
- 自動検査の対象は、生成、構造、リンク、参照先、front matter、構造化データ、生成元との対応など、機械的に観測でき、失敗原因を切り分けられる契約に限る。
|
|
88
|
+
- 説明の事実性、読者が手順を完了するための前提・操作・終了後の復帰手順、未検証範囲の伝達は、正本との照合を伴うレビューで検証する。pytest の成功をその根拠にしない。
|
|
89
|
+
- docs / spec / skill だけの作業では、TDD Test List と pytest を必須にしない。選んだ自動検査、レビュー、未実行の検証を理由付きで記録する。
|
|
90
|
+
- README、利用者向け docs、公開 API docstring、release notes を変更した場合は `docs-quality-review` を使い、未解決の修正必須事項を handoff または PR 本文に残す。
|
|
91
|
+
|
|
84
92
|
## Agent Skills
|
|
85
93
|
|
|
86
94
|
repo-local skill は `.agents/skills` を正本として管理する。`.github/skills` には重複配置しない。
|
|
@@ -92,6 +100,7 @@ repo-local skill は `.agents/skills` を正本として管理する。`.github/
|
|
|
92
100
|
- `spec-format`: `spec/wip` / `spec/complete` の作業仕様を作成、更新、完了移動する。
|
|
93
101
|
- `dev-journal`: 小さい設計観測や先送り事項を `spec/dev-journal.md` に記録する。
|
|
94
102
|
- `docs-wording`: README、docs、release notes の文言整理で、swbt-python 固有の訳語と残す英語表記をそろえる。
|
|
103
|
+
- `docs-quality-review`: README、利用者向け docs、公開 API docstring、release notes を、対象読者の利用手順、根拠、未検証範囲、読みやすさの観点で確認する。
|
|
95
104
|
- `source-audit`: Switch HID、Bumble、既存実装、実機ログ由来の値を根拠分類して記録する。
|
|
96
105
|
- `hardware-harness`: Bumble adapter、Bluetooth dongle、Switch 実機を使う検証の承認境界と記録項目を確認する。
|
|
97
106
|
- `tdd-workflow`: TDD Test List から red / green / refactor を進める。
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
# 周期送信型と Direct 送信型の公開分離 仕様書
|
|
2
|
+
|
|
3
|
+
## 1. 概要
|
|
4
|
+
|
|
5
|
+
### 1.1 目的
|
|
6
|
+
|
|
7
|
+
`0x30` input report の送信契機をライブラリが所有する型と、利用者が所有する型を公開 class で分離する。既存の `ProController` / `JoyConL` / `JoyConR` は周期送信契約を維持し、`DirectProController` / `DirectJoyConL` / `DirectJoyConR` は1操作と1件の送信を対応させる。
|
|
8
|
+
|
|
9
|
+
### 1.2 起点 / source
|
|
10
|
+
|
|
11
|
+
| source | 内容 | path |
|
|
12
|
+
|---|---|---|
|
|
13
|
+
| GitHub Issue #77 | 周期送信型と Direct 送信型を公開型として分離し、送信頻度の所有者を型で固定する | `https://github.com/niart120/swbt-python/issues/77` |
|
|
14
|
+
| initial API | 現行の `SwitchGamepad`、状態更新 API、`tap()`、`snapshot()` 契約 | `spec/initial/api.md` |
|
|
15
|
+
| input API contract | 状態更新は即時送信を保証せず、`tap()` だけが action API である現行判断 | `spec/complete/unit_021/SWITCH_GAMEPAD_INPUT_API_CONTRACT.md` |
|
|
16
|
+
| current runtime | `ControllerRuntime`、`ReportLoop`、`InputStateStore` による周期送信実装 | `src/swbt/gamepad/runtime.py`, `src/swbt/report_loop.py` |
|
|
17
|
+
|
|
18
|
+
### 1.3 use case
|
|
19
|
+
|
|
20
|
+
| actor / boundary | 入力または状態 | 期待する観測結果 | 制約 |
|
|
21
|
+
|---|---|---|---|
|
|
22
|
+
| 周期送信の利用者 | `ProController.apply(state)` または意味的入力操作 | local current state が更新され、後続の周期 report がその時点の状態を送る | 接続と即時送信を要求しない |
|
|
23
|
+
| Direct 送信の利用者 | `DirectProController.send(state)` | 指定状態の `0x30` をちょうど1件送り、送信完了後だけ current state を更新する | 接続済みを要求し、周期 task を開始しない |
|
|
24
|
+
| Direct 送信の利用者 | `press()` / `release()` / `sticks()` / `imu()` / `neutral()` | last successfully sent state から候補を作り、1件送信して成功後だけ確定する | 同時操作を直列化する |
|
|
25
|
+
| Switch host | output report / subcommand | reporting type に関係なく reply を受け取る | input report と同じ送信直列化境界を通す |
|
|
26
|
+
| lifecycle | `close(neutral=True/False)` | `True` は trailing neutral を試み、`False` は通常 input report を追加しない | Direct の `True` は利用者所有の送信契機に対する明示的な例外 |
|
|
27
|
+
|
|
28
|
+
## 2. 対象範囲
|
|
29
|
+
|
|
30
|
+
- `SwitchGamepad` を lifecycle、connection、status、意味的入力操作の共通抽象型として維持する。
|
|
31
|
+
- `PeriodicSwitchGamepad` と `DirectSwitchGamepad` を異なる公開抽象型として追加する。
|
|
32
|
+
- `PeriodicSwitchGamepad.apply(state)` と `DirectSwitchGamepad.send(state)` を分離する。
|
|
33
|
+
- 既存 `ProController` / `JoyConL` / `JoyConR` を `PeriodicSwitchGamepad` の具象型として維持する。
|
|
34
|
+
- `DirectProController` / `DirectJoyConL` / `DirectJoyConR` を追加する。
|
|
35
|
+
- input report と subcommand reply に共通する timer、IMU encoding、送信 lock、diagnostics を共通 sender に集約する。
|
|
36
|
+
- Direct の完全状態送信と意味的入力操作を、送信成功後 commit のトランザクションとして実装する。
|
|
37
|
+
- Direct の input operation を直列化し、`tap()` は押下から解放まで同じ操作 lock を保持する。
|
|
38
|
+
- `snapshot()`、`tap()`、`close()` の reporting type ごとの意味を test と公開文書で固定する。
|
|
39
|
+
- public root exports、API docs、usage、agent brief、initial design を新しい型境界へ追従させる。
|
|
40
|
+
|
|
41
|
+
## 3. 対象外
|
|
42
|
+
|
|
43
|
+
- raw HID bytes の public send API。
|
|
44
|
+
- Periodic と Direct の接続中切り替え、または runtime mode object の public API。
|
|
45
|
+
- Periodic 型への明示 `0x30` send API。
|
|
46
|
+
- Direct の `apply(state); send()`、引数なし `send()`、fire-and-forget queue。
|
|
47
|
+
- report period の精度、deadline scheduler、最低送信頻度の補完。
|
|
48
|
+
- mouse delta、姿勢モデル、macro / sequence runner、rumble 解釈の追加。
|
|
49
|
+
- report byte layout、subcommand payload、SPI data、HID descriptor の変更。
|
|
50
|
+
- Bumble adapter、pairing、HID advertising、Switch 実機を使う検証。
|
|
51
|
+
|
|
52
|
+
## 4. 関連 docs
|
|
53
|
+
|
|
54
|
+
- `spec/initial/README.md`
|
|
55
|
+
- `spec/initial/api.md`
|
|
56
|
+
- `spec/initial/architecture.md`
|
|
57
|
+
- `spec/initial/lifecycle.md`
|
|
58
|
+
- `spec/initial/testing.md`
|
|
59
|
+
- `spec/complete/unit_014/DEVICE_CLOSE_GRACEFUL_DISCONNECT.md`
|
|
60
|
+
- `spec/complete/unit_021/SWITCH_GAMEPAD_INPUT_API_CONTRACT.md`
|
|
61
|
+
- `spec/complete/unit_039/CONTROLLER_RUNTIME_EXTRACTION.md`
|
|
62
|
+
- `spec/complete/unit_040/PUBLIC_CONTROLLER_API_MODEL.md`
|
|
63
|
+
- `spec/complete/unit_045/INTERNAL_API_BOUNDARY_CLEANUP.md`
|
|
64
|
+
- `spec/complete/unit_049/IMU_SESSION_AND_ENCODING_REDESIGN.md`
|
|
65
|
+
|
|
66
|
+
## 5. 根拠監査
|
|
67
|
+
|
|
68
|
+
| 項目 | 要否 | 状態 | 根拠 / 理由 |
|
|
69
|
+
|---|---|---|---|
|
|
70
|
+
| Switch HID / report bytes | not applicable | not applicable | 既存 `0x30` builder、`0x21` reply、timer、IMU encoding を再利用し、ID、layout、定数を変更しない |
|
|
71
|
+
| Bumble / transport | not applicable | not applicable | `HidDeviceTransport.send_interrupt()` は完了まで待つ既存境界を利用する。Bumble object、SDP、L2CAP、adapter 仮定は追加しない |
|
|
72
|
+
| OS / driver / adapter | not applicable | not applicable | fake transport の unit / integration test だけで公開型と送信契約を検証する |
|
|
73
|
+
|
|
74
|
+
## 6. 振る舞い仕様
|
|
75
|
+
|
|
76
|
+
| 振る舞い | 入力・状態 | 期待結果 | 備考 |
|
|
77
|
+
|---|---|---|---|
|
|
78
|
+
| 公開型の分離 | root import と class hierarchy を調べる | Periodic / Direct の抽象型と6具象型が import でき、既存3型は Periodic に属する | mode object は公開しない |
|
|
79
|
+
| API の排他性 | method と constructor signature を調べる | Periodic は `apply(state)` を持ち `send(state)` を持たない。Direct は逆で、`report_period_us` を受け取らない | 共通意味操作は両型にある |
|
|
80
|
+
| Periodic 状態更新 | 未接続または接続中に状態操作する | local state を commit し、即時送信しない。接続中は後続周期 report が観測する | 現行後方互換 |
|
|
81
|
+
| Direct 完全状態送信 | 接続中に `send(state)` を await する | 指定状態の `0x30` を1件送り、transport 完了後に current state を更新する | background queue を作らない |
|
|
82
|
+
| Direct 非周期性 | Direct を接続し待機する | 自動 `0x30` を送らない | subcommand reply は自動処理する |
|
|
83
|
+
| Direct rollback | 未接続または送信失敗 | `ClosedError` または transport error を返し、report 成功前の current state を維持する | profile validation 失敗も commit しない |
|
|
84
|
+
| Direct 意味操作 | press / release / sticks / imu / neutral | last successfully sent state から候補を作り、各正常終了につき1件送り、成功後だけ commit する | `lstick` / `rstick` は `sticks` と同じ規則 |
|
|
85
|
+
| Direct 同時操作 | 複数 input operation を同時に開始する | operation lock の取得順に送信と commit が完了し、候補状態が失われない | transport completion の backpressure を返す |
|
|
86
|
+
| tap | held input がある状態で `tap()` | 両型とも押下と解放を送り、対象 button だけを解除する。Direct は押下から解放まで操作 lock を保持する | release 失敗時は最後に送信できた押下状態を維持する |
|
|
87
|
+
| subcommand 自動応答 | Direct 接続中に host output を注入する | `0x21` を自動送信し、prefix は送信順上の current state を使う | input と共通 sender lock / timer を使う |
|
|
88
|
+
| close | Direct で `neutral=True` / `False` | `True` は接続中に neutral 1件を試み、成功後 commit。`False` は input report を追加しない | transport cleanup は両型共通 |
|
|
89
|
+
| profile validation | Pro / Joy-Con L / Joy-Con R の Direct 操作 | Periodic と同じ capability を使い、不正候補を送信・commit しない | profile 実装を複製しない |
|
|
90
|
+
| snapshot | Periodic / Direct の current state を読む | Periodic は最新 local committed state、Direct は最後に正常送信した state を返す | 新しい接続 session は neutral baseline から始める |
|
|
91
|
+
|
|
92
|
+
## 7. TDD Test List
|
|
93
|
+
|
|
94
|
+
| status | item | type | layer | hardware | notes |
|
|
95
|
+
|---|---|---|---|---|---|
|
|
96
|
+
| refactor-done | 公開 root が Periodic / Direct 抽象型と Direct 3具象型を公開し、既存3具象型を Periodic として分類する | new | unit | no | 共通 interface と reporting type 固有 interface を分離し、class hierarchy と `__all__` を固定 |
|
|
97
|
+
| refactor-skipped | Periodic だけが `apply(state)` と `report_period_us` を公開し、Direct だけが `send(state)` を公開する | new | unit | no | 公開型分離の実装で expected-green。無効操作を runtime validation へ落とさない signature を固定 |
|
|
98
|
+
| refactor-skipped | 既存3具象型の状態操作が即時送信せず、周期 report と snapshot の現行契約を維持する | regression | integration | no | expected-green regression。press / apply / 非即時送信 / tap held input を確認 |
|
|
99
|
+
| refactor-done | Direct の `send(state)` が送信完了まで待ち、指定状態の `0x30` をちょうど1件送ってから snapshot を更新する | new | integration | no | 共通 `ReportSender` を抽出し、制御可能な fake transport で完了前後の snapshot と1件送信を確認 |
|
|
100
|
+
| refactor-skipped | Direct は接続後に周期 `0x30` を開始せず、host output には自動応答する | new | integration | no | send transaction の runtime 分岐で expected-green。待機中0件と `0x21` reply のみを確認 |
|
|
101
|
+
| refactor-skipped | Direct の未接続、送信失敗、profile validation 失敗が current state を変更しない | edge | integration | no | send transaction 実装で expected-green。full send と press の transport error、未接続、Joy-Con L unsupported state を固定 |
|
|
102
|
+
| refactor-done | Direct の press / release / sticks / imu / neutral が各1件送信し、成功後だけ状態を確定する | new | integration | no | candidate 生成、profile validation、送信成功後 commit を `_send_direct_update()` に集約 |
|
|
103
|
+
| refactor-skipped | Direct の同時入力操作が直列化され、開始順の候補状態と送信順を失わない | edge | integration | no | input operation lock 実装で expected-green。blocking fake transport で2操作目が1操作目完了まで送信開始しないことを固定 |
|
|
104
|
+
| refactor-done | `tap()` が両 reporting type で held input を維持し、Direct の押下・解放を直列化する | regression | integration | no | Direct 専用2段 transaction を追加し、押下から解放まで operation lock を保持 |
|
|
105
|
+
| refactor-skipped | Direct の tap release 失敗時に押下済み current state を維持し、release 再試行で neutral へ戻せる | edge | integration | no | Direct tap transaction で expected-green。最後に成功送信した押下stateと明示release再試行を固定 |
|
|
106
|
+
| refactor-done | Direct input と subcommand reply が共通 timer / send lock を通り、prefix と送信順が一致する | new | unit | no | reply builder 内の state snapshot を共通 sender lock の内側へ移し、timerとprefixを送信順へ一致させた |
|
|
107
|
+
| refactor-done | Direct の `close(neutral=True/False)` が trailing neutral の有無、commit、cleanup を契約どおり処理する | new | integration | no | Direct close を operation lock と共通 sender に接続し、`True` だけが送信成功後 neutral を commit |
|
|
108
|
+
| refactor-skipped | Direct Pro / Joy-Con L / Joy-Con R が同じ runtime と profile validation を共有する | new | integration | no | `_ControllerSpec` と共通runtime実装で expected-green。3 profile の正常入力と unsupported input を確認 |
|
|
109
|
+
| refactor-skipped | API docs、usage、agent brief、initial design が送信所有者、完了条件、snapshot、close の違いを説明する | new | unit | no | public docs test に完了条件、送信頻度所有者、subcommand、close の必須語を追加。文書だけの変更で追加 refactor は不要 |
|
|
110
|
+
|
|
111
|
+
## 8. 設計メモ
|
|
112
|
+
|
|
113
|
+
### 8.1 公開型
|
|
114
|
+
|
|
115
|
+
```text
|
|
116
|
+
SwitchGamepad
|
|
117
|
+
├── PeriodicSwitchGamepad
|
|
118
|
+
│ ├── ProController
|
|
119
|
+
│ ├── JoyConL
|
|
120
|
+
│ └── JoyConR
|
|
121
|
+
└── DirectSwitchGamepad
|
|
122
|
+
├── DirectProController
|
|
123
|
+
├── DirectJoyConL
|
|
124
|
+
└── DirectJoyConR
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
`SwitchGamepad` の共通意味操作は「現在の論理入力状態を遷移させる」ことを表す。正常終了の意味は、Periodic では local commit、Direct では1件の送信完了と commit である。
|
|
128
|
+
|
|
129
|
+
### 8.2 共通 sender
|
|
130
|
+
|
|
131
|
+
`ReportLoop` から次を共通 sender へ抽出する。
|
|
132
|
+
|
|
133
|
+
- input report / subcommand reply の send lock。
|
|
134
|
+
- timer byte と increment。
|
|
135
|
+
- connection-scoped IMU encoding。
|
|
136
|
+
- `InputReportBuilder` と `transport.send_interrupt()`。
|
|
137
|
+
- diagnostics の report counter / reason。
|
|
138
|
+
|
|
139
|
+
Periodic だけが scheduler と current state snapshot を所有する。Direct は scheduler を生成せず、input operation lock 内で candidate を共通 sender へ渡す。subcommand reply は両型で共通 sender の lock 内に入り、reply prefix の state も送信順の中で取得する。
|
|
140
|
+
|
|
141
|
+
### 8.3 Direct transaction
|
|
142
|
+
|
|
143
|
+
```text
|
|
144
|
+
input operation lock
|
|
145
|
+
-> last successfully sent state から candidate を構築
|
|
146
|
+
-> profile validation
|
|
147
|
+
-> common sender lock
|
|
148
|
+
-> 0x30 build
|
|
149
|
+
-> transport.send_interrupt() 完了
|
|
150
|
+
-> candidate commit
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
送信失敗では commit しない。`tap()` の release 送信失敗時は、押下 report が最後の成功送信なので押下状態を current state として維持する。
|
|
154
|
+
|
|
155
|
+
### 8.4 Tidy decision
|
|
156
|
+
|
|
157
|
+
```text
|
|
158
|
+
Tidy decision:
|
|
159
|
+
- classification: mixed
|
|
160
|
+
- action: split
|
|
161
|
+
- reason: 共通 sender 抽出は構造変更、公開型と Direct transaction は振る舞い変更である。sender の既存 wire / timer 回帰を先に固定し、Direct item を順に追加する。
|
|
162
|
+
- verification: report sender unit test、public API boundary test、fake transport integration test、標準 gate を順に実行する。
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
## 9. 対象ファイル
|
|
166
|
+
|
|
167
|
+
| path | change | 内容 |
|
|
168
|
+
|---|---|---|
|
|
169
|
+
| `src/swbt/gamepad/interface.py` | modify | 共通、Periodic、Direct の公開抽象型 |
|
|
170
|
+
| `src/swbt/gamepad/core.py` | modify | reporting type 別 runtime-backed base と6具象型 |
|
|
171
|
+
| `src/swbt/gamepad/runtime.py` | modify | reporting type ごとの状態操作、scheduler、close |
|
|
172
|
+
| `src/swbt/report_loop.py` | modify | 共通 sender 抽出と Periodic scheduler |
|
|
173
|
+
| `src/swbt/state_store.py` | modify | Direct の送信成功後 commit を支える内部境界 |
|
|
174
|
+
| `src/swbt/gamepad/output.py` | modify | sender lock 内で current state を使う reply builder |
|
|
175
|
+
| `src/swbt/gamepad/__init__.py`, `src/swbt/__init__.py` | modify | public exports |
|
|
176
|
+
| `src/swbt/_testing/gamepad.py` | modify | Direct fake transport constructor |
|
|
177
|
+
| `tests/unit/test_public_api_boundary.py` | modify | class hierarchy、signature、constructor |
|
|
178
|
+
| `tests/unit/test_package_import.py` | modify | root exports |
|
|
179
|
+
| `tests/unit/test_report_loop.py` | modify | 共通 sender、timer、送信直列化 |
|
|
180
|
+
| `tests/integration/test_switch_gamepad_fake_transport.py` | modify | Direct transaction、非周期性、rollback、tap、close、profile |
|
|
181
|
+
| `spec/initial/*.md` | modify | API、architecture、lifecycle、testing の安定契約 |
|
|
182
|
+
| `docs/api.md`, `docs/usage.md`, `docs/agent-brief.md` | modify | 公開利用面と生成指針 |
|
|
183
|
+
|
|
184
|
+
## 10. 検証
|
|
185
|
+
|
|
186
|
+
| command | result | notes |
|
|
187
|
+
|---|---|---|
|
|
188
|
+
| `uv run pytest tests/unit/test_public_api_boundary.py tests/unit/test_package_import.py -q` | pass | 個別 TDD と全 unit gate で公開型、signature、root exports を確認 |
|
|
189
|
+
| `uv run pytest tests/unit/test_public_api_boundary.py::test_reporting_types_and_direct_controllers_are_public_and_classified -q` | red | `PeriodicSwitchGamepad` が root に未公開の `AttributeError` を確認 |
|
|
190
|
+
| `uv run pytest tests/unit/test_public_api_boundary.py::test_reporting_types_and_direct_controllers_are_public_and_classified -q` | pass | 1 passed。2抽象型、既存 Periodic 3型、Direct 3型の階層と root export を確認 |
|
|
191
|
+
| `uv run pytest tests/unit/test_public_api_boundary.py::test_reporting_types_expose_only_their_owned_full_state_operation tests/unit/test_package_import.py -q` | pass | 5 passed。`apply` / `send` と `report_period_us` の排他性、root export 一覧を確認 |
|
|
192
|
+
| `uv run pytest tests/integration/test_switch_gamepad_fake_transport.py::test_press_buttons_are_reflected_in_periodic_report tests/integration/test_switch_gamepad_fake_transport.py::test_apply_updates_snapshot_and_next_periodic_report tests/integration/test_switch_gamepad_fake_transport.py::test_state_update_apis_do_not_send_immediate_interrupt_reports tests/integration/test_switch_gamepad_fake_transport.py::test_tap_releases_only_tapped_button_and_preserves_held_buttons -q` | pass | 4 passed。Periodic の周期送信、snapshot、非即時送信、held input を確認 |
|
|
193
|
+
| `uv run pytest tests/integration/test_switch_gamepad_fake_transport.py::test_direct_send_waits_for_transport_and_commits_exactly_one_report -q` | red | Direct send が transport へ到達せず、送信開始待ち timeout になる未実装状態を確認 |
|
|
194
|
+
| `uv run pytest tests/integration/test_switch_gamepad_fake_transport.py::test_direct_send_waits_for_transport_and_commits_exactly_one_report -q` | pass | 1 passed。transport 完了前は未完了かつ neutral、完了後は指定 `0x30` 1件と state commit を確認 |
|
|
195
|
+
| `uv run pytest tests/unit/test_report_loop.py tests/integration/test_switch_gamepad_fake_transport.py::test_press_buttons_are_reflected_in_periodic_report tests/integration/test_switch_gamepad_fake_transport.py::test_output_report_injection_sends_subcommand_reply -q` | red | sender 抽出直後に snapshot が lock 外となり `0x21` が周期 `0x30` より先行する回帰を検出 |
|
|
196
|
+
| `uv run pytest tests/unit/test_report_loop.py tests/integration/test_switch_gamepad_fake_transport.py::test_direct_send_waits_for_transport_and_commits_exactly_one_report tests/integration/test_switch_gamepad_fake_transport.py::test_press_buttons_are_reflected_in_periodic_report tests/integration/test_switch_gamepad_fake_transport.py::test_output_report_injection_sends_subcommand_reply -q` | pass | 7 passed。snapshot から送信までの周期 lock、Direct commit、subcommand 回帰を確認 |
|
|
197
|
+
| `uv run pytest tests/integration/test_switch_gamepad_fake_transport.py::test_direct_connection_is_non_periodic_and_still_replies_to_subcommands -q` | pass | 1 passed。接続後60msに自動 `0x30` がなく、Device Info に `0x21` だけを返すことを確認 |
|
|
198
|
+
| `uv run pytest tests/integration/test_switch_gamepad_fake_transport.py::test_direct_send_failures_do_not_change_last_successfully_sent_state tests/integration/test_switch_gamepad_fake_transport.py::test_direct_send_rejects_unsupported_profile_state_without_sending -q` | pass | 2 passed。未接続、transport error、profile validation error で last successfully sent state を維持 |
|
|
199
|
+
| `uv run pytest tests/integration/test_switch_gamepad_fake_transport.py::test_direct_semantic_operations_send_once_and_commit_after_success -q` | red | 未接続 `press()` が `ClosedError` を出さず local state を更新する Periodic 挙動を確認 |
|
|
200
|
+
| `uv run pytest tests/integration/test_switch_gamepad_fake_transport.py::test_direct_semantic_operations_send_once_and_commit_after_success tests/integration/test_switch_gamepad_fake_transport.py::test_direct_send_waits_for_transport_and_commits_exactly_one_report tests/integration/test_switch_gamepad_fake_transport.py::test_direct_send_failures_do_not_change_last_successfully_sent_state -q` | pass | 3 passed。7意味操作の1操作1送信、未接続拒否、full send / press failure rollback を確認 |
|
|
201
|
+
| `uv run pytest tests/integration/test_switch_gamepad_fake_transport.py::test_direct_concurrent_operations_are_serialized_without_lost_state -q` | pass | 1 passed。A送信完了前はB送信を開始せず、送信列A、A+Bと最終stateを確認 |
|
|
202
|
+
| `uv run pytest tests/integration/test_switch_gamepad_fake_transport.py::test_direct_tap_sends_press_and_release_once_while_preserving_held_input -q` | red | Direct tap が押下と解放を各2件送り、合計4件になる既存共通実装を確認 |
|
|
203
|
+
| `uv run pytest tests/integration/test_switch_gamepad_fake_transport.py::test_direct_tap_sends_press_and_release_once_while_preserving_held_input tests/integration/test_switch_gamepad_fake_transport.py::test_tap_releases_only_tapped_button_and_preserves_held_buttons tests/integration/test_switch_gamepad_fake_transport.py::test_tap_send_failure_releases_pressed_state -q` | pass | 3 passed。Direct 2件送信と held input、Periodic tap 回帰を確認 |
|
|
204
|
+
| `uv run pytest tests/integration/test_switch_gamepad_fake_transport.py::test_direct_tap_sends_press_and_release_once_while_preserving_held_input tests/integration/test_switch_gamepad_fake_transport.py::test_direct_tap_keeps_pressed_state_when_release_send_fails tests/integration/test_switch_gamepad_fake_transport.py::test_direct_tap_serializes_concurrent_input_until_release -q` | pass | 3 passed。release failure state、release再試行、tap完了前の concurrent press 待機を確認 |
|
|
205
|
+
| `uv run pytest tests/integration/test_switch_gamepad_fake_transport.py::test_direct_subcommand_reply_uses_state_committed_by_prior_serialized_input -q` | red | input送信後に並ぶ `0x21` prefix が送信前に取得した neutral state のままになる競合を確認 |
|
|
206
|
+
| `uv run pytest tests/integration/test_switch_gamepad_fake_transport.py::test_direct_subcommand_reply_uses_state_committed_by_prior_serialized_input tests/unit/test_report_loop.py tests/unit/test_gamepad_output_dispatcher.py -q` | pass | 6 passed。送信列 `0x30` timer 0、`0x21` timer 1、reply prefix の Button A と既存sender/dispatcher回帰を確認 |
|
|
207
|
+
| `uv run pytest tests/integration/test_switch_gamepad_fake_transport.py::test_direct_close_controls_trailing_neutral_report -q` | red | `close(neutral=True)` が local neutral だけを行い trailing `0x30` を追加しない状態を確認 |
|
|
208
|
+
| `uv run pytest tests/integration/test_switch_gamepad_fake_transport.py::test_direct_close_controls_trailing_neutral_report tests/integration/test_switch_gamepad_fake_transport.py::test_close_with_neutral_records_trailing_neutral_report tests/integration/test_switch_gamepad_fake_transport.py::test_connected_close_requests_disconnect_after_trailing_neutral tests/integration/test_switch_gamepad_fake_transport.py::test_close_treats_trailing_neutral_send_failure_as_best_effort -q` | pass | 4 passed。Direct `True/False` の追加report数とPeriodic close回帰を確認 |
|
|
209
|
+
| `uv run pytest tests/integration/test_switch_gamepad_fake_transport.py::test_direct_controller_profiles_share_send_and_validation_contract -q` | pass | 3 passed。Pro / Joy-Con L / Joy-Con R の supported 1件送信と unsupported rollback を確認 |
|
|
210
|
+
| `uv run pytest tests/unit/test_public_api_docstrings.py tests/unit/test_public_docs.py -q` | pass | 15 passed。公開 docstring、API docs、usage、agent brief の送信型契約を確認 |
|
|
211
|
+
| `uv run pytest tests/unit/test_report_loop.py -q` | pass | 4 passed。共通 sender の timer / lock 回帰を確認 |
|
|
212
|
+
| `uv run pytest tests/integration/test_switch_gamepad_fake_transport.py -q` | pass | 123 passed。Periodic / Direct の fake transport 契約を確認 |
|
|
213
|
+
| `uv sync --dev` | pass | 53 packages resolved |
|
|
214
|
+
| `uv run ruff format --check .` | pass | 94 files already formatted |
|
|
215
|
+
| `uv run ruff check .` | pass | All checks passed |
|
|
216
|
+
| `uv run ty check --no-progress` | pass | All checks passed |
|
|
217
|
+
| `uv run pytest tests/unit` | pass | 445 passed |
|
|
218
|
+
| `uv run pytest tests/integration` | pass | 125 passed |
|
|
219
|
+
|
|
220
|
+
## 11. 実機実行条件
|
|
221
|
+
|
|
222
|
+
| 項目 | 内容 |
|
|
223
|
+
|---|---|
|
|
224
|
+
| 実機要否 | not required |
|
|
225
|
+
| 承認範囲 | なし。新しい report byte、Bumble API、adapter 操作を追加せず fake transport で検証する |
|
|
226
|
+
| adapter | 未使用 |
|
|
227
|
+
| 実行遮断 | 環境変数による遮断は採用しない。wire fixture 差分または新しい実機仮説が出た場合だけ、明示承認、対象 adapter、command、cleanup plan を確認する |
|
|
228
|
+
| log / artifact | unit / integration test output、git diff、PR checks |
|
|
229
|
+
| cleanup | なし |
|
|
230
|
+
|
|
231
|
+
## 12. 先送り事項
|
|
232
|
+
|
|
233
|
+
- none
|
|
234
|
+
|
|
235
|
+
## 13. チェックリスト
|
|
236
|
+
|
|
237
|
+
- [x] 対象範囲と対象外を確認した
|
|
238
|
+
- [x] TDD Test List を作成した
|
|
239
|
+
- [x] 根拠監査が追加不要である理由を記録した
|
|
240
|
+
- [x] 実機実行条件を記録した
|
|
241
|
+
- [x] 公開型と constructor / method の排他性を実装した
|
|
242
|
+
- [x] 共通 sender と Direct transaction を実装した
|
|
243
|
+
- [x] Periodic の後方互換を確認した
|
|
244
|
+
- [x] Direct の rollback、直列化、tap、subcommand、close を確認した
|
|
245
|
+
- [x] 3 controller profile の Direct 型を確認した
|
|
246
|
+
- [x] initial design と public docs を更新した
|
|
247
|
+
- [x] 標準 gate と integration gate の結果を記録した
|
|
248
|
+
|
|
249
|
+
## 14. Self-review
|
|
250
|
+
|
|
251
|
+
| 観点 | 結果 | 根拠 |
|
|
252
|
+
|---|---|---|
|
|
253
|
+
| scope | pass | 公開 reporting type、共通 sender、Direct transaction、文書更新に限定。raw report API、mode 切替、scheduler 精度、macro は追加していない |
|
|
254
|
+
| 公開 API | pass | unit test で2抽象型、6具象型、`apply` / `send`、`report_period_us` の排他性と root export を固定 |
|
|
255
|
+
| transaction | pass | fake transport で transport 完了後 commit、未接続・入力型・profile・送信失敗 rollback、1操作1送信を確認 |
|
|
256
|
+
| concurrency | pass | input operation lock と sender lock の順序を確認。concurrent operation、Direct `tap()`、input / subcommand 競合テストが pass |
|
|
257
|
+
| lifecycle | pass | Direct の非周期性、subcommand 自動応答、`close(neutral=True/False)`、新 session の neutral baseline を確認 |
|
|
258
|
+
| Periodic 回帰 | pass | 既存状態更新の非即時送信、周期 report、held input、close、全 unit / integration gate が pass |
|
|
259
|
+
| 根拠 / 実機 | pass | wire layout、Bumble 仮定、実機条件を変更していない。実機・専用 USB Bluetooth ドングルは未使用で、今回の acceptance に不要 |
|
|
260
|
+
| 未解決指摘 | none | self-review で追加修正を要する不整合なし |
|
|
@@ -4,13 +4,14 @@
|
|
|
4
4
|
|
|
5
5
|
## 1. API 設計の方針
|
|
6
6
|
|
|
7
|
-
-
|
|
8
|
-
-
|
|
7
|
+
- lifecycle と意味的入力操作の共通型は `SwitchGamepad` にする
|
|
8
|
+
- 周期送信の共通型は `PeriodicSwitchGamepad`、具象型は `ProController` / `JoyConL` / `JoyConR` にする
|
|
9
|
+
- 利用者所有の Direct 送信の共通型は `DirectSwitchGamepad`、具象型は `DirectProController` / `DirectJoyConL` / `DirectJoyConR` にする
|
|
9
10
|
- Bluetooth や Bumble の詳細は public API に露出させない
|
|
10
11
|
- USB Bluetooth adapter 候補の確認は `list_adapters()` に分け、adapter open とは別の no-open API とする
|
|
11
12
|
- 入力状態は `InputState` として明示的に扱う
|
|
12
13
|
- 短い操作には `tap()`、`press()`、`release()` を提供する
|
|
13
|
-
- 完全な状態更新には `apply()` を提供する
|
|
14
|
+
- 完全な状態更新には Periodic の `apply()` と Direct の `send()` を提供する
|
|
14
15
|
- stick だけの状態更新には `lstick()`、`rstick()`、`sticks()` を提供し、axis 値は `Stick` に閉じ込める
|
|
15
16
|
- 終了時は `neutral()` または `close(neutral=True)` により入力を戻せるようにする
|
|
16
17
|
- API は `asyncio` 前提にする
|
|
@@ -22,10 +23,10 @@
|
|
|
22
23
|
|
|
23
24
|
```python
|
|
24
25
|
import asyncio
|
|
25
|
-
from swbt import
|
|
26
|
+
from swbt import Button, ProController
|
|
26
27
|
|
|
27
28
|
async def main() -> None:
|
|
28
|
-
async with
|
|
29
|
+
async with ProController(
|
|
29
30
|
adapter="usb:0",
|
|
30
31
|
key_store_path="switch-bond.json",
|
|
31
32
|
) as pad:
|
|
@@ -44,10 +45,10 @@ asyncio.run(main())
|
|
|
44
45
|
|
|
45
46
|
```python
|
|
46
47
|
import asyncio
|
|
47
|
-
from swbt import
|
|
48
|
+
from swbt import Button, ProController
|
|
48
49
|
|
|
49
50
|
async def main() -> None:
|
|
50
|
-
async with
|
|
51
|
+
async with ProController(adapter="usb:0", key_store_path="switch-bond.json") as pad:
|
|
51
52
|
await pad.connect(timeout=30.0)
|
|
52
53
|
|
|
53
54
|
await pad.press(Button.L, Button.R)
|
|
@@ -62,10 +63,10 @@ asyncio.run(main())
|
|
|
62
63
|
|
|
63
64
|
```python
|
|
64
65
|
import asyncio
|
|
65
|
-
from swbt import
|
|
66
|
+
from swbt import ProController, Stick
|
|
66
67
|
|
|
67
68
|
async def main() -> None:
|
|
68
|
-
async with
|
|
69
|
+
async with ProController(adapter="usb:0", key_store_path="switch-bond.json") as pad:
|
|
69
70
|
await pad.connect(timeout=30.0)
|
|
70
71
|
|
|
71
72
|
await pad.lstick(Stick.up())
|
|
@@ -81,10 +82,10 @@ asyncio.run(main())
|
|
|
81
82
|
|
|
82
83
|
```python
|
|
83
84
|
import asyncio
|
|
84
|
-
from swbt import Button, InputState,
|
|
85
|
+
from swbt import Button, InputState, ProController, Stick
|
|
85
86
|
|
|
86
87
|
async def main() -> None:
|
|
87
|
-
async with
|
|
88
|
+
async with ProController(adapter="usb:0", key_store_path="switch-bond.json") as pad:
|
|
88
89
|
await pad.connect(timeout=30.0)
|
|
89
90
|
|
|
90
91
|
state = InputState.neutral().with_buttons([Button.L, Button.R]).with_sticks(
|
|
@@ -104,13 +105,14 @@ asyncio.run(main())
|
|
|
104
105
|
|
|
105
106
|
```python
|
|
106
107
|
import asyncio
|
|
107
|
-
from swbt import
|
|
108
|
+
from swbt import Button
|
|
109
|
+
from swbt._testing.gamepad import make_pro_controller
|
|
108
110
|
from swbt.transport.fake import FakeHidTransport
|
|
109
111
|
|
|
110
112
|
async def main() -> None:
|
|
111
113
|
transport = FakeHidTransport()
|
|
112
114
|
|
|
113
|
-
async with
|
|
115
|
+
async with make_pro_controller(transport=transport) as pad:
|
|
114
116
|
pairing = asyncio.create_task(pad.pair(timeout=1.0))
|
|
115
117
|
await transport.connect()
|
|
116
118
|
await pairing
|
|
@@ -123,56 +125,67 @@ asyncio.run(main())
|
|
|
123
125
|
|
|
124
126
|
fake transport は unit test と integration test 用であり、実機接続には使わない。
|
|
125
127
|
|
|
126
|
-
## 3.
|
|
128
|
+
## 3. controller 型
|
|
129
|
+
|
|
130
|
+
`SwitchGamepad` は lifecycle、connection、status、意味的入力操作を共有する抽象型であり、直接生成しない。`PeriodicSwitchGamepad` と `DirectSwitchGamepad` が入力レポートの送信所有者を型として固定する。
|
|
131
|
+
|
|
132
|
+
```text
|
|
133
|
+
SwitchGamepad
|
|
134
|
+
├── PeriodicSwitchGamepad
|
|
135
|
+
│ ├── ProController
|
|
136
|
+
│ ├── JoyConL
|
|
137
|
+
│ └── JoyConR
|
|
138
|
+
└── DirectSwitchGamepad
|
|
139
|
+
├── DirectProController
|
|
140
|
+
├── DirectJoyConL
|
|
141
|
+
└── DirectJoyConR
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
Periodic の入力操作は local state の確定で正常終了し、レポートループが後続の入力レポートを送る。Direct の入力操作は入力レポート1件の送信完了後に state を確定して正常終了する。Direct の transport 送信が失敗した場合は、最後に正常送信した state を維持する。
|
|
127
145
|
|
|
128
146
|
### 3.1 初期化
|
|
129
147
|
|
|
130
148
|
```python
|
|
131
|
-
class
|
|
149
|
+
class ProController(PeriodicSwitchGamepad):
|
|
132
150
|
def __init__(
|
|
133
151
|
self,
|
|
134
152
|
*,
|
|
135
153
|
adapter: str | None = None,
|
|
136
154
|
key_store_path: str | None = None,
|
|
137
155
|
report_period_us: int | None = None,
|
|
138
|
-
device_name: str | None = None,
|
|
139
156
|
controller_colors: ControllerColors | None = None,
|
|
140
157
|
diagnostics: DiagnosticsConfig | None = None,
|
|
141
|
-
transport: HidDeviceTransport | None = None,
|
|
142
158
|
) -> None: ...
|
|
143
159
|
|
|
144
|
-
|
|
145
|
-
def
|
|
146
|
-
|
|
147
|
-
config: SwitchGamepadConfig,
|
|
160
|
+
class DirectProController(DirectSwitchGamepad):
|
|
161
|
+
def __init__(
|
|
162
|
+
self,
|
|
148
163
|
*,
|
|
164
|
+
adapter: str | None = None,
|
|
165
|
+
key_store_path: str | None = None,
|
|
166
|
+
controller_colors: ControllerColors | None = None,
|
|
149
167
|
diagnostics: DiagnosticsConfig | None = None,
|
|
150
|
-
|
|
151
|
-
) -> "SwitchGamepad": ...
|
|
168
|
+
) -> None: ...
|
|
152
169
|
```
|
|
153
170
|
|
|
154
|
-
|
|
171
|
+
`JoyConL` / `JoyConR` は `ProController` と同じ constructor 引数を持つ。`DirectJoyConL` / `DirectJoyConR` は `DirectProController` と同じ constructor 引数を持つ。
|
|
155
172
|
|
|
156
173
|
| 引数 | 意味 |
|
|
157
174
|
|---|---|
|
|
158
175
|
| `adapter` | Bumble transport に渡す adapter moniker |
|
|
159
176
|
| `key_store_path` | default Bumble transport が pairing key を保存する JSON key store path |
|
|
160
|
-
| `report_period_us` |
|
|
161
|
-
| `device_name` | HID Device として使う表示名。`None` は profile の既定名 |
|
|
177
|
+
| `report_period_us` | Periodic だけが受け取る入力レポートの送信周期。`None` は profile の既定値 |
|
|
162
178
|
| `controller_colors` | SPI profile で返す controller body / buttons / left grip / right grip の固定色 |
|
|
163
179
|
| `diagnostics` | trace と counter の設定 |
|
|
164
|
-
| `transport` | テストや別 transport 実装を注入するための引数 |
|
|
165
180
|
|
|
166
|
-
|
|
181
|
+
公開 constructor では `adapter` は必須である。transport 注入と profile 差し替えは内部 test helper に限定し、公開 extension point としない。
|
|
167
182
|
|
|
168
|
-
`key_store_path` は 1 つの仮想
|
|
183
|
+
`key_store_path` は 1 つの仮想 controller と対象機器の pairing storage を定義する構成値である。controller profile が異なる場合は同じ対象機器でも分ける。`key_store_path=None` は永続 bond を持たない一時的な仮想 controller を意味する。
|
|
169
184
|
|
|
170
|
-
`report_period_us=None` は profile
|
|
185
|
+
Periodic の `report_period_us=None` は profile が持つ既定周期を使う。Pro Controller の既定 profile では `8000us` になる。Direct はレポートループを持たず、`report_period_us` を受け取らない。
|
|
171
186
|
|
|
172
187
|
`controller_colors=None` は既定の Joy-Con-ish profile `ControllerColors(body=0x323232, buttons=0xFFFFFF, left_grip=0x00B2FF, right_grip=0xFF3B30)` を使う。`body`、`buttons`、`left_grip`、`right_grip` はそれぞれ独立した既定値を持つ。色は作成時に固定し、接続後の `set_color()`、`controller_colors=` setter、profile mutation API は提供しない。
|
|
173
188
|
|
|
174
|
-
`transport` 注入は public extension point として扱う。`SwitchGamepad` は injected transport を後から再設定しない。key store を必要とする custom transport は、その transport 自身の constructor で設定を受ける。`SwitchGamepadConfig.key_store_path` は default Bumble transport の構築と diagnostics metadata に使う。
|
|
175
|
-
|
|
176
189
|
### 3.2 接続操作
|
|
177
190
|
|
|
178
191
|
```python
|
|
@@ -195,7 +208,7 @@ async def try_connect(
|
|
|
195
208
|
async def close(self, *, neutral: bool = True) -> None: ...
|
|
196
209
|
```
|
|
197
210
|
|
|
198
|
-
`open()` は transport
|
|
211
|
+
`open()` は transport、callback、diagnostics、共通 sender を準備する。Periodic だけが report loop を準備し、Direct は周期 task を作らない。`open()` だけでは HID advertising、pairing、reconnect を開始しない。
|
|
199
212
|
|
|
200
213
|
`pair()` は初回 pairing のための入口である。内部では HID advertising と incoming 接続待ちを開始する。
|
|
201
214
|
|
|
@@ -203,7 +216,7 @@ async def close(self, *, neutral: bool = True) -> None: ...
|
|
|
203
216
|
|
|
204
217
|
`connect()` は通常利用向けの入口である。保存済み bond があれば `reconnect()` を優先し、bond がない場合は `allow_pairing=True` のときだけ `pair()` へ進む。
|
|
205
218
|
|
|
206
|
-
`close()`
|
|
219
|
+
`close()` は Periodic の送信 loop と共通 transport を停止する。`neutral=True` かつ接続中の場合、Direct でも終了処理の例外としてニュートラル入力を1件送信し、成功後に state を確定する。`neutral=False` は終了用入力レポートを追加しない。
|
|
207
220
|
|
|
208
221
|
`connect()` / `reconnect()` は成功した場合だけ戻る。接続できない場合は `ConnectionFailedError`、timeout は `ConnectionTimeoutError` を投げる。接続失敗の詳細 status が必要な場合は `try_connect()` / `try_reconnect()` を使い、`ConnectionResult` を読む。`pair()` は初回 pairing の明示入口であり、接続戦略の選択結果は返さない。
|
|
209
222
|
|
|
@@ -215,6 +228,7 @@ async def close(self, *, neutral: bool = True) -> None: ...
|
|
|
215
228
|
|
|
216
229
|
```python
|
|
217
230
|
async def apply(self, state: InputState) -> None: ...
|
|
231
|
+
async def send(self, state: InputState) -> None: ...
|
|
218
232
|
async def sticks(
|
|
219
233
|
self,
|
|
220
234
|
*,
|
|
@@ -231,11 +245,13 @@ async def release(self, *buttons: Button) -> None: ...
|
|
|
231
245
|
async def tap(self, *buttons: Button, duration: float = 0.08) -> None: ...
|
|
232
246
|
```
|
|
233
247
|
|
|
234
|
-
`press()`、`release()`、`lstick()`、`rstick()`、`sticks()`、`imu()`、`neutral()`、`apply()`
|
|
248
|
+
`apply()` は Periodic だけ、`send()` は Direct だけが提供する。Periodic の `press()`、`release()`、`lstick()`、`rstick()`、`sticks()`、`imu()`、`neutral()`、`apply()` は接続を要求せず、即時送信もしない。接続中は次の periodic report で反映される。
|
|
235
249
|
|
|
236
|
-
`apply()`
|
|
250
|
+
`apply()` と `send()` は完成済みの `InputState` で現在入力全体を置き換える。差分適用ではない。Direct の `send()` と意味的入力操作は接続済みを要求し、1操作につき入力レポートを1件送ってから state を確定する。未接続、profile validation、transport 送信失敗では state を変更しない。Direct の input operation は直列化し、候補 state の更新を失わない。
|
|
237
251
|
|
|
238
|
-
`
|
|
252
|
+
`lstick()` は left stick だけを置き換え、`rstick()` は right stick だけを置き換える。`sticks()` は左右どちらか、または両方の stick だけを置き換える。stick API は `Stick` だけを受け、tuple や raw int tuple は受けない。profile が対応しない button / stick update は state store へ commit する前に `UnsupportedInputError` とする。`imu()` は IMU 3 frame だけを置き換える。1 frame を渡した場合は 3 frame すべてに複製し、3 frame を渡した場合は順に設定する。0 個、2 個、4 個以上、`IMUFrame` 以外は `InvalidInputError` とする。
|
|
253
|
+
|
|
254
|
+
`tap()` は action API である。接続済みを要求し、押下 report と release report を即時送信する。release 対象は `tap()` に渡した button だけであり、既に押されていた他の button は維持する。Direct は押下から解放まで同じ input operation lock を保持する。解放送信に失敗した場合は、最後に正常送信した押下 state を維持する。
|
|
239
255
|
|
|
240
256
|
`tap()` の `duration` は秒単位とする。packet protocol へ duration を埋め込まない。
|
|
241
257
|
|
|
@@ -246,43 +262,40 @@ def snapshot(self) -> InputSnapshot: ...
|
|
|
246
262
|
def status(self) -> GamepadStatus: ...
|
|
247
263
|
```
|
|
248
264
|
|
|
249
|
-
`snapshot()` は現在入力の snapshot
|
|
265
|
+
`snapshot()` は現在入力の snapshot を返す。Periodic は最新の local state、Direct は最後に正常送信した state を返す。新しい接続 session は neutral baseline から始める。`status()` は接続状態、送信 report 数、最後に受け取った subcommand、最後の disconnect 理由などを返す。
|
|
250
266
|
|
|
251
267
|
`status()` は実機検証と利用者側の監視に使う。高頻度 control path では使わない。
|
|
252
268
|
|
|
253
269
|
### 3.5 context manager
|
|
254
270
|
|
|
255
271
|
```python
|
|
256
|
-
async with
|
|
272
|
+
async with ProController(adapter="usb:0", key_store_path="switch-bond.json") as pad:
|
|
257
273
|
await pad.connect(timeout=30.0)
|
|
258
274
|
await pad.tap(Button.A)
|
|
259
275
|
```
|
|
260
276
|
|
|
261
277
|
`async with` は `open()` と `close(neutral=True)` を呼ぶ resource scope である。`__aenter__()` は HID advertising、pairing、reconnect を開始しない。
|
|
262
278
|
|
|
263
|
-
## 3.6
|
|
279
|
+
## 3.6 Joy-Con L / R
|
|
264
280
|
|
|
265
281
|
```python
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
transport: HidDeviceTransport | None = None,
|
|
278
|
-
) -> None: ...
|
|
282
|
+
left = JoyConL(adapter="usb:0", key_store_path="switch-left-joycon-bond.json")
|
|
283
|
+
right = JoyConR(adapter="usb:0", key_store_path="switch-right-joycon-bond.json")
|
|
284
|
+
|
|
285
|
+
direct_left = DirectJoyConL(
|
|
286
|
+
adapter="usb:0",
|
|
287
|
+
key_store_path="switch-direct-left-joycon-bond.json",
|
|
288
|
+
)
|
|
289
|
+
direct_right = DirectJoyConR(
|
|
290
|
+
adapter="usb:0",
|
|
291
|
+
key_store_path="switch-direct-right-joycon-bond.json",
|
|
292
|
+
)
|
|
279
293
|
```
|
|
280
294
|
|
|
281
|
-
`
|
|
295
|
+
`JoyConL` / `DirectJoyConL` は Joy-Con L profile、`JoyConR` / `DirectJoyConR` は Joy-Con R profile を固定する。side string を受け取る wrapper は公開しない。接続、入力、diagnostics、`close(neutral=True)` の lifecycle は共通にする。
|
|
282
296
|
|
|
283
297
|
```python
|
|
284
|
-
async with
|
|
285
|
-
"left",
|
|
298
|
+
async with JoyConL(
|
|
286
299
|
adapter="usb:0",
|
|
287
300
|
key_store_path="switch-left-joycon-bond.json",
|
|
288
301
|
) as left:
|
|
@@ -290,7 +303,7 @@ async with JoyCon(
|
|
|
290
303
|
await left.tap(Button.L)
|
|
291
304
|
```
|
|
292
305
|
|
|
293
|
-
片側 Joy-Con が持たない button / stick は `UnsupportedInputError` とする。左 Joy-Con は A/B/X/Y、right stick などを扱わない。右 Joy-Con は D-pad、left stick などを扱わない。`InputState` を `apply()` する場合も同じ検査を行い、不正 state
|
|
306
|
+
片側 Joy-Con が持たない button / stick は `UnsupportedInputError` とする。左 Joy-Con は A/B/X/Y、right stick などを扱わない。右 Joy-Con は D-pad、left stick などを扱わない。`InputState` を `apply()` または `send()` する場合も同じ検査を行い、不正 state は送信・commit しない。
|
|
294
307
|
|
|
295
308
|
Pro Controller、Joy-Con L、Joy-Con R は HID identity と pairing key の対応を分けるため、別々の `key_store_path` を使う。同じ対象機器でも profile を変える場合は key store を共有しない。
|
|
296
309
|
|
|
@@ -319,7 +332,7 @@ class AdapterInfo:
|
|
|
319
332
|
def list_adapters() -> tuple[AdapterInfo, ...]: ...
|
|
320
333
|
```
|
|
321
334
|
|
|
322
|
-
`list_adapters()` は `
|
|
335
|
+
`list_adapters()` は `ProController(adapter=...)` などの公開具象型に渡す USB Bluetooth adapter 候補を返す。Nintendo Switch 本体や周辺 Bluetooth host は列挙しない。
|
|
323
336
|
|
|
324
337
|
この API は libusb の USB device enumeration と descriptor 読み取りを行うが、Bumble transport として adapter を開かない。Bluetooth controller power on、HID advertising、pairing、report loop も開始しない。
|
|
325
338
|
|
|
@@ -554,8 +567,8 @@ no-open adapter 列挙の失敗は `AdapterDiscoveryError` とする。利用者
|
|
|
554
567
|
すべての I/O と時間待ちは `asyncio` coroutine とする。
|
|
555
568
|
|
|
556
569
|
- `tap()` は内部で `asyncio.sleep(duration)` を使う
|
|
557
|
-
- `ReportLoop` は独立した task として動く
|
|
558
|
-
- `close()` は `ReportLoop` task を停止し、transport を閉じる
|
|
570
|
+
- Periodic の `ReportLoop` は独立した task として動く
|
|
571
|
+
- `close()` は Periodic の `ReportLoop` task を停止し、transport を閉じる
|
|
559
572
|
- callback 例外は diagnostics に記録し、必要に応じて接続を failed 状態へ遷移させる
|
|
560
573
|
|
|
561
574
|
## 11. 初期 API で公開しないもの
|