resonite-io 0.1.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.
- resonite_io-0.1.0/.gitignore +227 -0
- resonite_io-0.1.0/PKG-INFO +69 -0
- resonite_io-0.1.0/README.md +44 -0
- resonite_io-0.1.0/examples/README.md +119 -0
- resonite_io-0.1.0/examples/camera_view.py +86 -0
- resonite_io-0.1.0/examples/connection_ping.py +66 -0
- resonite_io-0.1.0/examples/context_menu_interact.py +88 -0
- resonite_io-0.1.0/examples/cursor_move.py +71 -0
- resonite_io-0.1.0/examples/dash_navigate.py +97 -0
- resonite_io-0.1.0/examples/display_config.py +96 -0
- resonite_io-0.1.0/examples/inventory_manage.py +89 -0
- resonite_io-0.1.0/examples/locomotion_drive.py +111 -0
- resonite_io-0.1.0/examples/manipulation_grab.py +75 -0
- resonite_io-0.1.0/examples/microphone_send.py +98 -0
- resonite_io-0.1.0/examples/speaker_record.py +84 -0
- resonite_io-0.1.0/examples/world_browse.py +99 -0
- resonite_io-0.1.0/pyproject.toml +195 -0
- resonite_io-0.1.0/src/resoio/__init__.py +124 -0
- resonite_io-0.1.0/src/resoio/_client.py +96 -0
- resonite_io-0.1.0/src/resoio/_generated/__init__.py +0 -0
- resonite_io-0.1.0/src/resoio/_generated/message_pool.py +3 -0
- resonite_io-0.1.0/src/resoio/_generated/py.typed +0 -0
- resonite_io-0.1.0/src/resoio/_generated/resonite_io/__init__.py +0 -0
- resonite_io-0.1.0/src/resoio/_generated/resonite_io/v1/__init__.py +4069 -0
- resonite_io-0.1.0/src/resoio/_socket.py +66 -0
- resonite_io-0.1.0/src/resoio/camera.py +87 -0
- resonite_io-0.1.0/src/resoio/cli/__init__.py +118 -0
- resonite_io-0.1.0/src/resoio/cli/context_menu.py +110 -0
- resonite_io-0.1.0/src/resoio/cli/cursor.py +95 -0
- resonite_io-0.1.0/src/resoio/cli/dash.py +155 -0
- resonite_io-0.1.0/src/resoio/cli/display.py +90 -0
- resonite_io-0.1.0/src/resoio/cli/inventory.py +327 -0
- resonite_io-0.1.0/src/resoio/cli/locomotion.py +518 -0
- resonite_io-0.1.0/src/resoio/cli/manipulate.py +194 -0
- resonite_io-0.1.0/src/resoio/cli/mic.py +333 -0
- resonite_io-0.1.0/src/resoio/cli/ping.py +78 -0
- resonite_io-0.1.0/src/resoio/cli/record.py +934 -0
- resonite_io-0.1.0/src/resoio/cli/world.py +567 -0
- resonite_io-0.1.0/src/resoio/connection.py +47 -0
- resonite_io-0.1.0/src/resoio/context_menu.py +176 -0
- resonite_io-0.1.0/src/resoio/cursor.py +91 -0
- resonite_io-0.1.0/src/resoio/dash.py +376 -0
- resonite_io-0.1.0/src/resoio/display.py +86 -0
- resonite_io-0.1.0/src/resoio/inventory.py +197 -0
- resonite_io-0.1.0/src/resoio/locomotion.py +183 -0
- resonite_io-0.1.0/src/resoio/manipulation.py +184 -0
- resonite_io-0.1.0/src/resoio/microphone.py +154 -0
- resonite_io-0.1.0/src/resoio/py.typed +0 -0
- resonite_io-0.1.0/src/resoio/speaker.py +83 -0
- resonite_io-0.1.0/src/resoio/world.py +403 -0
- resonite_io-0.1.0/tests/__init__.py +0 -0
- resonite_io-0.1.0/tests/conftest.py +55 -0
- resonite_io-0.1.0/tests/e2e/README.md +55 -0
- resonite_io-0.1.0/tests/e2e/__init__.py +0 -0
- resonite_io-0.1.0/tests/e2e/camera_stream.py +130 -0
- resonite_io-0.1.0/tests/e2e/conftest.py +94 -0
- resonite_io-0.1.0/tests/e2e/connection.py +19 -0
- resonite_io-0.1.0/tests/e2e/context_menu.py +192 -0
- resonite_io-0.1.0/tests/e2e/cursor.py +127 -0
- resonite_io-0.1.0/tests/e2e/dash.py +442 -0
- resonite_io-0.1.0/tests/e2e/display_resolution.py +199 -0
- resonite_io-0.1.0/tests/e2e/fixtures/generate_sine.py +104 -0
- resonite_io-0.1.0/tests/e2e/fixtures/sine_440hz_5s_mono_48k.wav +0 -0
- resonite_io-0.1.0/tests/e2e/inventory.py +356 -0
- resonite_io-0.1.0/tests/e2e/locomotion.py +335 -0
- resonite_io-0.1.0/tests/e2e/manipulation.py +224 -0
- resonite_io-0.1.0/tests/e2e/mic_auto_default.py +83 -0
- resonite_io-0.1.0/tests/e2e/mic_send.py +157 -0
- resonite_io-0.1.0/tests/e2e/speaker_record.py +214 -0
- resonite_io-0.1.0/tests/e2e/world.py +406 -0
- resonite_io-0.1.0/tests/helpers.py +5 -0
- resonite_io-0.1.0/tests/resoio/__init__.py +0 -0
- resonite_io-0.1.0/tests/resoio/cli/__init__.py +0 -0
- resonite_io-0.1.0/tests/resoio/cli/test_context_menu.py +260 -0
- resonite_io-0.1.0/tests/resoio/cli/test_cursor.py +154 -0
- resonite_io-0.1.0/tests/resoio/cli/test_dash.py +409 -0
- resonite_io-0.1.0/tests/resoio/cli/test_display.py +167 -0
- resonite_io-0.1.0/tests/resoio/cli/test_inventory.py +261 -0
- resonite_io-0.1.0/tests/resoio/cli/test_locomotion.py +950 -0
- resonite_io-0.1.0/tests/resoio/cli/test_manipulate.py +224 -0
- resonite_io-0.1.0/tests/resoio/cli/test_mic.py +411 -0
- resonite_io-0.1.0/tests/resoio/cli/test_ping.py +66 -0
- resonite_io-0.1.0/tests/resoio/cli/test_record.py +1219 -0
- resonite_io-0.1.0/tests/resoio/cli/test_world.py +1037 -0
- resonite_io-0.1.0/tests/resoio/test_api_contract.py +333 -0
- resonite_io-0.1.0/tests/resoio/test_camera.py +87 -0
- resonite_io-0.1.0/tests/resoio/test_connection.py +60 -0
- resonite_io-0.1.0/tests/resoio/test_context_menu.py +250 -0
- resonite_io-0.1.0/tests/resoio/test_cursor.py +109 -0
- resonite_io-0.1.0/tests/resoio/test_dash.py +585 -0
- resonite_io-0.1.0/tests/resoio/test_display.py +89 -0
- resonite_io-0.1.0/tests/resoio/test_inventory.py +183 -0
- resonite_io-0.1.0/tests/resoio/test_locomotion.py +199 -0
- resonite_io-0.1.0/tests/resoio/test_manipulation.py +298 -0
- resonite_io-0.1.0/tests/resoio/test_microphone.py +184 -0
- resonite_io-0.1.0/tests/resoio/test_proto_contract.py +615 -0
- resonite_io-0.1.0/tests/resoio/test_speaker.py +82 -0
- resonite_io-0.1.0/tests/resoio/test_world.py +711 -0
- resonite_io-0.1.0/uv.lock +1167 -0
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
# =============================================================================
|
|
2
|
+
# Python
|
|
3
|
+
# =============================================================================
|
|
4
|
+
|
|
5
|
+
# Byte-compiled / optimized
|
|
6
|
+
__pycache__/
|
|
7
|
+
*.py[cod]
|
|
8
|
+
*$py.class
|
|
9
|
+
*.so
|
|
10
|
+
|
|
11
|
+
# Distribution / packaging
|
|
12
|
+
.Python
|
|
13
|
+
build/
|
|
14
|
+
develop-eggs/
|
|
15
|
+
dist/
|
|
16
|
+
downloads/
|
|
17
|
+
eggs/
|
|
18
|
+
.eggs/
|
|
19
|
+
lib/
|
|
20
|
+
lib64/
|
|
21
|
+
parts/
|
|
22
|
+
sdist/
|
|
23
|
+
var/
|
|
24
|
+
wheels/
|
|
25
|
+
share/python-wheels/
|
|
26
|
+
*.egg-info/
|
|
27
|
+
.installed.cfg
|
|
28
|
+
*.egg
|
|
29
|
+
MANIFEST
|
|
30
|
+
|
|
31
|
+
# uv
|
|
32
|
+
# `python/uv.lock` は commit するので除外しない (lock を commit する方針)
|
|
33
|
+
.venv/
|
|
34
|
+
venv/
|
|
35
|
+
env/
|
|
36
|
+
ENV/
|
|
37
|
+
|
|
38
|
+
# Type checkers / linters / test runners
|
|
39
|
+
.pytest_cache/
|
|
40
|
+
.mypy_cache/
|
|
41
|
+
.pyright/
|
|
42
|
+
.ruff_cache/
|
|
43
|
+
.tox/
|
|
44
|
+
.nox/
|
|
45
|
+
.cache/
|
|
46
|
+
.hypothesis/
|
|
47
|
+
|
|
48
|
+
# Coverage
|
|
49
|
+
.coverage
|
|
50
|
+
.coverage.*
|
|
51
|
+
htmlcov/
|
|
52
|
+
coverage.xml
|
|
53
|
+
*.cover
|
|
54
|
+
|
|
55
|
+
# Jupyter / IPython
|
|
56
|
+
.ipynb_checkpoints
|
|
57
|
+
profile_default/
|
|
58
|
+
ipython_config.py
|
|
59
|
+
|
|
60
|
+
# pyenv
|
|
61
|
+
.python-version
|
|
62
|
+
|
|
63
|
+
# =============================================================================
|
|
64
|
+
# C# / .NET
|
|
65
|
+
# =============================================================================
|
|
66
|
+
|
|
67
|
+
# Build outputs
|
|
68
|
+
[Bb]in/
|
|
69
|
+
[Oo]bj/
|
|
70
|
+
[Oo]ut/
|
|
71
|
+
[Ll]og/
|
|
72
|
+
[Ll]ogs/
|
|
73
|
+
[Dd]ebug/
|
|
74
|
+
[Dd]ebugPublic/
|
|
75
|
+
[Rr]elease/
|
|
76
|
+
[Rr]eleases/
|
|
77
|
+
x64/
|
|
78
|
+
x86/
|
|
79
|
+
[Ww][Ii][Nn]32/
|
|
80
|
+
[Aa][Rr][Mm]/
|
|
81
|
+
[Aa][Rr][Mm]64/
|
|
82
|
+
bld/
|
|
83
|
+
|
|
84
|
+
# Visual Studio / Rider / VSCode user state
|
|
85
|
+
.vs/
|
|
86
|
+
.vscode/*
|
|
87
|
+
!.vscode/settings.json
|
|
88
|
+
!.vscode/tasks.json
|
|
89
|
+
!.vscode/launch.json
|
|
90
|
+
!.vscode/extensions.json
|
|
91
|
+
.idea/
|
|
92
|
+
*.user
|
|
93
|
+
*.suo
|
|
94
|
+
*.userprefs
|
|
95
|
+
*.userosscache
|
|
96
|
+
*.sln.docstates
|
|
97
|
+
|
|
98
|
+
# .NET tooling
|
|
99
|
+
.dotnet/
|
|
100
|
+
project.lock.json
|
|
101
|
+
project.fragment.lock.json
|
|
102
|
+
artifacts/
|
|
103
|
+
|
|
104
|
+
# Test results
|
|
105
|
+
[Tt]est[Rr]esult*/
|
|
106
|
+
[Bb]uild[Ll]og.*
|
|
107
|
+
*.VisualState.xml
|
|
108
|
+
TestResult.xml
|
|
109
|
+
nunit-*.xml
|
|
110
|
+
|
|
111
|
+
# Roslyn cache / build artifacts
|
|
112
|
+
*.tlog
|
|
113
|
+
*.lastbuildstate
|
|
114
|
+
*.cache
|
|
115
|
+
*.ilk
|
|
116
|
+
*.iobj
|
|
117
|
+
*.ipdb
|
|
118
|
+
*.meta
|
|
119
|
+
*_i.c
|
|
120
|
+
*_p.c
|
|
121
|
+
*_h.h
|
|
122
|
+
|
|
123
|
+
# NuGet
|
|
124
|
+
*.nupkg
|
|
125
|
+
*.snupkg
|
|
126
|
+
**/[Pp]ackages/*
|
|
127
|
+
!**/[Pp]ackages/build/
|
|
128
|
+
*.nuget.props
|
|
129
|
+
*.nuget.targets
|
|
130
|
+
|
|
131
|
+
# Compiled binaries (mod artifacts deploy 経由で配布する想定)
|
|
132
|
+
*.dll
|
|
133
|
+
*.pdb
|
|
134
|
+
*.exe
|
|
135
|
+
|
|
136
|
+
# =============================================================================
|
|
137
|
+
# Protobuf
|
|
138
|
+
# =============================================================================
|
|
139
|
+
|
|
140
|
+
# Python 側の protoc 出力は意図的に commit する (plan §3.C)
|
|
141
|
+
# `python/src/resoio/_generated/` は ignore せず
|
|
142
|
+
#
|
|
143
|
+
# C# 側は csproj の <Protobuf> ItemGroup から build 時生成される (obj/ に出る) ため
|
|
144
|
+
# obj/ の ignore でカバーされる
|
|
145
|
+
|
|
146
|
+
# =============================================================================
|
|
147
|
+
# Environment / secrets
|
|
148
|
+
# =============================================================================
|
|
149
|
+
|
|
150
|
+
.env
|
|
151
|
+
.env.*
|
|
152
|
+
!.env.example
|
|
153
|
+
|
|
154
|
+
# =============================================================================
|
|
155
|
+
# OS noise
|
|
156
|
+
# =============================================================================
|
|
157
|
+
|
|
158
|
+
.DS_Store
|
|
159
|
+
Thumbs.db
|
|
160
|
+
ehthumbs.db
|
|
161
|
+
Desktop.ini
|
|
162
|
+
$RECYCLE.BIN/
|
|
163
|
+
|
|
164
|
+
# =============================================================================
|
|
165
|
+
# Local artifacts
|
|
166
|
+
# =============================================================================
|
|
167
|
+
|
|
168
|
+
*.log
|
|
169
|
+
*.tmp
|
|
170
|
+
*.swp
|
|
171
|
+
*~
|
|
172
|
+
|
|
173
|
+
# =============================================================================
|
|
174
|
+
# Docs site (mkdocs)
|
|
175
|
+
# =============================================================================
|
|
176
|
+
|
|
177
|
+
# `just docs-build` の出力先 (repo root の mkdocs.yml の既定 site_dir)。
|
|
178
|
+
site/
|
|
179
|
+
|
|
180
|
+
# =============================================================================
|
|
181
|
+
# Host-side scripts venv
|
|
182
|
+
# =============================================================================
|
|
183
|
+
|
|
184
|
+
# `just host-agent` が起動時に `uv venv` で作る host 専用 venv。
|
|
185
|
+
# 既に `.venv/` で吸えるが、scripts/ 配下の意図を明示するため明記する。
|
|
186
|
+
scripts/.venv/
|
|
187
|
+
|
|
188
|
+
# =============================================================================
|
|
189
|
+
# E2E artifacts
|
|
190
|
+
# =============================================================================
|
|
191
|
+
|
|
192
|
+
# `just e2e-test camera_stream` 等が録画した MP4 / PNG の出力先。
|
|
193
|
+
# 動画は数 MB/run で commit する意味がないため ignore。
|
|
194
|
+
python/tests/e2e/e2e_artifacts/
|
|
195
|
+
|
|
196
|
+
# `just e2e-camera-v2` / `just resonite-screenshot` の出力先。
|
|
197
|
+
# repo-relative path で書き出すため repo root 直下に `tmp/` を作る。
|
|
198
|
+
tmp/
|
|
199
|
+
|
|
200
|
+
# =============================================================================
|
|
201
|
+
# Decompiled sources
|
|
202
|
+
# =============================================================================
|
|
203
|
+
|
|
204
|
+
# `just decompile` の出力先。Resonite の decompile 結果は配布物・改変対象では
|
|
205
|
+
# なく開発者個人の参照用なので commit しない。
|
|
206
|
+
decompiled/
|
|
207
|
+
|
|
208
|
+
# =============================================================================
|
|
209
|
+
# Gale profile
|
|
210
|
+
# =============================================================================
|
|
211
|
+
|
|
212
|
+
# Gale (Resonite mod manager) のカスタムプロファイル展開先。
|
|
213
|
+
# `just deploy-mod` の配置先 (gale/BepInEx/plugins/ResoniteIO/) も含めて
|
|
214
|
+
# host 側で Gale が管理するため、リポジトリには持ち込まない。
|
|
215
|
+
gale/
|
|
216
|
+
|
|
217
|
+
# =============================================================================
|
|
218
|
+
# Claude Code
|
|
219
|
+
# =============================================================================
|
|
220
|
+
|
|
221
|
+
# 並列 implementer が一時的に作る git worktree のルート。
|
|
222
|
+
.claude/worktrees/
|
|
223
|
+
.claude/settings.local.json
|
|
224
|
+
.claude/scheduled_tasks.lock
|
|
225
|
+
|
|
226
|
+
# agent memory runtime mirror (canonical tracked copy lives under memory/agents/)
|
|
227
|
+
.claude/agent-memory/
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: resonite-io
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Python client for Resonite IO
|
|
5
|
+
Project-URL: Homepage, https://github.com/MLShukai/ResoniteIO
|
|
6
|
+
Project-URL: Repository, https://github.com/MLShukai/ResoniteIO
|
|
7
|
+
Author: mlshukai
|
|
8
|
+
License: MIT
|
|
9
|
+
Classifier: Development Status :: 3 - Alpha
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
16
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
17
|
+
Classifier: Typing :: Typed
|
|
18
|
+
Requires-Python: >=3.12
|
|
19
|
+
Requires-Dist: argcomplete>=3.5
|
|
20
|
+
Requires-Dist: av>=14
|
|
21
|
+
Requires-Dist: betterproto2[grpclib]
|
|
22
|
+
Requires-Dist: numpy>=2.0
|
|
23
|
+
Requires-Dist: prompt-toolkit>=3.0
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
|
|
26
|
+
# resonite-io
|
|
27
|
+
|
|
28
|
+
Python client for [ResoniteIO](https://github.com/MLShukai/ResoniteIO) — a bidirectional IPC
|
|
29
|
+
bridge that turns [Resonite](https://resonite.com/) into a runtime environment for AI agents.
|
|
30
|
+
The `resonite-io` distribution imports as `resoio` and wraps the `resonite_io.v1` gRPC schema
|
|
31
|
+
(Unix Domain Socket transport, async via `grpclib`) into a friendly, fully typed client
|
|
32
|
+
library and a `resoio` CLI.
|
|
33
|
+
|
|
34
|
+
## Install
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
pip install resonite-io
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Requires
|
|
41
|
+
|
|
42
|
+
A Resonite client running the **ResoniteIO mod** on the same host (the two halves connect
|
|
43
|
+
over a Unix Domain Socket). See the documentation for installing the mod.
|
|
44
|
+
|
|
45
|
+
## Quick start
|
|
46
|
+
|
|
47
|
+
```python
|
|
48
|
+
import asyncio
|
|
49
|
+
|
|
50
|
+
from resoio import ConnectionClient
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
async def main() -> None:
|
|
54
|
+
async with ConnectionClient() as client:
|
|
55
|
+
response = await client.ping("hello")
|
|
56
|
+
print(response.message)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
asyncio.run(main())
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Documentation
|
|
63
|
+
|
|
64
|
+
- **Docs:** <https://mlshukai.github.io/ResoniteIO/>
|
|
65
|
+
- **Source:** <https://github.com/MLShukai/ResoniteIO>
|
|
66
|
+
|
|
67
|
+
## License
|
|
68
|
+
|
|
69
|
+
[MIT](https://github.com/MLShukai/ResoniteIO/blob/main/LICENSE)
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# resonite-io
|
|
2
|
+
|
|
3
|
+
Python client for [ResoniteIO](https://github.com/MLShukai/ResoniteIO) — a bidirectional IPC
|
|
4
|
+
bridge that turns [Resonite](https://resonite.com/) into a runtime environment for AI agents.
|
|
5
|
+
The `resonite-io` distribution imports as `resoio` and wraps the `resonite_io.v1` gRPC schema
|
|
6
|
+
(Unix Domain Socket transport, async via `grpclib`) into a friendly, fully typed client
|
|
7
|
+
library and a `resoio` CLI.
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
pip install resonite-io
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Requires
|
|
16
|
+
|
|
17
|
+
A Resonite client running the **ResoniteIO mod** on the same host (the two halves connect
|
|
18
|
+
over a Unix Domain Socket). See the documentation for installing the mod.
|
|
19
|
+
|
|
20
|
+
## Quick start
|
|
21
|
+
|
|
22
|
+
```python
|
|
23
|
+
import asyncio
|
|
24
|
+
|
|
25
|
+
from resoio import ConnectionClient
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
async def main() -> None:
|
|
29
|
+
async with ConnectionClient() as client:
|
|
30
|
+
response = await client.ping("hello")
|
|
31
|
+
print(response.message)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
asyncio.run(main())
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Documentation
|
|
38
|
+
|
|
39
|
+
- **Docs:** <https://mlshukai.github.io/ResoniteIO/>
|
|
40
|
+
- **Source:** <https://github.com/MLShukai/ResoniteIO>
|
|
41
|
+
|
|
42
|
+
## License
|
|
43
|
+
|
|
44
|
+
[MIT](https://github.com/MLShukai/ResoniteIO/blob/main/LICENSE)
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
# resoio examples
|
|
2
|
+
|
|
3
|
+
各モダリティの **最小 API 呼び出しサンプル** を 1 ファイル = 1 モダリティで
|
|
4
|
+
収録する。 `python/src/resoio/cli/` の production CLI や `python/tests/e2e/` の
|
|
5
|
+
回帰テストは "完全形" で重いため、ライブラリの呼び方を最短で把握したい場合は
|
|
6
|
+
こちらを読む。
|
|
7
|
+
|
|
8
|
+
## 前提
|
|
9
|
+
|
|
10
|
+
- ホスト側で Resonite が起動しており、`ResoniteIO` mod が load 済み
|
|
11
|
+
- mod が `~/.resonite-io/` 配下に UDS を bind 済み (初回環境構築は
|
|
12
|
+
[`.claude/skills/setup-resonite-env/SKILL.md`](../../.claude/skills/setup-resonite-env/SKILL.md))
|
|
13
|
+
- 依存は dev container 内にすべて閉じている (`uv sync` 済み)
|
|
14
|
+
|
|
15
|
+
モダリティ固有の追加前提:
|
|
16
|
+
|
|
17
|
+
- `microphone_send.py` — Resonite Settings → Audio Input で "ResoniteIO" の
|
|
18
|
+
virtual mic デバイスを選択しないと音は鳴らない (mod 起動後 1 回だけ)
|
|
19
|
+
- `locomotion_drive.py` — `SmoothLocomotionBase` が active な world (default
|
|
20
|
+
Home Cloud は OK。Teleport / NoClip / NoLocomotion world は不可)
|
|
21
|
+
- `display_config.py` — Resonite が desktop mode で起動していること (VR mode
|
|
22
|
+
では `ResolutionSettings` が異なる経路を通る)
|
|
23
|
+
- `world_browse.py` — cloud に login 済みで join 可能な公開セッションが見える
|
|
24
|
+
こと (session list が空 = signed out / 空 cloud の場合は notice を print して
|
|
25
|
+
安全に終了する)
|
|
26
|
+
- `context_menu_interact.py` — desktop の T-key radial menu が出せる状態
|
|
27
|
+
(LocalUser / InteractionHandler が attach 済みの world にいること)
|
|
28
|
+
- `dash_navigate.py` — userspace の Esc dash が開ける状態 (engine boot 済み)。
|
|
29
|
+
screen が少ない logged-out 状態でも開閉は可能だが navigation 先が減る
|
|
30
|
+
- `inventory_manage.py` — cloud に login 済み (inventory ops は実 cloud
|
|
31
|
+
inventory を叩く)。書込先は自分で mkdir する `/Inventory/__resoio_example__`
|
|
32
|
+
配下のみで、最後に rm -r で後片付けする
|
|
33
|
+
- `cursor_move.py` — Resonite が desktop mode で起動していること (cursor は
|
|
34
|
+
desktop window 座標を操作する。カーソル自体は screenshot に写りにくいので、
|
|
35
|
+
動いたことの可視確認は context menu を開く `tests/e2e/cursor.py` を参照)
|
|
36
|
+
|
|
37
|
+
## 実行
|
|
38
|
+
|
|
39
|
+
すべて引数なし。dev container 内で:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
uv run python python/examples/connection_ping.py
|
|
43
|
+
uv run python python/examples/camera_view.py
|
|
44
|
+
uv run python python/examples/speaker_record.py
|
|
45
|
+
uv run python python/examples/microphone_send.py
|
|
46
|
+
uv run python python/examples/locomotion_drive.py
|
|
47
|
+
uv run python python/examples/display_config.py
|
|
48
|
+
uv run python python/examples/manipulation_grab.py
|
|
49
|
+
uv run python python/examples/world_browse.py
|
|
50
|
+
uv run python python/examples/context_menu_interact.py
|
|
51
|
+
uv run python python/examples/dash_navigate.py
|
|
52
|
+
uv run python python/examples/inventory_manage.py
|
|
53
|
+
uv run python python/examples/cursor_move.py
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
各 example の内容:
|
|
57
|
+
|
|
58
|
+
| File | やること |
|
|
59
|
+
| -------------------------- | ------------------------------------------------------------------------------------------- |
|
|
60
|
+
| `connection_ping.py` | `Connection.Ping` を 1 回呼んで RTT と server timestamp を print |
|
|
61
|
+
| `camera_view.py` | 5 秒 streaming して fps と最終フレームの輝度統計を print |
|
|
62
|
+
| `speaker_record.py` | 5 秒 streaming して peak amplitude を print + `speaker_output.raw` に raw float32 LE で保存 |
|
|
63
|
+
| `microphone_send.py` | 440 Hz / 3 秒 mono sine wave を生成し virtual mic に送信 |
|
|
64
|
+
| `locomotion_drive.py` | 6 秒 scripted シナリオで forward → strafe → yaw → jump → neutral を流し、reset() で締める |
|
|
65
|
+
| `display_config.py` | 現在解像度 → 1024x768 apply → 元解像度に restore |
|
|
66
|
+
| `manipulation_grab.py` | primary hand で get_state → grab → release の最小サイクル (空き home では grabbed=False) |
|
|
67
|
+
| `world_browse.py` | session list → join → list_open_worlds → focus → leave (空 cloud は notice して終了) |
|
|
68
|
+
| `context_menu_interact.py` | T-key radial を open → get_state → highlight(0) → invoke(first enabled) → close |
|
|
69
|
+
| `dash_navigate.py` | Esc dash を open → list_screens → set_screen(key) → get_tree → invoke(first) → close |
|
|
70
|
+
| `inventory_manage.py` | 一時 dir を mkdir → cp -r → mv → list で確認 → finally で rm -r 後片付け |
|
|
71
|
+
| `cursor_move.py` | get_position → center(0.5,0.5) → move(0.25,0.25) → 元位置に restore |
|
|
72
|
+
|
|
73
|
+
## FAILED_PRECONDITION について
|
|
74
|
+
|
|
75
|
+
Resonite cold-boot 中 (UDS は bound 済みだが engine の `LocalUser` /
|
|
76
|
+
`FocusedWorld` がまだ attach されていない期間) は、各 bridge が
|
|
77
|
+
`grpclib.exceptions.GRPCError(status=Status.FAILED_PRECONDITION)` を返す。
|
|
78
|
+
|
|
79
|
+
各 example は `wait_for_ready()` という小さな inline retry helper を持ち、
|
|
80
|
+
1〜2 秒間隔で 60〜120 秒間 retry する。production レベルで同じことを
|
|
81
|
+
やりたい場合は、より厳密な実装例として `python/tests/e2e/*.py` の
|
|
82
|
+
`wait_for_*_ready()` 系を参照。
|
|
83
|
+
|
|
84
|
+
## 出力 artifact
|
|
85
|
+
|
|
86
|
+
- `speaker_output.raw` — `speaker_record.py` が生成する raw float32 LE stereo
|
|
87
|
+
(48 kHz)。再生は `ffplay`:
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
ffplay -f f32le -ar 48000 -ac 2 speaker_output.raw
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## production reference
|
|
94
|
+
|
|
95
|
+
examples では「最短コード」を優先しているため、以下は意図的に削っている:
|
|
96
|
+
|
|
97
|
+
- argparse / CLI flag 群 (引数は module-level constant で固定)
|
|
98
|
+
- Signal handler (Ctrl+C は asyncio の default 挙動に委ねる)
|
|
99
|
+
- Per-frame 詳細ログ
|
|
100
|
+
- WAV / MP4 header / muxing (raw float32 / 統計 print のみ)
|
|
101
|
+
- TTY 制御 (Locomotion は scripted シナリオのみ、対話操作なし)
|
|
102
|
+
- 厳密な error 分類 (FAILED_PRECONDITION 以外の status はそのまま投げる)
|
|
103
|
+
|
|
104
|
+
完全形が必要な場合は対応する CLI / e2e を参照:
|
|
105
|
+
|
|
106
|
+
| Example | CLI | E2E |
|
|
107
|
+
| -------------------------- | ----------------------------------------------------------- | ----------------------------------------------------------------------- |
|
|
108
|
+
| `connection_ping.py` | [`cli/ping.py`](../src/resoio/cli/ping.py) | [`tests/e2e/connection.py`](../tests/e2e/connection.py) |
|
|
109
|
+
| `camera_view.py` | [`cli/record.py`](../src/resoio/cli/record.py) (video 経路) | [`tests/e2e/camera_stream.py`](../tests/e2e/camera_stream.py) |
|
|
110
|
+
| `speaker_record.py` | [`cli/record.py`](../src/resoio/cli/record.py) (audio 経路) | [`tests/e2e/speaker_record.py`](../tests/e2e/speaker_record.py) |
|
|
111
|
+
| `microphone_send.py` | [`cli/mic.py`](../src/resoio/cli/mic.py) | [`tests/e2e/mic_send.py`](../tests/e2e/mic_send.py) |
|
|
112
|
+
| `locomotion_drive.py` | [`cli/locomotion.py`](../src/resoio/cli/locomotion.py) | [`tests/e2e/locomotion.py`](../tests/e2e/locomotion.py) |
|
|
113
|
+
| `display_config.py` | [`cli/display.py`](../src/resoio/cli/display.py) | [`tests/e2e/display_resolution.py`](../tests/e2e/display_resolution.py) |
|
|
114
|
+
| `manipulation_grab.py` | [`cli/manipulate.py`](../src/resoio/cli/manipulate.py) | [`tests/e2e/manipulation.py`](../tests/e2e/manipulation.py) |
|
|
115
|
+
| `world_browse.py` | [`cli/world.py`](../src/resoio/cli/world.py) | [`tests/e2e/world.py`](../tests/e2e/world.py) |
|
|
116
|
+
| `context_menu_interact.py` | [`cli/context_menu.py`](../src/resoio/cli/context_menu.py) | [`tests/e2e/context_menu.py`](../tests/e2e/context_menu.py) |
|
|
117
|
+
| `dash_navigate.py` | [`cli/dash.py`](../src/resoio/cli/dash.py) | [`tests/e2e/dash.py`](../tests/e2e/dash.py) |
|
|
118
|
+
| `inventory_manage.py` | [`cli/inventory.py`](../src/resoio/cli/inventory.py) | [`tests/e2e/inventory.py`](../tests/e2e/inventory.py) |
|
|
119
|
+
| `cursor_move.py` | [`cli/cursor.py`](../src/resoio/cli/cursor.py) | [`tests/e2e/cursor.py`](../tests/e2e/cursor.py) |
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
"""Minimal Camera stream example.
|
|
2
|
+
|
|
3
|
+
Streams RGBA frames for DURATION_S seconds, then prints the achieved
|
|
4
|
+
fps and basic luminance statistics of the final frame. No frames are
|
|
5
|
+
written to disk; numpy is the only non-stdlib dependency. Assumes a
|
|
6
|
+
Resonite client with the ResoniteIO mod loaded is running on the host.
|
|
7
|
+
|
|
8
|
+
Run from inside the dev container:
|
|
9
|
+
|
|
10
|
+
uv run python python/examples/camera_view.py
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
import asyncio
|
|
14
|
+
import time
|
|
15
|
+
|
|
16
|
+
import grpclib.exceptions
|
|
17
|
+
import numpy as np
|
|
18
|
+
from grpclib.const import Status
|
|
19
|
+
|
|
20
|
+
from resoio import CameraClient, Frame
|
|
21
|
+
|
|
22
|
+
SOCKET_PATH: str | None = None
|
|
23
|
+
DURATION_S = 5.0
|
|
24
|
+
WIDTH = 640
|
|
25
|
+
HEIGHT = 480
|
|
26
|
+
# fps_limit caps server emission so this demo does not burn CPU
|
|
27
|
+
# rendering at the engine's native framerate.
|
|
28
|
+
FPS_LIMIT = 30.0
|
|
29
|
+
READY_TIMEOUT_S = 120.0
|
|
30
|
+
READY_INTERVAL_S = 2.0
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
async def wait_for_ready() -> None:
|
|
34
|
+
"""Block until the Camera bridge yields a frame.
|
|
35
|
+
|
|
36
|
+
Cold-boot gap between UDS bind and LocalUser/FocusedWorld attach
|
|
37
|
+
surfaces as FAILED_PRECONDITION; retry until ``READY_TIMEOUT_S``.
|
|
38
|
+
"""
|
|
39
|
+
deadline = time.monotonic() + READY_TIMEOUT_S
|
|
40
|
+
while True:
|
|
41
|
+
try:
|
|
42
|
+
async with CameraClient(SOCKET_PATH) as cam:
|
|
43
|
+
async for _ in cam.stream(1, 1, 1.0):
|
|
44
|
+
return
|
|
45
|
+
except grpclib.exceptions.GRPCError as e:
|
|
46
|
+
if e.status != Status.FAILED_PRECONDITION:
|
|
47
|
+
raise
|
|
48
|
+
if time.monotonic() > deadline:
|
|
49
|
+
raise TimeoutError(
|
|
50
|
+
f"Camera did not become ready in {READY_TIMEOUT_S:.0f}s"
|
|
51
|
+
) from e
|
|
52
|
+
await asyncio.sleep(READY_INTERVAL_S)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
async def main() -> None:
|
|
56
|
+
await wait_for_ready()
|
|
57
|
+
count = 0
|
|
58
|
+
last: Frame | None = None
|
|
59
|
+
t0 = 0.0
|
|
60
|
+
elapsed = 0.0
|
|
61
|
+
async with CameraClient(SOCKET_PATH) as client:
|
|
62
|
+
async for frame in client.stream(WIDTH, HEIGHT, FPS_LIMIT):
|
|
63
|
+
if count == 0:
|
|
64
|
+
t0 = time.monotonic()
|
|
65
|
+
last = frame
|
|
66
|
+
count += 1
|
|
67
|
+
elapsed = time.monotonic() - t0
|
|
68
|
+
if elapsed >= DURATION_S:
|
|
69
|
+
break
|
|
70
|
+
|
|
71
|
+
fps = count / elapsed if elapsed > 0 else 0.0
|
|
72
|
+
print(f"frames={count} elapsed_s={elapsed:.3f} fps={fps:.2f}")
|
|
73
|
+
if last is not None:
|
|
74
|
+
# frame.pixels is a read-only RGBA8 view; row 0 is the image
|
|
75
|
+
# top. astype(float32) makes a writable copy for arithmetic.
|
|
76
|
+
rgb = last.pixels[..., :3].astype(np.float32)
|
|
77
|
+
lum = 0.299 * rgb[..., 0] + 0.587 * rgb[..., 1] + 0.114 * rgb[..., 2]
|
|
78
|
+
print(
|
|
79
|
+
f"shape={last.pixels.shape} dtype={last.pixels.dtype} "
|
|
80
|
+
f"lum_min={lum.min():.2f} lum_max={lum.max():.2f} "
|
|
81
|
+
f"lum_mean={lum.mean():.2f}"
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
if __name__ == "__main__":
|
|
86
|
+
asyncio.run(main())
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"""Minimal Connection.Ping example.
|
|
2
|
+
|
|
3
|
+
Sends a single ping over the Resonite IO UDS and prints the server
|
|
4
|
+
timestamp plus the measured round-trip time. Assumes a Resonite client
|
|
5
|
+
with the ResoniteIO mod loaded is running on the host.
|
|
6
|
+
|
|
7
|
+
Run from inside the dev container:
|
|
8
|
+
|
|
9
|
+
uv run python python/examples/connection_ping.py
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import asyncio
|
|
13
|
+
import time
|
|
14
|
+
|
|
15
|
+
import grpclib.exceptions
|
|
16
|
+
from grpclib.const import Status
|
|
17
|
+
|
|
18
|
+
from resoio import ConnectionClient
|
|
19
|
+
|
|
20
|
+
SOCKET_PATH: str | None = None
|
|
21
|
+
MESSAGE = "hello"
|
|
22
|
+
READY_TIMEOUT_S = 60.0
|
|
23
|
+
READY_INTERVAL_S = 2.0
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
async def wait_for_ready() -> None:
|
|
27
|
+
"""Block until Connection.Ping returns OK.
|
|
28
|
+
|
|
29
|
+
During cold boot the UDS may be bound before the engine is fully
|
|
30
|
+
ready, in which case the server replies with FAILED_PRECONDITION.
|
|
31
|
+
Retry until ``READY_TIMEOUT_S`` elapses.
|
|
32
|
+
"""
|
|
33
|
+
deadline = time.monotonic() + READY_TIMEOUT_S
|
|
34
|
+
while True:
|
|
35
|
+
try:
|
|
36
|
+
async with ConnectionClient(SOCKET_PATH) as client:
|
|
37
|
+
await client.ping("ready?")
|
|
38
|
+
return
|
|
39
|
+
except grpclib.exceptions.GRPCError as e:
|
|
40
|
+
if e.status != Status.FAILED_PRECONDITION:
|
|
41
|
+
raise
|
|
42
|
+
if time.monotonic() > deadline:
|
|
43
|
+
raise TimeoutError(
|
|
44
|
+
f"Connection did not become ready in {READY_TIMEOUT_S:.0f}s"
|
|
45
|
+
) from e
|
|
46
|
+
await asyncio.sleep(READY_INTERVAL_S)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
async def main() -> None:
|
|
50
|
+
await wait_for_ready()
|
|
51
|
+
async with ConnectionClient(SOCKET_PATH) as client:
|
|
52
|
+
# monotonic_ns is immune to wall-clock jumps (NTP step / DST)
|
|
53
|
+
# that would otherwise produce negative or inflated RTTs.
|
|
54
|
+
t0 = time.monotonic_ns()
|
|
55
|
+
resp = await client.ping(MESSAGE)
|
|
56
|
+
t1 = time.monotonic_ns()
|
|
57
|
+
rtt_ms = (t1 - t0) / 1e6
|
|
58
|
+
print(
|
|
59
|
+
f"message={resp.message} "
|
|
60
|
+
f"server_unix_nanos={resp.server_unix_nanos} "
|
|
61
|
+
f"rtt_ms={rtt_ms:.3f}"
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
if __name__ == "__main__":
|
|
66
|
+
asyncio.run(main())
|