dynamicalnodes 0.1.3__tar.gz → 0.1.4__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.
- {dynamicalnodes-0.1.3/src/dynamicalnodes.egg-info → dynamicalnodes-0.1.4}/PKG-INFO +1 -1
- {dynamicalnodes-0.1.3 → dynamicalnodes-0.1.4}/pyproject.toml +1 -1
- {dynamicalnodes-0.1.3 → dynamicalnodes-0.1.4}/src/dynamicalnodes/rosnode.py +41 -34
- {dynamicalnodes-0.1.3 → dynamicalnodes-0.1.4/src/dynamicalnodes.egg-info}/PKG-INFO +1 -1
- {dynamicalnodes-0.1.3 → dynamicalnodes-0.1.4}/LICENSE +0 -0
- {dynamicalnodes-0.1.3 → dynamicalnodes-0.1.4}/README.md +0 -0
- {dynamicalnodes-0.1.3 → dynamicalnodes-0.1.4}/setup.cfg +0 -0
- {dynamicalnodes-0.1.3 → dynamicalnodes-0.1.4}/src/dynamicalnodes/__init__.py +0 -0
- {dynamicalnodes-0.1.3 → dynamicalnodes-0.1.4}/src/dynamicalnodes/dynamical_system.py +0 -0
- {dynamicalnodes-0.1.3 → dynamicalnodes-0.1.4}/src/dynamicalnodes/ros2py_py2ros.py +0 -0
- {dynamicalnodes-0.1.3 → dynamicalnodes-0.1.4}/src/dynamicalnodes.egg-info/SOURCES.txt +0 -0
- {dynamicalnodes-0.1.3 → dynamicalnodes-0.1.4}/src/dynamicalnodes.egg-info/dependency_links.txt +0 -0
- {dynamicalnodes-0.1.3 → dynamicalnodes-0.1.4}/src/dynamicalnodes.egg-info/requires.txt +0 -0
- {dynamicalnodes-0.1.3 → dynamicalnodes-0.1.4}/src/dynamicalnodes.egg-info/top_level.txt +0 -0
|
@@ -7,7 +7,7 @@ build-backend = "setuptools.build_meta"
|
|
|
7
7
|
|
|
8
8
|
[project]
|
|
9
9
|
name = "dynamicalnodes"
|
|
10
|
-
version = "0.1.
|
|
10
|
+
version = "0.1.4"
|
|
11
11
|
description = "dynamicalnodes is a Python library for modeling general control systems."
|
|
12
12
|
readme = "README.md"
|
|
13
13
|
requires-python = ">=3.12"
|
|
@@ -71,19 +71,17 @@ class ROSNode:
|
|
|
71
71
|
- ``"key"`` (str, optional): Key into h() return dict. Required when
|
|
72
72
|
h() returns a dict and there are multiple publishers.
|
|
73
73
|
|
|
74
|
-
sync_mode : str, required when ``subs`` has more than one entry
|
|
75
|
-
``"any"`` — step whenever any subscription has fresh data.
|
|
76
|
-
``"all"`` — step only when every subscription has fresh data.
|
|
77
|
-
|
|
78
74
|
state_name : str, optional
|
|
79
75
|
Name of the state parameter in ``f``'s signature (e.g. ``"ck"`` for
|
|
80
76
|
``f(ck, ...)``) . Required for stateful systems; ignored if ``f`` is
|
|
81
77
|
``None``. ``eval()`` injects ``self._state`` under this name before
|
|
82
78
|
calling ``DynamicalSystem.eval()``.
|
|
83
79
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
80
|
+
Notes
|
|
81
|
+
-----
|
|
82
|
+
``sync_mode`` and ``timer_hz`` are not constructor parameters — they only
|
|
83
|
+
affect the generated ROS2 node's triggering behavior, so they're passed to
|
|
84
|
+
``write_ROSNode_to_rclpy()`` instead.
|
|
87
85
|
|
|
88
86
|
Examples
|
|
89
87
|
--------
|
|
@@ -114,26 +112,10 @@ class ROSNode:
|
|
|
114
112
|
state_name: Optional[str] = None,
|
|
115
113
|
subs: Optional[Sequence[SubDict]] = None,
|
|
116
114
|
pubs: Optional[Sequence[PubDict]] = None,
|
|
117
|
-
sync_mode: Optional[str] = None,
|
|
118
|
-
timer_hz: Optional[float] = None,
|
|
119
115
|
) -> None:
|
|
120
|
-
n_subs = len(subs) if subs else 0
|
|
121
|
-
if n_subs > 1 and sync_mode is None:
|
|
122
|
-
raise ValueError(
|
|
123
|
-
"sync_mode ('any' or 'all') is required when subs has "
|
|
124
|
-
"more than one subscription"
|
|
125
|
-
)
|
|
126
|
-
sync_mode = sync_mode or "any"
|
|
127
|
-
if sync_mode not in ("any", "all"):
|
|
128
|
-
raise ValueError(f"sync_mode must be 'any' or 'all', got: {sync_mode!r}")
|
|
129
|
-
if timer_hz is not None and timer_hz <= 0:
|
|
130
|
-
raise ValueError(f"timer_hz must be positive, got {timer_hz}")
|
|
131
|
-
|
|
132
116
|
self._dynamical_system = dynamical_system
|
|
133
117
|
self._state: Optional[Any] = None
|
|
134
118
|
self._state_key: Optional[str] = state_name
|
|
135
|
-
self._sync_mode = sync_mode
|
|
136
|
-
self._timer_hz = timer_hz
|
|
137
119
|
|
|
138
120
|
# -------- Parse subscriptions --------
|
|
139
121
|
self._subs: List[Tuple[str, type, Any, str, Optional[float], int, bool]] = []
|
|
@@ -320,6 +302,8 @@ class ROSNode:
|
|
|
320
302
|
path: str,
|
|
321
303
|
*,
|
|
322
304
|
node_name: str,
|
|
305
|
+
sync_mode: Optional[str] = None,
|
|
306
|
+
timer_hz: Optional[float] = None,
|
|
323
307
|
deps: Optional[List[Any]] = None,
|
|
324
308
|
initial_state: Optional[Any] = None,
|
|
325
309
|
initial_inputs: Optional[Dict[str, Any]] = None,
|
|
@@ -343,6 +327,13 @@ class ROSNode:
|
|
|
343
327
|
node_name : str
|
|
344
328
|
ROS2 node name (used for the generated class name, ``super().__init__``,
|
|
345
329
|
and logger messages).
|
|
330
|
+
sync_mode : str, optional
|
|
331
|
+
Required when there is more than one subscription. ``"any"`` —
|
|
332
|
+
step whenever any subscription has fresh data. ``"all"`` — step
|
|
333
|
+
only when every subscription has fresh data.
|
|
334
|
+
timer_hz : float, optional
|
|
335
|
+
Fires ``_run_step`` at this frequency instead of triggering from
|
|
336
|
+
subscription callbacks.
|
|
346
337
|
deps : list of callable, optional
|
|
347
338
|
Helper functions that ``f`` or ``h`` depend on. Each function's
|
|
348
339
|
source is inlined before the dynamics functions in the output file.
|
|
@@ -389,6 +380,10 @@ class ROSNode:
|
|
|
389
380
|
------
|
|
390
381
|
ValueError
|
|
391
382
|
If any function is a lambda (cannot be serialized).
|
|
383
|
+
ValueError
|
|
384
|
+
If there is more than one subscription and ``sync_mode`` is not
|
|
385
|
+
given, or ``sync_mode`` is not ``"any"``/``"all"``, or
|
|
386
|
+
``timer_hz`` is not positive.
|
|
392
387
|
ValueError
|
|
393
388
|
If the node has no subscriptions and no ``timer_hz`` — the
|
|
394
389
|
generated node would never step.
|
|
@@ -572,7 +567,19 @@ class ROSNode:
|
|
|
572
567
|
|
|
573
568
|
# ── validate ──────────────────────────────────────────────────────────
|
|
574
569
|
|
|
575
|
-
|
|
570
|
+
n_subs = len(self._subs)
|
|
571
|
+
if n_subs > 1 and sync_mode is None:
|
|
572
|
+
raise ValueError(
|
|
573
|
+
"sync_mode ('any' or 'all') is required when subs has "
|
|
574
|
+
"more than one subscription"
|
|
575
|
+
)
|
|
576
|
+
sync_mode = sync_mode or "any"
|
|
577
|
+
if sync_mode not in ("any", "all"):
|
|
578
|
+
raise ValueError(f"sync_mode must be 'any' or 'all', got: {sync_mode!r}")
|
|
579
|
+
if timer_hz is not None and timer_hz <= 0:
|
|
580
|
+
raise ValueError(f"timer_hz must be positive, got {timer_hz}")
|
|
581
|
+
|
|
582
|
+
if not self._subs and timer_hz is None and self._pubs_cfg:
|
|
576
583
|
raise ValueError(
|
|
577
584
|
"Cannot write a node with no subscriptions and no timer_hz — "
|
|
578
585
|
"the generated node would never step. Add timer_hz or subs."
|
|
@@ -819,17 +826,17 @@ class ROSNode:
|
|
|
819
826
|
f" {topic} ({msg_type.__name__}) [{_ref_name(py2ros, py2ros_cls)}{key_str}]"
|
|
820
827
|
)
|
|
821
828
|
ln("")
|
|
822
|
-
if
|
|
823
|
-
ln(f" step trigger: timer at {
|
|
829
|
+
if timer_hz is not None:
|
|
830
|
+
ln(f" step trigger: timer at {timer_hz} Hz")
|
|
824
831
|
else:
|
|
825
832
|
ln(" step trigger: event-driven (fires on each subscription callback)")
|
|
826
833
|
if subs:
|
|
827
834
|
sync_desc = (
|
|
828
835
|
"step only when every subscription has a fresh value"
|
|
829
|
-
if
|
|
836
|
+
if sync_mode == "all"
|
|
830
837
|
else "step when any subscription receives a fresh value"
|
|
831
838
|
)
|
|
832
|
-
ln(f" sync_mode={
|
|
839
|
+
ln(f" sync_mode={sync_mode!r}: {sync_desc}.")
|
|
833
840
|
_dynamic = dict(dynamic_params or {})
|
|
834
841
|
if _dynamic:
|
|
835
842
|
ln("")
|
|
@@ -902,12 +909,12 @@ class ROSNode:
|
|
|
902
909
|
ln(f" {msg_type.__name__}, {topic!r}, QOS,")
|
|
903
910
|
ln(f" )")
|
|
904
911
|
|
|
905
|
-
if
|
|
912
|
+
if timer_hz is not None:
|
|
906
913
|
blank()
|
|
907
914
|
ln(" # ── Timer " + "─" * 60)
|
|
908
|
-
period = 1.0 /
|
|
915
|
+
period = 1.0 / timer_hz
|
|
909
916
|
ln(f" self._timer = self.create_timer(")
|
|
910
|
-
ln(f" {period}, # {
|
|
917
|
+
ln(f" {period}, # {timer_hz} Hz")
|
|
911
918
|
ln(f" self._run_step,")
|
|
912
919
|
ln(f" callback_group=self._cb_group,")
|
|
913
920
|
ln(f" )")
|
|
@@ -950,7 +957,7 @@ class ROSNode:
|
|
|
950
957
|
else:
|
|
951
958
|
ln(f" ts = self.get_clock().now().nanoseconds * 1e-9")
|
|
952
959
|
ln(f" self._{arg}_buf.append((ts, arr))")
|
|
953
|
-
if
|
|
960
|
+
if timer_hz is None:
|
|
954
961
|
ln(f" self._run_step()")
|
|
955
962
|
|
|
956
963
|
# _fresh
|
|
@@ -1005,8 +1012,8 @@ class ROSNode:
|
|
|
1005
1012
|
)
|
|
1006
1013
|
|
|
1007
1014
|
blank()
|
|
1008
|
-
ln(f" # sync_mode={
|
|
1009
|
-
if
|
|
1015
|
+
ln(f" # sync_mode={sync_mode!r}")
|
|
1016
|
+
if sync_mode == "all":
|
|
1010
1017
|
cond = " or ".join(f"{a} is None" for _, _, _, _, a, _, _, _ in subs)
|
|
1011
1018
|
ln(f" if {cond}:")
|
|
1012
1019
|
ln(" return # not all inputs are fresh — skip this tick")
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{dynamicalnodes-0.1.3 → dynamicalnodes-0.1.4}/src/dynamicalnodes.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|