armw 0.1.0a0__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.
- armw-0.1.0a0/.gitignore +5 -0
- armw-0.1.0a0/PKG-INFO +16 -0
- armw-0.1.0a0/README.md +3 -0
- armw-0.1.0a0/armw/__init__.py +178 -0
- armw-0.1.0a0/armw/_version.py +16 -0
- armw-0.1.0a0/armw/base_node.py +110 -0
- armw-0.1.0a0/armw/base_parameter_server.py +20 -0
- armw-0.1.0a0/armw/doc_int_enum.py +64 -0
- armw-0.1.0a0/armw/globals.py +2 -0
- armw-0.1.0a0/armw/ros1/__init__.py +0 -0
- armw-0.1.0a0/armw/ros1/interfaces.py +27 -0
- armw-0.1.0a0/armw/ros1/ros1_node.py +112 -0
- armw-0.1.0a0/armw/ros1/ros1_parameter_server.py +27 -0
- armw-0.1.0a0/armw/ros2/__init__.py +0 -0
- armw-0.1.0a0/armw/ros2/interfaces.py +18 -0
- armw-0.1.0a0/armw/ros2/ros2_node.py +205 -0
- armw-0.1.0a0/armw/ros2/ros2_parameter_server.py +44 -0
- armw-0.1.0a0/armw/ros2/time.py +112 -0
- armw-0.1.0a0/armw/ros2/util.py +18 -0
- armw-0.1.0a0/armw/ros2/wait_for_message.py +52 -0
- armw-0.1.0a0/armw/ros_common.py +13 -0
- armw-0.1.0a0/armw.egg-info/PKG-INFO +16 -0
- armw-0.1.0a0/armw.egg-info/SOURCES.txt +25 -0
- armw-0.1.0a0/armw.egg-info/dependency_links.txt +1 -0
- armw-0.1.0a0/armw.egg-info/top_level.txt +1 -0
- armw-0.1.0a0/pyproject.toml +37 -0
- armw-0.1.0a0/setup.cfg +4 -0
armw-0.1.0a0/.gitignore
ADDED
armw-0.1.0a0/PKG-INFO
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: armw
|
|
3
|
+
Version: 0.1.0a0
|
|
4
|
+
Summary: ARMW
|
|
5
|
+
Author-email: Nathan McGuire <nmcguire@whoi.edu>
|
|
6
|
+
Project-URL: homepage, http://acomms.whoi.edu
|
|
7
|
+
Keywords: Library,Python Modules,Scientific/Engineering
|
|
8
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
9
|
+
Classifier: Intended Audience :: Developers
|
|
10
|
+
Classifier: Intended Audience :: Science/Research
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.6
|
|
12
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
13
|
+
Classifier: Topic :: Scientific/Engineering
|
|
14
|
+
Classifier: License :: OSI Approved :: GNU Lesser General Public License v3 or later (LGPLv3+)
|
|
15
|
+
Requires-Python: >=3.6
|
|
16
|
+
Description-Content-Type: text/plain
|
armw-0.1.0a0/README.md
ADDED
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import armw.globals
|
|
2
|
+
import armw.base_node
|
|
3
|
+
import armw.base_parameter_server
|
|
4
|
+
import armw.doc_int_enum
|
|
5
|
+
|
|
6
|
+
# Figure out the middleware version
|
|
7
|
+
|
|
8
|
+
MIDDLEWARE = ""
|
|
9
|
+
|
|
10
|
+
try:
|
|
11
|
+
import rospy
|
|
12
|
+
|
|
13
|
+
MIDDLEWARE = "ros1"
|
|
14
|
+
except:
|
|
15
|
+
pass
|
|
16
|
+
|
|
17
|
+
try:
|
|
18
|
+
import rclpy
|
|
19
|
+
|
|
20
|
+
MIDDLEWARE = "ros2"
|
|
21
|
+
except:
|
|
22
|
+
pass
|
|
23
|
+
|
|
24
|
+
# Do some stuff with it
|
|
25
|
+
if MIDDLEWARE == "ros1":
|
|
26
|
+
import armw.ros_common
|
|
27
|
+
from armw.ros1.ros1_node import Ros1Node
|
|
28
|
+
from armw.ros1.ros1_parameter_server import Ros1ParameterServer
|
|
29
|
+
import armw.ros1.interfaces
|
|
30
|
+
|
|
31
|
+
# function and class names
|
|
32
|
+
ArmwNode = armw.ros1.ros1_node.Ros1Node
|
|
33
|
+
ParameterServer = armw.ros1.ros1_parameter_server.Ros1ParameterServer
|
|
34
|
+
Publisher = rospy.Publisher
|
|
35
|
+
|
|
36
|
+
Time = rospy.Time
|
|
37
|
+
Duration = rospy.Duration
|
|
38
|
+
Rate = rospy.Rate
|
|
39
|
+
AnyMsg = rospy.AnyMsg
|
|
40
|
+
|
|
41
|
+
InterruptException = rospy.ROSInterruptException
|
|
42
|
+
|
|
43
|
+
main = armw.ros1.ros1_node.main
|
|
44
|
+
is_shutdown = rospy.is_shutdown
|
|
45
|
+
sleep = rospy.sleep
|
|
46
|
+
get_time = rospy.get_time
|
|
47
|
+
import_message = armw.ros1.interfaces.import_message
|
|
48
|
+
import_service_ = armw.ros1.interfaces.import_service_
|
|
49
|
+
deserialze_anymsg = armw.ros1.interfaces.deserialze_anymsg
|
|
50
|
+
canonicalize_name = rospy.names.canonicalize_name
|
|
51
|
+
package_and_msg_name = armw.ros_common.package_and_msg_name
|
|
52
|
+
|
|
53
|
+
# Log levels
|
|
54
|
+
DEBUG = rospy.DEBUG
|
|
55
|
+
INFO = rospy.INFO
|
|
56
|
+
WARN = rospy.WARN
|
|
57
|
+
ERROR = rospy.ERROR
|
|
58
|
+
elif MIDDLEWARE == "ros2":
|
|
59
|
+
import rclpy.publisher
|
|
60
|
+
|
|
61
|
+
import time
|
|
62
|
+
import armw.ros_common
|
|
63
|
+
import armw.ros2.ros2_node
|
|
64
|
+
import armw.ros2.ros2_parameter_server
|
|
65
|
+
import armw.ros2.interfaces
|
|
66
|
+
import armw.ros2.time
|
|
67
|
+
import armw.ros2.util
|
|
68
|
+
|
|
69
|
+
# function and class names
|
|
70
|
+
ArmwNode = armw.ros2.ros2_node.Ros2Node
|
|
71
|
+
ParameterServer = armw.ros2.ros2_parameter_server.Ros2ParameterServer
|
|
72
|
+
Publisher = rclpy.publisher.Publisher
|
|
73
|
+
|
|
74
|
+
Time = armw.ros2.time.Time
|
|
75
|
+
Duration = armw.ros2.time.Duration
|
|
76
|
+
Rate = armw.ros2.time.Rate
|
|
77
|
+
AnyMsg = None
|
|
78
|
+
|
|
79
|
+
InterruptException = Exception
|
|
80
|
+
|
|
81
|
+
main = armw.ros2.ros2_node.main
|
|
82
|
+
is_shutdown = lambda: not rclpy.ok()
|
|
83
|
+
sleep = time.sleep
|
|
84
|
+
get_time = armw.ros2.time.get_time
|
|
85
|
+
import_message = armw.ros2.interfaces.import_message
|
|
86
|
+
import_service_ = armw.ros2.interfaces.import_service_
|
|
87
|
+
deserialze_anymsg = armw.ros2.interfaces.deserialze_anymsg
|
|
88
|
+
canonicalize_name = armw.ros2.util.canonicalize_name
|
|
89
|
+
package_and_msg_name = armw.ros_common.package_and_msg_name
|
|
90
|
+
|
|
91
|
+
# Log levels
|
|
92
|
+
DEBUG = 1
|
|
93
|
+
INFO = 2
|
|
94
|
+
WARN = 3
|
|
95
|
+
ERROR = 4
|
|
96
|
+
else:
|
|
97
|
+
print("No middleware available")
|
|
98
|
+
exit(1)
|
|
99
|
+
|
|
100
|
+
ParameterEnum = armw.doc_int_enum.DocIntEnum
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def import_service(package_name: str, service_name: str):
|
|
104
|
+
if service_name.endswith("Request"):
|
|
105
|
+
service_name = service_name[:-7]
|
|
106
|
+
_, _, req = armw.import_service_(package_name, service_name)
|
|
107
|
+
return req
|
|
108
|
+
elif service_name.endswith("Response"):
|
|
109
|
+
service_name = service_name[:-8]
|
|
110
|
+
_, resp, _ = armw.import_service_(package_name, service_name)
|
|
111
|
+
return resp
|
|
112
|
+
else:
|
|
113
|
+
return armw.import_service_(package_name, service_name)
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def import_message_from_name(name):
|
|
117
|
+
pkg, msg = armw.package_and_msg_name(name)
|
|
118
|
+
return armw.import_message(pkg, msg)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def NODE() -> armw.base_node.BaseNode:
|
|
122
|
+
return armw.globals.NODE
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def fill_time(dest_object, source_object) -> armw.Time:
|
|
126
|
+
"""
|
|
127
|
+
Attempts to get around some time weirdness by doing lots of python weirdness
|
|
128
|
+
"""
|
|
129
|
+
|
|
130
|
+
seconds_fields = ["sec", "secs", "seconds"]
|
|
131
|
+
nanoseconds_fields = ["nsec", "nsecs", "nanosec", "nanosecs", "nanoseconds"]
|
|
132
|
+
|
|
133
|
+
seconds = None
|
|
134
|
+
nanoseconds = None
|
|
135
|
+
|
|
136
|
+
for field in seconds_fields:
|
|
137
|
+
if hasattr(source_object, field):
|
|
138
|
+
seconds = getattr(source_object, field)
|
|
139
|
+
break
|
|
140
|
+
|
|
141
|
+
for field in nanoseconds_fields:
|
|
142
|
+
if hasattr(source_object, field):
|
|
143
|
+
nanoseconds = getattr(source_object, field)
|
|
144
|
+
break
|
|
145
|
+
|
|
146
|
+
if seconds is None:
|
|
147
|
+
raise Exception(f"Source {source_object} does not have field encoding seconds")
|
|
148
|
+
if nanoseconds is None:
|
|
149
|
+
raise Exception(f"Source {source_object} does not have field encoding nanoseconds")
|
|
150
|
+
|
|
151
|
+
wrote_seconds = False
|
|
152
|
+
wrote_nanoseconds = False
|
|
153
|
+
|
|
154
|
+
for field in seconds_fields:
|
|
155
|
+
if hasattr(dest_object, field):
|
|
156
|
+
setattr(dest_object, field, seconds)
|
|
157
|
+
wrote_seconds = True
|
|
158
|
+
break
|
|
159
|
+
|
|
160
|
+
for field in nanoseconds_fields:
|
|
161
|
+
if hasattr(dest_object, field):
|
|
162
|
+
setattr(dest_object, field, nanoseconds)
|
|
163
|
+
wrote_nanoseconds = True
|
|
164
|
+
break
|
|
165
|
+
|
|
166
|
+
if not wrote_seconds:
|
|
167
|
+
raise Exception(f"Destination {dest_object} does not have field encoding seconds")
|
|
168
|
+
if not wrote_nanoseconds:
|
|
169
|
+
raise Exception(f"Destination {source_object} does not have field encoding nanoseconds")
|
|
170
|
+
|
|
171
|
+
return dest_object
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def get_time_object(time_object):
|
|
175
|
+
if "duration" in str(type(time_object)).lower():
|
|
176
|
+
return fill_time(armw.Duration(), time_object)
|
|
177
|
+
else:
|
|
178
|
+
return fill_time(armw.Time(), time_object)
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# file generated by setuptools_scm
|
|
2
|
+
# don't change, don't track in version control
|
|
3
|
+
TYPE_CHECKING = False
|
|
4
|
+
if TYPE_CHECKING:
|
|
5
|
+
from typing import Tuple, Union
|
|
6
|
+
VERSION_TUPLE = Tuple[Union[int, str], ...]
|
|
7
|
+
else:
|
|
8
|
+
VERSION_TUPLE = object
|
|
9
|
+
|
|
10
|
+
version: str
|
|
11
|
+
__version__: str
|
|
12
|
+
__version_tuple__: VERSION_TUPLE
|
|
13
|
+
version_tuple: VERSION_TUPLE
|
|
14
|
+
|
|
15
|
+
__version__ = version = '0.1.0a0'
|
|
16
|
+
__version_tuple__ = version_tuple = (0, 1, 0)
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Base node class that contains all the interfaces needed for the abstract middleware
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import armw.globals
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class BaseNode(object):
|
|
9
|
+
def __init__(self):
|
|
10
|
+
# Set the global node object before we run the rest of the constructor
|
|
11
|
+
armw.globals.NODE = self
|
|
12
|
+
|
|
13
|
+
self.update_rate = 1
|
|
14
|
+
|
|
15
|
+
def spin(self):
|
|
16
|
+
raise NotImplementedError
|
|
17
|
+
|
|
18
|
+
def loop_once(self):
|
|
19
|
+
pass
|
|
20
|
+
|
|
21
|
+
def shutdown(self):
|
|
22
|
+
pass
|
|
23
|
+
|
|
24
|
+
def log(self, level, message):
|
|
25
|
+
"""
|
|
26
|
+
Abstract function to log to console
|
|
27
|
+
:param level: 0 for info, 1 for warn, 2 for error
|
|
28
|
+
:param message: String to log
|
|
29
|
+
:return: None
|
|
30
|
+
"""
|
|
31
|
+
raise NotImplementedError
|
|
32
|
+
|
|
33
|
+
def log_debug(self, message, *args):
|
|
34
|
+
self.log(-1, message % args)
|
|
35
|
+
|
|
36
|
+
def log_info(self, message, *args):
|
|
37
|
+
self.log(0, message % args)
|
|
38
|
+
|
|
39
|
+
def log_warn(self, message, *args):
|
|
40
|
+
self.log(1, message % args)
|
|
41
|
+
|
|
42
|
+
def log_error(self, message, *args):
|
|
43
|
+
self.log(2, message % args)
|
|
44
|
+
|
|
45
|
+
def log_fatal(self, message, *args):
|
|
46
|
+
self.log(3, message % args)
|
|
47
|
+
|
|
48
|
+
def log_throttle(self, interval: int, level: int, message: str):
|
|
49
|
+
raise NotImplementedError
|
|
50
|
+
|
|
51
|
+
def log_debug_throttle(self, interval, message, *args):
|
|
52
|
+
self.log_throttle(interval, -1, message % args)
|
|
53
|
+
|
|
54
|
+
def log_info_throttle(self, interval, message, *args):
|
|
55
|
+
self.log_throttle(interval, 0, message % args)
|
|
56
|
+
|
|
57
|
+
def log_warn_throttle(self, interval, message, *args):
|
|
58
|
+
self.log_throttle(interval, 1, message % args)
|
|
59
|
+
|
|
60
|
+
def log_error_throttle(self, interval, message, *args):
|
|
61
|
+
self.log_throttle(interval, 2, message % args)
|
|
62
|
+
|
|
63
|
+
def get_name(self):
|
|
64
|
+
raise NotImplementedError
|
|
65
|
+
|
|
66
|
+
def get_namespace(self):
|
|
67
|
+
raise NotImplementedError
|
|
68
|
+
|
|
69
|
+
def search_param(self, parameter_name):
|
|
70
|
+
raise NotImplementedError
|
|
71
|
+
|
|
72
|
+
def has_param(self, parameter_name):
|
|
73
|
+
raise NotImplementedError
|
|
74
|
+
|
|
75
|
+
def read_param(self, parameter_name):
|
|
76
|
+
"""
|
|
77
|
+
Virtual method.
|
|
78
|
+
Override this for each middleware type
|
|
79
|
+
"""
|
|
80
|
+
raise NotImplementedError
|
|
81
|
+
|
|
82
|
+
def get_param(self, parameter_name, default=None):
|
|
83
|
+
"""
|
|
84
|
+
Returns the value for a given parameter name, or None if it can't find it
|
|
85
|
+
"""
|
|
86
|
+
raise NotImplementedError
|
|
87
|
+
|
|
88
|
+
def set_param(self, parameter_name, value):
|
|
89
|
+
raise NotImplementedError
|
|
90
|
+
|
|
91
|
+
def delete_param(self, parameter_name):
|
|
92
|
+
raise NotImplementedError
|
|
93
|
+
|
|
94
|
+
def publish(self, topic: str, message_type, queue_size=1, latch=False):
|
|
95
|
+
raise NotImplementedError
|
|
96
|
+
|
|
97
|
+
def subscribe(self, topic: str, message_type, callback, queue_size=1):
|
|
98
|
+
raise NotImplementedError
|
|
99
|
+
|
|
100
|
+
def create_service_server(self, name: str, service_type, callback):
|
|
101
|
+
raise NotImplementedError
|
|
102
|
+
|
|
103
|
+
def create_service_client(self, name: str, service_type):
|
|
104
|
+
raise NotImplementedError
|
|
105
|
+
|
|
106
|
+
def wait_for_message(self, topic, message_type, timeout=None):
|
|
107
|
+
raise NotImplementedError
|
|
108
|
+
|
|
109
|
+
def wait_for_service(self, name, timeout=None):
|
|
110
|
+
raise NotImplementedError
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Base class to provide a reasonable API to set up runtime-reconfigurable parameters
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from armw.base_node import BaseNode
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class BaseParameterServer(object):
|
|
9
|
+
def __init__(self, node_object: BaseNode, namespace: str = ""):
|
|
10
|
+
self.node = node_object
|
|
11
|
+
self.namespace = namespace
|
|
12
|
+
|
|
13
|
+
def add_parameter(self, name, description, default_value, min_value=None, max_value=None, parameter_type=None):
|
|
14
|
+
raise NotImplementedError
|
|
15
|
+
|
|
16
|
+
def start(self, callback):
|
|
17
|
+
raise NotImplementedError
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
from enum import IntEnum
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class DocIntEnum(IntEnum):
|
|
5
|
+
def __new__(cls, value, doc=None):
|
|
6
|
+
self = int.__new__(cls, value) # calling super().__new__(value) here would fail
|
|
7
|
+
self._value_ = value
|
|
8
|
+
if doc is not None:
|
|
9
|
+
self.__doc__ = doc
|
|
10
|
+
else:
|
|
11
|
+
self.__doc__ = ''
|
|
12
|
+
return self
|
|
13
|
+
|
|
14
|
+
@property
|
|
15
|
+
def info(self):
|
|
16
|
+
return f'{self.__class__}:{self.name}:[{self.value}]: {self.__doc__}'
|
|
17
|
+
|
|
18
|
+
@classmethod
|
|
19
|
+
def _get_field_index(cls, key: int):
|
|
20
|
+
try:
|
|
21
|
+
member_list = list(cls.__members__.items())
|
|
22
|
+
for i in range(len(member_list)): # Have to search through, because we might not have sequential values
|
|
23
|
+
member = member_list[i]
|
|
24
|
+
value = int(member[1])
|
|
25
|
+
if value == key:
|
|
26
|
+
return i
|
|
27
|
+
return None
|
|
28
|
+
except IndexError:
|
|
29
|
+
return None
|
|
30
|
+
|
|
31
|
+
@classmethod
|
|
32
|
+
def get_field(cls, key: int):
|
|
33
|
+
index = cls._get_field_index(key)
|
|
34
|
+
|
|
35
|
+
if index is not None:
|
|
36
|
+
return list(cls.__members__.items())[index][1]
|
|
37
|
+
else:
|
|
38
|
+
return None
|
|
39
|
+
|
|
40
|
+
@classmethod
|
|
41
|
+
def get_field_names(cls):
|
|
42
|
+
fields = list(cls.__members__.items())
|
|
43
|
+
return [str(field[0]) for field in fields]
|
|
44
|
+
|
|
45
|
+
@classmethod
|
|
46
|
+
def get_field_name(cls, key: int):
|
|
47
|
+
index = cls._get_field_index(key)
|
|
48
|
+
|
|
49
|
+
if index is not None:
|
|
50
|
+
return list(cls.__members__.items())[index][0]
|
|
51
|
+
else:
|
|
52
|
+
return None
|
|
53
|
+
|
|
54
|
+
@classmethod
|
|
55
|
+
def get_description(cls, key: int):
|
|
56
|
+
"""
|
|
57
|
+
Attempts to return the best description of a field that it can
|
|
58
|
+
"""
|
|
59
|
+
|
|
60
|
+
doc = cls.get_field(key).__doc__
|
|
61
|
+
if len(doc) > 0:
|
|
62
|
+
return doc
|
|
63
|
+
else:
|
|
64
|
+
return cls.get_field_name(key)
|
|
File without changes
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Functions to handle importing interface definitions
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import importlib
|
|
6
|
+
|
|
7
|
+
import roslib.message
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def import_message(package_name: str, message_name: str):
|
|
11
|
+
return getattr(importlib.import_module(f"{package_name}.msg"), message_name)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def import_service_(package_name: str, service_name: str):
|
|
15
|
+
service_class = getattr(importlib.import_module(f"{package_name}.srv"), service_name)
|
|
16
|
+
service_request = getattr(importlib.import_module(f"{package_name}.srv"), f"{service_name}Request")
|
|
17
|
+
service_response = getattr(importlib.import_module(f"{package_name}.srv"), f"{service_name}Response")
|
|
18
|
+
|
|
19
|
+
return service_class, service_response, service_request
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def deserialze_anymsg(msg_data):
|
|
23
|
+
topic_type = msg_data._connection_header['type']
|
|
24
|
+
topic_class = roslib.message.get_message_class(topic_type)
|
|
25
|
+
msg = topic_class()
|
|
26
|
+
msg.deserialize(msg_data._buff)
|
|
27
|
+
return msg
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
"""
|
|
2
|
+
ROS1 version
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import rospy
|
|
6
|
+
|
|
7
|
+
import armw.globals
|
|
8
|
+
from armw.base_node import BaseNode
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class Ros1Node(BaseNode):
|
|
12
|
+
def __init__(self):
|
|
13
|
+
super().__init__()
|
|
14
|
+
|
|
15
|
+
def spin(self):
|
|
16
|
+
# Issue status periodically.
|
|
17
|
+
rate = armw.Rate(self.update_rate)
|
|
18
|
+
while not armw.is_shutdown():
|
|
19
|
+
self.loop_once()
|
|
20
|
+
rate.sleep()
|
|
21
|
+
self.shutdown()
|
|
22
|
+
|
|
23
|
+
def log(self, level: int, message: str):
|
|
24
|
+
if level == -1:
|
|
25
|
+
rospy.logdebug(message)
|
|
26
|
+
elif level == 0:
|
|
27
|
+
rospy.loginfo(message)
|
|
28
|
+
elif level == 1:
|
|
29
|
+
rospy.logwarn(message)
|
|
30
|
+
elif level == 2:
|
|
31
|
+
rospy.logerr(message)
|
|
32
|
+
elif level == 3:
|
|
33
|
+
rospy.logfatal(message)
|
|
34
|
+
|
|
35
|
+
def log_throttle(self, interval: int, level: int, message: str):
|
|
36
|
+
if level == -1:
|
|
37
|
+
rospy.logdebug_throttle(interval, message)
|
|
38
|
+
elif level == 0:
|
|
39
|
+
rospy.loginfo_throttle(interval, message)
|
|
40
|
+
elif level == 1:
|
|
41
|
+
rospy.logwarn_throttle(interval, message)
|
|
42
|
+
elif level == 2:
|
|
43
|
+
rospy.logerr_throttle(interval, message)
|
|
44
|
+
|
|
45
|
+
def get_name(self):
|
|
46
|
+
return rospy.get_name()
|
|
47
|
+
|
|
48
|
+
def get_namespace(self):
|
|
49
|
+
return rospy.get_namespace()
|
|
50
|
+
|
|
51
|
+
def search_param(self, parameter_name):
|
|
52
|
+
return rospy.search_param(parameter_name)
|
|
53
|
+
|
|
54
|
+
def has_param(self, parameter_name):
|
|
55
|
+
return rospy.has_param(parameter_name)
|
|
56
|
+
|
|
57
|
+
def get_param(self, parameter_name, default=None):
|
|
58
|
+
"""
|
|
59
|
+
Returns the value for a given parameter name, or None if it can't find it
|
|
60
|
+
"""
|
|
61
|
+
return rospy.get_param(parameter_name, default)
|
|
62
|
+
|
|
63
|
+
def set_param(self, parameter_name, value):
|
|
64
|
+
rospy.set_param(parameter_name, value)
|
|
65
|
+
|
|
66
|
+
def delete_param(self, parameter_name):
|
|
67
|
+
rospy.delete_param(parameter_name)
|
|
68
|
+
|
|
69
|
+
def publish(self, topic: str, message_type, queue_size=1, latch=False):
|
|
70
|
+
return rospy.Publisher(topic, message_type, queue_size=queue_size, latch=latch)
|
|
71
|
+
|
|
72
|
+
def subscribe(self, topic: str, message_type, callback, queue_size=1):
|
|
73
|
+
if message_type is None:
|
|
74
|
+
from rospy import AnyMsg
|
|
75
|
+
message_type = AnyMsg
|
|
76
|
+
|
|
77
|
+
return rospy.Subscriber(topic, message_type, callback)
|
|
78
|
+
|
|
79
|
+
def create_service_server(self, name, service_type, callback):
|
|
80
|
+
return rospy.Service(name, service_type, callback)
|
|
81
|
+
|
|
82
|
+
def create_service_client(self, name: str, service_type):
|
|
83
|
+
return rospy.ServiceProxy(name, service_type)
|
|
84
|
+
|
|
85
|
+
def wait_for_message(self, topic, message_type, timeout=None):
|
|
86
|
+
if message_type is None:
|
|
87
|
+
from rospy import AnyMsg
|
|
88
|
+
message_type = AnyMsg
|
|
89
|
+
|
|
90
|
+
if timeout is None:
|
|
91
|
+
return rospy.wait_for_message(topic, message_type)
|
|
92
|
+
else:
|
|
93
|
+
return rospy.wait_for_message(topic, message_type, timeout=timeout)
|
|
94
|
+
|
|
95
|
+
def wait_for_service(self, name, timeout=None):
|
|
96
|
+
if timeout is None:
|
|
97
|
+
rospy.wait_for_service(name)
|
|
98
|
+
else:
|
|
99
|
+
return rospy.wait_for_service(name, timeout=timeout)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def main(node_class: type(BaseNode), node_name: str):
|
|
103
|
+
rospy.init_node(node_name)
|
|
104
|
+
|
|
105
|
+
armw.globals.NODE_NAME = node_name
|
|
106
|
+
armw.globals.NODE = node_class()
|
|
107
|
+
|
|
108
|
+
try:
|
|
109
|
+
armw.globals.NODE.spin()
|
|
110
|
+
rospy.loginfo(f"{node_name} shutdown")
|
|
111
|
+
except rospy.ROSInterruptException:
|
|
112
|
+
rospy.loginfo(f"{node_name} shutdown (interrupt)")
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import inspect
|
|
2
|
+
|
|
3
|
+
from ddynamic_reconfigure_python.ddynamic_reconfigure import DDynamicReconfigure
|
|
4
|
+
|
|
5
|
+
from armw.base_node import BaseNode
|
|
6
|
+
from armw.doc_int_enum import DocIntEnum
|
|
7
|
+
from armw.base_parameter_server import BaseParameterServer
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class Ros1ParameterServer(BaseParameterServer):
|
|
11
|
+
def __init__(self, node_object: BaseNode, namespace: str = ""):
|
|
12
|
+
super().__init__(node_object, namespace)
|
|
13
|
+
|
|
14
|
+
self.server = DDynamicReconfigure(namespace)
|
|
15
|
+
|
|
16
|
+
def add_parameter(self, name, description, default_value, min_value=None, max_value=None, parameter_type=None):
|
|
17
|
+
edit_method = ""
|
|
18
|
+
if inspect.isclass(parameter_type) and issubclass(parameter_type, DocIntEnum):
|
|
19
|
+
option_list = []
|
|
20
|
+
for value in parameter_type:
|
|
21
|
+
option_list.append(self.server.const(parameter_type.get_field_name(value), "int", int(value), parameter_type.get_description(value)))
|
|
22
|
+
edit_method = self.server.enum(option_list, parameter_type.__name__)
|
|
23
|
+
|
|
24
|
+
self.server.add_variable(name, description, default=default_value, min=min_value, max=max_value, edit_method=edit_method)
|
|
25
|
+
|
|
26
|
+
def start(self, callback):
|
|
27
|
+
self.server.start(callback)
|
|
File without changes
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Functions to handle importing interface definitions
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import importlib
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def import_message(package_name: str, message_name: str):
|
|
9
|
+
return getattr(importlib.import_module(f"{package_name}.msg"), message_name)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def import_service_(package_name: str, service_name: str):
|
|
13
|
+
service_class = getattr(importlib.import_module(f"{package_name}.srv"), service_name)
|
|
14
|
+
return service_class, service_class.Response, service_class.Request
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def deserialze_anymsg(msg_data):
|
|
18
|
+
return msg_data
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
"""
|
|
2
|
+
ROS2 version
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import time
|
|
6
|
+
import rclpy
|
|
7
|
+
from rclpy.callback_groups import MutuallyExclusiveCallbackGroup
|
|
8
|
+
from rclpy.node import Node
|
|
9
|
+
from rclpy.qos import QoSProfile, QoSDurabilityPolicy
|
|
10
|
+
|
|
11
|
+
from armw.base_node import BaseNode
|
|
12
|
+
import armw.globals
|
|
13
|
+
import armw.ros2.wait_for_message
|
|
14
|
+
|
|
15
|
+
executor: rclpy.executors.MultiThreadedExecutor = None
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class ServiceProxy(object):
|
|
19
|
+
def __init__(self, service_name, service_type, callback_group):
|
|
20
|
+
self._client = armw.NODE().create_client(service_type, service_name, callback_group=callback_group)
|
|
21
|
+
self.service_class = service_type.Request
|
|
22
|
+
|
|
23
|
+
# This causes a crash, so I'm going to leave it off
|
|
24
|
+
# def __del__(self):
|
|
25
|
+
# armw.NODE().destroy_client(self._client)
|
|
26
|
+
|
|
27
|
+
def __call__(self, *args, **kwargs):
|
|
28
|
+
if len(args) > 0:
|
|
29
|
+
req = args[0]
|
|
30
|
+
else:
|
|
31
|
+
req = self.service_class(**kwargs)
|
|
32
|
+
|
|
33
|
+
future = self._client.call_async(req)
|
|
34
|
+
executor.spin_until_future_complete(future)
|
|
35
|
+
return future.result()
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class Ros2Node(BaseNode, Node):
|
|
39
|
+
def __init__(self):
|
|
40
|
+
# Run parent constructors
|
|
41
|
+
Node.__init__(self, armw.globals.NODE_NAME, allow_undeclared_parameters=True, automatically_declare_parameters_from_overrides=True)
|
|
42
|
+
BaseNode.__init__(self)
|
|
43
|
+
|
|
44
|
+
self.timer_callback_group = MutuallyExclusiveCallbackGroup()
|
|
45
|
+
self.service_callback_group = MutuallyExclusiveCallbackGroup()
|
|
46
|
+
|
|
47
|
+
# Need so do some silly stuff to make services work
|
|
48
|
+
self.service_callbacks = {}
|
|
49
|
+
|
|
50
|
+
def spin(self):
|
|
51
|
+
timer_period = 1.0 / self.update_rate
|
|
52
|
+
self.timer = self.create_timer(timer_period, self.loop_once, callback_group=self.timer_callback_group)
|
|
53
|
+
|
|
54
|
+
def log(self, level: int, message: str):
|
|
55
|
+
if level == -1:
|
|
56
|
+
self.get_logger().debug(message)
|
|
57
|
+
elif level == 0:
|
|
58
|
+
self.get_logger().info(message)
|
|
59
|
+
elif level == 1:
|
|
60
|
+
self.get_logger().warn(message)
|
|
61
|
+
elif level == 2:
|
|
62
|
+
self.get_logger().error(message)
|
|
63
|
+
elif level == 3:
|
|
64
|
+
self.get_logger().fatal(message)
|
|
65
|
+
|
|
66
|
+
def log_throttle(self, interval: int, level: int, message: str):
|
|
67
|
+
if level == -1:
|
|
68
|
+
self.get_logger().debug(message, throttle_duration_sec=interval)
|
|
69
|
+
elif level == 0:
|
|
70
|
+
self.get_logger().info(message, throttle_duration_sec=interval)
|
|
71
|
+
elif level == 1:
|
|
72
|
+
self.get_logger().warn(message, throttle_duration_sec=interval)
|
|
73
|
+
elif level == 2:
|
|
74
|
+
self.get_logger().error(message, throttle_duration_sec=interval)
|
|
75
|
+
elif level == 3:
|
|
76
|
+
self.get_logger().fatal(message, throttle_duration_sec=interval)
|
|
77
|
+
|
|
78
|
+
def get_name(self):
|
|
79
|
+
return Node.get_name(self)
|
|
80
|
+
|
|
81
|
+
def get_namespace(self):
|
|
82
|
+
return Node.get_namespace(self)
|
|
83
|
+
|
|
84
|
+
def search_param(self, parameter_name):
|
|
85
|
+
"""
|
|
86
|
+
This isn't really a thing in ROS2
|
|
87
|
+
"""
|
|
88
|
+
|
|
89
|
+
return parameter_name
|
|
90
|
+
|
|
91
|
+
def get_param(self, parameter_name, default=None):
|
|
92
|
+
"""
|
|
93
|
+
Returns the value for a given parameter name, or None if it can't find it
|
|
94
|
+
"""
|
|
95
|
+
|
|
96
|
+
if parameter_name[0] == "~":
|
|
97
|
+
parameter_name = parameter_name[1:]
|
|
98
|
+
parameter_name = parameter_name.replace("/", ".")
|
|
99
|
+
|
|
100
|
+
try:
|
|
101
|
+
param_value = self.get_parameter(parameter_name).get_parameter_value()
|
|
102
|
+
if param_value.type == 0: # Parameter not set
|
|
103
|
+
return default
|
|
104
|
+
return rclpy.parameter.parameter_value_to_python(param_value)
|
|
105
|
+
except Exception as e:
|
|
106
|
+
self.log_error(f"Unable to read parameter {parameter_name}: {e}")
|
|
107
|
+
return default
|
|
108
|
+
|
|
109
|
+
def set_param(self, parameter_name, value):
|
|
110
|
+
if parameter_name[0] == "~":
|
|
111
|
+
parameter_name = parameter_name[1:]
|
|
112
|
+
parameter_name = parameter_name.replace("/", ".")
|
|
113
|
+
|
|
114
|
+
parameter = rclpy.Parameter(parameter_name, rclpy.Parameter.Type.from_parameter_value(value), value)
|
|
115
|
+
self.set_parameters([parameter])
|
|
116
|
+
|
|
117
|
+
def fix_topic_name(self, topic):
|
|
118
|
+
if topic[0] == "~":
|
|
119
|
+
topic = self.get_name() + topic[1:]
|
|
120
|
+
|
|
121
|
+
return topic
|
|
122
|
+
|
|
123
|
+
def publish(self, topic: str, message_type, queue_size=1, latch=False):
|
|
124
|
+
if latch:
|
|
125
|
+
qos = QoSProfile(depth=1, durability=QoSDurabilityPolicy.TRANSIENT_LOCAL)
|
|
126
|
+
else:
|
|
127
|
+
qos = queue_size
|
|
128
|
+
|
|
129
|
+
return self.create_publisher(message_type, self.fix_topic_name(topic), qos)
|
|
130
|
+
|
|
131
|
+
def subscribe(self, topic: str, message_type, callback, queue_size=1):
|
|
132
|
+
if message_type is None:
|
|
133
|
+
from ros2topic.api import get_msg_class
|
|
134
|
+
message_type = get_msg_class(self, topic, blocking=True)
|
|
135
|
+
|
|
136
|
+
return self.create_subscription(message_type, self.fix_topic_name(topic), callback, queue_size)
|
|
137
|
+
|
|
138
|
+
def create_service_server(self, name, service_type, callback):
|
|
139
|
+
self.service_callbacks[name] = callback
|
|
140
|
+
|
|
141
|
+
return self.create_service(service_type, name, lambda req, resp: self.service_callback(req, resp, name))
|
|
142
|
+
|
|
143
|
+
def service_callback(self, request, response, name):
|
|
144
|
+
response = self.service_callbacks[name](request)
|
|
145
|
+
return response
|
|
146
|
+
|
|
147
|
+
def create_service_client(self, name: str, service_type):
|
|
148
|
+
# return self.create_client(service_type, name)
|
|
149
|
+
return ServiceProxy(name, service_type, self.service_callback_group)
|
|
150
|
+
|
|
151
|
+
def wait_for_message(self, topic_name, topic_type, timeout=None):
|
|
152
|
+
if timeout is None:
|
|
153
|
+
time_to_wait = 100000 # Essentially forever
|
|
154
|
+
else:
|
|
155
|
+
time_to_wait = timeout
|
|
156
|
+
|
|
157
|
+
self.log_warn(f"Waiting for message on {topic_name} for {time_to_wait} seconds")
|
|
158
|
+
|
|
159
|
+
if topic_type is None:
|
|
160
|
+
from ros2topic.api import get_msg_class
|
|
161
|
+
topic_type = get_msg_class(self, topic_name, blocking=True)
|
|
162
|
+
|
|
163
|
+
success, msg = armw.ros2.wait_for_message.wait_for_message(topic_type, self, topic_name, time_to_wait=time_to_wait)
|
|
164
|
+
|
|
165
|
+
self.log_warn(f"Got message on {topic_name}")
|
|
166
|
+
return msg
|
|
167
|
+
|
|
168
|
+
def wait_for_service(self, service_name, timeout=None):
|
|
169
|
+
"""
|
|
170
|
+
This might not work correctly yet
|
|
171
|
+
"""
|
|
172
|
+
|
|
173
|
+
# Resolve the name
|
|
174
|
+
if service_name[0] != "/":
|
|
175
|
+
service_name = self.get_namespace() + "/" + service_name
|
|
176
|
+
|
|
177
|
+
# Wait
|
|
178
|
+
self.log_warn(f"Waiting for service {service_name}")
|
|
179
|
+
while True:
|
|
180
|
+
for service in self.get_service_names_and_types():
|
|
181
|
+
if service[0] == service_name:
|
|
182
|
+
self.log_warn(f"Got service {service_name}")
|
|
183
|
+
return
|
|
184
|
+
time.sleep(0.1)
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def main(node_class, node_name):
|
|
188
|
+
global executor
|
|
189
|
+
|
|
190
|
+
rclpy.init()
|
|
191
|
+
executor = rclpy.executors.MultiThreadedExecutor()
|
|
192
|
+
|
|
193
|
+
armw.globals.NODE_NAME = node_name
|
|
194
|
+
armw.globals.NODE = node_class()
|
|
195
|
+
|
|
196
|
+
try:
|
|
197
|
+
armw.globals.NODE.spin()
|
|
198
|
+
executor.add_node(armw.globals.NODE)
|
|
199
|
+
executor.spin()
|
|
200
|
+
rclpy.shutdown()
|
|
201
|
+
except KeyboardInterrupt:
|
|
202
|
+
pass
|
|
203
|
+
|
|
204
|
+
print(f"{node_name} shutdown")
|
|
205
|
+
raise Exception("Ayy") # For testing
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import inspect
|
|
2
|
+
from typing import List
|
|
3
|
+
|
|
4
|
+
import rclpy
|
|
5
|
+
from rcl_interfaces.msg import SetParametersResult
|
|
6
|
+
|
|
7
|
+
from armw.ros2.ros2_node import Ros2Node
|
|
8
|
+
from armw.doc_int_enum import DocIntEnum
|
|
9
|
+
from armw.base_parameter_server import BaseParameterServer
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class Ros2ParameterServer(BaseParameterServer):
|
|
13
|
+
def __init__(self, node_object: Ros2Node, namespace: str = ""):
|
|
14
|
+
super().__init__(node_object, namespace)
|
|
15
|
+
self.node = node_object
|
|
16
|
+
|
|
17
|
+
self.parameter_values = {}
|
|
18
|
+
|
|
19
|
+
self.callback = None
|
|
20
|
+
|
|
21
|
+
def add_parameter(self, name, description, default_value, min_value=None, max_value=None, parameter_type=None):
|
|
22
|
+
if inspect.isclass(parameter_type) and issubclass(parameter_type, DocIntEnum):
|
|
23
|
+
# TODO: no idea yet
|
|
24
|
+
pass
|
|
25
|
+
|
|
26
|
+
if not self.node.has_parameter(name):
|
|
27
|
+
self.node.declare_parameter(name, default_value)
|
|
28
|
+
|
|
29
|
+
self.parameter_values[name] = self.node.get_parameter(name).value
|
|
30
|
+
|
|
31
|
+
def start(self, callback):
|
|
32
|
+
self.node.add_on_set_parameters_callback(self.parameter_callback)
|
|
33
|
+
|
|
34
|
+
self.callback = callback
|
|
35
|
+
|
|
36
|
+
def parameter_callback(self, params: List[rclpy.Parameter]):
|
|
37
|
+
for param in params:
|
|
38
|
+
self.parameter_values[param.name] = param.value
|
|
39
|
+
|
|
40
|
+
if self.callback is not None:
|
|
41
|
+
self.callback(self.parameter_values, {})
|
|
42
|
+
return SetParametersResult(successful=True)
|
|
43
|
+
else:
|
|
44
|
+
return SetParametersResult(successful=False)
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import types
|
|
2
|
+
|
|
3
|
+
import rclpy
|
|
4
|
+
import rclpy.clock
|
|
5
|
+
import rclpy.duration
|
|
6
|
+
import builtin_interfaces
|
|
7
|
+
import builtin_interfaces.msg
|
|
8
|
+
import armw.globals
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def get_time():
|
|
12
|
+
if armw.globals.NODE is not None:
|
|
13
|
+
return armw.Time().now().to_sec()
|
|
14
|
+
else:
|
|
15
|
+
raise Exception("time is not initialized. Have you created a node?")
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class Duration(rclpy.duration.Duration):
|
|
19
|
+
def __init__(self, secs=0, nsecs=0):
|
|
20
|
+
super().__init__(nanoseconds=secs * 1000000000 + nsecs)
|
|
21
|
+
self.to_nsec = types.MethodType(lambda self: self.nanoseconds, self)
|
|
22
|
+
self.to_sec = types.MethodType(lambda self: self.nanoseconds / 1e9, self)
|
|
23
|
+
self.is_zero = types.MethodType(lambda self: self.nanoseconds == 0, self)
|
|
24
|
+
self.secs = secs
|
|
25
|
+
self.nsecs = nsecs
|
|
26
|
+
|
|
27
|
+
def __add__(self, other):
|
|
28
|
+
if isinstance(other, Time):
|
|
29
|
+
return armw.Time().from_sec(self.to_sec() + other.to_sec())
|
|
30
|
+
else:
|
|
31
|
+
Duration.__add__(self, other)
|
|
32
|
+
|
|
33
|
+
@classmethod
|
|
34
|
+
def from_sec(cls, secs):
|
|
35
|
+
return Duration(int(secs), nsecs=int((secs - int(secs)) * 1000000000))
|
|
36
|
+
|
|
37
|
+
@classmethod
|
|
38
|
+
def from_seconds(cls, secs):
|
|
39
|
+
return Duration(int(secs), nsecs=int((secs - int(secs)) * 1000000000))
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class Time(builtin_interfaces.msg.Time):
|
|
43
|
+
def __init__(self, secs=0, nsecs=0):
|
|
44
|
+
super().__init__(sec=secs, nanosec=nsecs)
|
|
45
|
+
|
|
46
|
+
def __add__(self, other):
|
|
47
|
+
if isinstance(other, rclpy.duration.Duration):
|
|
48
|
+
return armw.Time().from_sec(self.to_sec() + other.to_sec())
|
|
49
|
+
else:
|
|
50
|
+
Time.__add__(self, other)
|
|
51
|
+
|
|
52
|
+
def __sub__(self, other):
|
|
53
|
+
if isinstance(other, rclpy.duration.Duration):
|
|
54
|
+
return armw.Time().from_sec(self.to_sec() - other.to_sec())
|
|
55
|
+
else:
|
|
56
|
+
Time.__add__(self, other)
|
|
57
|
+
|
|
58
|
+
def __gt__(self, other):
|
|
59
|
+
return self.to_sec() > other.to_sec()
|
|
60
|
+
|
|
61
|
+
def __lt__(self, other):
|
|
62
|
+
return self.to_sec() < other.to_sec()
|
|
63
|
+
|
|
64
|
+
def __ge__(self, other):
|
|
65
|
+
return self.to_sec() >= other.to_sec()
|
|
66
|
+
|
|
67
|
+
def __le__(self, other):
|
|
68
|
+
return self.to_sec() <= other.to_sec()
|
|
69
|
+
|
|
70
|
+
def __eq__(self, other):
|
|
71
|
+
return self.to_sec() == other.to_sec()
|
|
72
|
+
|
|
73
|
+
def __ne__(self, other):
|
|
74
|
+
return self.to_sec() != other.to_sec()
|
|
75
|
+
|
|
76
|
+
def to_sec(self):
|
|
77
|
+
return self.sec + float(self.nanosec) / 1000000000
|
|
78
|
+
|
|
79
|
+
@classmethod
|
|
80
|
+
def from_sec(cls, secs: float):
|
|
81
|
+
return Time(secs=int(secs), nsecs=int((secs - int(secs)) * 1000000000))
|
|
82
|
+
|
|
83
|
+
@classmethod
|
|
84
|
+
def from_seconds(cls, secs: float):
|
|
85
|
+
return Time().from_sec(secs=secs)
|
|
86
|
+
|
|
87
|
+
@classmethod
|
|
88
|
+
def now(cls):
|
|
89
|
+
s = rclpy.clock.Clock().now().seconds_nanoseconds()
|
|
90
|
+
return Time(secs=s[0], nsecs=s[1])
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
class Rate(object):
|
|
94
|
+
def __init__(self, hz):
|
|
95
|
+
self._rate = armw.globals.NODE.create_rate(hz)
|
|
96
|
+
|
|
97
|
+
def __del__(self):
|
|
98
|
+
armw.globals.NODE.destroy_rate(self._rate)
|
|
99
|
+
|
|
100
|
+
def sleep(self):
|
|
101
|
+
self._rate.sleep()
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
if __name__ == '__main__':
|
|
105
|
+
a = Time(3, 5)
|
|
106
|
+
b = Duration(5, 6)
|
|
107
|
+
|
|
108
|
+
print(a)
|
|
109
|
+
print(b)
|
|
110
|
+
|
|
111
|
+
print(a + b)
|
|
112
|
+
print(b + a)
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
def canonicalize_name(name):
|
|
2
|
+
"""
|
|
3
|
+
Put name in canonical form. Double slashes '//' are removed and
|
|
4
|
+
name is returned without any trailing slash, e.g. /foo/bar
|
|
5
|
+
@param name: ROS name
|
|
6
|
+
@type name: str
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
# Straight up copied from here: https://docs.ros.org/en/melodic/api/rospy/html/rospy.names-pysrc.html
|
|
10
|
+
|
|
11
|
+
SEP = '/'
|
|
12
|
+
|
|
13
|
+
if not name or name == SEP:
|
|
14
|
+
return name
|
|
15
|
+
elif name[0] == SEP:
|
|
16
|
+
return '/' + '/'.join([x for x in name.split(SEP) if x])
|
|
17
|
+
else:
|
|
18
|
+
return '/'.join([x for x in name.split(SEP) if x])
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# Straight up copied from the ros2 rolling distro
|
|
2
|
+
|
|
3
|
+
from typing import Union
|
|
4
|
+
|
|
5
|
+
from rclpy.impl.implementation_singleton import rclpy_implementation as _rclpy
|
|
6
|
+
from rclpy.node import Node
|
|
7
|
+
from rclpy.qos import QoSProfile
|
|
8
|
+
from rclpy.signals import SignalHandlerGuardCondition
|
|
9
|
+
from rclpy.utilities import timeout_sec_to_nsec
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def wait_for_message(msg_type, node: 'Node', topic: str, *, qos_profile: Union[QoSProfile, int] = 1, time_to_wait=-1):
|
|
13
|
+
"""
|
|
14
|
+
Wait for the next incoming message.
|
|
15
|
+
|
|
16
|
+
:param msg_type: message type
|
|
17
|
+
:param node: node to initialize the subscription on
|
|
18
|
+
:param topic: topic name to wait for message
|
|
19
|
+
:param qos_profile: QoS profile to use for the subscription
|
|
20
|
+
:param time_to_wait: seconds to wait before returning
|
|
21
|
+
:returns: (True, msg) if a message was successfully received, (False, None) if message
|
|
22
|
+
could not be obtained or shutdown was triggered asynchronously on the context.
|
|
23
|
+
"""
|
|
24
|
+
context = node.context
|
|
25
|
+
wait_set = _rclpy.WaitSet(1, 1, 0, 0, 0, 0, context.handle)
|
|
26
|
+
wait_set.clear_entities()
|
|
27
|
+
|
|
28
|
+
sub = node.create_subscription(msg_type, topic, lambda _: None, qos_profile=qos_profile)
|
|
29
|
+
try:
|
|
30
|
+
wait_set.add_subscription(sub.handle)
|
|
31
|
+
sigint_gc = SignalHandlerGuardCondition(context=context)
|
|
32
|
+
wait_set.add_guard_condition(sigint_gc.handle)
|
|
33
|
+
|
|
34
|
+
timeout_nsec = timeout_sec_to_nsec(time_to_wait)
|
|
35
|
+
wait_set.wait(timeout_nsec)
|
|
36
|
+
|
|
37
|
+
subs_ready = wait_set.get_ready_entities('subscription')
|
|
38
|
+
guards_ready = wait_set.get_ready_entities('guard_condition')
|
|
39
|
+
|
|
40
|
+
if guards_ready:
|
|
41
|
+
if sigint_gc.handle.pointer in guards_ready:
|
|
42
|
+
return False, None
|
|
43
|
+
|
|
44
|
+
if subs_ready:
|
|
45
|
+
if sub.handle.pointer in subs_ready:
|
|
46
|
+
msg_info = sub.handle.take_message(sub.msg_type, sub.raw)
|
|
47
|
+
if msg_info is not None:
|
|
48
|
+
return True, msg_info[0]
|
|
49
|
+
finally:
|
|
50
|
+
node.destroy_subscription(sub)
|
|
51
|
+
|
|
52
|
+
return False, None
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
def package_and_msg_name(name):
|
|
2
|
+
"""
|
|
3
|
+
https://docs.ros.org/en/diamondback/api/roslib/html/python/roslib.names-pysrc.html#package_resource_name
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
if "/" in name:
|
|
7
|
+
val = tuple(name.split("/"))
|
|
8
|
+
if len(val) != 2:
|
|
9
|
+
raise ValueError("invalid name [%s]" % name)
|
|
10
|
+
else:
|
|
11
|
+
return val
|
|
12
|
+
else:
|
|
13
|
+
return '', name
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: armw
|
|
3
|
+
Version: 0.1.0a0
|
|
4
|
+
Summary: ARMW
|
|
5
|
+
Author-email: Nathan McGuire <nmcguire@whoi.edu>
|
|
6
|
+
Project-URL: homepage, http://acomms.whoi.edu
|
|
7
|
+
Keywords: Library,Python Modules,Scientific/Engineering
|
|
8
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
9
|
+
Classifier: Intended Audience :: Developers
|
|
10
|
+
Classifier: Intended Audience :: Science/Research
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.6
|
|
12
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
13
|
+
Classifier: Topic :: Scientific/Engineering
|
|
14
|
+
Classifier: License :: OSI Approved :: GNU Lesser General Public License v3 or later (LGPLv3+)
|
|
15
|
+
Requires-Python: >=3.6
|
|
16
|
+
Description-Content-Type: text/plain
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
.gitignore
|
|
2
|
+
README.md
|
|
3
|
+
pyproject.toml
|
|
4
|
+
armw/__init__.py
|
|
5
|
+
armw/_version.py
|
|
6
|
+
armw/base_node.py
|
|
7
|
+
armw/base_parameter_server.py
|
|
8
|
+
armw/doc_int_enum.py
|
|
9
|
+
armw/globals.py
|
|
10
|
+
armw/ros_common.py
|
|
11
|
+
armw.egg-info/PKG-INFO
|
|
12
|
+
armw.egg-info/SOURCES.txt
|
|
13
|
+
armw.egg-info/dependency_links.txt
|
|
14
|
+
armw.egg-info/top_level.txt
|
|
15
|
+
armw/ros1/__init__.py
|
|
16
|
+
armw/ros1/interfaces.py
|
|
17
|
+
armw/ros1/ros1_node.py
|
|
18
|
+
armw/ros1/ros1_parameter_server.py
|
|
19
|
+
armw/ros2/__init__.py
|
|
20
|
+
armw/ros2/interfaces.py
|
|
21
|
+
armw/ros2/ros2_node.py
|
|
22
|
+
armw/ros2/ros2_parameter_server.py
|
|
23
|
+
armw/ros2/time.py
|
|
24
|
+
armw/ros2/util.py
|
|
25
|
+
armw/ros2/wait_for_message.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
armw
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "armw"
|
|
3
|
+
dynamic = ["version"]
|
|
4
|
+
description = "ARMW"
|
|
5
|
+
readme = "README.txt"
|
|
6
|
+
keywords = ["Library", "Python Modules", "Scientific/Engineering"]
|
|
7
|
+
classifiers = [
|
|
8
|
+
"Development Status :: 5 - Production/Stable",
|
|
9
|
+
"Intended Audience :: Developers",
|
|
10
|
+
"Intended Audience :: Science/Research",
|
|
11
|
+
"Programming Language :: Python :: 3.6",
|
|
12
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
13
|
+
"Topic :: Scientific/Engineering",
|
|
14
|
+
"License :: OSI Approved :: GNU Lesser General Public License v3 or later (LGPLv3+)"
|
|
15
|
+
]
|
|
16
|
+
requires-python = ">=3.6"
|
|
17
|
+
dependencies = []
|
|
18
|
+
|
|
19
|
+
[[project.authors]]
|
|
20
|
+
name = "Nathan McGuire"
|
|
21
|
+
email = "nmcguire@whoi.edu"
|
|
22
|
+
|
|
23
|
+
[project.urls]
|
|
24
|
+
homepage = "http://acomms.whoi.edu"
|
|
25
|
+
|
|
26
|
+
[project.scripts]
|
|
27
|
+
# No scripts for now; we may add scripts in /bin in a future release
|
|
28
|
+
|
|
29
|
+
[project.optional-dependencies]
|
|
30
|
+
# No optional dependencies
|
|
31
|
+
|
|
32
|
+
[build-system]
|
|
33
|
+
requires = ["setuptools>=64", "wheel", "setuptools_scm>=8"]
|
|
34
|
+
build-backend = "setuptools.build_meta"
|
|
35
|
+
|
|
36
|
+
[tool.setuptools_scm]
|
|
37
|
+
write_to = "armw/_version.py"
|
armw-0.1.0a0/setup.cfg
ADDED