antioch-py 2.0.6__py3-none-any.whl → 3.0.12__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.

Files changed (109) hide show
  1. antioch/__init__.py +101 -0
  2. antioch/{module/execution.py → execution.py} +1 -1
  3. antioch/{module/input.py → input.py} +2 -4
  4. antioch/{module/module.py → module.py} +17 -34
  5. antioch/{module/node.py → node.py} +17 -16
  6. {antioch_py-2.0.6.dist-info → antioch_py-3.0.12.dist-info}/METADATA +8 -11
  7. antioch_py-3.0.12.dist-info/RECORD +61 -0
  8. {antioch_py-2.0.6.dist-info → antioch_py-3.0.12.dist-info}/WHEEL +1 -1
  9. antioch_py-3.0.12.dist-info/licenses/LICENSE +21 -0
  10. common/ark/__init__.py +6 -16
  11. common/ark/ark.py +23 -60
  12. common/ark/hardware.py +13 -37
  13. common/ark/kinematics.py +1 -1
  14. common/ark/module.py +22 -0
  15. common/ark/node.py +46 -3
  16. common/ark/scheduler.py +2 -29
  17. common/ark/sim.py +1 -1
  18. {antioch/module → common/ark}/token.py +17 -0
  19. common/assets/rigging.usd +0 -0
  20. common/constants.py +83 -4
  21. common/core/__init__.py +37 -24
  22. common/core/auth.py +87 -114
  23. common/core/container.py +261 -0
  24. common/core/registry.py +131 -152
  25. common/core/rome.py +251 -0
  26. common/core/telemetry.py +176 -0
  27. common/core/types.py +219 -0
  28. common/message/__init__.py +19 -3
  29. common/message/annotation.py +174 -23
  30. common/message/array.py +25 -1
  31. common/message/camera.py +23 -1
  32. common/message/color.py +32 -6
  33. common/message/detection.py +40 -0
  34. common/message/foxglove.py +20 -0
  35. common/message/frame.py +71 -7
  36. common/message/image.py +58 -9
  37. common/message/imu.py +24 -4
  38. common/message/joint.py +69 -10
  39. common/message/log.py +52 -7
  40. common/message/pir.py +22 -5
  41. common/message/plot.py +57 -0
  42. common/message/point.py +55 -6
  43. common/message/point_cloud.py +55 -19
  44. common/message/pose.py +59 -19
  45. common/message/quaternion.py +105 -92
  46. common/message/radar.py +195 -29
  47. common/message/twist.py +34 -0
  48. common/message/types.py +40 -5
  49. common/message/vector.py +180 -245
  50. common/sim/__init__.py +49 -0
  51. common/sim/objects.py +460 -0
  52. common/sim/state.py +11 -0
  53. common/utils/comms.py +30 -12
  54. common/utils/logger.py +26 -7
  55. antioch/message.py +0 -87
  56. antioch/module/__init__.py +0 -53
  57. antioch/session/__init__.py +0 -150
  58. antioch/session/ark.py +0 -504
  59. antioch/session/asset.py +0 -65
  60. antioch/session/error.py +0 -80
  61. antioch/session/record.py +0 -158
  62. antioch/session/scene.py +0 -1521
  63. antioch/session/session.py +0 -220
  64. antioch/session/task.py +0 -323
  65. antioch/session/views/__init__.py +0 -40
  66. antioch/session/views/animation.py +0 -189
  67. antioch/session/views/articulation.py +0 -245
  68. antioch/session/views/basis_curve.py +0 -186
  69. antioch/session/views/camera.py +0 -92
  70. antioch/session/views/collision.py +0 -75
  71. antioch/session/views/geometry.py +0 -74
  72. antioch/session/views/ground_plane.py +0 -63
  73. antioch/session/views/imu.py +0 -73
  74. antioch/session/views/joint.py +0 -64
  75. antioch/session/views/light.py +0 -175
  76. antioch/session/views/pir_sensor.py +0 -140
  77. antioch/session/views/radar.py +0 -73
  78. antioch/session/views/rigid_body.py +0 -282
  79. antioch/session/views/xform.py +0 -119
  80. antioch_py-2.0.6.dist-info/RECORD +0 -99
  81. antioch_py-2.0.6.dist-info/entry_points.txt +0 -2
  82. common/core/agent.py +0 -296
  83. common/core/task.py +0 -36
  84. common/rome/__init__.py +0 -9
  85. common/rome/client.py +0 -430
  86. common/rome/error.py +0 -16
  87. common/session/__init__.py +0 -54
  88. common/session/environment.py +0 -31
  89. common/session/sim.py +0 -240
  90. common/session/views/__init__.py +0 -263
  91. common/session/views/animation.py +0 -73
  92. common/session/views/articulation.py +0 -184
  93. common/session/views/basis_curve.py +0 -102
  94. common/session/views/camera.py +0 -147
  95. common/session/views/collision.py +0 -59
  96. common/session/views/geometry.py +0 -102
  97. common/session/views/ground_plane.py +0 -41
  98. common/session/views/imu.py +0 -66
  99. common/session/views/joint.py +0 -81
  100. common/session/views/light.py +0 -96
  101. common/session/views/pir_sensor.py +0 -115
  102. common/session/views/radar.py +0 -82
  103. common/session/views/rigid_body.py +0 -236
  104. common/session/views/viewport.py +0 -21
  105. common/session/views/xform.py +0 -39
  106. common/utils/usd.py +0 -12
  107. /antioch/{module/clock.py → clock.py} +0 -0
  108. {antioch_py-2.0.6.dist-info → antioch_py-3.0.12.dist-info}/top_level.txt +0 -0
  109. /common/message/{base.py → message.py} +0 -0
