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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dynamicalnodes
3
- Version: 0.1.3
3
+ Version: 0.1.4
4
4
  Summary: dynamicalnodes is a Python library for modeling general control systems.
5
5
  Author-email: Nehal Singh Mangat <nehalsinghmangat.software@gmail.com>
6
6
  License-Expression: MIT
@@ -7,7 +7,7 @@ build-backend = "setuptools.build_meta"
7
7
 
8
8
  [project]
9
9
  name = "dynamicalnodes"
10
- version = "0.1.3"
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
- timer_hz : float, optional
85
- In the generated node: fires ``_run_step`` at this frequency instead
86
- of triggering from subscription callbacks.
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
- if not self._subs and self._timer_hz is None and self._pubs_cfg:
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 self._timer_hz is not None:
823
- ln(f" step trigger: timer at {self._timer_hz} Hz")
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 self._sync_mode == "all"
836
+ if sync_mode == "all"
830
837
  else "step when any subscription receives a fresh value"
831
838
  )
832
- ln(f" sync_mode={self._sync_mode!r}: {sync_desc}.")
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 self._timer_hz is not None:
912
+ if timer_hz is not None:
906
913
  blank()
907
914
  ln(" # ── Timer " + "─" * 60)
908
- period = 1.0 / self._timer_hz
915
+ period = 1.0 / timer_hz
909
916
  ln(f" self._timer = self.create_timer(")
910
- ln(f" {period}, # {self._timer_hz} Hz")
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 self._timer_hz is None:
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={self._sync_mode!r}")
1009
- if self._sync_mode == "all":
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")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dynamicalnodes
3
- Version: 0.1.3
3
+ Version: 0.1.4
4
4
  Summary: dynamicalnodes is a Python library for modeling general control systems.
5
5
  Author-email: Nehal Singh Mangat <nehalsinghmangat.software@gmail.com>
6
6
  License-Expression: MIT
File without changes
File without changes
File without changes