spatialdds-ros 0.4.0__tar.gz → 0.4.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (110) hide show
  1. {spatialdds_ros-0.4.0/spatialdds_ros.egg-info → spatialdds_ros-0.4.1}/PKG-INFO +1 -1
  2. {spatialdds_ros-0.4.0 → spatialdds_ros-0.4.1}/package.xml +1 -1
  3. {spatialdds_ros-0.4.0 → spatialdds_ros-0.4.1}/setup.py +1 -1
  4. {spatialdds_ros-0.4.0 → spatialdds_ros-0.4.1}/spatialdds_ros/__init__.py +1 -1
  5. {spatialdds_ros-0.4.0 → spatialdds_ros-0.4.1}/spatialdds_ros/robot_node.py +97 -2
  6. {spatialdds_ros-0.4.0 → spatialdds_ros-0.4.1/spatialdds_ros.egg-info}/PKG-INFO +1 -1
  7. {spatialdds_ros-0.4.0 → spatialdds_ros-0.4.1}/spatialdds_ros.egg-info/SOURCES.txt +1 -0
  8. spatialdds_ros-0.4.1/test/test_node_honesty.py +45 -0
  9. {spatialdds_ros-0.4.0 → spatialdds_ros-0.4.1}/LICENSE +0 -0
  10. {spatialdds_ros-0.4.0 → spatialdds_ros-0.4.1}/README.md +0 -0
  11. {spatialdds_ros-0.4.0 → spatialdds_ros-0.4.1}/resource/spatialdds_ros +0 -0
  12. {spatialdds_ros-0.4.0 → spatialdds_ros-0.4.1}/setup.cfg +0 -0
  13. {spatialdds_ros-0.4.0 → spatialdds_ros-0.4.1}/spatialdds_idl18/PROVENANCE.md +0 -0
  14. {spatialdds_ros-0.4.0 → spatialdds_ros-0.4.1}/spatialdds_idl18/__init__.py +0 -0
  15. {spatialdds_ros-0.4.0 → spatialdds_ros-0.4.1}/spatialdds_idl18/_field_aliases.py +0 -0
  16. {spatialdds_ros-0.4.0 → spatialdds_ros-0.4.1}/spatialdds_idl18/builtin/__init__.py +0 -0
  17. {spatialdds_ros-0.4.0 → spatialdds_ros-0.4.1}/spatialdds_idl18/builtin/_types.py +0 -0
  18. {spatialdds_ros-0.4.0 → spatialdds_ros-0.4.1}/spatialdds_idl18/spatial/__init__.py +0 -0
  19. {spatialdds_ros-0.4.0 → spatialdds_ros-0.4.1}/spatialdds_idl18/spatial/anchors/__init__.py +0 -0
  20. {spatialdds_ros-0.4.0 → spatialdds_ros-0.4.1}/spatialdds_idl18/spatial/anchors/_anchors.py +0 -0
  21. {spatialdds_ros-0.4.0 → spatialdds_ros-0.4.1}/spatialdds_idl18/spatial/argeo/__init__.py +0 -0
  22. {spatialdds_ros-0.4.0 → spatialdds_ros-0.4.1}/spatialdds_idl18/spatial/argeo/_argeo.py +0 -0
  23. {spatialdds_ros-0.4.0 → spatialdds_ros-0.4.1}/spatialdds_idl18/spatial/common/__init__.py +0 -0
  24. {spatialdds_ros-0.4.0 → spatialdds_ros-0.4.1}/spatialdds_idl18/spatial/common/_types.py +0 -0
  25. {spatialdds_ros-0.4.0 → spatialdds_ros-0.4.1}/spatialdds_idl18/spatial/core/GnssService/__init__.py +0 -0
  26. {spatialdds_ros-0.4.0 → spatialdds_ros-0.4.1}/spatialdds_idl18/spatial/core/GnssService/_core.py +0 -0
  27. {spatialdds_ros-0.4.0 → spatialdds_ros-0.4.1}/spatialdds_idl18/spatial/core/__init__.py +0 -0
  28. {spatialdds_ros-0.4.0 → spatialdds_ros-0.4.1}/spatialdds_idl18/spatial/core/_core.py +0 -0
  29. {spatialdds_ros-0.4.0 → spatialdds_ros-0.4.1}/spatialdds_idl18/spatial/disco/__init__.py +0 -0
  30. {spatialdds_ros-0.4.0 → spatialdds_ros-0.4.1}/spatialdds_idl18/spatial/disco/_discovery.py +0 -0
  31. {spatialdds_ros-0.4.0 → spatialdds_ros-0.4.1}/spatialdds_idl18/spatial/events/__init__.py +0 -0
  32. {spatialdds_ros-0.4.0 → spatialdds_ros-0.4.1}/spatialdds_idl18/spatial/events/_events.py +0 -0
  33. {spatialdds_ros-0.4.0 → spatialdds_ros-0.4.1}/spatialdds_idl18/spatial/events/enums/__init__.py +0 -0
  34. {spatialdds_ros-0.4.0 → spatialdds_ros-0.4.1}/spatialdds_idl18/spatial/events/enums/crossing_direction_enum/__init__.py +0 -0
  35. {spatialdds_ros-0.4.0 → spatialdds_ros-0.4.1}/spatialdds_idl18/spatial/events/enums/crossing_direction_enum/_events.py +0 -0
  36. {spatialdds_ros-0.4.0 → spatialdds_ros-0.4.1}/spatialdds_idl18/spatial/events/enums/event_state_enum/__init__.py +0 -0
  37. {spatialdds_ros-0.4.0 → spatialdds_ros-0.4.1}/spatialdds_idl18/spatial/events/enums/event_state_enum/_events.py +0 -0
  38. {spatialdds_ros-0.4.0 → spatialdds_ros-0.4.1}/spatialdds_idl18/spatial/events/enums/event_type_enum/__init__.py +0 -0
  39. {spatialdds_ros-0.4.0 → spatialdds_ros-0.4.1}/spatialdds_idl18/spatial/events/enums/event_type_enum/_events.py +0 -0
  40. {spatialdds_ros-0.4.0 → spatialdds_ros-0.4.1}/spatialdds_idl18/spatial/events/enums/severity_enum/__init__.py +0 -0
  41. {spatialdds_ros-0.4.0 → spatialdds_ros-0.4.1}/spatialdds_idl18/spatial/events/enums/severity_enum/_events.py +0 -0
  42. {spatialdds_ros-0.4.0 → spatialdds_ros-0.4.1}/spatialdds_idl18/spatial/events/enums/zone_kind_enum/__init__.py +0 -0
  43. {spatialdds_ros-0.4.0 → spatialdds_ros-0.4.1}/spatialdds_idl18/spatial/events/enums/zone_kind_enum/_events.py +0 -0
  44. {spatialdds_ros-0.4.0 → spatialdds_ros-0.4.1}/spatialdds_idl18/spatial/mapping/__init__.py +0 -0
  45. {spatialdds_ros-0.4.0 → spatialdds_ros-0.4.1}/spatialdds_idl18/spatial/mapping/_mapping.py +0 -0
  46. {spatialdds_ros-0.4.0 → spatialdds_ros-0.4.1}/spatialdds_idl18/spatial/mapping/enums/__init__.py +0 -0
  47. {spatialdds_ros-0.4.0 → spatialdds_ros-0.4.1}/spatialdds_idl18/spatial/mapping/enums/alignment_method/__init__.py +0 -0
  48. {spatialdds_ros-0.4.0 → spatialdds_ros-0.4.1}/spatialdds_idl18/spatial/mapping/enums/alignment_method/_mapping.py +0 -0
  49. {spatialdds_ros-0.4.0 → spatialdds_ros-0.4.1}/spatialdds_idl18/spatial/mapping/enums/edge_type/__init__.py +0 -0
  50. {spatialdds_ros-0.4.0 → spatialdds_ros-0.4.1}/spatialdds_idl18/spatial/mapping/enums/edge_type/_mapping.py +0 -0
  51. {spatialdds_ros-0.4.0 → spatialdds_ros-0.4.1}/spatialdds_idl18/spatial/mapping/enums/map_event_kind/__init__.py +0 -0
  52. {spatialdds_ros-0.4.0 → spatialdds_ros-0.4.1}/spatialdds_idl18/spatial/mapping/enums/map_event_kind/_mapping.py +0 -0
  53. {spatialdds_ros-0.4.0 → spatialdds_ros-0.4.1}/spatialdds_idl18/spatial/mapping/enums/map_kind/__init__.py +0 -0
  54. {spatialdds_ros-0.4.0 → spatialdds_ros-0.4.1}/spatialdds_idl18/spatial/mapping/enums/map_kind/_mapping.py +0 -0
  55. {spatialdds_ros-0.4.0 → spatialdds_ros-0.4.1}/spatialdds_idl18/spatial/mapping/enums/map_status/__init__.py +0 -0
  56. {spatialdds_ros-0.4.0 → spatialdds_ros-0.4.1}/spatialdds_idl18/spatial/mapping/enums/map_status/_mapping.py +0 -0
  57. {spatialdds_ros-0.4.0 → spatialdds_ros-0.4.1}/spatialdds_idl18/spatial/owm/__init__.py +0 -0
  58. {spatialdds_ros-0.4.0 → spatialdds_ros-0.4.1}/spatialdds_idl18/spatial/owm/_owm.py +0 -0
  59. {spatialdds_ros-0.4.0 → spatialdds_ros-0.4.1}/spatialdds_idl18/spatial/semantics/__init__.py +0 -0
  60. {spatialdds_ros-0.4.0 → spatialdds_ros-0.4.1}/spatialdds_idl18/spatial/semantics/_semantics.py +0 -0
  61. {spatialdds_ros-0.4.0 → spatialdds_ros-0.4.1}/spatialdds_idl18/spatial/sensing/__init__.py +0 -0
  62. {spatialdds_ros-0.4.0 → spatialdds_ros-0.4.1}/spatialdds_idl18/spatial/sensing/common/__init__.py +0 -0
  63. {spatialdds_ros-0.4.0 → spatialdds_ros-0.4.1}/spatialdds_idl18/spatial/sensing/common/_common.py +0 -0
  64. {spatialdds_ros-0.4.0 → spatialdds_ros-0.4.1}/spatialdds_idl18/spatial/sensing/lidar/__init__.py +0 -0
  65. {spatialdds_ros-0.4.0 → spatialdds_ros-0.4.1}/spatialdds_idl18/spatial/sensing/lidar/_lidar.py +0 -0
  66. {spatialdds_ros-0.4.0 → spatialdds_ros-0.4.1}/spatialdds_idl18/spatial/sensing/rad/__init__.py +0 -0
  67. {spatialdds_ros-0.4.0 → spatialdds_ros-0.4.1}/spatialdds_idl18/spatial/sensing/rad/_rad.py +0 -0
  68. {spatialdds_ros-0.4.0 → spatialdds_ros-0.4.1}/spatialdds_idl18/spatial/sensing/radio/__init__.py +0 -0
  69. {spatialdds_ros-0.4.0 → spatialdds_ros-0.4.1}/spatialdds_idl18/spatial/sensing/radio/_radio.py +0 -0
  70. {spatialdds_ros-0.4.0 → spatialdds_ros-0.4.1}/spatialdds_idl18/spatial/sensing/rf_beam/__init__.py +0 -0
  71. {spatialdds_ros-0.4.0 → spatialdds_ros-0.4.1}/spatialdds_idl18/spatial/sensing/rf_beam/_rf_beam.py +0 -0
  72. {spatialdds_ros-0.4.0 → spatialdds_ros-0.4.1}/spatialdds_idl18/spatial/sensing/vision/__init__.py +0 -0
  73. {spatialdds_ros-0.4.0 → spatialdds_ros-0.4.1}/spatialdds_idl18/spatial/sensing/vision/_vision.py +0 -0
  74. {spatialdds_ros-0.4.0 → spatialdds_ros-0.4.1}/spatialdds_idl18/spatial/slam_frontend/__init__.py +0 -0
  75. {spatialdds_ros-0.4.0 → spatialdds_ros-0.4.1}/spatialdds_idl18/spatial/slam_frontend/_slam_frontend.py +0 -0
  76. {spatialdds_ros-0.4.0 → spatialdds_ros-0.4.1}/spatialdds_idl18/spatial/vio/__init__.py +0 -0
  77. {spatialdds_ros-0.4.0 → spatialdds_ros-0.4.1}/spatialdds_idl18/spatial/vio/_vio.py +0 -0
  78. {spatialdds_ros-0.4.0 → spatialdds_ros-0.4.1}/spatialdds_ros/adapter.py +0 -0
  79. {spatialdds_ros-0.4.0 → spatialdds_ros-0.4.1}/spatialdds_ros/announce.py +0 -0
  80. {spatialdds_ros-0.4.0 → spatialdds_ros-0.4.1}/spatialdds_ros/awareness.py +0 -0
  81. {spatialdds_ros-0.4.0 → spatialdds_ros-0.4.1}/spatialdds_ros/config.py +0 -0
  82. {spatialdds_ros-0.4.0 → spatialdds_ros-0.4.1}/spatialdds_ros/frames.py +0 -0
  83. {spatialdds_ros-0.4.0 → spatialdds_ros-0.4.1}/spatialdds_ros/kinematic.py +0 -0
  84. {spatialdds_ros-0.4.0 → spatialdds_ros-0.4.1}/spatialdds_ros/qos.py +0 -0
  85. {spatialdds_ros-0.4.0 → spatialdds_ros-0.4.1}/spatialdds_ros/recording.py +0 -0
  86. {spatialdds_ros-0.4.0 → spatialdds_ros-0.4.1}/spatialdds_ros/standalone.py +0 -0
  87. {spatialdds_ros-0.4.0 → spatialdds_ros-0.4.1}/spatialdds_ros/tempo.py +0 -0
  88. {spatialdds_ros-0.4.0 → spatialdds_ros-0.4.1}/spatialdds_ros/topics.py +0 -0
  89. {spatialdds_ros-0.4.0 → spatialdds_ros-0.4.1}/spatialdds_ros/transport.py +0 -0
  90. {spatialdds_ros-0.4.0 → spatialdds_ros-0.4.1}/spatialdds_ros.egg-info/dependency_links.txt +0 -0
  91. {spatialdds_ros-0.4.0 → spatialdds_ros-0.4.1}/spatialdds_ros.egg-info/entry_points.txt +0 -0
  92. {spatialdds_ros-0.4.0 → spatialdds_ros-0.4.1}/spatialdds_ros.egg-info/requires.txt +0 -0
  93. {spatialdds_ros-0.4.0 → spatialdds_ros-0.4.1}/spatialdds_ros.egg-info/top_level.txt +0 -0
  94. {spatialdds_ros-0.4.0 → spatialdds_ros-0.4.1}/spatialdds_ros.egg-info/zip-safe +0 -0
  95. {spatialdds_ros-0.4.0 → spatialdds_ros-0.4.1}/test/test_acceptance_bare_scene.py +0 -0
  96. {spatialdds_ros-0.4.0 → spatialdds_ros-0.4.1}/test/test_acceptance_mcap_gateway.py +0 -0
  97. {spatialdds_ros-0.4.0 → spatialdds_ros-0.4.1}/test/test_acceptance_two_domains.py +0 -0
  98. {spatialdds_ros-0.4.0 → spatialdds_ros-0.4.1}/test/test_acceptance_web_to_robot.py +0 -0
  99. {spatialdds_ros-0.4.0 → spatialdds_ros-0.4.1}/test/test_adapter.py +0 -0
  100. {spatialdds_ros-0.4.0 → spatialdds_ros-0.4.1}/test/test_awareness.py +0 -0
  101. {spatialdds_ros-0.4.0 → spatialdds_ros-0.4.1}/test/test_config.py +0 -0
  102. {spatialdds_ros-0.4.0 → spatialdds_ros-0.4.1}/test/test_frames.py +0 -0
  103. {spatialdds_ros-0.4.0 → spatialdds_ros-0.4.1}/test/test_generated_types.py +0 -0
  104. {spatialdds_ros-0.4.0 → spatialdds_ros-0.4.1}/test/test_kinematic.py +0 -0
  105. {spatialdds_ros-0.4.0 → spatialdds_ros-0.4.1}/test/test_no_ros_needed.py +0 -0
  106. {spatialdds_ros-0.4.0 → spatialdds_ros-0.4.1}/test/test_packaging.py +0 -0
  107. {spatialdds_ros-0.4.0 → spatialdds_ros-0.4.1}/test/test_qos_and_tempo.py +0 -0
  108. {spatialdds_ros-0.4.0 → spatialdds_ros-0.4.1}/test/test_readme.py +0 -0
  109. {spatialdds_ros-0.4.0 → spatialdds_ros-0.4.1}/test/test_transport.py +0 -0
  110. {spatialdds_ros-0.4.0 → spatialdds_ros-0.4.1}/test/test_types_resolve_at_definitions.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: spatialdds-ros