common/sim/__init__.py ADDED
@@ -0,0 +1,49 @@
1
+ from common.sim.objects import (
2
+ ArticulationConfig,
3
+ ArticulationJointConfig,
4
+ BasisCurveConfig,
5
+ BodyType,
6
+ CameraConfig,
7
+ CameraMode,
8
+ DistortionModel,
9
+ GeometryConfig,
10
+ GeometryType,
11
+ GroundPlaneConfig,
12
+ ImuConfig,
13
+ JointAxis,
14
+ JointConfig,
15
+ JointType,
16
+ LightConfig,
17
+ LightType,
18
+ MeshApproximation,
19
+ PirSensorConfig,
20
+ RadarConfig,
21
+ RadarScanParams,
22
+ RigidBodyConfig,
23
+ )
24
+ from common.sim.state import SimulationState
25
+
26
+ __all__ = [
27
+ "ArticulationConfig",
28
+ "ArticulationJointConfig",
29
+ "BasisCurveConfig",
30
+ "BodyType",
31
+ "CameraConfig",
32
+ "CameraMode",
33
+ "DistortionModel",
34
+ "GeometryConfig",
35
+ "GeometryType",
36
+ "GroundPlaneConfig",
37
+ "ImuConfig",
38
+ "JointAxis",
39
+ "JointConfig",
40
+ "JointType",
41
+ "LightConfig",
42
+ "LightType",
43
+ "MeshApproximation",
44
+ "PirSensorConfig",
45
+ "RadarConfig",
46
+ "RadarScanParams",
47
+ "RigidBodyConfig",
48
+ "SimulationState",
49
+ ]
common/sim/objects.py ADDED
@@ -0,0 +1,460 @@
1
+ from enum import Enum
2
+
3
+ from pydantic import Field
4
+ from pydantic.alias_generators import to_camel
5
+
6
+ from common.message import CameraInfo, Message, Pose, Vector3
7
+
8
+
9
+ class GeometryType(str, Enum):
10
+ """
11
+ Supported geometry types.
12
+ """
13
+
14
+ SPHERE = "sphere"
15
+ CUBE = "cube"
16
+ CYLINDER = "cylinder"
17
+ CONE = "cone"
18
+ CAPSULE = "capsule"
19
+ MESH = "mesh"
20
+
21
+
22
+ class MeshApproximation(str, Enum):
23
+ """
24
+ Collision mesh approximation type.
25
+
26
+ Values are stored in snake_case for consistency with Rust. Use to_usd()
27
+ to get the camelCase format required by USD/Isaac Sim.
28
+ """
29
+
30
+ NONE = "none"
31
+ CONVEX_HULL = "convex_hull"
32
+ CONVEX_DECOMPOSITION = "convex_decomposition"
33
+ BOUNDING_SPHERE = "bounding_sphere"
34
+ BOUNDING_CUBE = "bounding_cube"
35
+ MESH_SIMPLIFICATION = "mesh_simplification"
36
+ SDF = "sdf"
37
+ SPHERE_FILL = "sphere_fill"
38
+
39
+ def to_usd(self) -> str:
40
+ """
41
+ Convert to USD camelCase format.
42
+
43
+ :return: The USD camelCase format.
44
+ """
45
+
46
+ return to_camel(self.value)
47
+
48
+
49
+ class GeometryConfig(Message):
50
+ """
51
+ Configuration for creating geometry.
52
+ """
53
+
54
+ geometry_type: GeometryType = Field(description="Geometry type")
55
+ radius: float | None = Field(default=None, description="Radius for sphere/cylinder/cone/capsule")
56
+ height: float | None = Field(default=None, description="Height for cylinder/cone/capsule")
57
+ size: float | None = Field(default=None, description="Size for cube (uniform)")
58
+ color: Vector3 | None = Field(default=None, description="RGB color (0-1)")
59
+ opacity: float = Field(default=1.0, description="Opacity (0=transparent, 1=opaque)")
60
+ enable_collision: bool = Field(default=True, description="Whether to enable collision on this geometry")
61
+ static_friction: float = Field(default=0.5, description="Static friction coefficient")
62
+ dynamic_friction: float = Field(default=0.5, description="Dynamic friction coefficient")
63
+ restitution: float = Field(default=0.2, description="Restitution (bounciness)")
64
+ mesh_file_path: str | None = Field(default=None, description="Path to mesh file (FBX, OBJ, glTF, STL, etc.) - required for MESH type")
65
+ mesh_approximation: MeshApproximation = Field(default=MeshApproximation.CONVEX_DECOMPOSITION, description="Mesh approximation method")
66
+ contact_offset: float | None = Field(default=None, description="Distance at which collision detection begins")
67
+ rest_offset: float | None = Field(default=None, description="Minimum separation distance between objects")
68
+ torsional_patch_radius: float | None = Field(default=None, description="Radius for torsional friction calculations")
69
+ min_torsional_patch_radius: float | None = Field(default=None, description="Minimum radius for torsional friction")
70
+
71
+
72
+ class CameraMode(str, Enum):
73
+ """
74
+ Camera capture modes.
75
+ """
76
+
77
+ RGB = "rgb"
78
+ DEPTH = "depth"
79
+
80
+
81
+ class DistortionModel(str, Enum):
82
+ """
83
+ Camera lens distortion model types.
84
+ """
85
+
86
+ PINHOLE = "pinhole"
87
+ OPENCV_PINHOLE = "opencv_pinhole"
88
+ OPENCV_FISHEYE = "opencv_fisheye"
89
+ FTHETA = "ftheta"
90
+ KANNALA_BRANDT_K3 = "kannala_brandt_k3"
91
+ RAD_TAN_THIN_PRISM = "rad_tan_thin_prism"
92
+
93
+
94
+ class CameraConfig(Message):
95
+ """
96
+ Configuration for camera sensor.
97
+ """
98
+
99
+ mode: CameraMode = Field(default=CameraMode.RGB, description="Camera capture mode (RGB or depth)")
100
+ frequency: int = Field(default=30, description="Camera update frequency in Hz")
101
+ width: int = Field(default=640, description="Image width in pixels")
102
+ height: int = Field(default=480, description="Image height in pixels")
103
+ focal_length: float = Field(default=50.0, description="Focal length in mm")
104
+ sensor_width: float = Field(default=20.4, description="Physical sensor width in mm")
105
+ sensor_height: float = Field(default=15.3, description="Physical sensor height in mm")
106
+ near_clip: float = Field(default=0.1, description="Near clipping plane in meters")
107
+ far_clip: float = Field(default=1000.0, description="Far clipping plane in meters")
108
+ f_stop: float = Field(default=0.0, description="F-stop for depth of field")
109
+ focus_distance: float = Field(default=10.0, description="Focus distance in meters")
110
+ principal_point_x: float = Field(default=0.0, description="Principal point X offset in pixels")
111
+ principal_point_y: float = Field(default=0.0, description="Principal point Y offset in pixels")
112
+ distortion_model: DistortionModel = Field(default=DistortionModel.PINHOLE, description="Lens distortion model")
113
+ distortion_coefficients: list[float] | None = Field(default=None, description="Distortion coefficients")
114
+
115
+ def to_camera_info(self, frame_id: str = "camera_optical_frame") -> CameraInfo:
116
+ """
117
+ Convert camera configuration to CameraInfo with calculated intrinsics.
118
+
119
+ :param frame_id: The coordinate frame ID for the camera.
120
+ :return: CameraInfo with full intrinsics and distortion parameters.
121
+ """
122
+
123
+ # Convert mm to pixels for focal length
124
+ fx = self.width * self.focal_length / self.sensor_width
125
+ fy = self.height * self.focal_length / self.sensor_height
126
+
127
+ # Principal point (image center + offset)
128
+ cx = self.width / 2.0 + self.principal_point_x
129
+ cy = self.height / 2.0 + self.principal_point_y
130
+
131
+ return CameraInfo(
132
+ width=self.width,
133
+ height=self.height,
134
+ fx=fx,
135
+ fy=fy,
136
+ cx=cx,
137
+ cy=cy,
138
+ distortion_model=self.distortion_model.value,
139
+ distortion_coefficients=self.distortion_coefficients or [],
140
+ frame_id=frame_id,
141
+ )
142
+
143
+
144
+ class ArticulationJointConfig(Message):
145
+ """
146
+ Complete configuration for a single joint in an articulation.
147
+ """
148
+
149
+ path: str = Field(description="Name of the joint/DOF")
150
+ stiffness: float | None = Field(default=None, description="PD controller stiffness (Kp)")
151
+ damping: float | None = Field(default=None, description="PD controller damping (Kd)")
152
+ lower_limit: float | None = Field(default=None, description="Lower joint limit")
153
+ upper_limit: float | None = Field(default=None, description="Upper joint limit")
154
+ armature: float | None = Field(default=None, description="Joint armature")
155
+ friction_coefficient: float | None = Field(default=None, description="Joint friction coefficient")
156
+ max_velocity: float | None = Field(default=None, description="Maximum joint velocity")
157
+ max_effort: float | None = Field(default=None, description="Maximum joint effort")
158
+
159
+
160
+ class ArticulationConfig(Message):
161
+ """
162
+ Configuration for applying articulation root to a prim.
163
+ """
164
+
165
+ solver_position_iterations: int = Field(default=32, description="Number of position iterations for the solver")
166
+ solver_velocity_iterations: int = Field(default=1, description="Number of velocity iterations for the solver")
167
+ sleep_threshold: float = Field(default=0.005, description="Sleep threshold for the articulation")
168
+ stabilization_threshold: float = Field(default=0.001, description="Stabilization threshold for the articulation")
169
+ enable_self_collisions: bool = Field(default=False, description="Whether to enable self-collisions")
170
+ joint_configs: list[ArticulationJointConfig] = Field(default_factory=list, description="Per-joint configurations")
171
+
172
+
173
+ class JointType(str, Enum):
174
+ """
175
+ Types of joints supported by the simulator.
176
+ """
177
+
178
+ REVOLUTE = "revolute"
179
+ PRISMATIC = "prismatic"
180
+ FIXED = "fixed"
181
+ SPHERICAL = "spherical"
182
+ DISTANCE = "distance"
183
+ GENERIC = "generic"
184
+
185
+
186
+ class JointAxis(str, Enum):
187
+ """
188
+ Axis of motion for joints.
189
+ """
190
+
191
+ X = "x"
192
+ Y = "y"
193
+ Z = "z"
194
+
195
+
196
+ class JointConfig(Message):
197
+ """
198
+ Configuration for a joint object that connects two bodies.
199
+
200
+ The joint pivot point is defined by two local poses:
201
+ - parent_pose: Position/orientation of joint in parent's local frame.
202
+ - child_pose: Position/orientation of joint in child's local frame.
203
+ """
204
+
205
+ # Joint relationships
206
+ parent_path: str = Field(description="USD path to parent body")
207
+ child_path: str = Field(description="USD path to child body")
208
+
209
+ # Transforms - defines where joint attaches on each body
210
+ pose: Pose = Field(default_factory=Pose.identity, description="Joint pose in parent's local frame")
211
+ child_pose: Pose = Field(default_factory=Pose.identity, description="Joint pose in child's local frame")
212
+
213
+ # Joint properties
214
+ joint_type: JointType = Field(default=JointType.FIXED, description="Type of joint motion allowed")
215
+ axis: JointAxis = Field(default=JointAxis.X, description="Axis of motion for non-fixed joints")
216
+
217
+ # Motion limits (for revolute: degrees, for prismatic: meters)
218
+ lower_limit: float | None = Field(default=None, description="Lower motion limit")
219
+ upper_limit: float | None = Field(default=None, description="Upper motion limit")
220
+
221
+ # Physics properties
222
+ friction: float = Field(default=0.01, description="Joint friction (unitless)")
223
+ armature: float = Field(default=0.1, description="Joint armature (kg for prismatic, kg-m^2 for revolute)")
224
+
225
+ # Special properties
226
+ exclude_from_articulation: bool = Field(default=False, description="Whether to exclude this joint from articulation")
227
+
228
+
229
+ class BodyType(str, Enum):
230
+ """
231
+ Type of rigid body.
232
+ """
233
+
234
+ DYNAMIC = "dynamic"
235
+ KINEMATIC = "kinematic"
236
+
237
+
238
+ class RigidBodyConfig(Message):
239
+ """
240
+ Configuration for rigid body physics properties.
241
+
242
+ Note: Collision properties (friction, restitution, mesh approximation) are configured
243
+ on the geometry, not the rigid body.
244
+ """
245
+
246
+ body_type: BodyType = Field(default=BodyType.DYNAMIC, description="Type of rigid body")
247
+ mass: float = Field(default=1.0, description="Mass in kg")
248
+ density: float | None = Field(default=None, description="Density in kg/m³ (alternative to mass)")
249
+ center_of_mass: Vector3 | None = Field(default=None, description="Center of mass offset in body frame")
250
+ diagonal_inertia: Vector3 | None = Field(default=None, description="Diagonal inertia values (Ixx, Iyy, Izz)")
251
+ principal_axes: Vector3 | None = Field(default=None, description="Principal axes orientation as RPY")
252
+ sleep_threshold: float | None = Field(default=None, description="Mass-normalized kinetic energy threshold for sleeping")
253
+ linear_velocity: Vector3 | None = Field(default=None, description="Initial linear velocity")
254
+ angular_velocity: Vector3 | None = Field(default=None, description="Initial angular velocity")
255
+
256
+
257
+ class LightType(str, Enum):
258
+ """
259
+ Supported light types.
260
+ """
261
+
262
+ SPHERE = "sphere"
263
+ RECT = "rect"
264
+ DISK = "disk"
265
+ CYLINDER = "cylinder"
266
+ DISTANT = "distant"
267
+ DOME = "dome"
268
+
269
+
270
+ class LightConfig(Message):
271
+ """
272
+ Configuration for creating a light.
273
+ """
274
+
275
+ light_type: LightType = Field(default=LightType.SPHERE, description="Light type")
276
+ intensity: float = Field(default=30000.0, description="Light intensity")
277
+ exposure: float = Field(default=0.0, description="Light exposure")
278
+ color: Vector3 = Field(default_factory=Vector3.ones, description="RGB color (0-1)")
279
+ radius: float = Field(default=0.1, description="Radius for sphere lights (meters)")
280
+ width: float | None = Field(default=None, description="Width for rect lights (meters)")
281
+ height: float | None = Field(default=None, description="Height for rect/cylinder lights (meters)")
282
+ length: float | None = Field(default=None, description="Length for cylinder lights (meters)")
283
+ angle: float | None = Field(default=None, description="Angle for distant lights (degrees)")
284
+ texture_file: str | None = Field(default=None, description="Texture file for dome lights")
285
+
286
+
287
+ class GroundPlaneConfig(Message):
288
+ """
289
+ Configuration for creating a ground plane.
290
+ """
291
+
292
+ size: float = Field(default=5000.0, description="Size of the ground plane in meters")
293
+ z_position: float = Field(default=0.0, description="Z position of the ground plane")
294
+ color: Vector3 = Field(default_factory=lambda: Vector3(x=0.5, y=0.5, z=0.5), description="RGB color (0-1)")
295
+ static_friction: float = Field(default=0.5, description="Static friction coefficient")
296
+ dynamic_friction: float = Field(default=0.5, description="Dynamic friction coefficient")
297
+ restitution: float = Field(default=0.0, description="Restitution (bounciness)")
298
+
299
+
300
+ class ImuConfig(Message):
301
+ """
302
+ Configuration for IMU sensor.
303
+ """
304
+
305
+ frequency: int | None = Field(default=None, description="Sensor update frequency in Hz (optional, defaults to physics rate)")
306
+ linear_acceleration_filter_size: int = Field(default=10, description="Filter window size for linear acceleration")
307
+ angular_velocity_filter_size: int = Field(default=10, description="Filter window size for angular velocity")
308
+ orientation_filter_size: int = Field(default=10, description="Filter window size for orientation")
309
+
310
+
311
+ class RadarScanParams(Message):
312
+ """
313
+ Per-scan configuration for RTX radar.
314
+ """
315
+
316
+ max_azimuth: float = Field(default=66.0, description="Maximum azimuth angle in degrees (±FOV from center)")
317
+ max_elevation: float = Field(default=20.0, description="Maximum elevation angle in degrees (±FOV from center)")
318
+
319
+ max_range: float = Field(default=200.0, description="Maximum detection range in meters")
320
+ range_resolution: float = Field(default=0.4, description="Range resolution in meters")
321
+
322
+ azimuth_resolution: float = Field(default=1.3, description="Azimuth resolution at boresight in degrees")
323
+ elevation_resolution: float = Field(default=5.0, description="Elevation resolution at boresight in degrees")
324
+
325
+ azimuth_noise: float = Field(default=0.0, description="Azimuth measurement noise standard deviation in radians")
326
+ range_noise: float = Field(default=0.0, description="Range measurement noise standard deviation in meters")
327
+
328
+ time_offset_usec: int | None = Field(default=None, description="Time offset from frame time in microseconds")
329
+ vel_resolution: float | None = Field(default=None, description="Velocity resolution in m/s")
330
+
331
+ bins_from_spec: bool = Field(default=False, description="Whether to use bins from spec")
332
+ r_bins: int | None = Field(default=None, description="Number of range bins")
333
+ v_bins: int | None = Field(default=None, description="Number of velocity bins")
334
+ az_bins: int | None = Field(default=None, description="Number of azimuth bins")
335
+ el_bins: int | None = Field(default=None, description="Number of elevation bins")
336
+
337
+ cfar_offset: float | None = Field(default=None, description="CFAR threshold offset multiplier")
338
+ cfar_noise_mean: float | None = Field(default=None, description="CFAR noise mean")
339
+ cfar_noise_sdev: float | None = Field(default=None, description="CFAR noise standard deviation")
340
+ cfar_min_val: float | None = Field(default=None, description="CFAR minimum value threshold")
341
+
342
+ # CFAR window sizes: _t = training cells, _g = guard cells
343
+ cfar_rn_t: int | None = Field(default=None, description="CFAR range training cells")
344
+ cfar_rn_g: int | None = Field(default=None, description="CFAR range guard cells")
345
+ cfar_vn_t: int | None = Field(default=None, description="CFAR velocity training cells")
346
+ cfar_vn_g: int | None = Field(default=None, description="CFAR velocity guard cells")
347
+ cfar_azn_t: int | None = Field(default=None, description="CFAR azimuth training cells")
348
+ cfar_azn_g: int | None = Field(default=None, description="CFAR azimuth guard cells")
349
+ cfar_eln_t: int | None = Field(default=None, description="CFAR elevation training cells")
350
+ cfar_eln_g: int | None = Field(default=None, description="CFAR elevation guard cells")
351
+
352
+ rcs_tuning_coefficients: list[float] | None = Field(
353
+ default=None,
354
+ description="RCS tuning polynomial coefficients [offset, linear, quadratic]",
355
+ )
356
+
357
+ max_vel_mps_sequence: list[float] | None = Field(
358
+ default=None,
359
+ description="Maximum unambiguous velocity sequence in m/s (float array)",
360
+ )
361
+
362
+
363
+ class RadarConfig(Message):
364
+ """
365
+ Configuration for RTX radar sensor.
366
+ """
367
+
368
+ frequency: int | None = Field(default=10, description="Sensor update frequency in Hz")
369
+ scans: list[RadarScanParams] = Field(default_factory=lambda: [RadarScanParams()], description="Per-scan configuration")
370
+
371
+ cfar_mode: str | None = Field(default=None, description='CFAR mode (e.g. "2D", "4D")')
372
+ antenna_gain_mode: str | None = Field(default=None, description='Antenna gain mode (e.g. "COSINEFALLOFF")')
373
+ instance_time_offset_usec: int | None = Field(default=None, description="Radar instance time offset in microseconds")
374
+ wavelength_mm: float | None = Field(default=None, description="Operational wavelength in mm (e.g. 5.0 for 60GHz, 3.9 for 77GHz)")
375
+
376
+ @property
377
+ def num_scans(self) -> int:
378
+ return len(self.scans)
379
+
380
+
381
+ class PirSensorConfig(Message):
382
+ """
383
+ Configuration for PIR (Passive Infrared) motion sensor.
384
+
385
+ PIR sensors detect infrared radiation changes caused by moving warm objects.
386
+ Each PIR prim represents a single sensor (single-sensor model).
387
+ """
388
+
389
+ # Core sensor parameters
390
+ update_rate_hz: float = Field(default=60.0, description="Sensor update frequency in Hz")
391
+ max_range: float = Field(default=20.0, description="Maximum detection range in meters")
392
+
393
+ # FOV configuration
394
+ horiz_fov_deg: float = Field(default=150.0, description="Horizontal field-of-view in degrees")
395
+ vert_fov_deg: float = Field(default=60.0, description="Vertical field-of-view in degrees (symmetric)")
396
+
397
+ # Ray configuration
398
+ sensor_rays_horiz: int = Field(default=128, description="Number of rays per sensor in horizontal direction")
399
+ sensor_rays_vert: int = Field(default=16, description="Number of rays per sensor in vertical direction")
400
+
401
+ # DSP / electronics parameters
402
+ gain: float = Field(default=0.015, description="Amplifier gain")
403
+ hp_corner_hz: float = Field(default=0.4, description="High-pass filter corner frequency in Hz")
404
+ lp_corner_hz: float = Field(default=10.0, description="Low-pass filter corner frequency in Hz")
405
+ blind_time_s: float = Field(default=0.5, description="Blind time after detection in seconds")
406
+ pulse_counter: int = Field(default=2, description="Number of pulses required to trigger detection (1-4)")
407
+ window_time_s: float = Field(default=2.0, description="Window time for pulse counting in seconds")
408
+ count_mode: int = Field(default=0, description="Pulse counting mode (0: sign change required, 1: any crossing)")
409
+
410
+ # Lens parameters
411
+ lens_transmission: float = Field(default=0.9, description="Lens transmission coefficient (0-1)")
412
+ lens_segments_h: int = Field(default=6, description="Number of horizontal lens segments (facets)")
413
+
414
+ # Environment parameters
415
+ ambient_temp_c: float = Field(default=20.0, description="Ambient temperature in Celsius")
416
+
417
+ # Hard-coded threshold (if not none) overrides auto-calibration
418
+ threshold: float | None = Field(default=None, description="Detection threshold (auto-calibrated if None)")
419
+ threshold_scale: float = Field(default=1.0, description="Scale factor applied to auto-calibrated threshold")
420
+
421
+ # Pyroelectric element parameters
422
+ thermal_time_constant_s: float = Field(default=0.2, description="Element thermal time constant in seconds")
423
+ pyro_responsivity: float = Field(default=4000.0, description="Pyroelectric responsivity scaling factor")
424
+ noise_amplitude: float = Field(default=20e-6, description="Thermal/electronic noise amplitude")
425
+
426
+ # Auto-threshold calibration parameters
427
+ target_delta_t: float = Field(default=10.0, description="Target temperature difference for threshold calibration in Celsius")
428
+ target_distance: float = Field(default=5.0, description="Target distance for threshold calibration in meters")
429
+ target_emissivity: float = Field(default=0.98, description="Target emissivity for threshold calibration")
430
+ target_velocity_mps: float = Field(default=1.0, description="Target velocity for threshold calibration in m/s")
431
+
432
+
433
+ class BasisCurveConfig(Message):
434
+ """
435
+ Configuration for creating basis curves in simulation.
436
+
437
+ Supports two curve types:
438
+ - semi_circle: Creates an arc around a center point
439
+ - line: Creates a straight line between two points
440
+ """
441
+
442
+ # Curve type
443
+ curve_type: str = Field(default="line", description="Type of curve: 'semi_circle' or 'line'")
444
+
445
+ # Common parameters
446
+ guide: bool = Field(default=False, description="If True, curve is invisible to cameras")
447
+ color: Vector3 | None = Field(default=None, description="RGB color (0-1 range)")
448
+ width: float = Field(default=0.005, description="Width of the curve")
449
+
450
+ # Semi-circle parameters
451
+ center: Vector3 | None = Field(default=None, description="Center point for semi-circle")
452
+ radius: float | None = Field(default=None, description="Radius for semi-circle")
453
+ min_angle_deg: float | None = Field(default=None, description="Start angle in degrees for semi-circle")
454
+ max_angle_deg: float | None = Field(default=None, description="End angle in degrees for semi-circle")
455
+
456
+ # Line parameters (either end point OR angle_deg+length)
457
+ start: Vector3 | None = Field(default=None, description="Start point for line")
458
+ end: Vector3 | None = Field(default=None, description="End point for line")
459
+ angle_deg: float | None = Field(default=None, description="Angle in degrees for line (from start)")
460
+ length: float | None = Field(default=None, description="Length for line (with angle_deg)")
common/sim/state.py ADDED
@@ -0,0 +1,11 @@
1
+ from enum import Enum
2
+
3
+
4
+ class SimulationState(str, Enum):
5
+ """
6
+ Represents the current state of the simulation.
7
+ """
8
+
9
+ PLAYING = "playing"
10
+ PAUSED = "paused"
11
+ STOPPED = "stopped"
common/utils/comms.py CHANGED
@@ -1,12 +1,12 @@
1
1
  from __future__ import annotations
