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.
@@ -0,0 +1,5 @@
1
+ armw.egg-info
2
+ .idea
3
+ build
4
+
5
+ armw/_version.py
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,3 @@
1
+ # Agnostic Robot Middleware
2
+
3
+ A shim layer that will let the same code run in ROS1 and ROS2
@@ -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)
@@ -0,0 +1,2 @@
1
+ NODE = None
2
+ NODE_NAME = ""
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
+ 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
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+