robotrt-sdk 0.1.0b1__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.
- robotrt_sdk-0.1.0b1/PKG-INFO +112 -0
- robotrt_sdk-0.1.0b1/README.md +99 -0
- robotrt_sdk-0.1.0b1/pyproject.toml +25 -0
- robotrt_sdk-0.1.0b1/robotrt_sdk/__init__.py +67 -0
- robotrt_sdk-0.1.0b1/robotrt_sdk/_ffi.py +20 -0
- robotrt_sdk-0.1.0b1/robotrt_sdk/_ffi_loader.py +82 -0
- robotrt_sdk-0.1.0b1/robotrt_sdk/_ffi_signatures.py +359 -0
- robotrt_sdk-0.1.0b1/robotrt_sdk/_ffi_types.py +57 -0
- robotrt_sdk-0.1.0b1/robotrt_sdk/_version.py +1 -0
- robotrt_sdk-0.1.0b1/robotrt_sdk/action.py +250 -0
- robotrt_sdk-0.1.0b1/robotrt_sdk/buffer.py +116 -0
- robotrt_sdk-0.1.0b1/robotrt_sdk/core.py +45 -0
- robotrt_sdk-0.1.0b1/robotrt_sdk/errors.py +20 -0
- robotrt_sdk-0.1.0b1/robotrt_sdk/executor.py +610 -0
- robotrt_sdk-0.1.0b1/robotrt_sdk/mission.py +102 -0
- robotrt_sdk-0.1.0b1/robotrt_sdk/models.py +39 -0
- robotrt_sdk-0.1.0b1/robotrt_sdk/node.py +206 -0
- robotrt_sdk-0.1.0b1/robotrt_sdk/pubsub.py +159 -0
- robotrt_sdk-0.1.0b1/robotrt_sdk/service.py +139 -0
- robotrt_sdk-0.1.0b1/robotrt_sdk/status.py +509 -0
- robotrt_sdk-0.1.0b1/robotrt_sdk/utils.py +36 -0
- robotrt_sdk-0.1.0b1/robotrt_sdk.egg-info/PKG-INFO +112 -0
- robotrt_sdk-0.1.0b1/robotrt_sdk.egg-info/SOURCES.txt +29 -0
- robotrt_sdk-0.1.0b1/robotrt_sdk.egg-info/dependency_links.txt +1 -0
- robotrt_sdk-0.1.0b1/robotrt_sdk.egg-info/top_level.txt +1 -0
- robotrt_sdk-0.1.0b1/setup.cfg +4 -0
- robotrt_sdk-0.1.0b1/tests/test_data_plane_batch.py +109 -0
- robotrt_sdk-0.1.0b1/tests/test_embedded_zero_copy.py +90 -0
- robotrt_sdk-0.1.0b1/tests/test_executor.py +166 -0
- robotrt_sdk-0.1.0b1/tests/test_full_comms.py +136 -0
- robotrt_sdk-0.1.0b1/tests/test_status_mode_parity.py +123 -0
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: robotrt-sdk
|
|
3
|
+
Version: 0.1.0b1
|
|
4
|
+
Summary: RobotRT Python SDK (FFI thin wrapper)
|
|
5
|
+
Author: RobotRT Maintainers
|
|
6
|
+
License-Expression: Apache-2.0
|
|
7
|
+
Classifier: Development Status :: 4 - Beta
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Requires-Python: >=3.10
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
|
|
14
|
+
# RobotRT Python SDK (Embedded + Daemon)
|
|
15
|
+
|
|
16
|
+
This directory contains the Python-first SDK implementation on top of the frozen Core FFI ABI.
|
|
17
|
+
|
|
18
|
+
## Scope
|
|
19
|
+
|
|
20
|
+
- Embedded mode SDK surface for node and buffer lease lifecycle.
|
|
21
|
+
- Embedded mode full communication APIs: pub/sub, service, action, mission.
|
|
22
|
+
- Embedded mode executor APIs: SimpleExecutor, CallbackExecutor, and MultiThreadExecutor.
|
|
23
|
+
- Daemon mode status SDK surface for snapshot/list/info/watch query.
|
|
24
|
+
- Unified error mapping from C ABI status and error payload.
|
|
25
|
+
- Zero-copy readable view via borrowed lease pointer to Python `memoryview`.
|
|
26
|
+
|
|
27
|
+
## Embedded Communication API (MVP)
|
|
28
|
+
|
|
29
|
+
```python
|
|
30
|
+
import robotrt_sdk as sdk
|
|
31
|
+
|
|
32
|
+
with sdk.Node.create() as node:
|
|
33
|
+
pub = node.create_publisher("/demo/topic")
|
|
34
|
+
sub = node.create_subscriber("/demo/topic")
|
|
35
|
+
pub.publish_batch([b"hello", b"robotrt", b"batch"])
|
|
36
|
+
print(sub.recv_batch(8))
|
|
37
|
+
print("backpressure:", sub.backpressure_signal().value)
|
|
38
|
+
|
|
39
|
+
server = node.create_service_server("/demo/service")
|
|
40
|
+
client = node.create_service_client("/demo/service")
|
|
41
|
+
request_id = client.call(b"ping")
|
|
42
|
+
rid, req = server.recv_request()
|
|
43
|
+
server.reply(rid, b"pong")
|
|
44
|
+
print(client.poll_response(request_id))
|
|
45
|
+
|
|
46
|
+
action_server = node.create_action_server("/demo/action")
|
|
47
|
+
action_client = node.create_action_client("/demo/action")
|
|
48
|
+
goal_id, accepted = action_client.send_goal(b"goal")
|
|
49
|
+
gid, goal = action_server.recv_goal()
|
|
50
|
+
action_server.accept_goal(gid)
|
|
51
|
+
action_server.publish_feedback(gid, b"50%")
|
|
52
|
+
action_server.succeed(gid, b"done")
|
|
53
|
+
print(action_client.poll_result(goal_id))
|
|
54
|
+
|
|
55
|
+
mission = node.create_mission_session(42)
|
|
56
|
+
mission.open("demo_mission")
|
|
57
|
+
mission.send_command("start:demo")
|
|
58
|
+
mission.reconcile("cp-1", 2)
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Quick Start
|
|
62
|
+
|
|
63
|
+
1. Build the shared library:
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
cargo build -p core-ffi --lib
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
2. Run tests:
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
PYTHONPATH=bindings/python python3 -m unittest discover -s bindings/python/tests -p 'test_*.py'
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
3. Run the embedded zero-copy demo:
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
PYTHONPATH=bindings/python python3 bindings/python/examples/embedded_zero_copy_demo.py
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
4. Run the daemon status demo:
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
PYTHONPATH=bindings/python python3 bindings/python/examples/daemon_status_demo.py
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
5. Run additional examples:
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
PYTHONPATH=bindings/python python3 bindings/python/examples/full_comms_demo.py
|
|
91
|
+
PYTHONPATH=bindings/python python3 bindings/python/examples/executor_demo.py
|
|
92
|
+
PYTHONPATH=bindings/python python3 bindings/python/examples/status_watch_demo.py
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## Mode Switch API
|
|
96
|
+
|
|
97
|
+
```python
|
|
98
|
+
import robotrt_sdk as sdk
|
|
99
|
+
|
|
100
|
+
# Embedded mode: read local status report.
|
|
101
|
+
embedded = sdk.StatusClient(mode="embedded", report_path="artifacts/introspection/local-loop-status.json")
|
|
102
|
+
print(embedded.node_list())
|
|
103
|
+
|
|
104
|
+
# Daemon mode: query robotrt-host status service.
|
|
105
|
+
daemon = sdk.StatusClient(mode="daemon", endpoint="127.0.0.1:7588", timeout_ms=1000)
|
|
106
|
+
print(daemon.service_list())
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## Environment Variables
|
|
110
|
+
|
|
111
|
+
- `ROBOTRT_CORE_FFI_LIB`: optional absolute path to the `core-ffi` dynamic library.
|
|
112
|
+
- If not set, SDK auto-discovers under `target/debug` and `target/debug/deps`.
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# RobotRT Python SDK (Embedded + Daemon)
|
|
2
|
+
|
|
3
|
+
This directory contains the Python-first SDK implementation on top of the frozen Core FFI ABI.
|
|
4
|
+
|
|
5
|
+
## Scope
|
|
6
|
+
|
|
7
|
+
- Embedded mode SDK surface for node and buffer lease lifecycle.
|
|
8
|
+
- Embedded mode full communication APIs: pub/sub, service, action, mission.
|
|
9
|
+
- Embedded mode executor APIs: SimpleExecutor, CallbackExecutor, and MultiThreadExecutor.
|
|
10
|
+
- Daemon mode status SDK surface for snapshot/list/info/watch query.
|
|
11
|
+
- Unified error mapping from C ABI status and error payload.
|
|
12
|
+
- Zero-copy readable view via borrowed lease pointer to Python `memoryview`.
|
|
13
|
+
|
|
14
|
+
## Embedded Communication API (MVP)
|
|
15
|
+
|
|
16
|
+
```python
|
|
17
|
+
import robotrt_sdk as sdk
|
|
18
|
+
|
|
19
|
+
with sdk.Node.create() as node:
|
|
20
|
+
pub = node.create_publisher("/demo/topic")
|
|
21
|
+
sub = node.create_subscriber("/demo/topic")
|
|
22
|
+
pub.publish_batch([b"hello", b"robotrt", b"batch"])
|
|
23
|
+
print(sub.recv_batch(8))
|
|
24
|
+
print("backpressure:", sub.backpressure_signal().value)
|
|
25
|
+
|
|
26
|
+
server = node.create_service_server("/demo/service")
|
|
27
|
+
client = node.create_service_client("/demo/service")
|
|
28
|
+
request_id = client.call(b"ping")
|
|
29
|
+
rid, req = server.recv_request()
|
|
30
|
+
server.reply(rid, b"pong")
|
|
31
|
+
print(client.poll_response(request_id))
|
|
32
|
+
|
|
33
|
+
action_server = node.create_action_server("/demo/action")
|
|
34
|
+
action_client = node.create_action_client("/demo/action")
|
|
35
|
+
goal_id, accepted = action_client.send_goal(b"goal")
|
|
36
|
+
gid, goal = action_server.recv_goal()
|
|
37
|
+
action_server.accept_goal(gid)
|
|
38
|
+
action_server.publish_feedback(gid, b"50%")
|
|
39
|
+
action_server.succeed(gid, b"done")
|
|
40
|
+
print(action_client.poll_result(goal_id))
|
|
41
|
+
|
|
42
|
+
mission = node.create_mission_session(42)
|
|
43
|
+
mission.open("demo_mission")
|
|
44
|
+
mission.send_command("start:demo")
|
|
45
|
+
mission.reconcile("cp-1", 2)
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Quick Start
|
|
49
|
+
|
|
50
|
+
1. Build the shared library:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
cargo build -p core-ffi --lib
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
2. Run tests:
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
PYTHONPATH=bindings/python python3 -m unittest discover -s bindings/python/tests -p 'test_*.py'
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
3. Run the embedded zero-copy demo:
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
PYTHONPATH=bindings/python python3 bindings/python/examples/embedded_zero_copy_demo.py
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
4. Run the daemon status demo:
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
PYTHONPATH=bindings/python python3 bindings/python/examples/daemon_status_demo.py
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
5. Run additional examples:
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
PYTHONPATH=bindings/python python3 bindings/python/examples/full_comms_demo.py
|
|
78
|
+
PYTHONPATH=bindings/python python3 bindings/python/examples/executor_demo.py
|
|
79
|
+
PYTHONPATH=bindings/python python3 bindings/python/examples/status_watch_demo.py
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Mode Switch API
|
|
83
|
+
|
|
84
|
+
```python
|
|
85
|
+
import robotrt_sdk as sdk
|
|
86
|
+
|
|
87
|
+
# Embedded mode: read local status report.
|
|
88
|
+
embedded = sdk.StatusClient(mode="embedded", report_path="artifacts/introspection/local-loop-status.json")
|
|
89
|
+
print(embedded.node_list())
|
|
90
|
+
|
|
91
|
+
# Daemon mode: query robotrt-host status service.
|
|
92
|
+
daemon = sdk.StatusClient(mode="daemon", endpoint="127.0.0.1:7588", timeout_ms=1000)
|
|
93
|
+
print(daemon.service_list())
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## Environment Variables
|
|
97
|
+
|
|
98
|
+
- `ROBOTRT_CORE_FFI_LIB`: optional absolute path to the `core-ffi` dynamic library.
|
|
99
|
+
- If not set, SDK auto-discovers under `target/debug` and `target/debug/deps`.
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "robotrt-sdk"
|
|
7
|
+
version = "0.1.0b1"
|
|
8
|
+
description = "RobotRT Python SDK (FFI thin wrapper)"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
authors = [{ name = "RobotRT Maintainers" }]
|
|
12
|
+
license = "Apache-2.0"
|
|
13
|
+
classifiers = [
|
|
14
|
+
"Development Status :: 4 - Beta",
|
|
15
|
+
"Programming Language :: Python :: 3",
|
|
16
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
17
|
+
"Operating System :: OS Independent",
|
|
18
|
+
]
|
|
19
|
+
|
|
20
|
+
[tool.setuptools]
|
|
21
|
+
include-package-data = true
|
|
22
|
+
|
|
23
|
+
[tool.setuptools.packages.find]
|
|
24
|
+
where = ["."]
|
|
25
|
+
include = ["robotrt_sdk", "robotrt_sdk.*"]
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
from ._ffi import _CORE
|
|
2
|
+
from ._version import __version__
|
|
3
|
+
from .core import (
|
|
4
|
+
ActionClient,
|
|
5
|
+
ActionGoalHealth,
|
|
6
|
+
ActionServer,
|
|
7
|
+
BackpressureSignal,
|
|
8
|
+
CallbackGroupType,
|
|
9
|
+
BufferDescriptor,
|
|
10
|
+
BufferLease,
|
|
11
|
+
ExternalBufferRef,
|
|
12
|
+
LeaseView,
|
|
13
|
+
MissionSession,
|
|
14
|
+
Node,
|
|
15
|
+
Publisher,
|
|
16
|
+
CallbackExecutor,
|
|
17
|
+
MultiThreadExecutor,
|
|
18
|
+
MultiThreadExecutorStats,
|
|
19
|
+
RegisteredWork,
|
|
20
|
+
RegisteredWorkKind,
|
|
21
|
+
ServiceClient,
|
|
22
|
+
ServiceServer,
|
|
23
|
+
SimpleExecutor,
|
|
24
|
+
Subscriber,
|
|
25
|
+
TimerRegistration,
|
|
26
|
+
)
|
|
27
|
+
from .errors import ErrorInfo, RobotRtError
|
|
28
|
+
from .status import StatusClient
|
|
29
|
+
|
|
30
|
+
__all__ = [
|
|
31
|
+
"BufferDescriptor",
|
|
32
|
+
"BufferLease",
|
|
33
|
+
"ErrorInfo",
|
|
34
|
+
"LeaseView",
|
|
35
|
+
"Node",
|
|
36
|
+
"Publisher",
|
|
37
|
+
"Subscriber",
|
|
38
|
+
"ServiceClient",
|
|
39
|
+
"ServiceServer",
|
|
40
|
+
"ActionClient",
|
|
41
|
+
"ActionGoalHealth",
|
|
42
|
+
"ActionServer",
|
|
43
|
+
"BackpressureSignal",
|
|
44
|
+
"CallbackGroupType",
|
|
45
|
+
"CallbackExecutor",
|
|
46
|
+
"ExternalBufferRef",
|
|
47
|
+
"MissionSession",
|
|
48
|
+
"MultiThreadExecutor",
|
|
49
|
+
"MultiThreadExecutorStats",
|
|
50
|
+
"RobotRtError",
|
|
51
|
+
"RegisteredWork",
|
|
52
|
+
"RegisteredWorkKind",
|
|
53
|
+
"StatusClient",
|
|
54
|
+
"SimpleExecutor",
|
|
55
|
+
"TimerRegistration",
|
|
56
|
+
"__version__",
|
|
57
|
+
"abi_version",
|
|
58
|
+
"protocol_version",
|
|
59
|
+
]
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def abi_version() -> int:
|
|
63
|
+
return int(_CORE.lib.robotrt_core_abi_version())
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def protocol_version() -> int:
|
|
67
|
+
return int(_CORE.lib.robotrt_core_protocol_version())
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from ._ffi_loader import CoreFfi, _CORE
|
|
4
|
+
from ._ffi_types import (
|
|
5
|
+
RT_OK,
|
|
6
|
+
RtActionGoalHealth,
|
|
7
|
+
RtBufferDescriptor,
|
|
8
|
+
RtErrorInfo,
|
|
9
|
+
RtExternalBufferRef,
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
__all__ = [
|
|
13
|
+
"RT_OK",
|
|
14
|
+
"CoreFfi",
|
|
15
|
+
"RtActionGoalHealth",
|
|
16
|
+
"RtBufferDescriptor",
|
|
17
|
+
"RtErrorInfo",
|
|
18
|
+
"RtExternalBufferRef",
|
|
19
|
+
"_CORE",
|
|
20
|
+
]
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import ctypes
|
|
4
|
+
import os
|
|
5
|
+
import platform
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
from ._ffi_signatures import declare_signatures
|
|
9
|
+
from ._ffi_types import RT_OK, RtErrorInfo
|
|
10
|
+
from .errors import ErrorInfo, RobotRtError
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _workspace_root() -> Path:
|
|
14
|
+
return Path(__file__).resolve().parents[3]
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _dylib_name() -> str:
|
|
18
|
+
name = platform.system().lower()
|
|
19
|
+
if name == "darwin":
|
|
20
|
+
return "librobotrt_core_ffi.dylib"
|
|
21
|
+
if name == "windows":
|
|
22
|
+
return "robotrt_core_ffi.dll"
|
|
23
|
+
return "librobotrt_core_ffi.so"
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _resolve_library_path() -> Path:
|
|
27
|
+
env = os.environ.get("ROBOTRT_CORE_FFI_LIB")
|
|
28
|
+
if env:
|
|
29
|
+
p = Path(env).expanduser().resolve()
|
|
30
|
+
if p.exists():
|
|
31
|
+
return p
|
|
32
|
+
raise FileNotFoundError(f"ROBOTRT_CORE_FFI_LIB does not exist: {p}")
|
|
33
|
+
|
|
34
|
+
root = _workspace_root()
|
|
35
|
+
direct = root / "target" / "debug" / _dylib_name()
|
|
36
|
+
if direct.exists():
|
|
37
|
+
return direct
|
|
38
|
+
|
|
39
|
+
deps = root / "target" / "debug" / "deps"
|
|
40
|
+
if deps.exists():
|
|
41
|
+
suffixes = {".dylib", ".so", ".dll"}
|
|
42
|
+
for candidate in sorted(deps.iterdir()):
|
|
43
|
+
if candidate.suffix not in suffixes:
|
|
44
|
+
continue
|
|
45
|
+
if candidate.name.startswith("librobotrt_core_ffi") or candidate.name == _dylib_name():
|
|
46
|
+
return candidate
|
|
47
|
+
|
|
48
|
+
raise FileNotFoundError("robotrt core ffi dynamic library not found; run cargo build -p core-ffi --lib")
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class CoreFfi:
|
|
52
|
+
def __init__(self) -> None:
|
|
53
|
+
self.library_path = _resolve_library_path()
|
|
54
|
+
self.lib = ctypes.CDLL(str(self.library_path))
|
|
55
|
+
declare_signatures(self.lib)
|
|
56
|
+
|
|
57
|
+
def last_error(self) -> ErrorInfo:
|
|
58
|
+
info = RtErrorInfo()
|
|
59
|
+
self.lib.robotrt_error_last(ctypes.byref(info))
|
|
60
|
+
|
|
61
|
+
needed = ctypes.c_uint32(0)
|
|
62
|
+
self.lib.robotrt_error_message_copy(None, 0, ctypes.byref(needed))
|
|
63
|
+
message = ""
|
|
64
|
+
if needed.value > 0:
|
|
65
|
+
buf = ctypes.create_string_buffer(needed.value)
|
|
66
|
+
self.lib.robotrt_error_message_copy(buf, needed.value, ctypes.byref(needed))
|
|
67
|
+
message = buf.value.decode("utf-8", errors="replace")
|
|
68
|
+
|
|
69
|
+
return ErrorInfo(
|
|
70
|
+
code=int(info.code),
|
|
71
|
+
domain=int(info.domain),
|
|
72
|
+
retryable=bool(info.retryable),
|
|
73
|
+
message=message,
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
def check(self, status: int, context: str) -> None:
|
|
77
|
+
if status == RT_OK:
|
|
78
|
+
return
|
|
79
|
+
raise RobotRtError(self.last_error(), context)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
_CORE = CoreFfi()
|