2
2
 
3
- import os
4
3
  from collections.abc import Callable
5
4
  from threading import Event
6
5
  from typing import Any, TypeVar
7
6
 
8
7
  import zenoh
9
8
 
9
+ from common.constants import SHM_ENABLED, SHM_MESSAGE_SIZE_THRESHOLD_BYTES, SHM_POOL_SIZE_BYTES
10
10
  from common.message import Message
11
11
 
12
12
  DEFAULT_COMMS_SUBSCRIBER_RING_DEPTH = 4096
@@ -25,16 +25,38 @@ class CommsSession:
25
25
  def __init__(self):
26
26
  """
27
27
  Create a new communication session.
28
- """
29
28
 
30
- # Get router endpoint from environment or use localhost default
31
- router_endpoint = os.environ.get("ZENOH_ROUTER_ENDPOINT", "tcp/127.0.0.1:7447")
29
+ Uses peer mode with multicast discovery on the loopback interface for
30
+ automatic peer discovery without requiring a router.
31
+ """
32
32
 
33
33
  self.config = zenoh.Config()
34
+
35
+ # Enable peer mode (no Zenoh router)
34
36
  self.config.insert_json5("mode", '"peer"')
35
- self.config.insert_json5("listen/endpoints", '["tcp/0.0.0.0:0"]')
36
- self.config.insert_json5("scouting/multicast/enabled", "false")
37
- self.config.insert_json5("connect/endpoints", f'["{router_endpoint}"]') # Discovery via router
37
+
38
+ # Enable multicast discovery on loopback interface for peer discovery
39
+ self.config.insert_json5("scouting/multicast/enabled", "true")
40
+ self.config.insert_json5("scouting/multicast/interface", '"lo"')
41
+ self.config.insert_json5("listen/endpoints", '["tcp/127.0.0.1:0"]')
42
+
43
+ # Enable shared memory transport for high-performance IPC
44
+ # Falls back to TCP automatically when SHM is not available
45
+ if SHM_ENABLED:
46
+ self.config.insert_json5("transport/shared_memory/enabled", "true")
47
+ self.config.insert_json5(
48
+ "transport/shared_memory/transport_optimization/enabled",
49
+ "true",
50
+ )
51
+ self.config.insert_json5(
52
+ "transport/shared_memory/transport_optimization/pool_size",
53
+ str(SHM_POOL_SIZE_BYTES),
54
+ )
55
+ self.config.insert_json5(
56
+ "transport/shared_memory/transport_optimization/message_size_threshold",
57
+ str(SHM_MESSAGE_SIZE_THRESHOLD_BYTES),
58
+ )
59
+
38
60
  self.session = zenoh.open(self.config)
39
61
 
40
62
  def __del__(self) -> None:
@@ -91,11 +113,7 @@ class CommsSession:
91
113
 
92
114
  return CommsAsyncSubscriber(self.session, path)
93
115
 
94
- def declare_callback_subscriber(
95
- self,
96
- path: str,
97
- callback: Callable[[zenoh.Sample], None],
98
- ) -> zenoh.Subscriber[None]:
116
+ def declare_callback_subscriber(self, path: str, callback: Callable[[zenoh.Sample], None]) -> zenoh.Subscriber[None]:
99
117
  """
