|
Sep
02
2010
A Sampling of ROS Integration PackagesA computer science professor of mine, “back in the day,” once said that the greatest challenge in the future of software development will rest in the realm of integration. This is certainly true in robotics, if nowhere else. Due to the complexity involved with robotics, developers typically focus on very specific problems and develop very specific solutions to meet those challenges, accordingly. Path planning, SLAM, edge finding, handle manipulation, road recognition, planning with resource constraints, pattern and object recognition, D*, data mining algorithms, reinforcement learning, Kalman and particle filters are just some examples of “specialty” subjects in robotics and AI which people have developed pivotal software solutions to. An immense opportunity has existed, and will continue to exist, in bringing these together into more generalized solutions which are able to reap the benefits of the more specialized solutions within a cohesive whole. An obstinate challenge in doing just that has been the technical complexities involved with integrating the plethora of solutions into a grouping which facilitates seamless communications among these solutions. If someone were to attempt at integrating more than a handful of specialized solutions and frameworks, the time involved would be nearly prohibitive to accomplishing the desired goal. Robot Operating System (ROS) seems to be changing the game a bit to allow just such an endeavor to be more tractable. Now that core ROS development has stabilized, a large number of groups have been feverishly working on providing their “specialty” solutions as packages which seamlessly integrate with ROS. And that means that they are then much more inter-operable with just about any other package that works with ROS. There is truly a dizzying number of packages that have been provided for ROS (navigable at http://www.ros.org/browse/list.php). The packages range from algorithms for SLAM to hardware communication packages to wrappers for programming languages to wrappers for off the shelf systems and other existing software. It is this last category that gets me really jazzed. With such wrappers being introduced (not to be confused with “rappers,” a slightly different breed) , we’re now able to leverage a number of very solid and mature tools and frameworks without being bogged down with low level integration and communication issues. I’d like to briefly highlight a few ROS packages which provide just such accessibility to existing tools and frameworks:
The sampling above highlights just a few of the efforts by various groups to facilitate the integration of solid, existing tools and frameworks into ROS for easier communications with other packages and custom development. Ultimately, these efforts are lowering the barrier to integrate many great ideas and solutions into a more cohesive whole. This seems like a great indication of the current state of robotics…a sign that the industry is finally maturing enough that we’re now working towards integrating existing solutions – rather than re-inventing the wheel – in order to more aggressively push the envelope of what’s attainable and what’s imaginable. Billy McCafferty Aug
18
2010
Are additional layers of abstraction warranted?I was presented with the following discussion opener concerning the management of complexity via the introduction of abstraction…
I love this discussion! The heart of this is determining if the introduction of abstractions, for the sake of hiding complexity, is truly an overall benefit. In robotics, we’re ever dealing with increasing levels of abstraction for this very reason. For example, in Architectural Paradigms of Robotic Control, I briefly discussed the 3T architecture which has three separate layers, implemented as increasing levels of abstraction. E.g., the skill/servo layer would likely be implemented in C++, the sequencing/execution layer might be implemented as a sequence modeling language, such as ESL or NDDL, while the planning/deliberative layer might be implemented with a higher abstraction yet, such as with the Planning Domain Definition Language or Ontology with Polymorphic Types (Opt). In response to the concerns put forth, I would tend to agree that abstraction hides complexity and that it does make it more likely that you may be bitten by the hiding of the complexity. With that said, encapsulation of complexity into well formed abstractions is an inevitable step to facilitate taking on increasingly complex problems. For example, in the .NET world of data access, Fluent NHibernate is really just a way of hiding the complexities of NHibernate. NHibernate is really just a way of hiding the complexities of ADO.NET. ADO.NET is really just a way of hiding the complexities of communicating to a database via TCP/IP sockets, or whatever underlying mechanism is employed. Along the same vein, tools such as Herbal, NDDL, and ESL are similarly provided as a means to provide an abstraction for hiding complexity. Because these layers of complexity have been encapsulated in a manageable fashion, we’re now able to take on project work which would be far too complex to manage if we were using a lower level of implementation, e.g., pure C++, or Assembly for that matter. Indeed, there will be times when the added layers of abstraction will make it more difficult to tweak a low level capability, but the improved complexity management that the abstractions provide should far outweigh the sacrifice of losing some low level capabilities. I think the crux for determining if an abstraction is worthwhile:
If the answers to the above are yes, then I believe that the encapsulation of complexity, codified as a new layer of abstraction, is pulling its weight. Otherwise, you might not want to throw that Assembly language reference away just yet. Billy McCafferty Aug
03
2010
Simulation Environments for Mobile RoboticsWhile I wait for my $200K grant, my Darpa project award, Aldebaran to send me a few Nao’s, or Willow Garage to mail a PR2 my way (please contact me for shipping details), I spend much of my research time on the simulation side of robotics. In addition to being far less costly than purchasing hardware, working via simulators actually provides a number of benefits:
Obviously, it’s difficult to replace the experiences of working on real robots in real environments with real-world sensor errors, data fusion that doesn’t agree, and unpredictable dynamics, but simulators certainly provide a convenient means to try out new ideas or experiment in new areas. Along those lines, I’d like to highlight a few simulators for mobile robotics which may pique your interest:
The above list is certainly not exhaustive, but should give a good introduction of available simulation environments. Other environments not mentioned which also have support for mobile robotics development in simulated environments include MapleSim, Simbad, Carmen, Urbi (compatible with Webots) lpzrobots, Moby, and OpenSim. Finally, robot competitions occasionally include simulated environments in their challenges; robots.net keeps a terrific listing of available competitions at http://robots.net/rcfaq.html; the AAAI conferences and competitions are particularly good at coming up with novel hardware and simulation challenges which truly push the envelope of progress. Certainly enough to keep you busy for a while…not bad when you consider the many projects you can carry out before committing to buying a single piece of hardware! Enjoy! Jul
30
2010
Developing Well-Designed Packages for Robot Operating System (ROS), Part VIPart VI: Adding a UI Layer to the Package As the last and final chapter to this series of posts (Part I, II, III, IV, V), we’ll be adding a basic UI layer to facilitate user interaction with the underlying layers of our package. Specifically, a UI will be developed to allow the user (e.g., you) to start and stop the laser reporting application service via a wxWidgets interface. If you’re new to wxWidgets, it really is a terrific open-source UI package with very helpful online tutorials, a thriving community, and a very helpful book, Cross-Platform GUI Programming with wxWidgets – certainly a good reference to add to the bookshelf. Arguably, the sample code discussed below is very simplistic and only touches upon wxWidgets; with that said, it should demonstrate how to put the basics in place and to see how the UI layer interacts with the other layers of the package. Developing a UI layer with wxWidgets is quite straight forward; the UI itself is made up of two primary elements: a wxApp which is used to initialize the UI and a wxFrame which serves as the primary window. For the task at hand, the wxApp in the UI layer will be used to perform three primary tasks, in the order listed:
As a rule of thumb, the UI layer should only communicate to the rest of the package elements via the application services layer. E.g., the UI layer should not be invoking functions directly on domain objects found within ladar_reporter_core; instead, it should call tasks exposed by the application services layer which then coordinates and delegates activity to lower levels. Before we delve deeper, as a reminder of what the overall class diagram looks like, as developed over the previous posts, review the class diagram found within Part V. The current objective will be to add the UI layer, as illustrated in the package diagram found within Part I. To cut to the chase and download the end result of this post, click here. Show me the code! 1. Setup the Package Skeleton, Domain Layer, Application Services Layer, and Message Endpoint Layer If not done already, follow the steps in Part II, III, IV, and V to get everything in place. (Or simply download the source from Part V to skip all the action packed steps leading up to this post.) 2. Install wxWidgets Download and install wxWidgets. Instructions for Ubuntu and Debian may be found at http://wiki.wxpython.org/InstallingOnUbuntuOrDebian. 3. Define the UI events that the user may raise Create an enum class at src/ui/UiEvents.hpp to define UI events as follows: // UiEvents.hpp #ifndef GUARD_UiEvents #define GUARD_UiEvents namespace ladar_reporter_ui { enum UiEventType { UI_EVENT_Quit = 1, UI_EVENT_StartReporting = 2, UI_EVENT_StopReporting = 3 }; } #endif /* GUARD_UiEvents */ As suggested by the enum values, the user will be able to start the reporting process, stop it, and quit the application altogether. 4. Create the wxWidgets application header class Create src/ui/LadarReporterApp.hpp containing the following code: // LadarReporterApp.hpp #include <boost/shared_ptr.hpp> #include <ros/ros.h> #include "LaserScanEndpoint.hpp" #include "LaserScanReportingService.hpp" namespace ladar_reporter_ui { class LadarReporterApp : public wxApp { public: virtual bool OnInit(); virtual int OnExit(); private: void InitializeRos(); void InitializeApplicationServices(); void CreateMainWindow(); char** _argvForRos; ros::NodeHandlePtr _nodeHandlePtr; // Application services and dependencies. // Stored as pointers to postpone creation until ready to initialize. boost::shared_ptr<ladar_reporter_core::ILaserScanEndpoint> _laserScanEndpoint; boost::shared_ptr<ladar_reporter_application_services::LaserScanReportingService> _laserScanReportingService; }; } A few notes:
5. Create the wxWidgets application implementation class Create src/ui/LadarReporterApp.cpp containing the following code: // LadarReporterApp.cpp #include <wx/wx.h> #include "LadarReporterApp.hpp" #include "LadarReporterFrame.hpp" #include "LaserScanEndpoint.hpp" #include "UiEvents.hpp" using namespace ladar_reporter_application_services; using namespace ladar_reporter_core; using namespace ladar_reporter_message_endpoints; // Inform wxWidgets what to use as the wxApp IMPLEMENT_APP(ladar_reporter_ui::LadarReporterApp); // Implements LadarReporterApp& wxGetApp() globally DECLARE_APP(ladar_reporter_ui::LadarReporterApp); namespace ladar_reporter_ui { bool LadarReporterApp::OnInit() { // Order of initialization functions is critical: // 1) ROS must be initialized before message endpoint(s) can advertise InitializeRos(); // 2) Application services must be initialized before being passed to UI InitializeApplicationServices(); // 3) UI can be created with properly initialized ROS and application services CreateMainWindow(); return true; } int LadarReporterApp::OnExit() { for (int i = 0; i < argc; ++i) { free(_argvForRos[i]); } delete [] _argvForRos; return 0; } void LadarReporterApp::InitializeRos() { // create our own copy of argv, with regular char*s. _argvForRos = new char*[argc]; for (int i = 0; i < argc; ++i) { _argvForRos[i] = strdup( wxString( argv[i] ).mb_str() ); } ros::init(argc, _argvForRos, "ladar_reporter"); _nodeHandlePtr.reset(new ros::NodeHandle); } void LadarReporterApp::InitializeApplicationServices() { _laserScanEndpoint = boost::shared_ptr<ILaserScanEndpoint>( new LaserScanEndpoint()); _laserScanReportingService = boost::shared_ptr<LaserScanReportingService>( new LaserScanReportingService(_laserScanEndpoint)); } void LadarReporterApp::CreateMainWindow() { LadarReporterFrame * frame = new LadarReporterFrame( _laserScanReportingService, _("Ladar Reporter"), wxPoint(50, 50), wxSize(450, 200)); frame->Connect( ladar_reporter_ui::UI_EVENT_Quit, wxEVT_COMMAND_MENU_SELECTED, (wxObjectEventFunction) &LadarReporterFrame::OnQuit ); frame->Connect( ladar_reporter_ui::UI_EVENT_StartReporting, wxEVT_COMMAND_MENU_SELECTED, (wxObjectEventFunction) &LadarReporterFrame::OnStartReporting ); frame->Connect( ladar_reporter_ui::UI_EVENT_StopReporting, wxEVT_COMMAND_MENU_SELECTED, (wxObjectEventFunction) &LadarReporterFrame::OnStopReporting ); frame->Show(); SetTopWindow(frame); } } The direction for this class was taken from wxWidgets online tutorials along with reviewing the ROS turtlesim package, which is a real treasure trove for seeing how a much more sophisticated ROS UI is put together. (If you have not already, I strongly suggest you review the turtlesim code in detail.) 6. Create the wxWidgets frame header class Now that the wxWidgets application is in place, the frame, representing the UI window itself, needs to be developed. Accordingly, create src/ui/LadarReporterFrame.hpp containing the following code: // LadarReporterFrame.hpp #ifndef GUARD_LadarReporterFrame #define GUARD_LadarReporterFrame #include <wx/wx.h> #include "LaserScanReportingService.hpp" namespace ladar_reporter_ui { class LadarReporterFrame : public wxFrame { public: LadarReporterFrame( boost::shared_ptr<ladar_reporter_application_services::LaserScanReportingService> laserScanReportingService, const wxString& title, const wxPoint& pos, const wxSize& size); void OnQuit(wxCommandEvent& event); void OnStartReporting(wxCommandEvent& event); void OnStopReporting(wxCommandEvent& event); private: boost::shared_ptr<ladar_reporter_application_services::LaserScanReportingService> _laserScanReportingService; }; } #endif /* GUARD_LadarReporterFrame */ There are a couple of interesting bits in the header:
7. Create the wxWidgets frame implementation class Create src/ui/LadarReporterFrame.cpp containing the following code: // LadarReporterFrame.cpp #include "LadarReporterFrame.hpp" #include "UiEvents.hpp" using namespace ladar_reporter_application_services; namespace ladar_reporter_ui { LadarReporterFrame::LadarReporterFrame( boost::shared_ptr<LaserScanReportingService> laserScanReportingService, const wxString& title, const wxPoint& pos, const wxSize& size) : wxFrame( NULL, -1, title, pos, size ), _laserScanReportingService(laserScanReportingService) { wxMenuBar *menuBar = new wxMenuBar; wxMenu *menuAction = new wxMenu; menuAction->Append( UI_EVENT_StartReporting, _("&Start Reporting") ); menuAction->AppendSeparator(); menuAction->Append( UI_EVENT_StopReporting, _("S&top Reporting") ); menuAction->AppendSeparator(); menuAction->Append( UI_EVENT_Quit, _("E&xit") ); menuBar->Append(menuAction, _("&Action") ); SetMenuBar(menuBar); CreateStatusBar(); SetStatusText( _("Ready to begin reporting") ); } void LadarReporterFrame::OnStartReporting(wxCommandEvent& WXUNUSED(event)) { _laserScanReportingService->beginReporting(); SetStatusText( _("Laser scan reporting is running") ); } void LadarReporterFrame::OnStopReporting(wxCommandEvent& WXUNUSED(event)) { _laserScanReportingService->stopReporting(); SetStatusText( _("Laser scan reporting has been stopped") ); } void LadarReporterFrame::OnQuit(wxCommandEvent& WXUNUSED(event)) { Close(true); } } A few implementation notes:
There’s obviously a lot of wxWidgets related information which I am glossing over which is beyond the scope of these posts. The wxWidgets documentation referenced earlier should fill in any remaining gaps. 8. Configure CMake to Include the Header and Implementation With the header and implementation classes completed for the both the wxWidgets application and frame, we need to make a couple of minor modifications to CMake for their inclusion in the build.
9. Add a ROS wxWidgets Dependency to manifest.xml Since the package will be leveraging wxWidgets, a dependency needs to be added for the package to find and use this, accordingly:
10. Build and try out the UI Functionality We are now ready to try everything out. While it is generally possible to write unit tests for the UI layer, personal experience has shown that the UI changes too frequently to make such unit tests worth while. UI unit tests quickly become a maintenance headache and do not provide much more value than what the existing unit tests have already proven; i.e., we’ve already verified through unit tests that the heart of our package – the domain objects, the message endpoints, and the application services – are all working as expected…the UI is now “simply” the final touch. Enough babble, let’s see this baby in action:
Well, that about wraps it up, we started by laying out our architecture and systematically tackling each layer of the package with proper separation of concerns and unit testing to make sure we were doing what we said we were doing. As demonstrated with the layering approach that we developed, higher layers (e.g., application services and core) didn’t depend on lower layers (e.g., message endpoints and the ROS API). In fact, when possible, the lower layers actually depended on interfaces defined in the higher layers; e.g., the message endpoint implemented an interface defined in the higher core layer. (Although the class diagrams show core on the bottom, it’s actually reflecting the dependency inversion that was introduced.) This dependency inversion enabled a clean separation of concerns while allowing us to unit test the various layers in isolation of each other. I sincerely hope that this series has shed some light on how to properly architect a ROS package. While this series did not go into a granular level of detail with respect to using ROS and wxWidgets, it should have provided a good starting point for developing a solid package. The techniques described in this series have been honed over many years by demi-gods of development (e.g., Martin Fowler, Robert Martin, Kent Beck, Ward Cunningham, and many others) and continue to prove their value in enabling the development of maintainable, extensible applications which are enjoyable to work on. While ROS may be relatively new, the tried and trued lessons of professional development are quite timeless indeed. As always, your feedback, questions, comments, suggestions, and even rebuttals are most welcome. To delve a bit further into many of the patterns oriented topics discussed, I recommend reading Gregor Hohpe’s Enterprise Integration Patterns and Robert Martin’s Agile Software Development, Principles, Patterns, and Practices. And obviously, for anything ROS related, you’ll want to keep reading everything you can at http://www.ros.org/wiki/ (and here at sharprobotica.com, of course)! Enjoy! Jul
28
2010
Developing Well-Designed Packages for Robot Operating System (ROS), Part VPart V: Developing and Testing the ROS Message Endpoint [Author's note, July 28, 2010: Introduced Boost::shared_ptr to manage reference to message endpoint so as to enable postponed construction, as will be required in Part VI.] While this series (Part I, II, III, IV) has been specifically written to address writing well-designed packages for ROS, we’ve actually seen very little of ROS itself thus far. In fact, outside of the use of roscreate to generate the package basics and sensor_msgs::LaserScan for communicating laser scan data from the reader up to the application services layer, there’s been no indication that this application was actually intended to work with ROS now or ever. Ironically, this is exactly what we’d expect to see in a well designed ROS package. Each layer that we’ve developed – as initially outlined in Part I – is logically separated from each other’s context of responsibility. To illustrate, the upper layers do not directly depend on “service” layers, such as message endpoints. Instead, the lower layers depend on abstract service interfaces declared in the upper layers. This dependency inversion was enabled in Part IV with the creation of ILaserScanEndpoint, a separated interface. If all of this dependency inversion and separated interface mumbo-jumbo has your head spinning at all, take some time to delve deeper into this subject in Dependency Injection 101. While the actual message endpoint interface was created, only a test double was developed for testing the application service layer’s functionality. Accordingly, in this post, the concrete message endpoint “service,” which implements its separated interface, will be developed and tested. That’s right…we’ll finally actually talk to ROS! You can skip to the chase and download the source for this post. Before digging into the code, it’s important to take a moment to better understand the purpose and usefulness of the message endpoint. The message endpoint encapsulates communications to the messaging middleware similarly to how a data repository encapsulates communications to a database. By encapsulating such communications, the rest of the application (ROS package, in our case) may remain blissfully oblivious to details such as how to publish messages to a topic or translate between messages and domain layer objects. This separation of concerns helps to keep the application cleanly decoupled from the messaging middleware. Another benefit of this approach is enabling the development and testing of nearly the entirety of the application/package before “wiring” it up to the messaging middleware itself. This typically results in more reusable and readable code. If you haven’t already, I would encourage you to read the article Message-Based Systems for Maintainable, Asynchronous Development for a more complete discussion on message endpoints. Onward! Target Class Diagram The following diagram shows what the package will look like after completing the steps in this post…it’s beginning to look oddly familiar to the package diagram discussed in Part I of this series, isn’t it? If you’ve been following along, most of the elements have already been completed; only the concrete LaserScanEndpoint and LaserScanEndpointTests will need to be introduced along with a slight modification to the TestRunner.
1. Setup the Package Skeleton, Domain Layer and Application Services Layer If not done already, follow the steps in Part II, Part III, and Part IV to create the package and develop/test the domain and application service layers. (Or just download the code from Part IV as a starting point to save some time.) 2. Create the message endpoint header class. Create src/message_endpoints/LaserScanEndpoint.hpp containing the following code: // LaserScanEndpoint.hpp #ifndef GUARD_LaserScanEndpoint #define GUARD_LaserScanEndpoint #include <ros/ros.h> #include "sensor_msgs/LaserScan.h" #include "ILaserScanEndpoint.hpp" namespace ladar_reporter_message_endpoints { class LaserScanEndpoint : public ladar_reporter_core::ILaserScanEndpoint { public: LaserScanEndpoint(); void publish(const sensor_msgs::LaserScan& laserScan) const; private: // Create handle to node ros::NodeHandle _ladarReporterNode; ros::Publisher _laserReportPublisher; }; } #endif /* GUARD_LaserScanEndpoint */ The message endpoint header simply implements ILaserScanEndpoint and sets up handlers for holding the ROS NodeHandle and Publisher. The more interesting bits are found in the implementation details… 3. Create the message endpoint implementation class. Create src/message_endpoints/LaserScanEndpoint.cpp containing the following code: // LaserScanEndpoint.cpp #include <ros/ros.h> #include "sensor_msgs/LaserScan.h" #include "LaserScanEndpoint.hpp" namespace ladar_reporter_message_endpoints { LaserScanEndpoint::LaserScanEndpoint() // Setup topic for publishing laser scans to : _laserReportPublisher( _ladarReporterNode.advertise<sensor_msgs::LaserScan>("laser_report", 100)) { } void LaserScanEndpoint::publish(const sensor_msgs::LaserScan& laserScan) const { _laserReportPublisher.publish(laserScan); ros::spinOnce(); ROS_INFO("Published laser scan to laser_report topic with angle_min of: %f", laserScan.angle_min); }; } As you can see, there’s really not much to the actual publication process…which is what we were hoping for. The message endpoint should simply be a light way means to send and receive messages to/from the messaging middleware. This message endpoint does so as follows:
4. Configure CMake to Include the Header and Implementation With the header and implementation classes completed, we need to make a couple of minor modifications to CMake for their inclusion in the build.
5. Build the message endpoints Class Library In a terminal window, cd to /ladar_reporter and run Like with everything else thus far…it’s now time to test our new functionality. 6. Unit Test the LaserScanEndpoint Functionality While testing up to this point has been pretty straight-forward, we now need to incorporate ROS package initialization within the test itself.
The first five parts of this series conclude the primary elements of developing well-designed packages for Robot Operating System (ROS) using proven design patterns and proper separation of concerns. Obviously, this is not a trivially simple approach to developing ROS packages; indeed, it would be overkill for very simple packages. But as packages grow in size, scope, and complexity, techniques described in this series should help to establish a maintainable, extensible package which doesn’t get too unruly as it evolves. In Part VI, the final part in this series, we’ll look at adding a simple UI layer, using wxWidgets, to interact with the package functionality. Enjoy! Download the source for this post. For decades, since the dawn of Shakey, the software side of robotics was primarily constrained to the world of hardware-limited embedded systems which needed to be redeveloped from the ground up with every new robot. This approach limits code reuse, is painfully tricky to debug, and becomes a serious time sink with each hardware upgrade. Accordingly, increasing efforts have been made to create an operating system for robots to provide better hardware abstraction, enable greater code reuse and maintainability, and to facilitate the development of software regardless of the robotic platform that it will be run on. Does this sound similar to what DOS and Windows enabled for the PC revolution to occur? (I don’t think I need to try to convince you of what an impact that approach had in the PC world.) Accordingly, the hope is that a similar effect, if not monetarily, will occur in the robotics world. Indeed, it is likely that such a step must be taken to catalyze the world of robotics to become a true game changer, on par with the PC movement and the introduction of the internet itself. We seem to be an a moment in time that a practical robot OS is ready to be more fully embraced and leveraged. While there are a number on the market, time will tell if one will win out or if niches will exist to support a number of OSes concurrently within the robotics world. In the meantime, the work of organizations to introduce a common robot OS is making it exceedingly easier for software geeks (such as myself) to get deeper into robotics without having to put on our hardware-hacker hat as often. A few robot OSes which are making an impact in this direction include Microsoft Robotics Developer Studio (RDS), CARMEN, Player, Robot Operating System (which incidentally leverages Player), YARP (used by RobotCub), Orca, and Urbi. I’d like to share with you a little more information about one of these in particular, Urbi. Jean-Christophe Baillie, the CEO of Gostai (the company behind Urbi), took some time to share with me some Q&A concerning Urbi and information concerning why they decided to open source Urbi (a terrific move in my opinion). While I have not yet experimented with Urbi, I have found this information very helpful in understanding more of what Urbi is all about (and in piquing my interest to try it out):
I hope this helps to provide a basic introduction to what Urbi and its creator, Gostai, are all about. Although I have not yet used Urbi in my own research efforts, I plan to give it a try and will post my experiences when I get further into it. Enjoy! Jul
15
2010
Robot Operating System (ROS) Command Cheat SheetSo far, I have found ROS to be logical, concise and easy to use (after going through all of the tutorials and practicing with it a bit). Honestly, I’ve found it to be more intuitive and tractable (i.e., more enjoyable to work with) than Microsoft Robotics Developer Studio; albeit, I have not yet used the 2008 R3 release and intend on giving Microsoft RDS a more concerted and focused go around. While both RDS and ROS support messaging design patterns, so far I’m leaning towards ROS as a better enabler for cleanly separating components (e.g., ROS package) from the messaging middleware. One of the most daunting tasks of getting up to speed with ROS is learning and understanding the plethora of available commands and sequences of commands for performing common tasks. Fortunately, the ROS documentation – which is continually improving at a very fast rate – now includes a comprehensive cheat sheet for referencing ROS commands. Since I began using ROS, I’ve been maintaining my own cheat sheet which describes (my) most commonly used commands, descriptions of use, and example invocations (unsurprisingly, there is a lot of overlap with ROS’ cheat sheet). With my spreadsheet, I’ve also begun keeping track of common sequences of commands within my cheat sheet as a quick means to look up what series of commands are necessary to perform a common task, such as creating a new service from an existing one. This kind of information is certainly available via the ROS documentation, but keeping it consolidated has been a great help in reducing searching around for it. Finally, I also like to keep track of a glossary of common terms and other useful ROS info. As it has for me, you may find my ROS cheat sheet (an OpenOffice spreadsheet) as a useful starting point for maintaining your own list of common-sequences-of-commands and other at-your-finger-tips information for quickly navigating through ROS capabilities. Enjoy! Jul
13
2010
Sequencing Layer with ESL (Execution Support Language)In Architectural Paradigms of Robotic Control, I discussed a number of control architectures with a bias towards a hybrid approach, for facilitating reactive behaviors without precluding proper planning. With 3T, a common hybrid approach, the three layers include a skill layer for reactive behavior and actuator control, a sequencing (or execution) layer for sequencing behaviors based on relevant conditions, and a planning (or deliberative) layer for making plans for future actions. While the skill layer is typically developed in a low level language such as C++, the sequencing and planning layers frequently require a “higher” language to manage complexity and required flexibility. (E.g., a language using XML to express and execute first-order predicate logic without worrying about the low level implementation details of C++ control structure could be considered a “higher” language.) Indeed, Douglas Hofstadter, in his classic work Gödel, Escher, Bach, suggests that such higher level languages will most certainly be a prerequisite for developing more intelligent machines. ESL (Execution Support Language), developed by Ron Garret (the artist formerly known as Erann Gat), is one such higher language, built on Lisp, for the implementation of the sequencing layer of a hybrid control architecture. ESL is discussed in both Artificial Intelligence and Mobile Robotics and Springer Handbook of Robotics as being a language which:
After looking around for an implementation of ESL, I contacted Dr. Garret to find out where I might be able to find it. Amiably, Ron has made ESL available for download from his site. While I admit that I have not yet used ESL, I look forward to digging into Ron’s code to learn more about this seemingly solid approach to developing and managing a proper sequencing layer. While I am also familiar with Task Description Language (TDL) as an alternative to ESL, I am quite interested in hearing about any other approaches actively being taken to managing the sequencing/execution layer. I’ll certainly post more about ESL or other options as I research more on this topic. Incidentally, I’m also looking forward to digging into Herbal for the planning layer…but that’s for another post altogether! Enjoy! Jun
19
2010
Developing Well-Designed Packages for Robot Operating System (ROS), Part IVPart IV: Developing and Testing the Application Services [Author's note, July 28, 2010: Introduced Boost::shared_ptr to manage reference to message endpoint so as to enable postponed construction, as will be required in Part VI.] Ah, we’ve made it to the application services layer. After defining the architecture, setting up the package, and implementing the core layer which contains the domain logic of the application, we’re ready to take on developing the application services layer. In short, the service layer provides the external API to the underlying domain layer. Ideally, one should be able to develop the entirety of the “non-UI” portions of an application and have it exposed via the application services layer. In other words, one should be able to swap out the front end – say from a web front end to a Flash front end – without having the application services layer effected. If you’d like to go ahead and download the source for this article, click here. The application services layer is analogous to a task or coordination manager; i.e., it doesn’t know how to carry out the low level details of a particular action but it does know who is responsible for carrying out particular tasks. Accordingly, the application services layer of the package (or any application for that matter) is mostly made up of a number of publicly accessible methods which, in turn, pass responsibilities on to external service dependencies (e.g., a message endpoint) and the domain layer for execution. With that said, the services layer should still eschew direct dependencies on external services, such as communications with a messaging framework (e.g., ROS). Accordingly, in this post, we’ll materialize the application services layer of our package and give it two responsibilities:
But (there’s always a “but” isn’t there), while the application services layer should be responsible for passing messages received from the domain layer on to the messaging middleware, it should not have a direct dependency on that messaging middleware itself. This decoupling facilitates testing of the application services layer with test doubles, keeps a clean separation of concerns between task coordination and messaging, and provides greater flexibility with being able to modify/upgrade the messaging layer without affecting the application services layer. A moment needs to be taken to clarify the differences among application services, domain services, and “external resource” services.
Before we delve in, let’s briefly review what we plan to accomplish:
Enough with the chatter, let’s see some code! Target Class Diagram The following diagram shows what the package will look like after completing the steps in this post. While the individual elements will be discussed in more detail; the class diagram should serve as a good bird’s eye view of the current objectives. The elements with green check marks were completed in previous posts.
1. Setup the Package Skeleton and Domain Layer If not done already, follow the steps in Part II and Part III to create the package and develop/test the domain layer. 2. Create an interface for the message endpoint service which the application service layer will leverage to communicate with ROS. // ILaserScanEndpoint.hpp #ifndef GUARD_ILaserScanEndpoint #define GUARD_ILaserScanEndpoint #include "sensor_msgs/LaserScan.h" namespace ladar_reporter_core { class ILaserScanEndpoint { public: // Virtual destructor to pass pointer ownership without exposing base class [Meyers, 2005, Item 7] virtual ~ILaserScanEndpoint() {} virtual void publish(const sensor_msgs::LaserScan& laserScan) const = 0; }; } #endif /* GUARD_ILaserScanEndpoint */ There shouldn’t be anything too surprising in this interface. It simply exposes a method to publish a laser scan to the underlying messaging middleware. Why not put the interface in the application services layer, which intends to use it? A couple of good reasons come to mind: 1) since it’s a pure interface, having elements within core aware of it (or even directly dependent upon it) does not introduce any further coupling to the underlying external resource, the messaging middleware, and 2) in very simply packages, an application services layer might be overkill, so keeping the “external resource service interface” (there’s a mouthful) in the core layer facilitates either approach without having to move anything around if the selected approach changes during development. 3. Create the application service header class. Create src/application_services/LaserScanReportingService.hpp containing the following code: // LaserScanReportingService.hpp #ifndef GUARD_LaserScanReportingService #define GUARD_LaserScanReportingService #include <boost/shared_ptr.hpp> #include "ILaserScanEndpoint.hpp" namespace ladar_reporter_application_services { class LaserScanReportingService { public: explicit LaserScanReportingService(boost::shared_ptr<ladar_reporter_core::ILaserScanEndpoint> laserScanEndpoint); void beginReporting() const; void stopReporting() const; private: // Forward declare the implementation class class LaserScanReportingServiceImpl; boost::shared_ptr<LaserScanReportingServiceImpl> _pImpl; }; } #endif /* GUARD_LaserScanReportingService */ A few notes:
4. Create the application service implementation class. Create src/application_services/LaserScanReportingService.cpp containing the following code: // LaserScanReportingService.cpp #include "sensor_msgs/LaserScan.h" #include "ILaserScanListener.hpp" #include "LaserScanReader.hpp" #include "LaserScanReportingService.hpp" using namespace ladar_reporter_core; namespace ladar_reporter_application_services { // Private implementation of LaserScanReportingService class LaserScanReportingService::LaserScanReportingServiceImpl : public ladar_reporter_core::ILaserScanListener { public: explicit LaserScanReportingServiceImpl(boost::shared_ptr<ILaserScanEndpoint> laserScanEndpoint); void onLaserScanAvailableEvent(const sensor_msgs::LaserScan& laserScan) const; LaserScanReader laserScanReader; private: boost::shared_ptr<ILaserScanEndpoint> _laserScanEndpoint; }; LaserScanReportingService::LaserScanReportingService(boost::shared_ptr<ILaserScanEndpoint> laserScanEndpoint) : _pImpl(new LaserScanReportingServiceImpl(laserScanEndpoint)) { // Wire up the reader to the handler of laser scan reports _pImpl->laserScanReader.attach(*_pImpl); } LaserScanReportingService::LaserScanReportingServiceImpl::LaserScanReportingServiceImpl(boost::shared_ptr<ILaserScanEndpoint> laserScanEndpoint) : _laserScanEndpoint(laserScanEndpoint) { } void LaserScanReportingService::beginReporting() const { _pImpl->laserScanReader.beginReading(); } void LaserScanReportingService::stopReporting() const { _pImpl->laserScanReader.stopReading(); } void LaserScanReportingService::LaserScanReportingServiceImpl::onLaserScanAvailableEvent(const sensor_msgs::LaserScan& laserScan) const { // Send laserScan to the message end point _laserScanEndpoint->publish(laserScan); }; } A few notes:
As mentioned previously, the above implementation could be done without a private implementation design pattern, but this serves to illustrate how such a pattern may be leveraged when warranted. 5. Configure CMake to Include the Header and Implementation With the header and implementation classes completed, we need to make a couple of minor modifications to CMake for their inclusion in the build.
6. Build the application services Class Library In a terminal window, cd to /ladar_reporter and run Like before, we’re not done yet…it’s now time to test our new functionality. 7. Unit Test the LaserScanReportingService Functionality When we go to test the functionality of the laser scan reporting application service, it will likely be quickly noticed that there’s a missing dependency which will be needed to test the functionality of this class: a concrete implementation of ILaserScanEndpoint.hpp. The job of the message endpoint class will be to take laser scans and publish them to the appropriate topic on ROS. But we’re just not there yet…what we’d really like to do is to be able to test the functionality of the application service layer – LaserScanReportingService.cpp to be specific – without necessitating the presence of the message endpoint and ROS itself. While we’ll have to cross that bridge eventually (in Part V to be exact), we’re not currently interested in doing integration tests. Instead, we’re only interested in testing the behavior of the application service regardless of its integration with ROS. Accordingly, a “stub” object will be employed to stand in for an actual message endpoint. In this case, the stub object is nothing more than a concrete implementation of ILaserScanEndpoint.hpp; but instead of publishing the laser scan to ROS, it’ll do something testable, such as keep a tally, or possibly a temporary queue, of laser scans “published” which can then be verified with testing asserts. If you’re new to unit testing, you’ll want to read about test doubles, Martin Fowler’s Mocks Aren’t Stubs, and XUnit Test Patterns for a more comprehensive treatment of the subject. Onward with testing…
While running the tests, you should see a few messages published to the message endpoint stub. This demonstrates that all of the interactions among our core and application service layers are occurring exactly as expected. Now for the fun part… In Part V, we’ll finally take a look at using all of these pieces together in order to publish messages to a ROS topic via a message endpoint. Enjoy! May
20
2010
Microsoft RDS R3 Now Available…and free (as in beer)!Whether ROS and other recent moves have had a direct impact on the decision or not, it looks like Microsoft is getting the message that professional robotics software isn’t just the realm of licensed, proprietary software, but frequently supported by world class software solutions which are often free and open source. In a step in this direction, Microsoft has released Microsoft 2008 Robotics Developer Studio (RDS), making it available completely free of charge with all of the bells and whistles provided with the previous standard edition. http://www.microsoft.com/robotics/ Enjoy! |