spatialdds-ros 0.3.0__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.
Files changed (87) hide show
  1. spatialdds_idl18/PROVENANCE.md +66 -0
  2. spatialdds_idl18/__init__.py +9 -0
  3. spatialdds_idl18/_field_aliases.py +26 -0
  4. spatialdds_idl18/builtin/__init__.py +11 -0
  5. spatialdds_idl18/builtin/_types.py +28 -0
  6. spatialdds_idl18/spatial/__init__.py +33 -0
  7. spatialdds_idl18/spatial/anchors/__init__.py +20 -0
  8. spatialdds_idl18/spatial/anchors/_anchors.py +102 -0
  9. spatialdds_idl18/spatial/argeo/__init__.py +22 -0
  10. spatialdds_idl18/spatial/argeo/_argeo.py +97 -0
  11. spatialdds_idl18/spatial/common/__init__.py +27 -0
  12. spatialdds_idl18/spatial/common/_types.py +104 -0
  13. spatialdds_idl18/spatial/core/GnssService/__init__.py +17 -0
  14. spatialdds_idl18/spatial/core/GnssService/_core.py +27 -0
  15. spatialdds_idl18/spatial/core/__init__.py +40 -0
  16. spatialdds_idl18/spatial/core/_core.py +338 -0
  17. spatialdds_idl18/spatial/disco/__init__.py +34 -0
  18. spatialdds_idl18/spatial/disco/_discovery.py +252 -0
  19. spatialdds_idl18/spatial/events/__init__.py +29 -0
  20. spatialdds_idl18/spatial/events/_events.py +164 -0
  21. spatialdds_idl18/spatial/events/enums/__init__.py +19 -0
  22. spatialdds_idl18/spatial/events/enums/crossing_direction_enum/__init__.py +11 -0
  23. spatialdds_idl18/spatial/events/enums/crossing_direction_enum/_events.py +25 -0
  24. spatialdds_idl18/spatial/events/enums/event_state_enum/__init__.py +11 -0
  25. spatialdds_idl18/spatial/events/enums/event_state_enum/_events.py +26 -0
  26. spatialdds_idl18/spatial/events/enums/event_type_enum/__init__.py +11 -0
  27. spatialdds_idl18/spatial/events/enums/event_type_enum/_events.py +36 -0
  28. spatialdds_idl18/spatial/events/enums/severity_enum/__init__.py +11 -0
  29. spatialdds_idl18/spatial/events/enums/severity_enum/_events.py +26 -0
  30. spatialdds_idl18/spatial/events/enums/zone_kind_enum/__init__.py +11 -0
  31. spatialdds_idl18/spatial/events/enums/zone_kind_enum/_events.py +31 -0
  32. spatialdds_idl18/spatial/mapping/__init__.py +30 -0
  33. spatialdds_idl18/spatial/mapping/_mapping.py +168 -0
  34. spatialdds_idl18/spatial/mapping/enums/__init__.py +19 -0
  35. spatialdds_idl18/spatial/mapping/enums/alignment_method/__init__.py +11 -0
  36. spatialdds_idl18/spatial/mapping/enums/alignment_method/_mapping.py +31 -0
  37. spatialdds_idl18/spatial/mapping/enums/edge_type/__init__.py +11 -0
  38. spatialdds_idl18/spatial/mapping/enums/edge_type/_mapping.py +34 -0
  39. spatialdds_idl18/spatial/mapping/enums/map_event_kind/__init__.py +11 -0
  40. spatialdds_idl18/spatial/mapping/enums/map_event_kind/_mapping.py +29 -0
  41. spatialdds_idl18/spatial/mapping/enums/map_kind/__init__.py +11 -0
  42. spatialdds_idl18/spatial/mapping/enums/map_kind/_mapping.py +32 -0
  43. spatialdds_idl18/spatial/mapping/enums/map_status/__init__.py +11 -0
  44. spatialdds_idl18/spatial/mapping/enums/map_status/_mapping.py +27 -0
  45. spatialdds_idl18/spatial/owm/__init__.py +22 -0
  46. spatialdds_idl18/spatial/owm/_owm.py +103 -0
  47. spatialdds_idl18/spatial/semantics/__init__.py +22 -0
  48. spatialdds_idl18/spatial/semantics/_semantics.py +174 -0
  49. spatialdds_idl18/spatial/sensing/__init__.py +21 -0
  50. spatialdds_idl18/spatial/sensing/common/__init__.py +34 -0
  51. spatialdds_idl18/spatial/sensing/common/_common.py +192 -0
  52. spatialdds_idl18/spatial/sensing/lidar/__init__.py +32 -0
  53. spatialdds_idl18/spatial/sensing/lidar/_lidar.py +135 -0
  54. spatialdds_idl18/spatial/sensing/rad/__init__.py +33 -0
  55. spatialdds_idl18/spatial/sensing/rad/_rad.py +191 -0
  56. spatialdds_idl18/spatial/sensing/radio/__init__.py +22 -0
  57. spatialdds_idl18/spatial/sensing/radio/_radio.py +141 -0
  58. spatialdds_idl18/spatial/sensing/rf_beam/__init__.py +25 -0
  59. spatialdds_idl18/spatial/sensing/rf_beam/_rf_beam.py +117 -0
  60. spatialdds_idl18/spatial/sensing/vision/__init__.py +36 -0
  61. spatialdds_idl18/spatial/sensing/vision/_vision.py +170 -0
  62. spatialdds_idl18/spatial/slam_frontend/__init__.py +22 -0
  63. spatialdds_idl18/spatial/slam_frontend/_slam_frontend.py +144 -0
  64. spatialdds_idl18/spatial/vio/__init__.py +20 -0
  65. spatialdds_idl18/spatial/vio/_vio.py +134 -0
  66. spatialdds_ros/__init__.py +6 -0
  67. spatialdds_ros/adapter.py +336 -0
  68. spatialdds_ros/announce.py +231 -0
  69. spatialdds_ros/config.py +293 -0
  70. spatialdds_ros/frames.py +219 -0
  71. spatialdds_ros/kinematic.py +140 -0
  72. spatialdds_ros/qos.py +101 -0
  73. spatialdds_ros/recording.py +167 -0
  74. spatialdds_ros/robot_node.py +287 -0
  75. spatialdds_ros/standalone.py +221 -0
  76. spatialdds_ros/tempo.py +114 -0
  77. spatialdds_ros/topics.py +64 -0
  78. spatialdds_ros/transport.py +170 -0
  79. spatialdds_ros-0.3.0.data/data/share/ament_index/resource_index/packages/spatialdds_ros +0 -0
  80. spatialdds_ros-0.3.0.data/data/share/spatialdds_ros/package.xml +29 -0
  81. spatialdds_ros-0.3.0.dist-info/METADATA +331 -0
  82. spatialdds_ros-0.3.0.dist-info/RECORD +87 -0
  83. spatialdds_ros-0.3.0.dist-info/WHEEL +5 -0
  84. spatialdds_ros-0.3.0.dist-info/entry_points.txt +3 -0
  85. spatialdds_ros-0.3.0.dist-info/licenses/LICENSE +21 -0
  86. spatialdds_ros-0.3.0.dist-info/top_level.txt +2 -0
  87. spatialdds_ros-0.3.0.dist-info/zip-safe +1 -0