100
118
  Create a new callback subscriber for the given path.
101
119
 
common/utils/logger.py CHANGED
@@ -1,29 +1,36 @@
1
+ import re
2
+
1
3
  from common.message import Log, LogLevel, Message
2
4
  from common.utils.comms import CommsSession
3
5
 
6
+ # Valid channel pattern: alphanumeric, underscore, hyphen, period, slash
7
+ # Must start with alphanumeric, cannot have consecutive slashes or end with slash
8
+ CHANNEL_PATTERN = re.compile(r"^[a-zA-Z0-9](?:[a-zA-Z0-9_.\-]*(?:/[a-zA-Z0-9][a-zA-Z0-9_.\-]*)*)?$")
9
+
4
10
 
5
11
  class Logger:
6
12
  """
7
13
  Logger that publishes structured logs to the communication system.
14
+
15
+ Creates its own Zenoh session for publishing logs.
8
16
  """
9
17
 
10
18
  def __init__(
11
19
  self,
12
- comms: CommsSession,
13
20
  base_channel: str | None = None,
14
21
  debug: bool = False,
15
- print_logs: bool = False,
22
+ print_logs: bool = True,
16
23
  ) -> None:
17
24
  """
18
25
  Initialize the logger.
19
26
 
20
- :param comms: Comms session.
21
27
  :param base_channel: Optional base channel for logs and telemetry.
22
28
  :param debug: Whether to run in debug mode.
23
29
  :param print_logs: Whether to print logs to stdout.
24
30
  """