3
- Version: 0.4.0
3
+ Version: 0.4.1
4
4
  Summary: A ROS 2 robot as a SpatialDDS participant
5
5
  Home-page: https://github.com/OpenArCloud/spatialdds-ros
6
6
  Author: Open AR Cloud
@@ -2,7 +2,7 @@
2
2
  <?xml-model href="http://download.ros.org/schema/package_format3.xsd" schematypens="http://relaxng.org/ns/structure/1.0"?>
3
3
  <package format="3">
4
4
  <name>spatialdds_ros</name>
5
- <version>0.4.0</version>
5
+ <version>0.4.1</version>
6
6
  <description>
7
7
  Makes a ROS 2 robot a SpatialDDS participant: its pose published as a
8
8
  spatial.owm entity on scene-scoped topics, goto commands consumed from
@@ -42,7 +42,7 @@ LONG_DESCRIPTION = (HERE / "README.md").read_text(encoding="utf-8")
42
42
 
43
43
  setup(
44
44
  name="spatialdds-ros",
45
- version="0.4.0",
45
+ version="0.4.1",
46
46
  # `spatialdds_idl18` ships alongside: the generated bindings annotate
47
47
  # nested members with their own fully-qualified names, so the package has
48
48
  # to be importable as `spatialdds_idl18` and cannot be nested inside this
@@ -3,4 +3,4 @@
3
3
  See docs/extraction-census.md for what is configurable and why.
4
4
  """
5
5
 
6
- __version__ = "0.4.0"
6
+ __version__ = "0.4.1"
@@ -198,6 +198,7 @@ def main(argv=None) -> int: # pragma: no cover
198
198
  from tf2_ros import Buffer, TransformListener
199
199
  self._tf_buffer = Buffer()
200
200
  self._tf_listener = TransformListener(self._tf_buffer, self)
201
+ self._tf_freshness = TfFreshness()
201
202
  self._read_pose = self._pose_from_tf
202
203
  elif source == "amcl":
203
204
  from geometry_msgs.msg import PoseWithCovarianceStamped
@@ -225,6 +226,14 @@ def main(argv=None) -> int: # pragma: no cover
225
226
  # not converged yet simply has no transform, and the adapter's
226
227
  # own silence handling already describes that state correctly.
227
228
  return
229
+ # A transform the buffer has already answered with is not a new
230
+ # observation. Asked for "the latest", the buffer keeps returning
231
+ # the last transform it heard after its publisher has died, and
232
+ # calling that a sighting every tick kept a dead robot ACTIVE and
233
+ # freshly stamped forever -- the floor republishing a ghost the
234
+ # adapter's silence rule could never catch (finding 10).
235
+ if not self._tf_freshness.new(tf.header.stamp):
236
+ return
228
237
  t = tf.transform.translation
229
238
  self.adapter.observed(t.x, t.y, _yaw_of(tf.transform.rotation))
230
239
 
@@ -378,6 +387,9 @@ def main(argv=None) -> int: # pragma: no cover
378
387
  self._goal_type = NavigateToPose
379
388
  self._client = ActionClient(node, NavigateToPose, action_name)
380
389
  self._action_name = action_name
390
+ # Only the latest goal may report. A goal superseded by a newer
391
+ # one ends too, and its ending is not news about the robot.
392
+ self._seq = 0
381
393
 
382
394
  def available(self) -> Optional[str]:
383
395
  if not self._client.server_is_ready():
@@ -397,8 +409,48 @@ def main(argv=None) -> int: # pragma: no cover
397
409
  goal.pose.pose.position.y = y
398
410
  goal.pose.pose.position.z = self._node.config.ground_z
399
411
  goal.pose.pose.orientation.w = 1.0
400
- self._client.send_goal_async(goal)
401
- return f"nav2 accepted a goal at ({x:.2f}, {y:.2f})"
412
+ self._seq += 1
413
+ seq = self._seq
414
+ future = self._client.send_goal_async(goal)
415
+ future.add_done_callback(
416
+ lambda f: self._on_response(f, seq, x, y))
417
+ # Sent, not accepted: nav2 has not answered yet. Until 0.4.1 this
418
+ # said "accepted", and whatever nav2 then decided went nowhere: a
419
+ # rejected or aborted goal reached the robot's log and no client
420
+ # (finding 11).
421
+ return f"sent a goal at ({x:.2f}, {y:.2f}) to nav2"
422
+
423
+ def _on_response(self, future, seq: int, x: float, y: float) -> None:
424
+ if seq != self._seq:
425
+ return
426
+ try:
427
+ handle = future.result()
428
+ except Exception as error: # noqa: BLE001
429
+ self._refuse(f"goal ({x:.2f}, {y:.2f}) could not be sent to "
430
+ f"nav2: {error}")
431
+ return
432
+ if not handle.accepted:
433
+ self._refuse(goal_outcome(False, None, x, y))
434
+ return
435
+ handle.get_result_async().add_done_callback(
436
+ lambda f: self._on_result(f, seq, x, y))
437
+
438
+ def _on_result(self, future, seq: int, x: float, y: float) -> None:
439
+ if seq != self._seq:
440
+ return
441
+ try:
442
+ status = future.result().status
443
+ except Exception as error: # noqa: BLE001
444
+ self._refuse(f"goal ({x:.2f}, {y:.2f}): nav2 gave no result: "
445
+ f"{error}")
446
+ return
447
+ why = goal_outcome(True, status, x, y)
448
+ if why:
449
+ self._refuse(why)
450
+
451
+ def _refuse(self, why: str) -> None:
452
+ self._node.adapter.refused(why)
453
+ self._node.get_logger().info(why)
402
454
 
403
455
  try:
404
456
  node = SpatialDdsRobot()
@@ -416,6 +468,49 @@ def main(argv=None) -> int: # pragma: no cover
416
468
  return 0
417
469
 
418
470
 
471
+ class TfFreshness:
472
+ """Whether a looked-up transform is a new observation, by its stamp.
473
+
474
+ Compared stamp to stamp, never to a clock, so a sim on its own time and a
475
+ robot on wall time are treated alike: a transform is new if its stamp is
476
+ not the one the buffer answered with last time.
477
+ """
478
+
479
+ def __init__(self) -> None:
480
+ self._last: Optional[tuple] = None
481
+
482
+ def new(self, stamp) -> bool:
483
+ key = (int(stamp.sec), int(stamp.nanosec))
484
+ if key == self._last:
485
+ return False
486
+ self._last = key
487
+ return True
488
+
489
+
490
+ #: action_msgs/GoalStatus, the three endings.
491
+ GOAL_SUCCEEDED, GOAL_CANCELED, GOAL_ABORTED = 4, 5, 6
492
+
493
+
494
+ def goal_outcome(accepted: bool, status: Optional[int],
495
+ x: float, y: float) -> Optional[str]:
496
+ """The refusal a goal's ending owes its requester, or None if it arrived.
497
+
498
+ A goal nav2 would not take, or gave up on, travels to whoever asked on the
499
+ robot's latched record (`RobotAdapter.refused`), as the kinematic fallback's
500
+ refusals always have.
501
+ """
502
+ if not accepted:
503
+ return f"goal ({x:.2f}, {y:.2f}) rejected by nav2"
504
+ if status == GOAL_SUCCEEDED:
505
+ return None
506
+ if status == GOAL_ABORTED:
507
+ return (f"goal ({x:.2f}, {y:.2f}) abandoned: nav2 could not reach it "
508
+ f"(no plan, or the controller gave up)")
509
+ if status == GOAL_CANCELED:
510
+ return f"goal ({x:.2f}, {y:.2f}) cancelled before it was reached"
511
+ return f"goal ({x:.2f}, {y:.2f}) ended in nav2 status {status}"
512
+
513
+
419
514
  def _yaw_of(q) -> float:
420
515
  """Yaw from a ROS quaternion message."""
421
516
  return math.atan2(2.0 * (q.w * q.z + q.x * q.y),
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: spatialdds-ros
3
- Version: 0.4.0
3
+ Version: 0.4.1
4
4
  Summary: A ROS 2 robot as a SpatialDDS participant
5
5
  Home-page: https://github.com/OpenArCloud/spatialdds-ros
6
6
  Author: Open AR Cloud
@@ -101,6 +101,7 @@ test/test_frames.py
101
101
  test/test_generated_types.py
102
102
  test/test_kinematic.py
103
103
  test/test_no_ros_needed.py
104
+ test/test_node_honesty.py
104
105
  test/test_packaging.py
105
106
  test/test_qos_and_tempo.py
106
107
  test/test_readme.py
@@ -0,0 +1,45 @@
1
+ """The nav2 node says only what it saw: findings 10 and 11, without ROS.
2
+
3
+ The node itself needs rclpy, so its two decisions are module-level and are
4
+ checked here directly; the ROS run that shows them end to end is SpatialDDS-
5
+ demo's `robot-captures.spec.ts`, "the sim dies...", against the nav2 tier.
6
+ """
7
+ from types import SimpleNamespace
8
+
9
+ from spatialdds_ros.robot_node import (
10
+ GOAL_ABORTED, GOAL_CANCELED, GOAL_SUCCEEDED, TfFreshness, goal_outcome)
11
+
12
+
13
+ def stamp(sec, nanosec=0):
14
+ return SimpleNamespace(sec=sec, nanosec=nanosec)
15
+
16
+
17
+ def test_a_transform_already_answered_is_not_a_new_observation():
18
+ """Finding 10: the buffer's last transform, asked for again after its
19
+ publisher died, must not count as a sighting."""
20
+ fresh = TfFreshness()
21
+ assert fresh.new(stamp(100, 5)) is True
22
+ assert fresh.new(stamp(100, 5)) is False # the sim died here
23
+ assert fresh.new(stamp(100, 5)) is False
24
+ assert fresh.new(stamp(100, 6)) is True # and came back
25
+
26
+
27
+ def test_freshness_is_stamp_to_stamp_not_against_a_clock():
28
+ """A sim on its own time and a robot on wall time are treated alike."""
29
+ fresh = TfFreshness()
30
+ assert fresh.new(stamp(0, 1)) is True
31
+ assert fresh.new(stamp(1792950000, 0)) is True
32
+
33
+
34
+ def test_a_goal_that_arrived_owes_no_refusal():
35
+ assert goal_outcome(True, GOAL_SUCCEEDED, 1, 2) is None
36
+
37
+
38
+ def test_every_other_ending_is_a_refusal_naming_the_goal():
39
+ """Finding 11: rejected, aborted and cancelled goals reach the requester."""
40
+ for accepted, status, word in ((False, None, "rejected"),
41
+ (True, GOAL_ABORTED, "abandoned"),
42
+ (True, GOAL_CANCELED, "cancelled"),
43
+ (True, 2, "status 2")):
44
+ why = goal_outcome(accepted, status, 10.0, -12.0)
45
+ assert why and "(10.00, -12.00)" in why and word in why, why
File without changes
File without changes
File without changes