loop-sdk 0.1.0__tar.gz → 0.1.2__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.
- {loop_sdk-0.1.0 → loop_sdk-0.1.2}/.github/workflows/publish.yml +8 -1
- loop_sdk-0.1.2/CONTRIBUTING.md +91 -0
- {loop_sdk-0.1.0 → loop_sdk-0.1.2}/PKG-INFO +1 -1
- loop_sdk-0.1.2/README.md +195 -0
- {loop_sdk-0.1.0 → loop_sdk-0.1.2}/proto/loop/foundation/source/v1/source_ingest.proto +2 -8
- loop_sdk-0.1.2/src/loop_sdk/gen/v1/source_ingest_pb2.py +112 -0
- {loop_sdk-0.1.0 → loop_sdk-0.1.2}/src/loop_sdk/gen/v1/source_ingest_pb2.pyi +161 -79
- {loop_sdk-0.1.0 → loop_sdk-0.1.2}/src/loop_sdk/gen/v1/source_ingest_pb2_grpc.py +13 -145
- {loop_sdk-0.1.0 → loop_sdk-0.1.2}/src/loop_sdk/source/domain/schema.py +6 -1
- {loop_sdk-0.1.0 → loop_sdk-0.1.2}/src/loop_sdk/source/robot_step_sender.py +26 -18
- {loop_sdk-0.1.0 → loop_sdk-0.1.2}/src/loop_sdk/source/service/producer_session.py +4 -0
- {loop_sdk-0.1.0 → loop_sdk-0.1.2}/tests/unit/source/domain/test_schema.py +7 -3
- {loop_sdk-0.1.0 → loop_sdk-0.1.2}/tests/unit/source/test_robot_step_sender.py +22 -0
- loop_sdk-0.1.0/README.md +0 -152
- loop_sdk-0.1.0/src/loop_sdk/gen/v1/source_ingest_pb2.py +0 -132
- {loop_sdk-0.1.0 → loop_sdk-0.1.2}/.gitignore +0 -0
- {loop_sdk-0.1.0 → loop_sdk-0.1.2}/Makefile +0 -0
- {loop_sdk-0.1.0 → loop_sdk-0.1.2}/docs/data_model/README.md +0 -0
- {loop_sdk-0.1.0 → loop_sdk-0.1.2}/docs/data_model/build_descriptor.py +0 -0
- {loop_sdk-0.1.0 → loop_sdk-0.1.2}/docs/data_model/build_journey.py +0 -0
- {loop_sdk-0.1.0 → loop_sdk-0.1.2}/docs/data_model/build_sequence.py +0 -0
- {loop_sdk-0.1.0 → loop_sdk-0.1.2}/docs/data_model/build_visualizer.py +0 -0
- {loop_sdk-0.1.0 → loop_sdk-0.1.2}/docs/data_model/consumer_plan.html +0 -0
- {loop_sdk-0.1.0 → loop_sdk-0.1.2}/docs/data_model/descriptor.html +0 -0
- {loop_sdk-0.1.0 → loop_sdk-0.1.2}/docs/data_model/extract_real_examples.py +0 -0
- {loop_sdk-0.1.0 → loop_sdk-0.1.2}/docs/data_model/flow.html +0 -0
- {loop_sdk-0.1.0 → loop_sdk-0.1.2}/docs/data_model/flow_sequence.html +0 -0
- {loop_sdk-0.1.0 → loop_sdk-0.1.2}/docs/data_model/journey.html +0 -0
- {loop_sdk-0.1.0 → loop_sdk-0.1.2}/docs/data_model/real_examples.json +0 -0
- {loop_sdk-0.1.0 → loop_sdk-0.1.2}/docs/data_model/visualizer.html +0 -0
- {loop_sdk-0.1.0 → loop_sdk-0.1.2}/docs/dev_requirements.md +0 -0
- {loop_sdk-0.1.0 → loop_sdk-0.1.2}/docs/foundation_request_consumer_rpc.md +0 -0
- {loop_sdk-0.1.0 → loop_sdk-0.1.2}/docs/pm_questions.md +0 -0
- {loop_sdk-0.1.0 → loop_sdk-0.1.2}/docs/source_connection_spec.md +0 -0
- {loop_sdk-0.1.0 → loop_sdk-0.1.2}/docs/spec_discrepancies.md +0 -0
- {loop_sdk-0.1.0 → loop_sdk-0.1.2}/examples/README.md +0 -0
- {loop_sdk-0.1.0 → loop_sdk-0.1.2}/examples/policy_action_consumer.py +0 -0
- {loop_sdk-0.1.0 → loop_sdk-0.1.2}/examples/quickstart.py +0 -0
- {loop_sdk-0.1.0 → loop_sdk-0.1.2}/examples/robot_step_producer.py +0 -0
- {loop_sdk-0.1.0 → loop_sdk-0.1.2}/proto/buf.gen.yaml +0 -0
- {loop_sdk-0.1.0 → loop_sdk-0.1.2}/proto/buf.yaml +0 -0
- {loop_sdk-0.1.0 → loop_sdk-0.1.2}/pyproject.toml +0 -0
- {loop_sdk-0.1.0 → loop_sdk-0.1.2}/src/loop_sdk/__init__.py +0 -0
- {loop_sdk-0.1.0 → loop_sdk-0.1.2}/src/loop_sdk/gen/__init__.py +0 -0
- {loop_sdk-0.1.0 → loop_sdk-0.1.2}/src/loop_sdk/gen/__init__.pyi +0 -0
- {loop_sdk-0.1.0 → loop_sdk-0.1.2}/src/loop_sdk/gen/v1/__init__.py +0 -0
- {loop_sdk-0.1.0 → loop_sdk-0.1.2}/src/loop_sdk/gen/v1/__init__.pyi +0 -0
- {loop_sdk-0.1.0 → loop_sdk-0.1.2}/src/loop_sdk/source/__init__.py +0 -0
- {loop_sdk-0.1.0 → loop_sdk-0.1.2}/src/loop_sdk/source/client.py +0 -0
- {loop_sdk-0.1.0 → loop_sdk-0.1.2}/src/loop_sdk/source/consumer.py +0 -0
- {loop_sdk-0.1.0 → loop_sdk-0.1.2}/src/loop_sdk/source/domain/__init__.py +0 -0
- {loop_sdk-0.1.0 → loop_sdk-0.1.2}/src/loop_sdk/source/domain/config.py +0 -0
- {loop_sdk-0.1.0 → loop_sdk-0.1.2}/src/loop_sdk/source/domain/frame.py +0 -0
- {loop_sdk-0.1.0 → loop_sdk-0.1.2}/src/loop_sdk/source/domain/source_exception.py +0 -0
- {loop_sdk-0.1.0 → loop_sdk-0.1.2}/src/loop_sdk/source/domain/source_kind.py +0 -0
- {loop_sdk-0.1.0 → loop_sdk-0.1.2}/src/loop_sdk/source/domain/stats.py +0 -0
- {loop_sdk-0.1.0 → loop_sdk-0.1.2}/src/loop_sdk/source/outbound/__init__.py +0 -0
- {loop_sdk-0.1.0 → loop_sdk-0.1.2}/src/loop_sdk/source/outbound/frame_queue.py +0 -0
- {loop_sdk-0.1.0 → loop_sdk-0.1.2}/src/loop_sdk/source/outbound/grpc_source_client.py +0 -0
- {loop_sdk-0.1.0 → loop_sdk-0.1.2}/src/loop_sdk/source/outbound/grpc_source_reader.py +0 -0
- {loop_sdk-0.1.0 → loop_sdk-0.1.2}/src/loop_sdk/source/outbound/proto_mapping.py +0 -0
- {loop_sdk-0.1.0 → loop_sdk-0.1.2}/src/loop_sdk/source/port/__init__.py +0 -0
- {loop_sdk-0.1.0 → loop_sdk-0.1.2}/src/loop_sdk/source/port/source_client.py +0 -0
- {loop_sdk-0.1.0 → loop_sdk-0.1.2}/src/loop_sdk/source/port/source_reader.py +0 -0
- {loop_sdk-0.1.0 → loop_sdk-0.1.2}/src/loop_sdk/source/service/__init__.py +0 -0
- {loop_sdk-0.1.0 → loop_sdk-0.1.2}/src/loop_sdk/source/setup/__init__.py +0 -0
- {loop_sdk-0.1.0 → loop_sdk-0.1.2}/src/loop_sdk/source/setup/config.py +0 -0
- {loop_sdk-0.1.0 → loop_sdk-0.1.2}/tests/integration/__init__.py +0 -0
- {loop_sdk-0.1.0 → loop_sdk-0.1.2}/tests/integration/source/__init__.py +0 -0
- {loop_sdk-0.1.0 → loop_sdk-0.1.2}/tests/integration/source/outbound/__init__.py +0 -0
- {loop_sdk-0.1.0 → loop_sdk-0.1.2}/tests/integration/source/outbound/test_consumer_lifecycle.py +0 -0
- {loop_sdk-0.1.0 → loop_sdk-0.1.2}/tests/integration/source/outbound/test_consumer_roundtrip.py +0 -0
- {loop_sdk-0.1.0 → loop_sdk-0.1.2}/tests/integration/source/outbound/test_grpc_roundtrip.py +0 -0
- {loop_sdk-0.1.0 → loop_sdk-0.1.2}/tests/unit/__init__.py +0 -0
- {loop_sdk-0.1.0 → loop_sdk-0.1.2}/tests/unit/source/__init__.py +0 -0
- {loop_sdk-0.1.0 → loop_sdk-0.1.2}/tests/unit/source/domain/__init__.py +0 -0
- {loop_sdk-0.1.0 → loop_sdk-0.1.2}/tests/unit/source/outbound/__init__.py +0 -0
- {loop_sdk-0.1.0 → loop_sdk-0.1.2}/tests/unit/source/outbound/test_frame_queue.py +0 -0
- {loop_sdk-0.1.0 → loop_sdk-0.1.2}/tests/unit/source/outbound/test_policy_action_decode.py +0 -0
- {loop_sdk-0.1.0 → loop_sdk-0.1.2}/tests/unit/source/outbound/test_proto_mapping.py +0 -0
- {loop_sdk-0.1.0 → loop_sdk-0.1.2}/tests/unit/source/outbound/test_robot_config_negotiation.py +0 -0
- {loop_sdk-0.1.0 → loop_sdk-0.1.2}/tests/unit/source/outbound/test_send_latency.py +0 -0
- {loop_sdk-0.1.0 → loop_sdk-0.1.2}/tests/unit/source/service/__init__.py +0 -0
- {loop_sdk-0.1.0 → loop_sdk-0.1.2}/tests/unit/source/service/test_producer_session.py +0 -0
- {loop_sdk-0.1.0 → loop_sdk-0.1.2}/tools/replay_fidelity_check.py +0 -0
- {loop_sdk-0.1.0 → loop_sdk-0.1.2}/uv.lock +0 -0
|
@@ -19,7 +19,10 @@ name: Publish to PyPI
|
|
|
19
19
|
on:
|
|
20
20
|
push:
|
|
21
21
|
tags:
|
|
22
|
-
-
|
|
22
|
+
# Only clean version tags trigger a release. `[0-9]+` = one or more digits
|
|
23
|
+
# (so 2- or 3-digit segments are fine), `.` is literal. No other tag fires.
|
|
24
|
+
- "v[0-9]+.[0-9]+.[0-9]+" # vX.Y.Z -> PyPI
|
|
25
|
+
- "v[0-9]+.[0-9]+.[0-9]+-test*" # vX.Y.Z-test… -> TestPyPI (rehearsal)
|
|
23
26
|
|
|
24
27
|
permissions:
|
|
25
28
|
contents: read
|
|
@@ -34,6 +37,8 @@ jobs:
|
|
|
34
37
|
url: https://test.pypi.org/p/loop-sdk
|
|
35
38
|
permissions:
|
|
36
39
|
id-token: write # OIDC token for Trusted Publishing
|
|
40
|
+
contents: read # read the repo at checkout (required for PRIVATE repos;
|
|
41
|
+
# job-level permissions REPLACE the top-level block)
|
|
37
42
|
steps:
|
|
38
43
|
- uses: actions/checkout@v4
|
|
39
44
|
with:
|
|
@@ -66,6 +71,8 @@ jobs:
|
|
|
66
71
|
url: https://pypi.org/p/loop-sdk
|
|
67
72
|
permissions:
|
|
68
73
|
id-token: write # OIDC token for Trusted Publishing
|
|
74
|
+
contents: read # read the repo at checkout (required for PRIVATE repos;
|
|
75
|
+
# job-level permissions REPLACE the top-level block)
|
|
69
76
|
steps:
|
|
70
77
|
- uses: actions/checkout@v4
|
|
71
78
|
with:
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# Contributing to loop-sdk
|
|
2
|
+
|
|
3
|
+
Maintainer notes: how to test the package and how to release it. (User-facing
|
|
4
|
+
usage lives in the [README](README.md).)
|
|
5
|
+
|
|
6
|
+
The project uses **[uv](https://docs.astral.sh/uv/)**. The dev tools (pytest,
|
|
7
|
+
ruff, pyright) aren't pinned as dependencies, so the commands below pull them on
|
|
8
|
+
demand with `uv run --with` / `uvx`.
|
|
9
|
+
|
|
10
|
+
## Testing
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
# Unit + integration tests
|
|
14
|
+
uv run --with pytest python -m pytest
|
|
15
|
+
|
|
16
|
+
# Unit only (skip the integration-marked tests that touch sockets/threads)
|
|
17
|
+
uv run --with pytest python -m pytest tests/unit
|
|
18
|
+
|
|
19
|
+
# Lint and format
|
|
20
|
+
uv run --with ruff ruff check src tests
|
|
21
|
+
uv run --with ruff ruff format --check src tests
|
|
22
|
+
|
|
23
|
+
# Type-check
|
|
24
|
+
uvx pyright src tests
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
The generated gRPC stubs under `src/loop_sdk/gen/` are excluded from lint and
|
|
28
|
+
type-checking — don't hand-edit them (see below).
|
|
29
|
+
|
|
30
|
+
## Regenerating the gRPC stubs
|
|
31
|
+
|
|
32
|
+
The proto contract is vendored at `proto/loop/foundation/source/v1/` (owned
|
|
33
|
+
upstream by the loop foundation module). After editing it, regenerate:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
make proto # buf lint + buf generate + protoletariat (needs buf on PATH + uv)
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
This rewrites `src/loop_sdk/gen/`. Never edit those files by hand; change the
|
|
40
|
+
`.proto` and regenerate. Field numbers must stay in sync with the foundation
|
|
41
|
+
proto, or producer/consumer wire compatibility breaks.
|
|
42
|
+
|
|
43
|
+
## Releasing to PyPI
|
|
44
|
+
|
|
45
|
+
Versioning is **tag-driven** (`hatch-vcs`): a tag `vX.Y.Z` becomes package version
|
|
46
|
+
`X.Y.Z` — there is no version to bump in `pyproject.toml`. A push of a version tag
|
|
47
|
+
triggers `.github/workflows/publish.yml`, which builds the sdist + wheel and
|
|
48
|
+
publishes via **PyPI Trusted Publishing (OIDC)** — no API tokens.
|
|
49
|
+
|
|
50
|
+
Only clean version tags trigger a release:
|
|
51
|
+
|
|
52
|
+
| Tag | Publishes to | Version |
|
|
53
|
+
|---|---|---|
|
|
54
|
+
| `vX.Y.Z` (e.g. `v0.1.0`, `v1.20.300`) | **PyPI** | `X.Y.Z` |
|
|
55
|
+
| `vX.Y.Z-test` (or `-test2`, …) | **TestPyPI** (rehearsal) | `X.Y.Z.dev<run>` (unique per run) |
|
|
56
|
+
| anything else (`v1`, `v1.2`, `nightly`, …) | nothing | — |
|
|
57
|
+
|
|
58
|
+
### Cut a release
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
# (optional) rehearse on TestPyPI first
|
|
62
|
+
git tag v0.1.0-test && git push origin v0.1.0-test
|
|
63
|
+
|
|
64
|
+
# the real release
|
|
65
|
+
git checkout main && git pull
|
|
66
|
+
git tag v0.1.0 && git push origin v0.1.0
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
A tag-triggered run uses the workflow file **as it exists at the tagged commit**,
|
|
70
|
+
so if you change the workflow, tag on top of that change (or re-tag).
|
|
71
|
+
|
|
72
|
+
### One-time Trusted Publishing setup
|
|
73
|
+
|
|
74
|
+
Register a trusted publisher on **each** index (they're separate sites with
|
|
75
|
+
separate accounts). Project `loop-sdk`, owner `configinc`, repo `loop-sdk`,
|
|
76
|
+
workflow `publish.yml`:
|
|
77
|
+
|
|
78
|
+
- **pypi.org** → environment `pypi`
|
|
79
|
+
- **test.pypi.org** → environment `testpypi`
|
|
80
|
+
|
|
81
|
+
Before the project exists on an index, add it as a **pending publisher**. A
|
|
82
|
+
mismatch surfaces as an `invalid-publisher` error on the publish step.
|
|
83
|
+
|
|
84
|
+
### Notes
|
|
85
|
+
|
|
86
|
+
- **Private repo is fine.** Trusted Publishing works regardless of repo
|
|
87
|
+
visibility; the published package stays public. The publish jobs grant
|
|
88
|
+
`contents: read` (needed for `actions/checkout` on a private repo) alongside
|
|
89
|
+
`id-token: write`.
|
|
90
|
+
- The published wheel contains the `loop_sdk` package (including the generated
|
|
91
|
+
stubs); `proto/`, `tests/`, and `examples/` are not shipped.
|
loop_sdk-0.1.2/README.md
ADDED
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
# loop-sdk
|
|
2
|
+
|
|
3
|
+
[English](#loop-sdk) · [한국어](#한국어)
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
pip install loop-sdk # Python ≥ 3.10
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
The Python client that connects your robot to **Loop**.
|
|
10
|
+
|
|
11
|
+
Loop records your robot's runs as episodes and deploys trained policies back onto
|
|
12
|
+
it. This SDK is the robot's side of that link — a single gRPC connection it manages
|
|
13
|
+
for you (handshake, retries, reconnection, backpressure) — and it does two things:
|
|
14
|
+
|
|
15
|
+
- **Record your robot.** Stream its per-tick state and actions to Loop as named
|
|
16
|
+
channels. An operator presses record in the Loop UI and the run is saved as an
|
|
17
|
+
episode; you just keep streaming — you never manage recording yourself.
|
|
18
|
+
- **Run a Loop-hosted policy.** Subscribe to a deployment's action stream and
|
|
19
|
+
execute the actions it sends, instead of standing up your own inference server.
|
|
20
|
+
|
|
21
|
+
Most people start with recording. It's one class — `RobotStepSender` — and three
|
|
22
|
+
calls: `connect`, `send` each control tick, `disconnect`.
|
|
23
|
+
|
|
24
|
+
## Stream your robot in
|
|
25
|
+
|
|
26
|
+
```python
|
|
27
|
+
import time
|
|
28
|
+
|
|
29
|
+
from loop_sdk import RobotStepSender, RobotConfig, RobotConfigOptions
|
|
30
|
+
|
|
31
|
+
control_hz = 20 # the control rate; updated when Loop negotiates one below
|
|
32
|
+
|
|
33
|
+
# At Open, Loop picks a config from `options`; apply it to your robot and confirm.
|
|
34
|
+
# (robot-control-interface assigns this to env.control_hz.)
|
|
35
|
+
def apply_robot_config(cfg: RobotConfig) -> RobotConfig | None:
|
|
36
|
+
global control_hz
|
|
37
|
+
control_hz = cfg.control_hz
|
|
38
|
+
return cfg
|
|
39
|
+
|
|
40
|
+
# loop_addr defaults to the LOOP_ADDR env var (else localhost:50051),
|
|
41
|
+
# source_id to "robot-step". Advertise the configs you can open with.
|
|
42
|
+
sender = RobotStepSender(
|
|
43
|
+
options=RobotConfigOptions(control_hz=(10, 20, 30), action_space=("target_cartesian_delta",)),
|
|
44
|
+
apply_config=apply_robot_config,
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
sender.connect()
|
|
48
|
+
while running: # your control loop
|
|
49
|
+
t_capture_us = time.time_ns() // 1_000 # capture time, epoch microseconds
|
|
50
|
+
# A nested dict — the SDK flattens it to dotted channel keys. The FIRST send
|
|
51
|
+
# fixes the layout, so this first frame must be complete. A numeric list is ONE
|
|
52
|
+
# vector channel (robot0.action.joint_position); build key[0], key[1], … yourself
|
|
53
|
+
# for per-index scalar channels.
|
|
54
|
+
sender.send(t_capture_us, {
|
|
55
|
+
"robot0": {
|
|
56
|
+
"observation": {"robot_state": {"joint_positions": [0.12, -0.34, 1.57], "gripper": 0.80}},
|
|
57
|
+
"action": {"joint_position": [0.13, -0.33, 1.55]},
|
|
58
|
+
"policy_action": [0.01, -0.02, 0.00], # or None when teleop is driving
|
|
59
|
+
},
|
|
60
|
+
})
|
|
61
|
+
wait_for_next_tick(control_hz) # your loop's rate control — send does not pace
|
|
62
|
+
sender.disconnect() # on shutdown (not for pauses)
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
A few things worth knowing:
|
|
66
|
+
|
|
67
|
+
- **You name the channels.** Send a nested dict; the SDK flattens it to dotted keys
|
|
68
|
+
(`robot0.observation...`). The **first `send` fixes the layout**, so include every
|
|
69
|
+
channel you'll ever send in that first frame — give "no reading" ones `None`.
|
|
70
|
+
- **The operator runs recording, not you.** Keep streaming; Loop captures when told
|
|
71
|
+
to. `send` never blocks and doesn't pace your loop — your loop owns its own rate.
|
|
72
|
+
- **Config negotiation (when your robot can open in more than one config).**
|
|
73
|
+
Advertise the choices via `options`; at connect Loop picks one and the SDK calls
|
|
74
|
+
your `apply_config` to apply it (e.g. the control rate), after validating it
|
|
75
|
+
against what you advertised. Nothing to negotiate? Drop `options`/`apply_config`.
|
|
76
|
+
Need the config settled *before* the first send? Call `sender.declare(channels)`
|
|
77
|
+
once after `connect()`.
|
|
78
|
+
|
|
79
|
+
## Run a Loop-hosted policy
|
|
80
|
+
|
|
81
|
+
The mirror of streaming in: instead of calling an inference server, subscribe to a
|
|
82
|
+
deployment's policy-action source and execute the actions it produces. See
|
|
83
|
+
[`examples/policy_action_consumer.py`](examples/policy_action_consumer.py).
|
|
84
|
+
|
|
85
|
+
## Examples
|
|
86
|
+
|
|
87
|
+
[`examples/`](examples/): `quickstart.py` (the smallest version of the above) ·
|
|
88
|
+
`robot_step_producer.py` (a full dual-arm producer with config negotiation) ·
|
|
89
|
+
`policy_action_consumer.py` (reading a policy back out). Run with
|
|
90
|
+
`LOOP_ADDR=host:port python examples/<file>.py`.
|
|
91
|
+
|
|
92
|
+
## Lower level
|
|
93
|
+
|
|
94
|
+
`RobotStepSender` is robot-only and opinionated. For per-channel metadata
|
|
95
|
+
(`role` / `unit` / `rot_type`) or non-robot source kinds (tactile, marker), use
|
|
96
|
+
`loop_sdk.SourceProducer` / `SourceConsumer` directly — the high-level sender is
|
|
97
|
+
built on them. **Developing & releasing:** [`CONTRIBUTING.md`](CONTRIBUTING.md).
|
|
98
|
+
Internals & specs: [`docs/`](docs/).
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
<a id="한국어"></a>
|
|
103
|
+
|
|
104
|
+
# loop-sdk (한국어)
|
|
105
|
+
|
|
106
|
+
[English](#loop-sdk) · [한국어](#한국어)
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
pip install loop-sdk # Python ≥ 3.10
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
로봇을 **Loop**에 연결하는 Python 클라이언트입니다.
|
|
113
|
+
|
|
114
|
+
Loop는 로봇의 실행을 에피소드로 **녹화**하고, 학습된 정책을 로봇에 **배포**하는 플랫폼입니다.
|
|
115
|
+
이 SDK는 그 연결의 로봇 쪽 — SDK가 알아서 관리하는 단일 gRPC 연결(핸드셰이크·재시도·재연결·
|
|
116
|
+
백프레셔) — 이고, 두 가지를 합니다:
|
|
117
|
+
|
|
118
|
+
- **로봇 녹화.** 매 tick의 상태·액션을 named 채널로 Loop에 스트리밍합니다. operator가 Loop
|
|
119
|
+
UI에서 녹화를 누르면 그 실행이 에피소드로 저장됩니다 — 너는 계속 스트리밍만 하면 되고,
|
|
120
|
+
녹화 자체를 관리하지 않습니다.
|
|
121
|
+
- **Loop가 호스팅한 정책 실행.** 자체 추론 서버를 띄우는 대신, 배포(deployment)의 액션
|
|
122
|
+
스트림을 구독해 그 액션을 실행합니다.
|
|
123
|
+
|
|
124
|
+
대부분 녹화부터 시작합니다. 클래스 하나 — `RobotStepSender` — 와 세 번의 호출:
|
|
125
|
+
`connect`, 매 제어 tick `send`, `disconnect`.
|
|
126
|
+
|
|
127
|
+
## 로봇 스트리밍
|
|
128
|
+
|
|
129
|
+
```python
|
|
130
|
+
import time
|
|
131
|
+
|
|
132
|
+
from loop_sdk import RobotStepSender, RobotConfig, RobotConfigOptions
|
|
133
|
+
|
|
134
|
+
control_hz = 20 # 제어율; 아래에서 Loop가 협상하면 갱신됨
|
|
135
|
+
|
|
136
|
+
# Open 시 Loop가 `options` 중 하나를 고름 → 로봇에 적용하고 확인 반환.
|
|
137
|
+
# (robot-control-interface는 이를 env.control_hz에 대입.)
|
|
138
|
+
def apply_robot_config(cfg: RobotConfig) -> RobotConfig | None:
|
|
139
|
+
global control_hz
|
|
140
|
+
control_hz = cfg.control_hz
|
|
141
|
+
return cfg
|
|
142
|
+
|
|
143
|
+
# loop_addr 기본값 = LOOP_ADDR 환경변수(없으면 localhost:50051), source_id 기본값 = "robot-step".
|
|
144
|
+
# 열 수 있는 config들을 광고.
|
|
145
|
+
sender = RobotStepSender(
|
|
146
|
+
options=RobotConfigOptions(control_hz=(10, 20, 30), action_space=("target_cartesian_delta",)),
|
|
147
|
+
apply_config=apply_robot_config,
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
sender.connect()
|
|
151
|
+
while running: # 네 제어 루프
|
|
152
|
+
t_capture_us = time.time_ns() // 1_000 # 캡처 시각, epoch 마이크로초
|
|
153
|
+
# 중첩 dict → SDK가 점-키로 평탄화. 첫 send가 레이아웃을 고정하므로 첫 프레임은 완전해야.
|
|
154
|
+
# 숫자 리스트는 벡터 채널 하나(robot0.action.joint_position); per-index 스칼라 채널을
|
|
155
|
+
# 원하면 key[0], key[1], … 를 직접 만들어 넣을 것.
|
|
156
|
+
sender.send(t_capture_us, {
|
|
157
|
+
"robot0": {
|
|
158
|
+
"observation": {"robot_state": {"joint_positions": [0.12, -0.34, 1.57], "gripper": 0.80}},
|
|
159
|
+
"action": {"joint_position": [0.13, -0.33, 1.55]},
|
|
160
|
+
"policy_action": [0.01, -0.02, 0.00], # teleop이 몰면 None
|
|
161
|
+
},
|
|
162
|
+
})
|
|
163
|
+
wait_for_next_tick(control_hz) # 네 루프의 rate control — send는 페이싱 안 함
|
|
164
|
+
sender.disconnect() # 종료 시에만 (일시정지엔 금지)
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
알아두면 좋은 것:
|
|
168
|
+
|
|
169
|
+
- **채널 이름은 네가 짓는다.** 중첩 dict을 보내면 SDK가 점-키(`robot0.observation...`)로
|
|
170
|
+
평탄화합니다. **첫 `send`가 레이아웃을 고정**하므로, 앞으로 보낼 모든 채널을 첫 프레임에
|
|
171
|
+
넣으세요 — 이번엔 값이 없는 채널은 `None`으로.
|
|
172
|
+
- **녹화는 operator가 한다.** 너는 계속 스트리밍만, Loop가 지시받을 때 캡처합니다. `send`는
|
|
173
|
+
블로킹하지 않고 루프를 페이싱하지도 않습니다 — 케이던스는 네 루프 몫.
|
|
174
|
+
- **Config 협상 (로봇이 여러 config로 열릴 수 있을 때).** 가능한 선택지를 `options`로 광고하면,
|
|
175
|
+
연결 시 Loop가 하나를 고르고 SDK가 그걸 — 네가 광고한 것에 대해 검증한 뒤 — `apply_config`로
|
|
176
|
+
적용합니다(예: 제어율). 협상할 게 없으면 `options`/`apply_config`를 빼면 됩니다. 첫 send
|
|
177
|
+
*전에* config를 확정하고 싶으면 `connect()` 뒤에 `sender.declare(channels)`를 한 번 호출하세요.
|
|
178
|
+
|
|
179
|
+
## Loop 정책 실행
|
|
180
|
+
|
|
181
|
+
스트리밍의 반대 방향: 추론 서버를 호출하는 대신, 배포의 policy-action 소스를 구독해 그 액션을
|
|
182
|
+
실행합니다. [`examples/policy_action_consumer.py`](examples/policy_action_consumer.py) 참고.
|
|
183
|
+
|
|
184
|
+
## 예제
|
|
185
|
+
|
|
186
|
+
[`examples/`](examples/): `quickstart.py`(위 코드의 최소 버전) · `robot_step_producer.py`(전체
|
|
187
|
+
dual-arm 생산자 + config 협상) · `policy_action_consumer.py`(정책 되읽기). 실행:
|
|
188
|
+
`LOOP_ADDR=host:port python examples/<file>.py`.
|
|
189
|
+
|
|
190
|
+
## 저수준
|
|
191
|
+
|
|
192
|
+
`RobotStepSender`는 로봇 전용이고 의견이 강합니다. 채널별 메타(`role`/`unit`/`rot_type`)나
|
|
193
|
+
비로봇 소스 종류(tactile, marker)가 필요하면 `loop_sdk.SourceProducer` / `SourceConsumer`를
|
|
194
|
+
직접 쓰세요 — 고수준 sender가 그 위에 얹혀 있습니다. **개발·배포:** [`CONTRIBUTING.md`](CONTRIBUTING.md).
|
|
195
|
+
내부 구조·스펙: [`docs/`](docs/).
|
|
@@ -260,32 +260,26 @@ enum CameraPixelFormat {
|
|
|
260
260
|
// compatibility-gate fields. proto3 has no `required`; the SDK and foundation
|
|
261
261
|
// enforce presence of the required fields.
|
|
262
262
|
message RobotConfig {
|
|
263
|
-
reserved 7, 8;
|
|
264
|
-
reserved "robot_server_version", "rci_version";
|
|
265
|
-
|
|
266
263
|
int32 control_hz = 1;
|
|
267
264
|
string action_space = 2;
|
|
268
265
|
optional string gripper_type = 3;
|
|
269
266
|
optional string finger_type = 4;
|
|
270
267
|
optional string robot_type = 5;
|
|
271
268
|
optional string robot_firmware_version = 6;
|
|
272
|
-
optional string teleoperation_version =
|
|
269
|
+
optional string teleoperation_version = 7;
|
|
273
270
|
}
|
|
274
271
|
|
|
275
272
|
// Per-axis candidate lists a robot source advertises at Describe time. The
|
|
276
273
|
// server picks one value per axis. control_hz and action_space must carry at
|
|
277
274
|
// least one candidate; the rest may be empty (that axis is not negotiated).
|
|
278
275
|
message RobotConfigOptions {
|
|
279
|
-
reserved 7, 8;
|
|
280
|
-
reserved "robot_server_version", "rci_version";
|
|
281
|
-
|
|
282
276
|
repeated int32 control_hz = 1;
|
|
283
277
|
repeated string action_space = 2;
|
|
284
278
|
repeated string gripper_type = 3;
|
|
285
279
|
repeated string finger_type = 4;
|
|
286
280
|
repeated string robot_type = 5;
|
|
287
281
|
repeated string robot_firmware_version = 6;
|
|
288
|
-
repeated string teleoperation_version =
|
|
282
|
+
repeated string teleoperation_version = 7;
|
|
289
283
|
}
|
|
290
284
|
|
|
291
285
|
// ---------------------------------------------------------------------------
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
"""Generated protocol buffer code."""
|
|
2
|
+
from google.protobuf import descriptor as _descriptor
|
|
3
|
+
from google.protobuf import descriptor_pool as _descriptor_pool
|
|
4
|
+
from google.protobuf import runtime_version as _runtime_version
|
|
5
|
+
from google.protobuf import symbol_database as _symbol_database
|
|
6
|
+
from google.protobuf.internal import builder as _builder
|
|
7
|
+
_runtime_version.ValidateProtobufRuntimeVersion(_runtime_version.Domain.PUBLIC, 7, 35, 1, '', 'v1/source_ingest.proto')
|
|
8
|
+
_sym_db = _symbol_database.Default()
|
|
9
|
+
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x16v1/source_ingest.proto\x12\x19loop.foundation.source.v1"\xaa\x04\n\x0bClientEvent\x12\x1b\n\tclient_id\x18\x01 \x01(\tR\x08clientId\x12\x1d\n\ncommand_id\x18\x02 \x01(\tR\tcommandId\x12>\n\x05ready\x18\n \x01(\x0b2&.loop.foundation.source.v1.ClientReadyH\x00R\x05ready\x12Z\n\x11source_discovered\x18\x0b \x01(\x0b2+.loop.foundation.source.v1.SourceDiscoveredH\x00R\x10sourceDiscovered\x12Q\n\x0escan_completed\x18\x0c \x01(\x0b2(.loop.foundation.source.v1.ScanCompletedH\x00R\rscanCompleted\x12a\n\x14stream_state_changed\x18\r \x01(\x0b2-.loop.foundation.source.v1.StreamStateChangedH\x00R\x12streamStateChanged\x12D\n\theartbeat\x18\x0e \x01(\x0b2$.loop.foundation.source.v1.HeartbeatH\x00R\theartbeat\x12>\n\x05error\x18\x0f \x01(\x0b2&.loop.foundation.source.v1.ClientErrorH\x00R\x05errorB\x07\n\x05event"\xb9\x02\n\x0fRecorderCommand\x12\x1d\n\ncommand_id\x18\x01 \x01(\tR\tcommandId\x12A\n\x08describe\x18\n \x01(\x0b2#.loop.foundation.source.v1.DescribeH\x00R\x08describe\x12<\n\x04open\x18\x0b \x01(\x0b2&.loop.foundation.source.v1.OpenCommandH\x00R\x04open\x128\n\x05close\x18\x0c \x01(\x0b2 .loop.foundation.source.v1.CloseH\x00R\x05close\x12A\n\x08shutdown\x18\r \x01(\x0b2#.loop.foundation.source.v1.ShutdownH\x00R\x08shutdownB\t\n\x07command"\'\n\x0bClientReady\x12\x18\n\x07version\x18\x01 \x01(\tR\x07version"\n\n\x08Describe"\x8c\x03\n\x0bOpenCommand\x12\x1d\n\nsession_id\x18\x01 \x01(\tR\tsessionId\x12\x19\n\x08start_us\x18\x02 \x01(\x03R\x07startUs\x12\x1d\n\nsource_ids\x18\x03 \x03(\tR\tsourceIds\x12H\n\x07tactile\x18\n \x01(\x0b2,.loop.foundation.source.v1.TactileOpenParamsH\x00R\x07tactile\x12B\n\x05robot\x18\x0b \x01(\x0b2*.loop.foundation.source.v1.RobotOpenParamsH\x00R\x05robot\x12E\n\x06marker\x18\x0c \x01(\x0b2+.loop.foundation.source.v1.MarkerOpenParamsH\x00R\x06marker\x12E\n\x06camera\x18\r \x01(\x0b2+.loop.foundation.source.v1.CameraOpenParamsH\x00R\x06cameraB\x08\n\x06params"^\n\x05Close\x12\x1d\n\nsession_id\x18\x01 \x01(\tR\tsessionId\x12\x17\n\x07stop_us\x18\x02 \x01(\x03R\x06stopUs\x12\x1d\n\nsource_ids\x18\x03 \x03(\tR\tsourceIds""\n\x08Shutdown\x12\x16\n\x06reason\x18\x01 \x01(\tR\x06reason"z\n\rScanCompleted\x12)\n\x10discovered_count\x18\x01 \x01(\x05R\x0fdiscoveredCount\x12>\n\x06errors\x18\x02 \x03(\x0b2&.loop.foundation.source.v1.ClientErrorR\x06errors".\n\tHeartbeat\x12!\n\x0cmonotonic_us\x18\x01 \x01(\x03R\x0bmonotonicUs"X\n\x0bClientError\x12\x12\n\x04code\x18\x01 \x01(\tR\x04code\x12\x18\n\x07message\x18\x02 \x01(\tR\x07message\x12\x1b\n\tsource_id\x18\x03 \x01(\tR\x08sourceId"\xf1\x01\n\x12StreamStateChanged\x12\x1d\n\nsession_id\x18\x01 \x01(\tR\tsessionId\x12\x1b\n\tsource_id\x18\x02 \x01(\tR\x08sourceId\x12<\n\x05state\x18\x03 \x01(\x0e2&.loop.foundation.source.v1.StreamStateR\x05state\x12\x16\n\x06detail\x18\x04 \x01(\tR\x06detail\x12>\n\x05robot\x18\x05 \x01(\x0b2&.loop.foundation.source.v1.RobotConfigH\x00R\x05robotB\t\n\x07applied"\x99\x03\n\x10SourceDiscovered\x12\x1b\n\tsource_id\x18\x01 \x01(\tR\x08sourceId\x12\x12\n\x04name\x18\x02 \x01(\tR\x04name\x12\x12\n\x04path\x18\x03 \x01(\tR\x04path\x12N\n\x07tactile\x18\n \x01(\x0b22.loop.foundation.source.v1.TactileSourceDescriptorH\x00R\x07tactile\x12H\n\x05robot\x18\x0b \x01(\x0b20.loop.foundation.source.v1.RobotSourceDescriptorH\x00R\x05robot\x12K\n\x06marker\x18\x0c \x01(\x0b21.loop.foundation.source.v1.MarkerSourceDescriptorH\x00R\x06marker\x12K\n\x06camera\x18\r \x01(\x0b21.loop.foundation.source.v1.CameraSourceDescriptorH\x00R\x06cameraB\x0c\n\ndescriptor"j\n\x17TactileSourceDescriptor\x12O\n\x08channels\x18\x01 \x03(\x0b23.loop.foundation.source.v1.TactileChannelDescriptorR\x08channels"\x8a\x01\n\x18TactileChannelDescriptor\x12\x10\n\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n\x05label\x18\x02 \x01(\tR\x05label\x12#\n\rsample_format\x18\x03 \x01(\tR\x0csampleFormat\x12!\n\x0csample_count\x18\x04 \x01(\x05R\x0bsampleCount"\xc2\x01\n\x15RobotSourceDescriptor\x12M\n\x08channels\x18\x01 \x03(\x0b21.loop.foundation.source.v1.RobotChannelDescriptorR\x08channels\x12Z\n\x11available_options\x18\x02 \x01(\x0b2-.loop.foundation.source.v1.RobotConfigOptionsR\x10availableOptions"\x85\x02\n\x16RobotChannelDescriptor\x12\x10\n\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n\x05label\x18\x02 \x01(\tR\x05label\x12:\n\x04role\x18\x03 \x01(\x0e2&.loop.foundation.source.v1.ChannelRoleR\x04role\x12\x12\n\x04unit\x18\x04 \x01(\tR\x04unit\x12G\n\x08rot_type\x18\x05 \x01(\x0e2,.loop.foundation.source.v1.RobotRotationTypeR\x07rotType\x12\x14\n\x05group\x18\x06 \x01(\tR\x05group\x12\x14\n\x05range\x18\x07 \x03(\x01R\x05range"\x18\n\x16MarkerSourceDescriptor"\xb4\x01\n\x16CameraSourceDescriptor\x12A\n\x04rtsp\x18\x01 \x01(\x0b2-.loop.foundation.source.v1.RtspCameraEndpointR\x04rtsp\x12W\n\x12available_settings\x18\x02 \x03(\x0b2(.loop.foundation.source.v1.CameraSettingR\x11availableSettings"n\n\x12RtspCameraEndpoint\x12\x10\n\x03uri\x18\x01 \x01(\tR\x03uri\x12F\n\ttransport\x18\x02 \x01(\x0e2(.loop.foundation.source.v1.RtspTransportR\ttransport"\x8d\x02\n\rCameraSetting\x12K\n\nresolution\x18\x01 \x01(\x0b2+.loop.foundation.source.v1.CameraResolutionR\nresolution\x12\x10\n\x03fps\x18\x02 \x01(\x01R\x03fps\x12O\n\x0cpixel_format\x18\x03 \x01(\x0e2,.loop.foundation.source.v1.CameraPixelFormatR\x0bpixelFormat\x12\'\n\x0fhorizontal_flip\x18\x04 \x01(\x08R\x0ehorizontalFlip\x12#\n\rvertical_flip\x18\x05 \x01(\x08R\x0cverticalFlip"@\n\x10CameraResolution\x12\x14\n\x05width\x18\x01 \x01(\x05R\x05width\x12\x16\n\x06height\x18\x02 \x01(\x05R\x06height"\x9b\x03\n\x0bRobotConfig\x12\x1d\n\ncontrol_hz\x18\x01 \x01(\x05R\tcontrolHz\x12!\n\x0caction_space\x18\x02 \x01(\tR\x0bactionSpace\x12&\n\x0cgripper_type\x18\x03 \x01(\tH\x00R\x0bgripperType\x88\x01\x01\x12$\n\x0bfinger_type\x18\x04 \x01(\tH\x01R\nfingerType\x88\x01\x01\x12"\n\nrobot_type\x18\x05 \x01(\tH\x02R\trobotType\x88\x01\x01\x129\n\x16robot_firmware_version\x18\x06 \x01(\tH\x03R\x14robotFirmwareVersion\x88\x01\x01\x128\n\x15teleoperation_version\x18\x07 \x01(\tH\x04R\x14teleoperationVersion\x88\x01\x01B\x0f\n\r_gripper_typeB\x0e\n\x0c_finger_typeB\r\n\x0b_robot_typeB\x19\n\x17_robot_firmware_versionB\x18\n\x16_teleoperation_version"\xa4\x02\n\x12RobotConfigOptions\x12\x1d\n\ncontrol_hz\x18\x01 \x03(\x05R\tcontrolHz\x12!\n\x0caction_space\x18\x02 \x03(\tR\x0bactionSpace\x12!\n\x0cgripper_type\x18\x03 \x03(\tR\x0bgripperType\x12\x1f\n\x0bfinger_type\x18\x04 \x03(\tR\nfingerType\x12\x1d\n\nrobot_type\x18\x05 \x03(\tR\trobotType\x124\n\x16robot_firmware_version\x18\x06 \x03(\tR\x14robotFirmwareVersion\x123\n\x15teleoperation_version\x18\x07 \x03(\tR\x14teleoperationVersion"\x13\n\x11TactileOpenParams"d\n\x0fRobotOpenParams\x12Q\n\x10requested_config\x18\x01 \x01(\x0b2&.loop.foundation.source.v1.RobotConfigR\x0frequestedConfig"\x12\n\x10MarkerOpenParams"i\n\x10CameraOpenParams\x12U\n\x11requested_setting\x18\x01 \x01(\x0b2(.loop.foundation.source.v1.CameraSettingR\x10requestedSetting"\x86\x01\n\x0bSampleBatch\x12\x1d\n\nsession_id\x18\x01 \x01(\tR\tsessionId\x12\x1b\n\tsource_id\x18\x02 \x01(\tR\x08sourceId\x12;\n\x07samples\x18\x03 \x03(\x0b2!.loop.foundation.source.v1.SampleR\x07samples"\xf5\x02\n\x06Sample\x12!\n\x0ctimestamp_us\x18\x01 \x01(\x03R\x0btimestampUs\x12\x1a\n\x08sequence\x18\x02 \x01(\x04R\x08sequence\x12E\n\x07tactile\x18\n \x01(\x0b2).loop.foundation.source.v1.TactilePayloadH\x00R\x07tactile\x12?\n\x05robot\x18\x0b \x01(\x0b2\'.loop.foundation.source.v1.RobotPayloadH\x00R\x05robot\x12B\n\x06marker\x18\x0c \x01(\x0b2(.loop.foundation.source.v1.MarkerPayloadH\x00R\x06marker\x12U\n\rpolicy_action\x18\r \x01(\x0b2..loop.foundation.source.v1.PolicyActionPayloadH\x00R\x0cpolicyActionB\t\n\x07payload"]\n\x0eTactilePayload\x12K\n\x08channels\x18\x01 \x03(\x0b2/.loop.foundation.source.v1.TactileChannelSampleR\x08channels"Y\n\x14TactileChannelSample\x12\x10\n\x03key\x18\x01 \x01(\tR\x03key\x12\x10\n\x03raw\x18\x02 \x01(\x0cR\x03raw\x12\x1d\n\nvalues_i16\x18\x03 \x03(\x05R\tvaluesI16"\xc7\x01\n\x0cRobotPayload\x12H\n\x05state\x18\x02 \x03(\x0b22.loop.foundation.source.v1.RobotPayload.StateEntryR\x05state\x1a_\n\nStateEntry\x12\x10\n\x03key\x18\x01 \x01(\tR\x03key\x12;\n\x05value\x18\x02 \x01(\x0b2%.loop.foundation.source.v1.RobotValueR\x05value:\x028\x01J\x04\x08\x01\x10\x02R\x06values"\xd4\x01\n\nRobotValue\x12\x18\n\x06scalar\x18\x01 \x01(\x01H\x00R\x06scalar\x12>\n\x05array\x18\x02 \x01(\x0b2&.loop.foundation.source.v1.DoubleArrayH\x00R\x05array\x12\x1d\n\tint_value\x18\x03 \x01(\x03H\x00R\x08intValue\x12\x1f\n\nbool_value\x18\x04 \x01(\x08H\x00R\tboolValue\x12#\n\x0cstring_value\x18\x05 \x01(\tH\x00R\x0bstringValueB\x07\n\x05value"%\n\x0bDoubleArray\x12\x16\n\x06values\x18\x01 \x03(\x01R\x06values"\x9c\x02\n\x13PolicyActionPayload\x12G\n\x07actions\x18\x01 \x03(\x0b2-.loop.foundation.source.v1.PolicyActionVectorR\x07actions\x12?\n\x1ainput_sample_set_source_id\x18\x02 \x01(\tH\x00R\x16inputSampleSetSourceId\x88\x01\x01\x12>\n\x19input_sample_set_sequence\x18\x03 \x01(\x03H\x01R\x16inputSampleSetSequence\x88\x01\x01B\x1d\n\x1b_input_sample_set_source_idB\x1c\n\x1a_input_sample_set_sequence",\n\x12PolicyActionVector\x12\x16\n\x06values\x18\x01 \x03(\x01R\x06values"A\n\rMarkerPayload\x12\x14\n\x05label\x18\x01 \x01(\tR\x05label\x12\x1a\n\x08category\x18\x02 \x01(\tR\x08category"a\n\x15StreamSamplesResponse\x12\x1d\n\nsession_id\x18\x01 \x01(\tR\tsessionId\x12)\n\x10accepted_samples\x18\x02 \x01(\x04R\x0facceptedSamples"N\n\x10SubscribeRequest\x12\x1b\n\tsource_id\x18\x01 \x01(\tR\x08sourceId\x12\x1d\n\nsession_id\x18\x02 \x01(\tR\tsessionId"\x9d\x01\n\x0eSubscribeEvent\x12;\n\x06sample\x18\x01 \x01(\x0b2!.loop.foundation.source.v1.SampleH\x00R\x06sample\x12E\n\x05state\x18\x02 \x01(\x0b2-.loop.foundation.source.v1.StreamStateChangedH\x00R\x05stateB\x07\n\x05event*x\n\x0bStreamState\x12\x1c\n\x18STREAM_STATE_UNSPECIFIED\x10\x00\x12\x18\n\x14STREAM_STATE_STARTED\x10\x01\x12\x18\n\x14STREAM_STATE_STOPPED\x10\x02\x12\x17\n\x13STREAM_STATE_FAILED\x10\x03*X\n\x0bChannelRole\x12\x1c\n\x18CHANNEL_ROLE_UNSPECIFIED\x10\x00\x12\x15\n\x11CHANNEL_ROLE_CORE\x10\x01\x12\x14\n\x10CHANNEL_ROLE_AUX\x10\x02*\xa0\x01\n\x11RobotRotationType\x12#\n\x1fROBOT_ROTATION_TYPE_UNSPECIFIED\x10\x00\x12"\n\x1eROBOT_ROTATION_TYPE_QUATERNION\x10\x01\x12\x1d\n\x19ROBOT_ROTATION_TYPE_EULER\x10\x02\x12#\n\x1fROBOT_ROTATION_TYPE_ROTATION_6D\x10\x03*_\n\rRtspTransport\x12\x1e\n\x1aRTSP_TRANSPORT_UNSPECIFIED\x10\x00\x12\x16\n\x12RTSP_TRANSPORT_TCP\x10\x01\x12\x16\n\x12RTSP_TRANSPORT_UDP\x10\x02*\x90\x02\n\x11CameraPixelFormat\x12#\n\x1fCAMERA_PIXEL_FORMAT_UNSPECIFIED\x10\x00\x12\x1d\n\x19CAMERA_PIXEL_FORMAT_MJPEG\x10\x01\x12\x1c\n\x18CAMERA_PIXEL_FORMAT_YUYV\x10\x02\x12\x1c\n\x18CAMERA_PIXEL_FORMAT_UYVY\x10\x03\x12\x1d\n\x19CAMERA_PIXEL_FORMAT_RGB24\x10\x04\x12\x1d\n\x19CAMERA_PIXEL_FORMAT_BGR24\x10\x05\x12\x1c\n\x18CAMERA_PIXEL_FORMAT_H264\x10\x06\x12\x1f\n\x1bCAMERA_PIXEL_FORMAT_UNKNOWN\x10\x072\xe5\x01\n\x13SourceIngestService\x12a\n\x07Connect\x12&.loop.foundation.source.v1.ClientEvent\x1a*.loop.foundation.source.v1.RecorderCommand(\x010\x01\x12k\n\rStreamSamples\x12&.loop.foundation.source.v1.SampleBatch\x1a0.loop.foundation.source.v1.StreamSamplesResponse(\x012z\n\x11SourceReadService\x12e\n\tSubscribe\x12+.loop.foundation.source.v1.SubscribeRequest\x1a).loop.foundation.source.v1.SubscribeEvent0\x01b\x06proto3')
|
|
10
|
+
_globals = globals()
|
|
11
|
+
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
|
|
12
|
+
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'v1.source_ingest_pb2', _globals)
|
|
13
|
+
if not _descriptor._USE_C_DESCRIPTORS:
|
|
14
|
+
DESCRIPTOR._loaded_options = None
|
|
15
|
+
_globals['_ROBOTPAYLOAD_STATEENTRY']._loaded_options = None
|
|
16
|
+
_globals['_ROBOTPAYLOAD_STATEENTRY']._serialized_options = b'8\x01'
|
|
17
|
+
_globals['_STREAMSTATE']._serialized_start = 6650
|
|
18
|
+
_globals['_STREAMSTATE']._serialized_end = 6770
|
|
19
|
+
_globals['_CHANNELROLE']._serialized_start = 6772
|
|
20
|
+
_globals['_CHANNELROLE']._serialized_end = 6860
|
|
21
|
+
_globals['_ROBOTROTATIONTYPE']._serialized_start = 6863
|
|
22
|
+
_globals['_ROBOTROTATIONTYPE']._serialized_end = 7023
|
|
23
|
+
_globals['_RTSPTRANSPORT']._serialized_start = 7025
|
|
24
|
+
_globals['_RTSPTRANSPORT']._serialized_end = 7120
|
|
25
|
+
_globals['_CAMERAPIXELFORMAT']._serialized_start = 7123
|
|
26
|
+
_globals['_CAMERAPIXELFORMAT']._serialized_end = 7395
|
|
27
|
+
_globals['_CLIENTEVENT']._serialized_start = 54
|
|
28
|
+
_globals['_CLIENTEVENT']._serialized_end = 608
|
|
29
|
+
_globals['_RECORDERCOMMAND']._serialized_start = 611
|
|
30
|
+
_globals['_RECORDERCOMMAND']._serialized_end = 924
|
|
31
|
+
_globals['_CLIENTREADY']._serialized_start = 926
|
|
32
|
+
_globals['_CLIENTREADY']._serialized_end = 965
|
|
33
|
+
_globals['_DESCRIBE']._serialized_start = 967
|
|
34
|
+
_globals['_DESCRIBE']._serialized_end = 977
|
|
35
|
+
_globals['_OPENCOMMAND']._serialized_start = 980
|
|
36
|
+
_globals['_OPENCOMMAND']._serialized_end = 1376
|
|
37
|
+
_globals['_CLOSE']._serialized_start = 1378
|
|
38
|
+
_globals['_CLOSE']._serialized_end = 1472
|
|
39
|
+
_globals['_SHUTDOWN']._serialized_start = 1474
|
|
40
|
+
_globals['_SHUTDOWN']._serialized_end = 1508
|
|
41
|
+
_globals['_SCANCOMPLETED']._serialized_start = 1510
|
|
42
|
+
_globals['_SCANCOMPLETED']._serialized_end = 1632
|
|
43
|
+
_globals['_HEARTBEAT']._serialized_start = 1634
|
|
44
|
+
_globals['_HEARTBEAT']._serialized_end = 1680
|
|
45
|
+
_globals['_CLIENTERROR']._serialized_start = 1682
|
|
46
|
+
_globals['_CLIENTERROR']._serialized_end = 1770
|
|
47
|
+
_globals['_STREAMSTATECHANGED']._serialized_start = 1773
|
|
48
|
+
_globals['_STREAMSTATECHANGED']._serialized_end = 2014
|
|
49
|
+
_globals['_SOURCEDISCOVERED']._serialized_start = 2017
|
|
50
|
+
_globals['_SOURCEDISCOVERED']._serialized_end = 2426
|
|
51
|
+
_globals['_TACTILESOURCEDESCRIPTOR']._serialized_start = 2428
|
|
52
|
+
_globals['_TACTILESOURCEDESCRIPTOR']._serialized_end = 2534
|
|
53
|
+
_globals['_TACTILECHANNELDESCRIPTOR']._serialized_start = 2537
|
|
54
|
+
_globals['_TACTILECHANNELDESCRIPTOR']._serialized_end = 2675
|
|
55
|
+
_globals['_ROBOTSOURCEDESCRIPTOR']._serialized_start = 2678
|
|
56
|
+
_globals['_ROBOTSOURCEDESCRIPTOR']._serialized_end = 2872
|
|
57
|
+
_globals['_ROBOTCHANNELDESCRIPTOR']._serialized_start = 2875
|
|
58
|
+
_globals['_ROBOTCHANNELDESCRIPTOR']._serialized_end = 3136
|
|
59
|
+
_globals['_MARKERSOURCEDESCRIPTOR']._serialized_start = 3138
|
|
60
|
+
_globals['_MARKERSOURCEDESCRIPTOR']._serialized_end = 3162
|
|
61
|
+
_globals['_CAMERASOURCEDESCRIPTOR']._serialized_start = 3165
|
|
62
|
+
_globals['_CAMERASOURCEDESCRIPTOR']._serialized_end = 3345
|
|
63
|
+
_globals['_RTSPCAMERAENDPOINT']._serialized_start = 3347
|
|
64
|
+
_globals['_RTSPCAMERAENDPOINT']._serialized_end = 3457
|
|
65
|
+
_globals['_CAMERASETTING']._serialized_start = 3460
|
|
66
|
+
_globals['_CAMERASETTING']._serialized_end = 3729
|
|
67
|
+
_globals['_CAMERARESOLUTION']._serialized_start = 3731
|
|
68
|
+
_globals['_CAMERARESOLUTION']._serialized_end = 3795
|
|
69
|
+
_globals['_ROBOTCONFIG']._serialized_start = 3798
|
|
70
|
+
_globals['_ROBOTCONFIG']._serialized_end = 4209
|
|
71
|
+
_globals['_ROBOTCONFIGOPTIONS']._serialized_start = 4212
|
|
72
|
+
_globals['_ROBOTCONFIGOPTIONS']._serialized_end = 4504
|
|
73
|
+
_globals['_TACTILEOPENPARAMS']._serialized_start = 4506
|
|
74
|
+
_globals['_TACTILEOPENPARAMS']._serialized_end = 4525
|
|
75
|
+
_globals['_ROBOTOPENPARAMS']._serialized_start = 4527
|
|
76
|
+
_globals['_ROBOTOPENPARAMS']._serialized_end = 4627
|
|
77
|
+
_globals['_MARKEROPENPARAMS']._serialized_start = 4629
|
|
78
|
+
_globals['_MARKEROPENPARAMS']._serialized_end = 4647
|
|
79
|
+
_globals['_CAMERAOPENPARAMS']._serialized_start = 4649
|
|
80
|
+
_globals['_CAMERAOPENPARAMS']._serialized_end = 4754
|
|
81
|
+
_globals['_SAMPLEBATCH']._serialized_start = 4757
|
|
82
|
+
_globals['_SAMPLEBATCH']._serialized_end = 4891
|
|
83
|
+
_globals['_SAMPLE']._serialized_start = 4894
|
|
84
|
+
_globals['_SAMPLE']._serialized_end = 5267
|
|
85
|
+
_globals['_TACTILEPAYLOAD']._serialized_start = 5269
|
|
86
|
+
_globals['_TACTILEPAYLOAD']._serialized_end = 5362
|
|
87
|
+
_globals['_TACTILECHANNELSAMPLE']._serialized_start = 5364
|
|
88
|
+
_globals['_TACTILECHANNELSAMPLE']._serialized_end = 5453
|
|
89
|
+
_globals['_ROBOTPAYLOAD']._serialized_start = 5456
|
|
90
|
+
_globals['_ROBOTPAYLOAD']._serialized_end = 5655
|
|
91
|
+
_globals['_ROBOTPAYLOAD_STATEENTRY']._serialized_start = 5546
|
|
92
|
+
_globals['_ROBOTPAYLOAD_STATEENTRY']._serialized_end = 5641
|
|
93
|
+
_globals['_ROBOTVALUE']._serialized_start = 5658
|
|
94
|
+
_globals['_ROBOTVALUE']._serialized_end = 5870
|
|
95
|
+
_globals['_DOUBLEARRAY']._serialized_start = 5872
|
|
96
|
+
_globals['_DOUBLEARRAY']._serialized_end = 5909
|
|
97
|
+
_globals['_POLICYACTIONPAYLOAD']._serialized_start = 5912
|
|
98
|
+
_globals['_POLICYACTIONPAYLOAD']._serialized_end = 6196
|
|
99
|
+
_globals['_POLICYACTIONVECTOR']._serialized_start = 6198
|
|
100
|
+
_globals['_POLICYACTIONVECTOR']._serialized_end = 6242
|
|
101
|
+
_globals['_MARKERPAYLOAD']._serialized_start = 6244
|
|
102
|
+
_globals['_MARKERPAYLOAD']._serialized_end = 6309
|
|
103
|
+
_globals['_STREAMSAMPLESRESPONSE']._serialized_start = 6311
|
|
104
|
+
_globals['_STREAMSAMPLESRESPONSE']._serialized_end = 6408
|
|
105
|
+
_globals['_SUBSCRIBEREQUEST']._serialized_start = 6410
|
|
106
|
+
_globals['_SUBSCRIBEREQUEST']._serialized_end = 6488
|
|
107
|
+
_globals['_SUBSCRIBEEVENT']._serialized_start = 6491
|
|
108
|
+
_globals['_SUBSCRIBEEVENT']._serialized_end = 6648
|
|
109
|
+
_globals['_SOURCEINGESTSERVICE']._serialized_start = 7398
|
|
110
|
+
_globals['_SOURCEINGESTSERVICE']._serialized_end = 7627
|
|
111
|
+
_globals['_SOURCEREADSERVICE']._serialized_start = 7629
|
|
112
|
+
_globals['_SOURCEREADSERVICE']._serialized_end = 7751
|