25
31
 
26
- self._log_publisher = comms.declare_publisher("_logs")
32
+ self._comms = CommsSession()
33
+ self._log_publisher = self._comms.declare_publisher("_logs")
27
34
  self._base_channel = base_channel
28
35
  self._debug = debug
29
36
  self._print_logs = print_logs
@@ -79,12 +86,16 @@ class Logger:
79
86
  """
80
87
  Record telemetry data from a Message or a JSON-serializable dictionary.
81
88
 
82
- :param channel: Telemetry channel (alphanumeric, underscore, hyphen, period, slash).
89
+ :param channel: Telemetry channel (alphanumeric with underscore/hyphen/period/slash).
83
90
  :param telemetry: The message or dict to record.
91
+ :raises ValueError: If channel format is invalid.
84
92
  """
85
93
 
86
- if not all(c.isalnum() or c in ("_", "-", ".", "/") for c in channel):
87
- raise ValueError(f"Invalid channel format: {channel}")
94
+ if not CHANNEL_PATTERN.match(channel):
95
+ raise ValueError(
96
+ f"Invalid channel '{channel}': must be alphanumeric with underscore/hyphen/period/slash, "
97
+ "start and end with alphanumeric, no consecutive slashes"
98
+ )
88
99
 
89
100
  # Pack telemetry data based on type
90
101
  if isinstance(telemetry, Message):
@@ -102,6 +113,14 @@ class Logger:
102
113
  )
103
114
  )
104
115
 
116
+ def close(self) -> None:
117
+ """
118
+ Close the logger and release resources.
119
+ """
120
+
121
+ self._log_publisher.close()
122
+ self._comms.close()
123
+
105
124
  def _log(self, level: LogLevel, message: str) -> None:
106
125
  """
107
126
  Send a log message through Zenoh.