antioch-py 2.0.6__py3-none-any.whl
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.
Potentially problematic release.
This version of antioch-py might be problematic. Click here for more details.
- antioch/__init__.py +0 -0
- antioch/message.py +87 -0
- antioch/module/__init__.py +53 -0
- antioch/module/clock.py +62 -0
- antioch/module/execution.py +278 -0
- antioch/module/input.py +127 -0
- antioch/module/module.py +218 -0
- antioch/module/node.py +357 -0
- antioch/module/token.py +42 -0
- antioch/session/__init__.py +150 -0
- antioch/session/ark.py +504 -0
- antioch/session/asset.py +65 -0
- antioch/session/error.py +80 -0
- antioch/session/record.py +158 -0
- antioch/session/scene.py +1521 -0
- antioch/session/session.py +220 -0
- antioch/session/task.py +323 -0
- antioch/session/views/__init__.py +40 -0
- antioch/session/views/animation.py +189 -0
- antioch/session/views/articulation.py +245 -0
- antioch/session/views/basis_curve.py +186 -0
- antioch/session/views/camera.py +92 -0
- antioch/session/views/collision.py +75 -0
- antioch/session/views/geometry.py +74 -0
- antioch/session/views/ground_plane.py +63 -0
- antioch/session/views/imu.py +73 -0
- antioch/session/views/joint.py +64 -0
- antioch/session/views/light.py +175 -0
- antioch/session/views/pir_sensor.py +140 -0
- antioch/session/views/radar.py +73 -0
- antioch/session/views/rigid_body.py +282 -0
- antioch/session/views/xform.py +119 -0
- antioch_py-2.0.6.dist-info/METADATA +115 -0
- antioch_py-2.0.6.dist-info/RECORD +99 -0
- antioch_py-2.0.6.dist-info/WHEEL +5 -0
- antioch_py-2.0.6.dist-info/entry_points.txt +2 -0
- antioch_py-2.0.6.dist-info/top_level.txt +2 -0
- common/__init__.py +0 -0
- common/ark/__init__.py +60 -0
- common/ark/ark.py +128 -0
- common/ark/hardware.py +121 -0
- common/ark/kinematics.py +31 -0
- common/ark/module.py +85 -0
- common/ark/node.py +94 -0
- common/ark/scheduler.py +439 -0
- common/ark/sim.py +33 -0
- common/assets/__init__.py +3 -0
- common/constants.py +47 -0
- common/core/__init__.py +52 -0
- common/core/agent.py +296 -0
- common/core/auth.py +305 -0
- common/core/registry.py +331 -0
- common/core/task.py +36 -0
- common/message/__init__.py +59 -0
- common/message/annotation.py +89 -0
- common/message/array.py +500 -0
- common/message/base.py +517 -0
- common/message/camera.py +91 -0
- common/message/color.py +139 -0
- common/message/frame.py +50 -0
- common/message/image.py +171 -0
- common/message/imu.py +14 -0
- common/message/joint.py +47 -0
- common/message/log.py +31 -0
- common/message/pir.py +16 -0
- common/message/point.py +109 -0
- common/message/point_cloud.py +63 -0
- common/message/pose.py +148 -0
- common/message/quaternion.py +273 -0
- common/message/radar.py +58 -0
- common/message/types.py +37 -0
- common/message/vector.py +786 -0
- common/rome/__init__.py +9 -0
- common/rome/client.py +430 -0
- common/rome/error.py +16 -0
- common/session/__init__.py +54 -0
- common/session/environment.py +31 -0
- common/session/sim.py +240 -0
- common/session/views/__init__.py +263 -0
- common/session/views/animation.py +73 -0
- common/session/views/articulation.py +184 -0
- common/session/views/basis_curve.py +102 -0
- common/session/views/camera.py +147 -0
- common/session/views/collision.py +59 -0
- common/session/views/geometry.py +102 -0
- common/session/views/ground_plane.py +41 -0
- common/session/views/imu.py +66 -0
- common/session/views/joint.py +81 -0
- common/session/views/light.py +96 -0
- common/session/views/pir_sensor.py +115 -0
- common/session/views/radar.py +82 -0
- common/session/views/rigid_body.py +236 -0
- common/session/views/viewport.py +21 -0
- common/session/views/xform.py +39 -0
- common/utils/__init__.py +4 -0
- common/utils/comms.py +571 -0
- common/utils/logger.py +123 -0
- common/utils/time.py +42 -0
- common/utils/usd.py +12 -0
common/message/color.py
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from pydantic import field_validator
|
|
4
|
+
|
|
5
|
+
from common.message.base import Message
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Color(Message):
|
|
9
|
+
"""
|
|
10
|
+
An RGBA color with values in the range [0.0, 1.0].
|
|
11
|
+
|
|
12
|
+
Used in image annotations and visualization.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
_type = "antioch/color"
|
|
16
|
+
r: float
|
|
17
|
+
g: float
|
|
18
|
+
b: float
|
|
19
|
+
a: float
|
|
20
|
+
|
|
21
|
+
@field_validator("r", "g", "b", "a")
|
|
22
|
+
@classmethod
|
|
23
|
+
def validate_range(cls, v: float) -> float:
|
|
24
|
+
"""
|
|
25
|
+
Validate that color values are in the range [0.0, 1.0].
|
|
26
|
+
|
|
27
|
+
:param v: The color value to validate.
|
|
28
|
+
:return: The validated color value.
|
|
29
|
+
:raises ValueError: If the value is not in [0.0, 1.0].
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
if not 0.0 <= v <= 1.0:
|
|
33
|
+
raise ValueError(f"Color values must be in range [0.0, 1.0], got {v}")
|
|
34
|
+
return v
|
|
35
|
+
|
|
36
|
+
def __repr__(self) -> str:
|
|
37
|
+
"""
|
|
38
|
+
Return a readable string representation.
|
|
39
|
+
|
|
40
|
+
:return: String representation.
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
return f"Color(r={self.r}, g={self.g}, b={self.b}, a={self.a})"
|
|
44
|
+
|
|
45
|
+
def __str__(self) -> str:
|
|
46
|
+
"""
|
|
47
|
+
Return a readable string representation.
|
|
48
|
+
|
|
49
|
+
:return: String representation.
|
|
50
|
+
"""
|
|
51
|
+
|
|
52
|
+
return f"Color(r={self.r}, g={self.g}, b={self.b}, a={self.a})"
|
|
53
|
+
|
|
54
|
+
@classmethod
|
|
55
|
+
def rgba(cls, r: float, g: float, b: float, a: float = 1.0) -> Color:
|
|
56
|
+
"""
|
|
57
|
+
Create a color from RGBA values.
|
|
58
|
+
|
|
59
|
+
:param r: Red component [0.0, 1.0].
|
|
60
|
+
:param g: Green component [0.0, 1.0].
|
|
61
|
+
:param b: Blue component [0.0, 1.0].
|
|
62
|
+
:param a: Alpha component [0.0, 1.0]. Defaults to 1.0 (opaque).
|
|
63
|
+
:return: A Color instance.
|
|
64
|
+
"""
|
|
65
|
+
|
|
66
|
+
return cls(r=r, g=g, b=b, a=a)
|
|
67
|
+
|
|
68
|
+
@classmethod
|
|
69
|
+
def rgb(cls, r: float, g: float, b: float) -> Color:
|
|
70
|
+
"""
|
|
71
|
+
Create an opaque color from RGB values.
|
|
72
|
+
|
|
73
|
+
:param r: Red component [0.0, 1.0].
|
|
74
|
+
:param g: Green component [0.0, 1.0].
|
|
75
|
+
:param b: Blue component [0.0, 1.0].
|
|
76
|
+
:return: A Color instance with alpha = 1.0.
|
|
77
|
+
"""
|
|
78
|
+
|
|
79
|
+
return cls(r=r, g=g, b=b, a=1.0)
|
|
80
|
+
|
|
81
|
+
@classmethod
|
|
82
|
+
def red(cls) -> Color:
|
|
83
|
+
"""
|
|
84
|
+
Create a red color.
|
|
85
|
+
|
|
86
|
+
:return: Red color (1.0, 0.0, 0.0, 1.0).
|
|
87
|
+
"""
|
|
88
|
+
|
|
89
|
+
return cls(r=1.0, g=0.0, b=0.0, a=1.0)
|
|
90
|
+
|
|
91
|
+
@classmethod
|
|
92
|
+
def green(cls) -> Color:
|
|
93
|
+
"""
|
|
94
|
+
Create a green color.
|
|
95
|
+
|
|
96
|
+
:return: Green color (0.0, 1.0, 0.0, 1.0).
|
|
97
|
+
"""
|
|
98
|
+
|
|
99
|
+
return cls(r=0.0, g=1.0, b=0.0, a=1.0)
|
|
100
|
+
|
|
101
|
+
@classmethod
|
|
102
|
+
def blue(cls) -> Color:
|
|
103
|
+
"""
|
|
104
|
+
Create a blue color.
|
|
105
|
+
|
|
106
|
+
:return: Blue color (0.0, 0.0, 1.0, 1.0).
|
|
107
|
+
"""
|
|
108
|
+
|
|
109
|
+
return cls(r=0.0, g=0.0, b=1.0, a=1.0)
|
|
110
|
+
|
|
111
|
+
@classmethod
|
|
112
|
+
def white(cls) -> Color:
|
|
113
|
+
"""
|
|
114
|
+
Create a white color.
|
|
115
|
+
|
|
116
|
+
:return: White color (1.0, 1.0, 1.0, 1.0).
|
|
117
|
+
"""
|
|
118
|
+
|
|
119
|
+
return cls(r=1.0, g=1.0, b=1.0, a=1.0)
|
|
120
|
+
|
|
121
|
+
@classmethod
|
|
122
|
+
def black(cls) -> Color:
|
|
123
|
+
"""
|
|
124
|
+
Create a black color.
|
|
125
|
+
|
|
126
|
+
:return: Black color (0.0, 0.0, 0.0, 1.0).
|
|
127
|
+
"""
|
|
128
|
+
|
|
129
|
+
return cls(r=0.0, g=0.0, b=0.0, a=1.0)
|
|
130
|
+
|
|
131
|
+
@classmethod
|
|
132
|
+
def transparent(cls) -> Color:
|
|
133
|
+
"""
|
|
134
|
+
Create a transparent color.
|
|
135
|
+
|
|
136
|
+
:return: Transparent color (0.0, 0.0, 0.0, 0.0).
|
|
137
|
+
"""
|
|
138
|
+
|
|
139
|
+
return cls(r=0.0, g=0.0, b=0.0, a=0.0)
|
common/message/frame.py
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
from pydantic import Field
|
|
2
|
+
|
|
3
|
+
from common.message.base import Message
|
|
4
|
+
from common.message.quaternion import Quaternion
|
|
5
|
+
from common.message.vector import Vector3
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class FrameTransform(Message):
|
|
9
|
+
"""
|
|
10
|
+
A transform between two reference frames in 3D space.
|
|
11
|
+
|
|
12
|
+
:param parent_frame_id: Name of the parent frame.
|
|
13
|
+
:param child_frame_id: Name of the child frame.
|
|
14
|
+
:param translation: Translation component of the transform.
|
|
15
|
+
:param rotation: Rotation component of the transform.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
_type = "antioch/frame_transform"
|
|
19
|
+
parent_frame_id: str = Field(description="Name of the parent frame")
|
|
20
|
+
child_frame_id: str = Field(description="Name of the child frame")
|
|
21
|
+
translation: Vector3 = Field(description="Translation component of the transform")
|
|
22
|
+
rotation: Quaternion = Field(description="Rotation component of the transform")
|
|
23
|
+
|
|
24
|
+
@classmethod
|
|
25
|
+
def identity(cls, parent_frame_id: str, child_frame_id: str) -> "FrameTransform":
|
|
26
|
+
"""
|
|
27
|
+
Create an identity transform between two frames.
|
|
28
|
+
|
|
29
|
+
:param parent_frame_id: Name of the parent frame.
|
|
30
|
+
:param child_frame_id: Name of the child frame.
|
|
31
|
+
:return: Identity frame transform.
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
return cls(
|
|
35
|
+
parent_frame_id=parent_frame_id,
|
|
36
|
+
child_frame_id=child_frame_id,
|
|
37
|
+
translation=Vector3.zeros(),
|
|
38
|
+
rotation=Quaternion.identity(),
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class FrameTransforms(Message):
|
|
43
|
+
"""
|
|
44
|
+
An array of FrameTransform messages.
|
|
45
|
+
|
|
46
|
+
:param transforms: Array of transforms.
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
_type = "antioch/frame_transforms"
|
|
50
|
+
transforms: list[FrameTransform] = Field(default_factory=list, description="Array of transforms")
|
common/message/image.py
ADDED
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from enum import Enum
|
|
4
|
+
|
|
5
|
+
import numpy as np
|
|
6
|
+
|
|
7
|
+
from common.message.base import Message
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class ImageEncoding(str, Enum):
|
|
11
|
+
"""
|
|
12
|
+
Image encodings with associated metadata.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
# Monochrome images
|
|
16
|
+
MONO8 = "mono8"
|
|
17
|
+
MONO16 = "mono16"
|
|
18
|
+
|
|
19
|
+
# Color images
|
|
20
|
+
RGB8 = "rgb8"
|
|
21
|
+
RGBA8 = "rgba8"
|
|
22
|
+
BGR8 = "bgr8"
|
|
23
|
+
BGRA8 = "bgra8"
|
|
24
|
+
|
|
25
|
+
# Depth images
|
|
26
|
+
DEPTH_U16 = "16UC1"
|
|
27
|
+
DEPTH_F32 = "32FC1"
|
|
28
|
+
|
|
29
|
+
@property
|
|
30
|
+
def bytes_per_pixel(self) -> int:
|
|
31
|
+
"""
|
|
32
|
+
Get the number of bytes per pixel for this encoding.
|
|
33
|
+
|
|
34
|
+
:return: The number of bytes per pixel.
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
return {
|
|
38
|
+
ImageEncoding.MONO8: 1,
|
|
39
|
+
ImageEncoding.MONO16: 2,
|
|
40
|
+
ImageEncoding.RGB8: 3,
|
|
41
|
+
ImageEncoding.RGBA8: 4,
|
|
42
|
+
ImageEncoding.BGR8: 3,
|
|
43
|
+
ImageEncoding.BGRA8: 4,
|
|
44
|
+
ImageEncoding.DEPTH_U16: 2,
|
|
45
|
+
ImageEncoding.DEPTH_F32: 4,
|
|
46
|
+
}[self]
|
|
47
|
+
|
|
48
|
+
@property
|
|
49
|
+
def numpy_dtype(self) -> np.dtype:
|
|
50
|
+
"""
|
|
51
|
+
Get the numpy dtype for this encoding.
|
|
52
|
+
|
|
53
|
+
:return: The numpy dtype.
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
dtype_map = {
|
|
57
|
+
ImageEncoding.MONO8: np.uint8,
|
|
58
|
+
ImageEncoding.MONO16: np.uint16,
|
|
59
|
+
ImageEncoding.RGB8: np.uint8,
|
|
60
|
+
ImageEncoding.RGBA8: np.uint8,
|
|
61
|
+
ImageEncoding.BGR8: np.uint8,
|
|
62
|
+
ImageEncoding.BGRA8: np.uint8,
|
|
63
|
+
ImageEncoding.DEPTH_U16: np.uint16,
|
|
64
|
+
ImageEncoding.DEPTH_F32: np.float32,
|
|
65
|
+
}
|
|
66
|
+
return np.dtype(dtype_map[self])
|
|
67
|
+
|
|
68
|
+
@property
|
|
69
|
+
def channels(self) -> int:
|
|
70
|
+
"""
|
|
71
|
+
Get the number of color channels.
|
|
72
|
+
|
|
73
|
+
:return: The number of color channels.
|
|
74
|
+
"""
|
|
75
|
+
|
|
76
|
+
if self in (
|
|
77
|
+
ImageEncoding.MONO8,
|
|
78
|
+
ImageEncoding.MONO16,
|
|
79
|
+
ImageEncoding.DEPTH_U16,
|
|
80
|
+
ImageEncoding.DEPTH_F32,
|
|
81
|
+
):
|
|
82
|
+
return 1
|
|
83
|
+
elif self in (ImageEncoding.RGB8, ImageEncoding.BGR8):
|
|
84
|
+
return 3
|
|
85
|
+
elif self in (ImageEncoding.RGBA8, ImageEncoding.BGRA8):
|
|
86
|
+
return 4
|
|
87
|
+
else:
|
|
88
|
+
return 1
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
class Image(Message):
|
|
92
|
+
"""
|
|
93
|
+
An image with raw data.
|
|
94
|
+
|
|
95
|
+
:param encoding: The encoding of the image.
|
|
96
|
+
:param width: The width of the image in pixels.
|
|
97
|
+
:param height: The height of the image in pixels.
|
|
98
|
+
:param data: The raw image data bytes.
|
|
99
|
+
"""
|
|
100
|
+
|
|
101
|
+
_type = "antioch/image"
|
|
102
|
+
encoding: ImageEncoding
|
|
103
|
+
width: int
|
|
104
|
+
height: int
|
|
105
|
+
data: bytes
|
|
106
|
+
|
|
107
|
+
@classmethod
|
|
108
|
+
def from_numpy(cls, array: np.ndarray, encoding: ImageEncoding | None = None) -> Image:
|
|
109
|
+
"""
|
|
110
|
+
Create an Image from a numpy array.
|
|
111
|
+
|
|
112
|
+
For uint8 encodings (RGB8, RGBA8, MONO8), pixel values should be in the range [0, 255].
|
|
113
|
+
For float32 depth encodings (DEPTH_F32), values are typically in meters.
|
|
114
|
+
For uint16 encodings (DEPTH_U16, MONO16), values use the full uint16 range.
|
|
115
|
+
|
|
116
|
+
If the array dtype doesn't match the encoding's dtype, it will be converted
|
|
117
|
+
via astype().
|
|
118
|
+
|
|
119
|
+
:param array: The numpy array containing image data.
|
|
120
|
+
:param encoding: The image encoding (auto-detected if None).
|
|
121
|
+
:return: An Image instance.
|
|
122
|
+
:raises ValueError: If array shape doesn't match a supported format.
|
|
123
|
+
"""
|
|
124
|
+
|
|
125
|
+
# Auto-detect encoding based on array shape and dtype
|
|
126
|
+
if encoding is None:
|
|
127
|
+
if array.ndim == 2:
|
|
128
|
+
# Grayscale or depth
|
|
129
|
+
if array.dtype == np.uint8:
|
|
130
|
+
encoding = ImageEncoding.MONO8
|
|
131
|
+
elif array.dtype == np.uint16:
|
|
132
|
+
encoding = ImageEncoding.DEPTH_U16
|
|
133
|
+
elif array.dtype == np.float32:
|
|
134
|
+
encoding = ImageEncoding.DEPTH_F32
|
|
135
|
+
else:
|
|
136
|
+
raise ValueError(f"Unsupported dtype for 2D array: {array.dtype}")
|
|
137
|
+
elif array.ndim == 3:
|
|
138
|
+
# Color image
|
|
139
|
+
channels = array.shape[2]
|
|
140
|
+
if channels == 3:
|
|
141
|
+
encoding = ImageEncoding.RGB8
|
|
142
|
+
elif channels == 4:
|
|
143
|
+
encoding = ImageEncoding.RGBA8
|
|
144
|
+
else:
|
|
145
|
+
raise ValueError(f"Unsupported number of channels: {channels}")
|
|
146
|
+
else:
|
|
147
|
+
raise ValueError(f"Unsupported array dimensions: {array.ndim}")
|
|
148
|
+
|
|
149
|
+
# Convert array dtype to match encoding if necessary
|
|
150
|
+
expected_dtype = encoding.numpy_dtype
|
|
151
|
+
if array.dtype != expected_dtype:
|
|
152
|
+
array = array.astype(expected_dtype)
|
|
153
|
+
|
|
154
|
+
# Standard packing (handles both contiguous and non-contiguous)
|
|
155
|
+
if array.ndim < 2:
|
|
156
|
+
raise ValueError(f"Image array must be at least 2D, got shape {array.shape}")
|
|
157
|
+
height, width = array.shape[:2]
|
|
158
|
+
data = array.tobytes()
|
|
159
|
+
return cls(encoding=encoding, width=width, height=height, data=data)
|
|
160
|
+
|
|
161
|
+
def to_numpy(self) -> np.ndarray:
|
|
162
|
+
"""
|
|
163
|
+
Convert the image to a numpy array.
|
|
164
|
+
|
|
165
|
+
:return: A numpy array with the image data.
|
|
166
|
+
"""
|
|
167
|
+
|
|
168
|
+
# Standard contiguous buffer
|
|
169
|
+
shape = (self.height, self.width) if self.encoding.channels == 1 else (self.height, self.width, self.encoding.channels)
|
|
170
|
+
array = np.frombuffer(self.data, dtype=self.encoding.numpy_dtype)
|
|
171
|
+
return array.reshape(shape)
|
common/message/imu.py
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
from common.message.base import Message
|
|
2
|
+
from common.message.quaternion import Quaternion
|
|
3
|
+
from common.message.vector import Vector3
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class ImuSample(Message):
|
|
7
|
+
"""
|
|
8
|
+
IMU sensor sample data.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
_type = "antioch/imu_sample"
|
|
12
|
+
linear_acceleration: Vector3
|
|
13
|
+
angular_velocity: Vector3
|
|
14
|
+
orientation: Quaternion
|
common/message/joint.py
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
from common.message.base import Message
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class JointState(Message):
|
|
5
|
+
"""
|
|
6
|
+
State of a single joint.
|
|
7
|
+
|
|
8
|
+
Represents the complete physical state of a joint including its position,
|
|
9
|
+
velocity, and measured effort (force/torque).
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
_type = "antioch/joint_state"
|
|
13
|
+
position: float
|
|
14
|
+
velocity: float
|
|
15
|
+
effort: float
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class JointTarget(Message):
|
|
19
|
+
"""
|
|
20
|
+
Control target for a single joint.
|
|
21
|
+
|
|
22
|
+
Specifies desired position, velocity, and/or effort targets for a joint's
|
|
23
|
+
PD controller. All fields are optional - omitted values are not controlled.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
_type = "antioch/joint_target"
|
|
27
|
+
position: float | None = None
|
|
28
|
+
velocity: float | None = None
|
|
29
|
+
effort: float | None = None
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class JointStates(Message):
|
|
33
|
+
"""
|
|
34
|
+
Collection of joint states for an actuator group.
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
_type = "antioch/joint_states"
|
|
38
|
+
states: list[JointState]
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class JointTargets(Message):
|
|
42
|
+
"""
|
|
43
|
+
Collection of joint targets for an actuator group.
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
_type = "antioch/joint_targets"
|
|
47
|
+
targets: list[JointTarget]
|
common/message/log.py
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import time
|
|
2
|
+
from enum import Enum
|
|
3
|
+
|
|
4
|
+
from pydantic import Field
|
|
5
|
+
|
|
6
|
+
from common.message.base import Message
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class LogLevel(str, Enum):
|
|
10
|
+
"""
|
|
11
|
+
Log level.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
DEBUG = "debug"
|
|
15
|
+
INFO = "info"
|
|
16
|
+
WARNING = "warning"
|
|
17
|
+
ERROR = "error"
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class Log(Message):
|
|
21
|
+
"""
|
|
22
|
+
Log entry structure.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
_type = "antioch/log"
|
|
26
|
+
timestamp_us: int = Field(default_factory=lambda: int(time.time_ns() // 1000))
|
|
27
|
+
let_us: int
|
|
28
|
+
level: LogLevel
|
|
29
|
+
message: str | None = None
|
|
30
|
+
channel: str | None = None
|
|
31
|
+
telemetry: bytes | None = None
|
common/message/pir.py
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
from pydantic import Field
|
|
2
|
+
|
|
3
|
+
from common.message.base import Message
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class PirStatus(Message):
|
|
7
|
+
"""
|
|
8
|
+
PIR sensor status containing detection state and signal information.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
_type = "antioch/pir_status"
|
|
12
|
+
is_detected: bool = Field(description="Whether motion is currently detected")
|
|
13
|
+
signal_strength: float = Field(description="Current analog signal value after filtering")
|
|
14
|
+
threshold: float = Field(description="Current detection threshold")
|
|
15
|
+
element_flux: list[float] = Field(default_factory=list, description="Raw accumulated IR flux per element")
|
|
16
|
+
element_signal: list[float] = Field(default_factory=list, description="Pyroelectric voltage signal per element (proportional to dT/dt)")
|
common/message/point.py
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from common.message.base import Message
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Point2(Message):
|
|
7
|
+
"""
|
|
8
|
+
A point representing a position in 2D space.
|
|
9
|
+
|
|
10
|
+
Used in image annotations and 2D coordinate systems.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
_type = "antioch/point2"
|
|
14
|
+
x: float
|
|
15
|
+
y: float
|
|
16
|
+
|
|
17
|
+
def __repr__(self) -> str:
|
|
18
|
+
"""
|
|
19
|
+
Return a readable string representation.
|
|
20
|
+
|
|
21
|
+
:return: String representation.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
return f"Point2(x={self.x}, y={self.y})"
|
|
25
|
+
|
|
26
|
+
def __str__(self) -> str:
|
|
27
|
+
"""
|
|
28
|
+
Return a readable string representation.
|
|
29
|
+
|
|
30
|
+
:return: String representation.
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
return f"Point2(x={self.x}, y={self.y})"
|
|
34
|
+
|
|
35
|
+
@classmethod
|
|
36
|
+
def new(cls, x: float, y: float) -> Point2:
|
|
37
|
+
"""
|
|
38
|
+
Create a new 2D point.
|
|
39
|
+
|
|
40
|
+
:param x: The x coordinate.
|
|
41
|
+
:param y: The y coordinate.
|
|
42
|
+
:return: A 2D point.
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
return cls(x=x, y=y)
|
|
46
|
+
|
|
47
|
+
@classmethod
|
|
48
|
+
def zero(cls) -> Point2:
|
|
49
|
+
"""
|
|
50
|
+
Create a point at the origin.
|
|
51
|
+
|
|
52
|
+
:return: A point at (0, 0).
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
return cls(x=0.0, y=0.0)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class Point3(Message):
|
|
59
|
+
"""
|
|
60
|
+
A point representing a position in 3D space.
|
|
61
|
+
|
|
62
|
+
Used in 3D graphics and spatial coordinate systems.
|
|
63
|
+
"""
|
|
64
|
+
|
|
65
|
+
_type = "antioch/point3"
|
|
66
|
+
x: float
|
|
67
|
+
y: float
|
|
68
|
+
z: float
|
|
69
|
+
|
|
70
|
+
def __repr__(self) -> str:
|
|
71
|
+
"""
|
|
72
|
+
Return a readable string representation.
|
|
73
|
+
|
|
74
|
+
:return: String representation.
|
|
75
|
+
"""
|
|
76
|
+
|
|
77
|
+
return f"Point3(x={self.x}, y={self.y}, z={self.z})"
|
|
78
|
+
|
|
79
|
+
def __str__(self) -> str:
|
|
80
|
+
"""
|
|
81
|
+
Return a readable string representation.
|
|
82
|
+
|
|
83
|
+
:return: String representation.
|
|
84
|
+
"""
|
|
85
|
+
|
|
86
|
+
return f"Point3(x={self.x}, y={self.y}, z={self.z})"
|
|
87
|
+
|
|
88
|
+
@classmethod
|
|
89
|
+
def new(cls, x: float, y: float, z: float) -> Point3:
|
|
90
|
+
"""
|
|
91
|
+
Create a new 3D point.
|
|
92
|
+
|
|
93
|
+
:param x: The x coordinate.
|
|
94
|
+
:param y: The y coordinate.
|
|
95
|
+
:param z: The z coordinate.
|
|
96
|
+
:return: A 3D point.
|
|
97
|
+
"""
|
|
98
|
+
|
|
99
|
+
return cls(x=x, y=y, z=z)
|
|
100
|
+
|
|
101
|
+
@classmethod
|
|
102
|
+
def zero(cls) -> Point3:
|
|
103
|
+
"""
|
|
104
|
+
Create a point at the origin.
|
|
105
|
+
|
|
106
|
+
:return: A point at (0, 0, 0).
|
|
107
|
+
"""
|
|
108
|
+
|
|
109
|
+
return cls(x=0.0, y=0.0, z=0.0)
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import struct
|
|
2
|
+
|
|
3
|
+
from pydantic import Field, model_validator
|
|
4
|
+
|
|
5
|
+
from common.message.base import Message
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class PointCloud(Message):
|
|
9
|
+
"""
|
|
10
|
+
A collection of 3D points.
|
|
11
|
+
|
|
12
|
+
:param frame_id: Frame of reference.
|
|
13
|
+
:param x: X coordinates of points.
|
|
14
|
+
:param y: Y coordinates of points.
|
|
15
|
+
:param z: Z coordinates of points.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
_type = "antioch/point_cloud"
|
|
19
|
+
frame_id: str = Field(default="", description="Frame of reference")
|
|
20
|
+
x: list[float] = Field(description="X coordinates of points")
|
|
21
|
+
y: list[float] = Field(description="Y coordinates of points")
|
|
22
|
+
z: list[float] = Field(description="Z coordinates of points")
|
|
23
|
+
|
|
24
|
+
@model_validator(mode="after")
|
|
25
|
+
def validate_array_lengths(self) -> "PointCloud":
|
|
26
|
+
"""
|
|
27
|
+
Validate that all coordinate arrays have the same length.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
lengths = [len(self.x), len(self.y), len(self.z)]
|
|
31
|
+
|
|
32
|
+
if len(set(lengths)) > 1:
|
|
33
|
+
raise ValueError(f"All coordinate arrays must have the same length: x={len(self.x)}, y={len(self.y)}, z={len(self.z)}")
|
|
34
|
+
|
|
35
|
+
return self
|
|
36
|
+
|
|
37
|
+
def to_bytes(self) -> bytes:
|
|
38
|
+
"""
|
|
39
|
+
Pack point cloud data into bytes for Foxglove.
|
|
40
|
+
|
|
41
|
+
:return: Packed data with x, y, z for each point.
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
data = bytearray()
|
|
45
|
+
for i in range(len(self.x)):
|
|
46
|
+
data.extend(struct.pack("<fff", self.x[i], self.y[i], self.z[i]))
|
|
47
|
+
|
|
48
|
+
return bytes(data)
|
|
49
|
+
|
|
50
|
+
@staticmethod
|
|
51
|
+
def combine(point_clouds: list["PointCloud"], frame_id: str = "") -> "PointCloud":
|
|
52
|
+
"""
|
|
53
|
+
Combine multiple point clouds into a single point cloud.
|
|
54
|
+
|
|
55
|
+
:param point_clouds: List of point clouds to combine.
|
|
56
|
+
:param frame_id: Frame of reference for the combined point cloud.
|
|
57
|
+
:return: Combined point cloud with all points.
|
|
58
|
+
"""
|
|
59
|
+
|
|
60
|
+
all_x = sum((pc.x for pc in point_clouds), [])
|
|
61
|
+
all_y = sum((pc.y for pc in point_clouds), [])
|
|
62
|
+
all_z = sum((pc.z for pc in point_clouds), [])
|
|
63
|
+
return PointCloud(frame_id=frame_id, x=all_x, y=all_y, z=all_z)
|