dynamicalnodes 0.1__tar.gz → 0.1.2__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.2/PKG-INFO +80 -0
- dynamicalnodes-0.1.2/README.md +38 -0
- {dynamicalnodes-0.1 → dynamicalnodes-0.1.2}/pyproject.toml +21 -2
- dynamicalnodes-0.1.2/src/dynamicalnodes/__init__.py +29 -0
- {dynamicalnodes-0.1 → dynamicalnodes-0.1.2}/src/dynamicalnodes/dynamical_system.py +5 -0
- {dynamicalnodes-0.1 → dynamicalnodes-0.1.2}/src/dynamicalnodes/ros2py_py2ros.py +42 -3
- dynamicalnodes-0.1.2/src/dynamicalnodes/rosnode.py +1141 -0
- dynamicalnodes-0.1.2/src/dynamicalnodes.egg-info/PKG-INFO +80 -0
- {dynamicalnodes-0.1 → dynamicalnodes-0.1.2}/src/dynamicalnodes.egg-info/SOURCES.txt +0 -1
- {dynamicalnodes-0.1 → dynamicalnodes-0.1.2}/src/dynamicalnodes.egg-info/requires.txt +4 -1
- dynamicalnodes-0.1/PKG-INFO +0 -82
- dynamicalnodes-0.1/README.md +0 -50
- dynamicalnodes-0.1/src/dynamicalnodes/__init__.py +0 -35
- dynamicalnodes-0.1/src/dynamicalnodes/rosnode.py +0 -658
- dynamicalnodes-0.1/src/dynamicalnodes/rostools.py +0 -372
- dynamicalnodes-0.1/src/dynamicalnodes.egg-info/PKG-INFO +0 -82
- {dynamicalnodes-0.1 → dynamicalnodes-0.1.2}/LICENSE +0 -0
- {dynamicalnodes-0.1 → dynamicalnodes-0.1.2}/setup.cfg +0 -0
- {dynamicalnodes-0.1 → dynamicalnodes-0.1.2}/src/dynamicalnodes.egg-info/dependency_links.txt +0 -0
- {dynamicalnodes-0.1 → dynamicalnodes-0.1.2}/src/dynamicalnodes.egg-info/top_level.txt +0 -0
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: dynamicalnodes
|
|
3
|
+
Version: 0.1.2
|
|
4
|
+
Summary: dynamicalnodes is a Python library for modeling general control systems.
|
|
5
|
+
Author-email: Nehal Singh Mangat <nehalsinghmangat.software@gmail.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/nehalsinghmangat/dynamicalnodes
|
|
8
|
+
Project-URL: Documentation, https://nehalsinghmangat.github.io/dynamicalnodes
|
|
9
|
+
Project-URL: Issues, https://github.com/nehalsinghmangat/dynamicalnodes/issues
|
|
10
|
+
Keywords: control,estimation,ros2
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Intended Audience :: Science/Research
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Classifier: Topic :: Scientific/Engineering
|
|
16
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
17
|
+
Requires-Python: >=3.12
|
|
18
|
+
Description-Content-Type: text/markdown
|
|
19
|
+
License-File: LICENSE
|
|
20
|
+
Requires-Dist: numpy>=1.24
|
|
21
|
+
Provides-Extra: dev
|
|
22
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
23
|
+
Requires-Dist: pytest-doctestplus>=1.0.0; extra == "dev"
|
|
24
|
+
Requires-Dist: flake8>=6.0; extra == "dev"
|
|
25
|
+
Requires-Dist: black>=24.0; extra == "dev"
|
|
26
|
+
Requires-Dist: isort>=6.0; extra == "dev"
|
|
27
|
+
Requires-Dist: mypy>=1.0; extra == "dev"
|
|
28
|
+
Requires-Dist: build>=0.10; extra == "dev"
|
|
29
|
+
Requires-Dist: twine>=4.0; extra == "dev"
|
|
30
|
+
Provides-Extra: docs
|
|
31
|
+
Requires-Dist: sphinx>=8.0; extra == "docs"
|
|
32
|
+
Requires-Dist: sphinx-rtd-theme>=1.3; extra == "docs"
|
|
33
|
+
Requires-Dist: sphinx-copybutton>=0.5; extra == "docs"
|
|
34
|
+
Requires-Dist: myst_nb; extra == "docs"
|
|
35
|
+
Requires-Dist: sphinx-autodoc-typehints>=1.24.0; extra == "docs"
|
|
36
|
+
Requires-Dist: matplotlib>=3.9; extra == "docs"
|
|
37
|
+
Requires-Dist: scipy>=1.11; extra == "docs"
|
|
38
|
+
Requires-Dist: jupyter; extra == "docs"
|
|
39
|
+
Requires-Dist: jaxtyping>=0.2; extra == "docs"
|
|
40
|
+
Requires-Dist: beartype>=0.15; extra == "docs"
|
|
41
|
+
Dynamic: license-file
|
|
42
|
+
|
|
43
|
+
# dynamicalnodes: Control Systems on ROS 2
|
|
44
|
+
|
|
45
|
+
> _"Give me a [control system] long enough and a [computer] on which to place it, and I shall move the world."_ - Archimedes
|
|
46
|
+
|
|
47
|
+
**dynamicalnodes** is a Python development framework that bridges the gap between control systems and their implementation onto ROS 2. Designed for hobbyists, students, and academics alike, this framework won't cure cancer, but it can do the next best thing: make robotics easier.
|
|
48
|
+
|
|
49
|
+
To get started, or to explore what this framework has to offer, click here: [dynamicalnodes Documentation](https://nehalsinghmangat.github.io/dynamicalnodes).
|
|
50
|
+
|
|
51
|
+
For a 22 second video describing this framework's intended workflow for, click here: [SAIL 2025 -- Presenting dynamicalnodes](https://youtu.be/5GbVHo6QZrw).
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
## Turtlebot: Whiteboard -> Python -> ROS -> World
|
|
55
|
+
<table>
|
|
56
|
+
<tr>
|
|
57
|
+
<td align="center">
|
|
58
|
+
<img src="docs/source/_static/figure_turtlebot_pipeline/turtlebot.svg" alt="TurtleBot Overview" width="400"/><br/>
|
|
59
|
+
</td>
|
|
60
|
+
<td align="center">
|
|
61
|
+
<img src="docs/source/_static/figure_turtlebot_pipeline/turtlesim_software.gif" alt="Turtle Software" width="200"/><br/>
|
|
62
|
+
</td>
|
|
63
|
+
<td align="center">
|
|
64
|
+
<img src="docs/source/_static/figure_turtlebot_pipeline/turtlesim_simulation.gif" alt="Turtle Simulation" width="200"/><br/>
|
|
65
|
+
</td>
|
|
66
|
+
<td align="center">
|
|
67
|
+
<img src="docs/source/_static/figure_turtlebot_pipeline/turtlesim_hardware.gif" alt="Turtle Hardware" width="200"/><br/>
|
|
68
|
+
</td>
|
|
69
|
+
</tr>
|
|
70
|
+
</table>
|
|
71
|
+
|
|
72
|
+
<p align="center">
|
|
73
|
+
<img src="docs/source/_static/figure_turtlebot_pipeline/labels_for_steps_from_theory_to_hardware.png" alt="Step Labels" width="800"/>
|
|
74
|
+
</p>
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# dynamicalnodes: Control Systems on ROS 2
|
|
2
|
+
|
|
3
|
+
> _"Give me a [control system] long enough and a [computer] on which to place it, and I shall move the world."_ - Archimedes
|
|
4
|
+
|
|
5
|
+
**dynamicalnodes** is a Python development framework that bridges the gap between control systems and their implementation onto ROS 2. Designed for hobbyists, students, and academics alike, this framework won't cure cancer, but it can do the next best thing: make robotics easier.
|
|
6
|
+
|
|
7
|
+
To get started, or to explore what this framework has to offer, click here: [dynamicalnodes Documentation](https://nehalsinghmangat.github.io/dynamicalnodes).
|
|
8
|
+
|
|
9
|
+
For a 22 second video describing this framework's intended workflow for, click here: [SAIL 2025 -- Presenting dynamicalnodes](https://youtu.be/5GbVHo6QZrw).
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
## Turtlebot: Whiteboard -> Python -> ROS -> World
|
|
13
|
+
<table>
|
|
14
|
+
<tr>
|
|
15
|
+
<td align="center">
|
|
16
|
+
<img src="docs/source/_static/figure_turtlebot_pipeline/turtlebot.svg" alt="TurtleBot Overview" width="400"/><br/>
|
|
17
|
+
</td>
|
|
18
|
+
<td align="center">
|
|
19
|
+
<img src="docs/source/_static/figure_turtlebot_pipeline/turtlesim_software.gif" alt="Turtle Software" width="200"/><br/>
|
|
20
|
+
</td>
|
|
21
|
+
<td align="center">
|
|
22
|
+
<img src="docs/source/_static/figure_turtlebot_pipeline/turtlesim_simulation.gif" alt="Turtle Simulation" width="200"/><br/>
|
|
23
|
+
</td>
|
|
24
|
+
<td align="center">
|
|
25
|
+
<img src="docs/source/_static/figure_turtlebot_pipeline/turtlesim_hardware.gif" alt="Turtle Hardware" width="200"/><br/>
|
|
26
|
+
</td>
|
|
27
|
+
</tr>
|
|
28
|
+
</table>
|
|
29
|
+
|
|
30
|
+
<p align="center">
|
|
31
|
+
<img src="docs/source/_static/figure_turtlebot_pipeline/labels_for_steps_from_theory_to_hardware.png" alt="Step Labels" width="800"/>
|
|
32
|
+
</p>
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
|
|
@@ -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.2"
|
|
11
11
|
description = "dynamicalnodes is a Python library for modeling general control systems."
|
|
12
12
|
readme = "README.md"
|
|
13
13
|
requires-python = ">=3.12"
|
|
@@ -16,6 +16,14 @@ authors = [
|
|
|
16
16
|
{ name = "Nehal Singh Mangat", email = "nehalsinghmangat.software@gmail.com" }
|
|
17
17
|
]
|
|
18
18
|
keywords = ["control", "estimation", "ros2"]
|
|
19
|
+
classifiers = [
|
|
20
|
+
"Development Status :: 3 - Alpha",
|
|
21
|
+
"Intended Audience :: Science/Research",
|
|
22
|
+
"Programming Language :: Python :: 3",
|
|
23
|
+
"Programming Language :: Python :: 3.12",
|
|
24
|
+
"Topic :: Scientific/Engineering",
|
|
25
|
+
"Topic :: Software Development :: Libraries",
|
|
26
|
+
]
|
|
19
27
|
dependencies = [
|
|
20
28
|
"numpy>=1.24",
|
|
21
29
|
]
|
|
@@ -35,21 +43,32 @@ dev = [
|
|
|
35
43
|
docs = [
|
|
36
44
|
"sphinx>=8.0",
|
|
37
45
|
"sphinx-rtd-theme>=1.3",
|
|
46
|
+
"sphinx-copybutton>=0.5",
|
|
38
47
|
"myst_nb",
|
|
39
48
|
"sphinx-autodoc-typehints>=1.24.0",
|
|
40
|
-
"matplotlib>=3.
|
|
49
|
+
"matplotlib>=3.9",
|
|
41
50
|
"scipy>=1.11",
|
|
42
51
|
"jupyter",
|
|
52
|
+
"jaxtyping>=0.2",
|
|
53
|
+
"beartype>=0.15",
|
|
43
54
|
]
|
|
44
55
|
|
|
45
56
|
[project.urls]
|
|
46
57
|
Homepage = "https://github.com/nehalsinghmangat/dynamicalnodes"
|
|
47
58
|
Documentation = "https://nehalsinghmangat.github.io/dynamicalnodes"
|
|
59
|
+
Issues = "https://github.com/nehalsinghmangat/dynamicalnodes/issues"
|
|
48
60
|
|
|
49
61
|
[tool.pytest.ini_options]
|
|
50
62
|
addopts = "--doctest-modules"
|
|
51
63
|
doctest_optionflags = ["NORMALIZE_WHITESPACE", "ELLIPSIS"]
|
|
52
64
|
|
|
65
|
+
[tool.isort]
|
|
66
|
+
profile = "black"
|
|
67
|
+
|
|
68
|
+
[tool.mypy]
|
|
69
|
+
python_version = "3.12"
|
|
70
|
+
strict = true
|
|
71
|
+
|
|
53
72
|
[tool.setuptools]
|
|
54
73
|
package-dir = {"" = "src"}
|
|
55
74
|
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"""
|
|
2
|
+
dynamicalnodes: A modular Python framework for dynamical systems, estimation, and ROS2 integration.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from .dynamical_system import DynamicalSystem
|
|
6
|
+
from .rosnode import ROSNode
|
|
7
|
+
|
|
8
|
+
# Lazy import — requires a live ROS2 installation
|
|
9
|
+
_ROS2_MODULES = {
|
|
10
|
+
"ros2py_py2ros": ("ros2py_py2ros", None),
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def __getattr__(name):
|
|
15
|
+
if name in _ROS2_MODULES:
|
|
16
|
+
module_name, attr = _ROS2_MODULES[name]
|
|
17
|
+
try:
|
|
18
|
+
import importlib
|
|
19
|
+
mod = importlib.import_module(f".{module_name}", package=__name__)
|
|
20
|
+
return mod if attr is None else getattr(mod, attr)
|
|
21
|
+
except ImportError as e:
|
|
22
|
+
raise ImportError(
|
|
23
|
+
f"{name} requires ROS2 dependencies. Install with: pip install dynamicalnodes[ros2]\n"
|
|
24
|
+
f"Original error: {e}"
|
|
25
|
+
) from e
|
|
26
|
+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
__all__ = ["DynamicalSystem", "ROSNode"]
|
|
@@ -186,6 +186,11 @@ class DynamicalSystem:
|
|
|
186
186
|
Keyword arguments passed to f and h. Each function receives
|
|
187
187
|
only the kwargs it declares in its signature via `_smart_call`.
|
|
188
188
|
|
|
189
|
+
A reserved key ``_state`` may be passed to inject the current
|
|
190
|
+
state without knowing ``f``'s first parameter name. It is
|
|
191
|
+
removed from kwargs before dispatch and inserted under the
|
|
192
|
+
correct name automatically.
|
|
193
|
+
|
|
189
194
|
Returns
|
|
190
195
|
-------
|
|
191
196
|
Any
|
|
@@ -24,6 +24,7 @@ from turtlesim.msg import Pose
|
|
|
24
24
|
from nav_msgs.msg import Odometry, Path
|
|
25
25
|
from sensor_msgs.msg import (
|
|
26
26
|
Imu,
|
|
27
|
+
NavSatFix,
|
|
27
28
|
LaserScan,
|
|
28
29
|
Image,
|
|
29
30
|
CompressedImage,
|
|
@@ -558,6 +559,42 @@ def py2ros_imu(arr: NDArray) -> Imu:
|
|
|
558
559
|
return msg
|
|
559
560
|
|
|
560
561
|
|
|
562
|
+
# ---------------------------------------------------------------------------
|
|
563
|
+
# sensor_msgs/NavSatFix <-> np.ndarray(3,) [latitude, longitude, altitude]
|
|
564
|
+
# ---------------------------------------------------------------------------
|
|
565
|
+
|
|
566
|
+
def ros2py_nav_sat_fix(msg: NavSatFix) -> NDArray:
|
|
567
|
+
"""
|
|
568
|
+
NavSatFix → [latitude, longitude, altitude]
|
|
569
|
+
|
|
570
|
+
>>> from sensor_msgs.msg import NavSatFix
|
|
571
|
+
>>> msg = NavSatFix()
|
|
572
|
+
>>> msg.latitude, msg.longitude, msg.altitude = 37.5, -122.0, 10.0
|
|
573
|
+
>>> ros2py_nav_sat_fix(msg).tolist()
|
|
574
|
+
[37.5, -122.0, 10.0]
|
|
575
|
+
"""
|
|
576
|
+
return np.array([msg.latitude, msg.longitude, msg.altitude], dtype=float)
|
|
577
|
+
|
|
578
|
+
|
|
579
|
+
def py2ros_nav_sat_fix(arr: NDArray) -> NavSatFix:
|
|
580
|
+
"""
|
|
581
|
+
[latitude, longitude, altitude] → NavSatFix
|
|
582
|
+
|
|
583
|
+
>>> import numpy as np
|
|
584
|
+
>>> msg = py2ros_nav_sat_fix(np.array([37.5, -122.0, 10.0]))
|
|
585
|
+
>>> msg.latitude, msg.longitude, msg.altitude
|
|
586
|
+
(37.5, -122.0, 10.0)
|
|
587
|
+
"""
|
|
588
|
+
flat = np.asarray(arr, dtype=float).ravel()
|
|
589
|
+
if flat.size != 3:
|
|
590
|
+
raise ValueError(f"py2ros_nav_sat_fix expected length 3, got {flat.size}")
|
|
591
|
+
msg = NavSatFix()
|
|
592
|
+
msg.latitude = float(flat[0])
|
|
593
|
+
msg.longitude = float(flat[1])
|
|
594
|
+
msg.altitude = float(flat[2])
|
|
595
|
+
return msg
|
|
596
|
+
|
|
597
|
+
|
|
561
598
|
# ---------------------------------------------------------------------------
|
|
562
599
|
# nav_msgs/Odometry <-> np.ndarray(13,)
|
|
563
600
|
# (pose + twist; covariances ignored)
|
|
@@ -907,9 +944,9 @@ def py2ros_uint8_multiarray(arr: NDArray) -> UInt8MultiArray:
|
|
|
907
944
|
# std_msgs/Float64 <-> scalar float (as 1D array)
|
|
908
945
|
# ---------------------------------------------------------------------------
|
|
909
946
|
|
|
910
|
-
def ros2py_float64(msg: Float64) ->
|
|
911
|
-
"""Convert Float64 message to
|
|
912
|
-
return
|
|
947
|
+
def ros2py_float64(msg: Float64) -> float:
|
|
948
|
+
"""Convert Float64 message to Python float."""
|
|
949
|
+
return float(msg.data)
|
|
913
950
|
|
|
914
951
|
|
|
915
952
|
def py2ros_float64(arr: NDArray) -> Float64:
|
|
@@ -994,6 +1031,7 @@ ROS2PY_DEFAULT = {
|
|
|
994
1031
|
AccelStamped: ros2py_accel_stamped,
|
|
995
1032
|
Vector3Stamped: ros2py_vector3_stamped,
|
|
996
1033
|
Imu: ros2py_imu,
|
|
1034
|
+
NavSatFix: ros2py_nav_sat_fix,
|
|
997
1035
|
Odometry: ros2py_odometry,
|
|
998
1036
|
Path: ros2py_path,
|
|
999
1037
|
JointState: ros2py_joint_state,
|
|
@@ -1019,6 +1057,7 @@ PY2ROS_DEFAULT = {
|
|
|
1019
1057
|
AccelStamped: py2ros_accel_stamped,
|
|
1020
1058
|
Vector3Stamped: py2ros_vector3_stamped,
|
|
1021
1059
|
Imu: py2ros_imu,
|
|
1060
|
+
NavSatFix: py2ros_nav_sat_fix,
|
|
1022
1061
|
Odometry: py2ros_odometry,
|
|
1023
1062
|
Path: py2ros_path,
|
|
1024
1063
|
JointState: py2ros_joint_state,
|