@@ -0,0 +1,170 @@
1
+ """
2
+ Endpoints, with the §3.3.3 QoS actually applied.
3
+
4
+ Thin on purpose, with two jobs. Every endpoint this library creates goes
5
+ through one place that resolves a profile name to a `Qos`, so the profiles in
6
+ `qos.py` are true on the wire rather than decorative; and every read goes
7
+ through one place that knows `take()` returns lifecycle notifications as well
8
+ as payloads. Both are the kind of rule that is wrong in one spot or wrong
9
+ everywhere, and one enforcement point is the difference. The prototype's
10
+ `typed_transport` exists for the same reasons and this follows its shape.
11
+ """
12
+ from __future__ import annotations
13
+
14
+ from typing import Any, List, NamedTuple, Optional, Type
15
+
16
+ from cyclonedds.core import InstanceState
17
+ from cyclonedds.domain import DomainParticipant
18
+ from cyclonedds.pub import DataWriter, Publisher
19
+ from cyclonedds.sub import DataReader, Subscriber
20
+ from cyclonedds.topic import Topic
21
+
22
+ from spatialdds_ros.qos import PROFILES, QosProfile
23
+
24
+
25
+ class UnknownQosProfile(KeyError):
26
+ """A profile name that is not one of the three this library uses."""
27
+
28
+
29
+ def _profile(name: str) -> QosProfile:
30
+ try:
31
+ return PROFILES[name]
32
+ except KeyError:
33
+ raise UnknownQosProfile(
34
+ f"{name!r} is not a profile this library carries; expected one of "
35
+ f"{', '.join(sorted(PROFILES))}") from None
36
+
37
+
38
+ def _topic(participant: DomainParticipant, topic_name: str,
39
+ datatype: Type) -> Topic:
40
+ """
41
+ The topic, with **default QoS**. The profile rides on the endpoints.
42
+
43
+ A `Topic` object's QoS must be identical for every creation of the same
44
+ name within a participant: DDS refuses the second with
45
+ `DDS_RETCODE_INCONSISTENT_POLICY`, and the refusal lands on whichever
46
+ endpoint happened to be constructed later, which makes it look like a
47
+ problem with that endpoint rather than with the pair.
48
+
49
+ That makes topic QoS a contract between **every participant in the
50
+ process**, not a property of this library's lanes — and nothing in this
51
+ API said so. The first real consumer paid for it. SpatialDDS-demo applies
52
+ the same §3.3.3 profiles to endpoints only; it asked for an entity reader
53
+ on a topic this library had already created carrying the profile, and was
54
+ refused. Both conventions are legal DDS. They are not legal together, and
55
+ the library had quietly made itself the one that must be matched.
56
+
57
+ So it now asks for the least it can: the default, which is what any
58
+ participant that passes no QoS gets, including every ROS and DDS example.
59
+
60
+ Nothing leaves the wire. Matching is a reader/writer negotiation over
61
+ *endpoint* QoS — a topic's QoS enters it only as a default for endpoints
62
+ that ask for one, and these never do; both are always given the profile
63
+ explicitly. `test_transport.py` pins both halves: that the policies still
64
+ reach the endpoints, and that a library writer still exchanges with a
65
+ reader built the other convention's way.
66
+
67
+ Lifespan was excluded here for this exact reason on 2026-10-18, one policy
68
+ at a time, when an announce writer with a `ttl_sec` failed against a reader
69
+ opened first. This is that lesson finished.
70
+ """
71
+ return Topic(participant, topic_name, datatype)
72
+
73
+
74
+ def make_writer(participant: DomainParticipant, topic_name: str,
75
+ datatype: Type, qos_profile: str, *,
76
+ lifespan_sec: Optional[float] = None) -> DataWriter:
77
+ profile = _profile(qos_profile)
78
+ topic = _topic(participant, topic_name, datatype)
79
+ writer_qos = profile.to_qos(lifespan_sec=lifespan_sec)
80
+ return DataWriter(Publisher(participant, qos=profile.to_qos()), topic,
81
+ qos=writer_qos)
82
+
83
+
84
+ def make_reader(participant: DomainParticipant, topic_name: str,
85
+ datatype: Type, qos_profile: str) -> DataReader:
86
+ profile = _profile(qos_profile)
87
+ topic = _topic(participant, topic_name, datatype)
88
+ return DataReader(Subscriber(participant, qos=profile.to_qos()), topic,
89
+ qos=profile.to_qos())
90
+
91
+
92
+ STATE_NAMES = {int(InstanceState.Alive): "ALIVE",
93
+ int(InstanceState.NotAliveDisposed): "NOT_ALIVE_DISPOSED",
94
+ int(InstanceState.NotAliveNoWriters): "NOT_ALIVE_NO_WRITERS"}
95
+
96
+
97
+ class TakenSample(NamedTuple):
98
+ """
99
+ A sample and what the middleware said about its instance.
100
+
101
+ `data` is `None` for a lifecycle notification — a dispose or a
102
+ writer-gone — which carries instance state and no payload. It is *not*
103
+ the only way a not-alive state arrives: a valid sample that has not been
104
+ read when its writer disposes carries the state on its own `sample_info`
105
+ instead, and no separate notification is generated. So `data` and `alive`
106
+ are independent, and a caller that wants the lifecycle must read `alive`
107
+ rather than infer it from a missing payload.
108
+ """
109
+ data: Optional[Any]
110
+ alive: bool
111
+ instance_handle: Optional[int]
112
+ instance_state: Any
113
+
114
+ @property
115
+ def state_name(self) -> str:
116
+ """For logs. `instance_state` is a plain int, not an enum member."""
117
+ return STATE_NAMES.get(int(self.instance_state),
118
+ f"UNKNOWN({int(self.instance_state)})")
119
+
120
+
121
+ def take_with_state(reader: DataReader, *, n: int = 100) -> List[TakenSample]:
122
+ """
123
+ Samples **and their instance state**, so a caller can act on disposal.
124
+
125
+ CycloneDDS's `take()` does not return payloads only. It also yields
126
+ `InvalidSample` objects — dispose and no-writer notifications, carrying
127
+ `sample_info` with `valid_data == False` and no fields of the datatype at
128
+ all. A read that assumes every sample is a payload reads an attribute off
129
+ one and raises.
130
+ """
131
+ out: List[TakenSample] = []
132
+ for sample in (reader.take(N=n) or []):
133
+ if sample is None:
134
+ continue
135
+ info = getattr(sample, "sample_info", None)
136
+ valid = bool(getattr(info, "valid_data", True)) if info else True
137
+ state = (getattr(info, "instance_state", InstanceState.Alive)
138
+ if info else InstanceState.Alive)
139
+ out.append(TakenSample(
140
+ data=sample if valid else None,
141
+ alive=valid and state == InstanceState.Alive,
142
+ instance_handle=getattr(info, "instance_handle", None) if info
143
+ else None,
144
+ instance_state=state))
145
+ return out
146
+
147
+
148
+ def take_samples(reader: DataReader, *, n: int = 100) -> List[Any]:
149
+ """
150
+ Payloads only — lifecycle notifications dropped, not mistaken for one.
151
+
152
+ Disposal is not a signal this library *acts* on: the adapter owns exactly
153
+ one key and never disposes it, and going quiet is reported as `UNOBSERVED`,
154
+ which is a weaker statement than "this robot is gone". But not acting on
155
+ an event is not the same as not receiving it, and this filtered only
156
+ `None` until 2026-10-19 — so the first peer that joined a lane and left
157
+ killed the robot reading it:
158
+
159
+ AttributeError: 'InvalidSample' object has no attribute 'subject_id'
160
+
161
+ Two call sites, both reached by an ordinary deployment. `poll_commands`
162
+ crashed when any requester's participant went away; `already_published`
163
+ would have crashed at startup on the tombstone of the robot that ran
164
+ before it — and a tombstone must not count as a live conflict either,
165
+ which it now cannot, because it never arrives as a record.
166
+
167
+ Nothing in this library's 153 tests produced one: every writer outlived
168
+ every reader. `test_transport.py` now keeps a peer that dies.
169
+ """
170
+ return [s.data for s in take_with_state(reader, n=n) if s.data is not None]
@@ -0,0 +1,29 @@
1
+ <?xml version="1.0"?>
2
+ <?xml-model href="http://download.ros.org/schema/package_format3.xsd" schematypens="http://relaxng.org/ns/structure/1.0"?>
3
+ <package format="3">
4
+ <name>spatialdds_ros</name>
5
+ <version>0.3.0</version>
6
+ <description>
7
+ Makes a ROS 2 robot a SpatialDDS participant: its pose published as a
8
+ spatial.owm entity on scene-scoped topics, goto commands consumed from
9
+ ModelCommand and dispatched to nav2, and its presence announced — on the
10
+ same DDS bus ROS already runs on. A type adapter on a shared bus, not a
11
+ protocol gateway.
12
+ </description>
13
+ <maintainer email="fiberhog@gmail.com">James Jackson</maintainer>
14
+ <license>MIT</license>
15
+
16
+ <buildtool_depend>ament_python</buildtool_depend>
17
+
18
+ <exec_depend>rclpy</exec_depend>
19
+ <exec_depend>geometry_msgs</exec_depend>
20
+ <exec_depend>nav_msgs</exec_depend>
21
+ <exec_depend>nav2_msgs</exec_depend>
22
+ <exec_depend>tf2_ros</exec_depend>
23
+
24
+ <test_depend>python3-pytest</test_depend>
25
+
26
+ <export>
27
+ <build_type>ament_python</build_type>
28
+ </export>
29
+ </package>
@@ -0,0 +1,331 @@
1
+ Metadata-Version: 2.4
2
+ Name: spatialdds-ros
3
+ Version: 0.3.0
4
+ Summary: A ROS 2 robot as a SpatialDDS participant
5
+ Home-page: https://github.com/OpenArCloud/spatialdds-ros
6
+ Author: Open AR Cloud
7
+ Maintainer: James Jackson
8
+ Maintainer-email: fiberhog@gmail.com
9
+ License: MIT
10
+ Project-URL: Source, https://github.com/OpenArCloud/spatialdds-ros
11
+ Project-URL: Issues, https://github.com/OpenArCloud/spatialdds-ros/issues
12
+ Project-URL: Changelog, https://github.com/OpenArCloud/spatialdds-ros/releases
13
+ Keywords: spatialdds,ros2,dds,robotics,nav2,digital-twin,spatial-computing
14
+ Classifier: Development Status :: 4 - Beta
15
+ Classifier: Intended Audience :: Developers
16
+ Classifier: Intended Audience :: Science/Research
17
+ Classifier: License :: OSI Approved :: MIT License
18
+ Classifier: Operating System :: OS Independent
19
+ Classifier: Programming Language :: Python :: 3
20
+ Classifier: Programming Language :: Python :: 3.10
21
+ Classifier: Programming Language :: Python :: 3.11
22
+ Classifier: Programming Language :: Python :: 3.12
23
+ Classifier: Topic :: Scientific/Engineering
24
+ Classifier: Topic :: System :: Networking
25
+ Requires-Python: >=3.10
26
+ Description-Content-Type: text/markdown
27
+ License-File: LICENSE
28
+ Requires-Dist: cyclonedds>=0.10
29
+ Provides-Extra: test
30
+ Requires-Dist: pytest; extra == "test"
31
+ Dynamic: author
32
+ Dynamic: classifier
33
+ Dynamic: description
34
+ Dynamic: description-content-type
35
+ Dynamic: home-page
36
+ Dynamic: keywords
37
+ Dynamic: license
38
+ Dynamic: license-file
39
+ Dynamic: maintainer
40
+ Dynamic: maintainer-email
41
+ Dynamic: project-url
42
+ Dynamic: provides-extra
43
+ Dynamic: requires-dist
44
+ Dynamic: requires-python
45
+ Dynamic: summary
46
+
47
+ # spatialdds_ros
48
+
49
+ Make a ROS 2 robot a SpatialDDS participant: its pose published as a
50
+ `spatial.owm` entity on scene-scoped topics, `goto` commands consumed from
51
+ `ModelCommand` and dispatched to nav2, and its presence announced — all on the
52
+ same DDS bus ROS already runs on.
53
+
54
+ **A type adapter on a shared bus, not a protocol gateway.** Nothing is
55
+ relayed. ROS and SpatialDDS meet on the wire because both are DDS, so this is
56
+ one process holding both stacks, publishing the model as model and reading ROS
57
+ as ROS.
58
+
59
+ ## Installing, and what you are not installing
60
+
61
+ ```sh
62
+ pip install spatialdds-ros
63
+ ```
64
+
65
+ That gets `cyclonedds` and nothing else. **No ROS.** The library puts a robot
66
+ on a SpatialDDS bus, reads commands off it and drives a navigator, and none of
67
+ that needs a ROS install — so `rclpy` is not a dependency, and the absence is
68
+ deliberate rather than an omission waiting to be tidied up.
69
+
70
+ The split runs through the whole package:
71
+
72
+ | dependency | declared in | what it is for |
73
+ |---|---|---|
74
+ | `cyclonedds` | `install_requires` | the bus. Everything here needs it |
75
+ | `rclpy`, `tf2_ros`, `nav2_msgs` | `package.xml` | the ROS node alone, resolved by `rosdep`, never by pip |
76
+
77
+ Only `spatialdds_ros.robot_node` requires a ROS 2 environment. It imports
78
+ `rclpy` inside its entry point rather than at module scope, which is what
79
+ lets the other twelve modules — and the entire test suite — run on a machine
80
+ that has never had ROS installed.
81
+
82
+ **That claim is a gate, not a promise.**
83
+ [`test/test_no_ros_needed.py`](https://github.com/OpenArCloud/spatialdds-ros/blob/main/test/test_no_ros_needed.py)
84
+ walks every module reachable from `import spatialdds_ros` and fails on a
85
+ module-scope `import rclpy` anywhere in it. By source inspection, so it
86
+ catches the violation on a ROS workstation where the import would have
87
+ succeeded — a test that only checked "the import worked" would pass there and
88
+ fail in CI. If the split ever stops being real, that is where it is reported.
89
+
90
+ ## A robot on the bus, with no robot
91
+
92
+ No ROS, no nav2, no hardware:
93
+
94
+ ```sh
95
+ pip install -e .
96
+ python3 -m spatialdds_ros.standalone \
97
+ --scene bare-scene \
98
+ --entity-id ent:robot:r1 \
99
+ --frame-id map/bare-scene
100
+ ```
101
+
102
+ ```
103
+ robot: scene bare-scene, entity ent:robot:r1 in frame map/bare-scene, DDS domain 0
104
+ robot: no anchor configured, so this adapter publishes no FrameTransform: its
105
+ entity and poses are on the bus and complete, and it makes no claim
106
+ about where that frame sits on the earth. Another participant may own
107
+ that claim — a venue publishing its own georeference is the ordinary
108
+ case, and then this is the correct configuration. If nobody publishes
109
+ one, a consumer cannot place this robot on a globe and should say so
110
+ rather than assume a position
111
+ robot: announce: svc:robot:r1 as OTHER, 3 topics, ttl 300s
112
+ ```
113
+
114
+ It is now on three lanes, and discoverable on a fourth:
115
+
116
+ | lane | topic | QoS |
117
+ |---|---|---|
118
+ | what exists | `spatialdds/bare-scene/world/entity/v1` | `MODEL_LATCHED` |
119
+ | where it is | `spatialdds/bare-scene/world/model_pose/v1` | `MODEL_FAST` |
120
+ | what to do | `spatialdds/bare-scene/world/model_command/v1` | `MODEL_COMMAND` |
121
+ | who it is | `spatialdds/discovery/announce/v1` | `MODEL_LATCHED` |
122
+
123
+ Add `--bounds -10,-10,10,10` and it will refuse a goal that leaves the box, so
124
+ the refusal path can be exercised without hardware.
125
+
126
+ ## On a real robot
127
+
128
+ ```sh
129
+ ros2 run spatialdds_ros robot_node --ros-args \
130
+ -p scene:=bare-scene \
131
+ -p entity_id:=ent:robot:r1 \
132
+ -p frame_id:=map/bare-scene
133
+ ```
134
+
135
+ The pose input defaults to the TF chain `map -> base_link`. That default
136
+ matters: it is what nav2 itself consumes, it works whatever the localizer is,
137
+ and it assumes no topic name. `pose_source:=amcl` and `pose_source:=odom` are
138
+ available, and `odom` logs its own consequence — odom drifts against map, so
139
+ publishing it as a georeferenced pose is correct only where `map -> odom` is
140
+ static and identity, which is a simulator and not a localizing robot.
141
+
142
+ `ROS 2 Humble` and `Jazzy` are the named targets. **The ROS buildfarm is
143
+ deferred** — there is no released binary package; install from source or with
144
+ pip.
145
+
146
+ ## Configuration
147
+
148
+ Three parameters have no default and the node refuses to start without them,
149
+ because every possible default would be a false statement about the world:
150
+
151
+ | parameter | why there is no default |
152
+ |---|---|
153
+ | `scene` | which world this robot is in. A wrong scene is an empty bus, not an error |
154
+ | `entity_id` | which robot it is. A default collides the moment a second one runs |
155
+ | `frame_id` | which frame its pose is in. A pose without its frame is meaningless |
156
+
157
+ Everything else has one: `domain` 0, `ground_z` 0.0, `pose_source` tf,
158
+ `nav2_action` navigate_to_pose, `source_id` derived from `entity_id`,
159
+ `content_refs` and `external_refs` empty.
160
+
161
+ ### What the robot *is*, and what it is *also known as*
162
+
163
+ ```sh
164
+ python3 -m spatialdds_ros.standalone \
165
+ --scene bare-scene --entity-id ent:robot:r1 --frame-id map/bare-scene \
166
+ --content-ref catalog:dcef7c31-1d5f-54aa-9344-899912f9d34a \
167
+ --external-ref wikidata=Q4810574
168
+ ```
169
+
170
+ `content_refs` is how a consumer finds out what to *draw*. A client that reads
171
+ a `catalog:` reference can resolve the model behind it; without one it has
172
+ nothing to resolve and will render a marker. That is not a bug in the client —
173
+ it is the honest rendering of a robot that has not said what it looks like.
174
+
175
+ `external_refs` is what it is also known as: a wikidata id, an
176
+ asset-management number, a fleet number.
177
+
178
+ **Both are empty unless configured, and empty is a statement.** The adapter
179
+ publishes what the operator told it and nothing else — it invents no reference
180
+ and guesses no asset, because a reference nobody authorised is a claim on
181
+ nobody's authority. An empty sequence says "this robot names no content"; it
182
+ is not an accident of omission, and `test/test_adapter.py` asserts both
183
+ directions.
184
+
185
+ On a ROS robot the same two are string-array parameters, with `external_refs`
186
+ as `key=value` entries:
187
+
188
+ ```sh
189
+ ros2 run spatialdds_ros robot_node --ros-args \
190
+ -p scene:=bare-scene -p entity_id:=ent:robot:r1 \
191
+ -p frame_id:=map/bare-scene \
192
+ -p content_refs:='[catalog:dcef7c31-1d5f-54aa-9344-899912f9d34a]' \
193
+ -p external_refs:='[wikidata=Q4810574]'
194
+ ```
195
+
196
+ ### Property keys
197
+
198
+ The adapter publishes two entity properties, and these names are **the
199
+ convention** rather than this library's private spelling — a consumer should
200
+ be able to read any `spatialdds_ros` robot without diffing two records to
201
+ discover what they are called:
202
+
203
+ | key | meaning |
204
+ |---|---|
205
+ | `label` | the human-facing name, from `label` or the `entity_id`'s tail |
206
+ | `nav.status` | what became of the last goal, held until a new one is accepted |
207
+
208
+ Unprefixed on purpose. An earlier consumer had published `demo.label` and
209
+ `demo.nav_status`, which scoped a general fact to one deployment; a second
210
+ consumer reading that record would have had to know whose demo it was.
211
+
212
+ ### The anchor, and what its absence means for a consumer
213
+
214
+ `anchor_lat_deg`, `anchor_lon_deg` and `anchor_height_m` say where the robot's
215
+ frame sits on the earth. Set all three and the node publishes a
216
+ `FrameTransform` from that frame to `earth-fixed`. Set none and it publishes
217
+ none — a supported mode, not a misconfiguration. Set one or two and it refuses
218
+ to start, because a partial anchor is not a weaker claim about where the frame
219
+ is, it is an unusable one.
220
+
221
+ **What a reader of the scene sees in that mode.** The robot's entity and its
222
+ poses are present, complete and moving, and *this adapter* makes no claim
223
+ about where that frame sits on the earth.
224
+
225
+ Whether anybody else does is not something it can see. **A venue publishing
226
+ its own georeference is the ordinary case**, and then anchorless is the
227
+ correct configuration — the scene keeps the single claim and the robot does
228
+ not contradict it. That is how SpatialDDS-demo runs. If nobody publishes one,
229
+ a client that wants to place the robot on a globe cannot, and should say so
230
+ rather than assume a position.
231
+
232
+ The note said "nothing earth-references them" until 2026-10-19, which assumed
233
+ this adapter was the only thing that could. In the first real deployment it
234
+ was not, and anchorless was correct *because* somebody else owned the claim —
235
+ the opposite of the absence the wording described.
236
+
237
+ The degradation is deliberate either way. The alternative, defaulting the
238
+ anchor to the frame origin, would have every unconfigured robot claiming to be
239
+ in the Gulf of Guinea with a pose that looks entirely plausible. A default is
240
+ not neutral when the thing being defaulted is a claim.
241
+
242
+ ## What it claims, and what it will not
243
+
244
+ The adapter publishes `basis = OBSERVED` and offers no parameter to change it:
245
+ a self-localizing robot is reporting where it has worked out that it is, which
246
+ is a sighting rather than an assertion. It claims no extent — a robot locates
247
+ itself, it does not legislate a region, and an extent would be read as
248
+ keep-out by anything applying the `DECLARED` rule.
249
+
250
+ Three things it refuses to do quietly:
251
+
252
+ - **It does not dispose its entity when the feed goes quiet.** Disposal says
253
+ the robot is gone; silence only licenses "this is where it was last seen,
254
+ not where it is". The record is republished `UNOBSERVED` with a reason and
255
+ the last pose kept.
256
+ - **It does not accept a goal nothing will act on.** No pose publishing, or
257
+ nav2 not accepting goals, is declined *with the reason* — a declined goal
258
+ must not look like an ignored one.
259
+ - **It does not let an accepted goal fail silently.** Whether a goal is
260
+ drivable can depend on where the robot is when it meets a boundary, so the
261
+ refusal arrives after the acceptance, on the latched record, and stays there
262
+ until a new goal is accepted.
263
+
264
+ QoS and topic names are not configurable. Both are interop contracts whose
265
+ failure mode is silence: a mismatched QoS means endpoints that never meet with
266
+ nothing reporting it, and a hand-written topic name means a robot publishing
267
+ where nobody is listening.
268
+
269
+ ### Sharing a participant with another SpatialDDS library
270
+
271
+ The §3.3.3 profiles go on the **endpoints**. The `Topic` objects this library
272
+ creates carry default QoS, and that is deliberate rather than an omission.
273
+
274
+ A topic name is unique within a participant, and its QoS has to agree among
275
+ every live `Topic` object for that name — in either direction, with the
276
+ refusal (`DDS_RETCODE_INCONSISTENT_POLICY`) landing on whichever call came
277
+ second. Putting the profile on the topic therefore made topic QoS a contract
278
+ every *other* participant in the process had to match, and nothing in this
279
+ API said so. Version 0.2.0 did that, and SpatialDDS-demo was refused an entity
280
+ reader on a lane this adapter had already opened — both conventions legal DDS,
281
+ neither legal beside the other.
282
+
283
+ So this library now asks for the least it can: the default, which is what any
284
+ participant that passes no QoS gets, including every ROS and DDS example.
285
+ Nothing leaves the wire — matching is a reader/writer negotiation over
286
+ endpoint QoS, and a topic's QoS enters it only as a default for endpoints that
287
+ ask for one, which these never do.
288
+
289
+ If you run another SpatialDDS stack in the same process and it creates its
290
+ topics with an explicit QoS, it is the one imposing the contract, and the two
291
+ cannot share that name. `test/test_transport.py::TwoConventionsOnOneTopic`
292
+ pins all of it, including a library writer exchanging real samples with a
293
+ reader built the other convention's way.
294
+
295
+ ### Reads see the lifecycle
296
+
297
+ `transport.take_samples` returns payloads; `transport.take_with_state` returns
298
+ them alongside instance state for a caller that wants the lifecycle. Both
299
+ matter because CycloneDDS's `take()` does not return payloads only — it also
300
+ yields `InvalidSample` notifications for a dispose or a departed writer, and a
301
+ read that assumes otherwise raises on the first peer that joins a lane and
302
+ leaves. Until 0.2.1 this library made that assumption in one place, which
303
+ reached two call sites, and the first real deployment found it within a
304
+ minute. [`docs/failability.md`](https://github.com/OpenArCloud/spatialdds-ros/blob/main/docs/failability.md) §3 has the tracebacks.
305
+
306
+ This adapter still does not *act* on disposal: it owns one key, never disposes
307
+ it, and reports going quiet as `UNOBSERVED` — a weaker statement than "this
308
+ robot is gone". Not acting on an event is not the same as not receiving one.
309
+
310
+ ## Tests
311
+
312
+ ```sh
313
+ python3 -m pytest test/ -q
314
+ ```
315
+
316
+ Everything runs with no ROS installed — that is a gate
317
+ (`test/test_no_ros_needed.py`), not a habit, since one module-scope
318
+ `import rclpy` would take the whole suite offline. The DDS-backed tests need
319
+ `cyclonedds` and skip with a reason without it.
320
+
321
+ | document | what is in it |
322
+ |---|---|
323
+ | [`docs/extraction-census.md`](https://github.com/OpenArCloud/spatialdds-ros/blob/main/docs/extraction-census.md) | what became a parameter, what the library fills, and why |
324
+ | [`docs/failability.md`](https://github.com/OpenArCloud/spatialdds-ros/blob/main/docs/failability.md) | the two claims broken deliberately, with the failures |
325
+ | [`docs/spec-queue.md`](https://github.com/OpenArCloud/spatialdds-ros/blob/main/docs/spec-queue.md) | two things for the specification rather than worked around |
326
+ | [`spatialdds_idl18/PROVENANCE.md`](https://github.com/OpenArCloud/spatialdds-ros/blob/main/spatialdds_idl18/PROVENANCE.md) | where the vendored 1.8 bindings came from |
327
+ | [`docs/RELEASING.md`](https://github.com/OpenArCloud/spatialdds-ros/blob/main/docs/RELEASING.md) | how a version reaches PyPI, and how the artifact is checked against the commit |
328
+
329
+ ## Licence
330
+
331
+ MIT. See [`LICENSE`](https://github.com/OpenArCloud/spatialdds-ros/blob/main/LICENSE).
@@ -0,0 +1,87 @@
1
+ spatialdds_idl18/PROVENANCE.md,sha256=67ABiglFknhMc2420c5re6w9Mk0jzy7fIroCFwD3Fy0,3120
2
+ spatialdds_idl18/__init__.py,sha256=rfl2cis6sDbulhllvI21ttr67V6nZs2wN9LAwM0MLn8,345
3
+ spatialdds_idl18/_field_aliases.py,sha256=0O5N-Nm5PUPOvbmvjLHfHhCbepUnlKAzTKQ-gvBF4b4,801
4
+ spatialdds_idl18/builtin/__init__.py,sha256=FjNPEwEu1CAfEA1GQYtfVImm-kpCkEtkjqw7xz4wO08,166
5
+ spatialdds_idl18/builtin/_types.py,sha256=qfCX7JvoMmOqGQMfdAqntR8sNIcxwj6ph9Hpw1Skriw,597
6
+ spatialdds_idl18/spatial/__init__.py,sha256=tpmCAmyg6HNzlzidV5TipJYF4HmtPg5smw0CyjrGoLU,522
7
+ spatialdds_idl18/spatial/anchors/__init__.py,sha256=gMH2qBzXZuPNnw_CPtps_f3OKYQ2gIabE8P35IKmb_w,431
8
+ spatialdds_idl18/spatial/anchors/_anchors.py,sha256=x9rTD5kJJnAKSXGd5P6lGAVDszfJWZOeAbCsvY2Z2yU,2972
9
+ spatialdds_idl18/spatial/argeo/__init__.py,sha256=zJeYNlCn6Uhoj5cc1fXyaUHVqUlz0ETWykPf3WJxCf0,461
10
+ spatialdds_idl18/spatial/argeo/_argeo.py,sha256=S7OsTy6dm-gICrK1f17iY84R7xzCkHDTQM8NHHUsyhA,3214
11
+ spatialdds_idl18/spatial/common/__init__.py,sha256=IJqLXqKKp90TytZCDlyKHpWVdS-SaehJn3FoUqsxbPc,549
12
+ spatialdds_idl18/spatial/common/_types.py,sha256=wQYCdNuMTm433yvTBcsT8TJOz5_0hRccai0jZRbBbhM,3278
13
+ spatialdds_idl18/spatial/core/__init__.py,sha256=OYdv4aLYoYWxMdto29DH7f3XE3t90cUHDq4hhwcFmQI,933
14
+ spatialdds_idl18/spatial/core/_core.py,sha256=jxja36n3SP-HvU5I2XOHOS0oT2k654NgTPNJrixIoSc,10051
15
+ spatialdds_idl18/spatial/core/GnssService/__init__.py,sha256=u9p9cIOXpkfYdJJD9gGhRE731oJAiqudEtR_o9x0kMI,294
16
+ spatialdds_idl18/spatial/core/GnssService/_core.py,sha256=anv8eF4fkB4oLibGj6Z-AH9Gprsfd8RSw3S2WxocDu4,526
17
+ spatialdds_idl18/spatial/disco/__init__.py,sha256=7j0NiaNprvOkNRJ0WinG_LULhVDNh2kjrTIO1ZkT5tw,811
18
+ spatialdds_idl18/spatial/disco/_discovery.py,sha256=czy9pO7C0DHOvBoFDMXFd8CSrsN_0CrSiHcGB19VsEc,8399
19
+ spatialdds_idl18/spatial/events/__init__.py,sha256=sMoXRjGPsChKZMzP3-M24EQYa5MIqK061KwOxa222RA,606
20
+ spatialdds_idl18/spatial/events/_events.py,sha256=Vzo-8dHxiGTgYOvW896oSkup-QFgn3blrbsKamLlWT4,6244
21
+ spatialdds_idl18/spatial/events/enums/__init__.py,sha256=gKCTnBEMrei7IW9c43yh00mejNg0HpPq1HVmvyXliPw,407
22
+ spatialdds_idl18/spatial/events/enums/crossing_direction_enum/__init__.py,sha256=LLyuCy7U3XgBE7ERkms9CvahNxd6J_b7hKGvkC03krU,230
23
+ spatialdds_idl18/spatial/events/enums/crossing_direction_enum/_events.py,sha256=dT3Q_6VplUYVBKRBjB12s4zhxF4ZExhmCluMK2zOvsQ,697
24
+ spatialdds_idl18/spatial/events/enums/event_state_enum/__init__.py,sha256=-SN9-B3xJlumTt-LUW_aAVge9_WdIh28FkZJXHc_gIM,209
25
+ spatialdds_idl18/spatial/events/enums/event_state_enum/_events.py,sha256=qACxsDdGvUdM2M9_T3EjlGaxLwysNWoFaBrIF4odNmg,662
26
+ spatialdds_idl18/spatial/events/enums/event_type_enum/__init__.py,sha256=qFkOUmiw_3LX0m-cyHxy98_IJzTCd8N-sz5V7Ku616o,206
27
+ spatialdds_idl18/spatial/events/enums/event_type_enum/_events.py,sha256=Q8AhyDJTlzaVN7GC83CIzgP_5hZzjjmFDRgC0wtBjMk,875
28
+ spatialdds_idl18/spatial/events/enums/severity_enum/__init__.py,sha256=b91jFRWGQvNCxT2RFYUh50qbHTpretq71KMoQwP6Zws,202
29
+ spatialdds_idl18/spatial/events/enums/severity_enum/_events.py,sha256=IT8MrlrMEIx0YmqH3vn5Iz1rYU2SZV_XgCZRFUrA_uI,638
30
+ spatialdds_idl18/spatial/events/enums/zone_kind_enum/__init__.py,sha256=CFglMhgHRw8vRwSSREH6MCc8qsOSBl9OltWcfaElu78,203
31
+ spatialdds_idl18/spatial/events/enums/zone_kind_enum/_events.py,sha256=QKYO3oySpoaxQC_5_B6sZWI0UOC2AacRHFqPlMK5cYg,749
32
+ spatialdds_idl18/spatial/mapping/__init__.py,sha256=ehXVRIi9mwGG3puvkvV-Oh8JelQ1ZcK5qWscgWUre6I,615
33
+ spatialdds_idl18/spatial/mapping/_mapping.py,sha256=ErJvTQ0MEdLDL7Fo91-hJ2CEQYtaOW6WrBBEpQZ4fS0,6065
34
+ spatialdds_idl18/spatial/mapping/enums/__init__.py,sha256=3d4icZMbGZsD8x1NlcKaqhdAcdJVI9Co-5rRsCOT40o,360
35
+ spatialdds_idl18/spatial/mapping/enums/alignment_method/__init__.py,sha256=U6X5WUL-by1gklTk6Csqy-pnQjrOeL-POc8HmumbnLg,221
36
+ spatialdds_idl18/spatial/mapping/enums/alignment_method/_mapping.py,sha256=HHDtUREYp_vVHunsehnMeKTtFDUmoyqq7oiqhTTTVIA,780
37
+ spatialdds_idl18/spatial/mapping/enums/edge_type/__init__.py,sha256=6AHJl56olb6zpQ-cJLe75sbSkPqPd7ibWdOGEHGNugo,200
38
+ spatialdds_idl18/spatial/mapping/enums/edge_type/_mapping.py,sha256=Xr1SjRO6l_zLEA0MmkzNa77CNCj_G02iCqvX6vIFgvg,755
39
+ spatialdds_idl18/spatial/mapping/enums/map_event_kind/__init__.py,sha256=7TGaQ89E72vw1DiPZjlAvfWO1s7g82jgTfDaecXH76k,213
40
+ spatialdds_idl18/spatial/mapping/enums/map_event_kind/_mapping.py,sha256=8KTd1j5HUfMF2yEoojpkDBDeRUjNQLQXda7uCCZTNyg,736
41
+ spatialdds_idl18/spatial/mapping/enums/map_kind/__init__.py,sha256=KMdWg8BuUHCq4PzYRUHW1lGbDULnFAOhkrmRniKc6d0,197
42
+ spatialdds_idl18/spatial/mapping/enums/map_kind/_mapping.py,sha256=UZ8SMFOAFRU8lG_rk3mlaga1mFnmZsM84s2ciKiDBIo,749
43
+ spatialdds_idl18/spatial/mapping/enums/map_status/__init__.py,sha256=ap8tCIXZHTba1q2MVfmwIrlX83WJNrWrzhqQJ885X_M,203
44
+ spatialdds_idl18/spatial/mapping/enums/map_status/_mapping.py,sha256=a4Ap4za3raiURz901Ir0HbSUACeOl2x1eRLCTsbI3vY,666
45
+ spatialdds_idl18/spatial/owm/__init__.py,sha256=ZmtBwjuaxr4hSsPo6UZhEhnMRxe2lamkPq0W3aH57J0,419
46
+ spatialdds_idl18/spatial/owm/_owm.py,sha256=hOuwQok5jrt5KwSZIUdBygaRyaO0KaEXf3o3y_emQpM,3234
47
+ spatialdds_idl18/spatial/semantics/__init__.py,sha256=dX70aCF7eEyKcrt51sjlClzwBpSU3BeOkMJZG3poAAA,495
48
+ spatialdds_idl18/spatial/semantics/_semantics.py,sha256=MvQf4NF8od5er9_dasgjQxxy7sOavG5mY-MLPRMnWVA,5632
49
+ spatialdds_idl18/spatial/sensing/__init__.py,sha256=0u6NAEhJ-gKliI1d_w-8NkDyvXEsfH4sRsBjkxn5Www,324
50
+ spatialdds_idl18/spatial/sensing/common/__init__.py,sha256=ckiFGF0A5t1MUdBNr1Fb1wdF-_DBYkNj2005QLgeYXw,715
51
+ spatialdds_idl18/spatial/sensing/common/_common.py,sha256=PGhRim6RJqRueWppvqGEvw8pxpAyL5DEXqQyTcmn7iU,5822
52
+ spatialdds_idl18/spatial/sensing/lidar/__init__.py,sha256=yaXo7sOpYoWJwuoZdN93yzpYMlE1IbP3xcwUNHfOPM0,723
53
+ spatialdds_idl18/spatial/sensing/lidar/_lidar.py,sha256=RRg6tgZQ5aORnmPiVIqplz3wlRBKZP4q9n3GgZG21ss,5443
54
+ spatialdds_idl18/spatial/sensing/rad/__init__.py,sha256=kyPygAAuRk2LXNZTFJvldRs1viBz-Rdk_GPA_JgtLEg,770
55
+ spatialdds_idl18/spatial/sensing/rad/_rad.py,sha256=o5vMMvzoQYgCqrOyd5VrvkIxewDqTo4ifNLN12uzIf4,7251
56
+ spatialdds_idl18/spatial/sensing/radio/__init__.py,sha256=DgT6t8m1bNTkqraZEWykmvvH4zvkq9uG8u355vBE2bA,491
57
+ spatialdds_idl18/spatial/sensing/radio/_radio.py,sha256=fPcT4gVwozFPIkDcah5RLgbQFiOvRkS3VhmRL5wEyY4,4526
58
+ spatialdds_idl18/spatial/sensing/rf_beam/__init__.py,sha256=rWSB35LKhsRP0SuJuRlZMBBKKFgH-TmQE-g_UdpqdZ4,554
59
+ spatialdds_idl18/spatial/sensing/rf_beam/_rf_beam.py,sha256=p-_CqixMWJvFzKtP_UT_A4cBBJMGT8rmtmRTJC-mw1s,4307
60
+ spatialdds_idl18/spatial/sensing/vision/__init__.py,sha256=SU_FtKZbQY_8DATrIenMAmWb085t6X7aRKddgDagePw,809
61
+ spatialdds_idl18/spatial/sensing/vision/_vision.py,sha256=6Zkmwi2iA661IdoHQxrIzQJFRv2CSFZKqUVtUWH62YA,6396
62
+ spatialdds_idl18/spatial/slam_frontend/__init__.py,sha256=mOaidTGrjOcf5vy52X_s3gsqKli8UdECKegk6Av1fwY,495
63
+ spatialdds_idl18/spatial/slam_frontend/_slam_frontend.py,sha256=CR3uLamKCDNirk3FypB3mmRRKJw3a1vCuo5t-ZBpddk,4115
64
+ spatialdds_idl18/spatial/vio/__init__.py,sha256=sjPzFWP42rzkeJB9L89vu4K41U3tjCY6nxwRXTg-0SQ,441
65
+ spatialdds_idl18/spatial/vio/_vio.py,sha256=OKjV_J4aepdaHQ-9qTH2W62iyU5XjEv2EBFUeW3OnP0,4148
66
+ spatialdds_ros/__init__.py,sha256=T6dmOFukGKDGFGW-tpaf2aGM7qhrq8bf-6nzhFKbfvs,138
67
+ spatialdds_ros/adapter.py,sha256=Zc_bX_tEKxq86JAG6izI2ScUbI9x72MpSwqd47zGnCI,15271
68
+ spatialdds_ros/announce.py,sha256=KiaU1yTWkb9VQi4MFG4zLEeCcyyqZSa4jFtBE7oB0gs,10607
69
+ spatialdds_ros/config.py,sha256=bMF6CJqm391HKpwdnSSx3K84DKCJ9iozyVkcKizG7vg,13385
70
+ spatialdds_ros/frames.py,sha256=gj8y6d7q9kL76HqKeWbMrKnKlyH0tm9dxOLBpBdnLyQ,8428
71
+ spatialdds_ros/kinematic.py,sha256=cUonIQP6Esuifkhgw8t1cCDOabcjxMIfwojq9V331eo,5905
72
+ spatialdds_ros/qos.py,sha256=y2XYmElnjHO4vfUlaapXOK1K8DHZZD44xn2jNG_PQ-0,4673
73
+ spatialdds_ros/recording.py,sha256=CKS6wjv-lulnMGAjqFA0uWLc-4blMHo3FOADj7jbmFU,7135
74
+ spatialdds_ros/robot_node.py,sha256=kgWXg-akb3cP26bSszq618CJrUmOxuZJNr_NRgzj_8M,12204
75
+ spatialdds_ros/standalone.py,sha256=N2gaEjRcSz6t8nwAeI-Y0wArl6y19K7R95LOUznxs7Q,9073
76
+ spatialdds_ros/tempo.py,sha256=kiIfrHhXaTWYxAxkjakE4LHAui4rt8szihD2SvTr734,4500
77
+ spatialdds_ros/topics.py,sha256=S0g9QKnt7uI_Wepa-_MqvHbLspqNhn9TuwOit2kheb4,2314
78
+ spatialdds_ros/transport.py,sha256=l_WXiEQWbFEHlViMPF5SWIVwZJ_BB8MzrE0vwkMzOWY,7600
79
+ spatialdds_ros-0.3.0.data/data/share/ament_index/resource_index/packages/spatialdds_ros,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
80
+ spatialdds_ros-0.3.0.data/data/share/spatialdds_ros/package.xml,sha256=2M48WVIZbJjMHJ2KQKRiwmrP2dVvwfNGCiKmseFiE5Y,1043
81
+ spatialdds_ros-0.3.0.dist-info/licenses/LICENSE,sha256=4rWO0Ng52-5yK5ns3A0M5Y1ZbOtFkBuFuzXxHwHSa0w,1070
82
+ spatialdds_ros-0.3.0.dist-info/METADATA,sha256=_l3wgrTwCbJRj3YKqjBF3UR-nizTNRjQaJ50yfeQFc0,15301
83
+ spatialdds_ros-0.3.0.dist-info/WHEEL,sha256=mOdXR9C3wMVov7cSY8PY-XCzo3SmmWCHvGQr5csJoao,91
84
+ spatialdds_ros-0.3.0.dist-info/entry_points.txt,sha256=HAaYrvo_oFbdb2zud59LJA_ENEhBgAi7jrj_Au8udlI,110
85
+ spatialdds_ros-0.3.0.dist-info/top_level.txt,sha256=2t0qZAbyjcevZW6h2wnwh6VRjc5TzIeURXsL9EbcaT4,32
86
+ spatialdds_ros-0.3.0.dist-info/zip-safe,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
87
+ spatialdds_ros-0.3.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (84.2.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,3 @@
1
+ [console_scripts]
2
+ kinematic_node = spatialdds_ros.standalone:main
3
+ robot_node = spatialdds_ros.robot_node:main
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Open AR Cloud
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,2 @@
1
+ spatialdds_idl18
2
+ spatialdds_ros
@@ -0,0 +1 @@
1
+