Ros message types and Object Oriented Programming
Has anyone found a good way to incorporate ROS message types into an object oriented approach?
It bothers me how messages represent things that should be seen as objects in an OOP mindset, yet the class associated with each message type is just a dumb data class, auto-generated by the ros build system.
Here's a toy example of what my fantasy ros msg would look like.
------------- Point.msg ----------------
float64 x
float64 y
float64 z
# indicates that Point.py defines methods for this ros msg type
@implementation 'point_package/Point.py'
------------- Point.py ----------------
# omitting method bodies for conciseness
class Point(ROSMessage):
ORIGIN = Point(0, 0, 0)
def __init__(self, x, y, z):
def __init__(self, point):
def euclidean_distance(self, other_point):
------------- PointRecorder.py ----------------
# keeps a record of all the points sent to it and tracks which is closest to the origin
class PointRecorder(object):
def __init__(self):
self.points = []
self.closest_point_to_origin = None
self.closest_distance = float('inf')
rospy.Service('point_recorder', RecordPoint, self.record_point)
def record_point(self, request):
new_point_closest = False
self.points.append(request.point)
distance_from_origin = request.point.euclidean_distance(Point.ORIGIN)
if distance_from_origin < self.closest_distance:
new_point_closest = True
self.closest_distance = distance_from_origin
self.closest_point_to_origin = request.point
return RecordPointResponse(new_point_closest)
------------- RecordPoint.srv ----------------
point_package/Point point
---
bool new_point_is_closest
This code is just a toy example, but it illustrates a few things I'd like to be able to do with ros message classes.
- Have a special parent class for all ros message types
class Point(ROSMessage):
- Define relevant methods on the message class, e.g.
euclidean_distance
- Define named constant members of the message class, e.g.
ORIGIN
- Define other kinds of constructors on the message class, e.g.
def __init__(self, point):
- Unpack a ros service request or response and be able to call a method on one of its members, e.g.
request.point.euclidean_distance(Point.ORIGIN)
I've considered extending the auto-generated msg classes. Ros will accept a message with a member that's a subclass of the intended class, but this makes me nervous. Inheritance is often the wrong solution, and I anticipate pitfalls. Furthermore, that wouldn't work for unpacking requests and responses, as in request.point.euclidean_distance(Point.ORIGIN)
.
The best solution I've come up with so far is to use composition and wrap a ros msg in a useful class. The class would have methods that operate on the msg, be constructed from a msg, and be able to return its underlying msg.
------------- EnhancedPoint.py ----------------
# omitting some method bodies for conciseness
class EnhancedPoint(object):
ORIGIN = Point(0, 0, 0)
def __init__(self, x, y, z):
self.point = Point(x, y, z)
def __init__(self, point):
self.point = copy(point)
def euclidean_distance(self, other_enhanced_point):
def to_msg(self):
return copy(self.point)
With this approach, calling a method on a member extraced from a ros srv request or response is possible, but clunky. EnhancedPoint(request.point).euclidean_distance(EnhancedPoint.ORIGIN)
I'm not looking forward to constructing an EnhancedPoint
every time I want to do something with a point.
Does anyone have a better way?