PSU 541: Artificial Intelligence Professor: Bart Massey Fall 2004 By David Allen, 12-17-2004 Final Project: PSAS Rocket Simulator Introduction ------------ The Portland State Aerospace Society (PSAS) [1] has the goal to be the first amateur group to build an actively guided rocket. The group needs a flexible simulated environment to test current and future intelligent avionics systems. The PSAS team needed to improve upon their original simulator [2] which was written by Bart Massey in Nickle [3]. The primary requirements for a new simulator were for a fully automated simulation, for a faster simulation rate, for a more complete rocket systems model, and for a more complex physics model. My primary goals in building the first version of the new simulator were to meet the critical simulation needs by December 11th 2004, so that the team could make a go or no-go decision for the December 18th launch, and to create a solid and unit tested code-base for physical system simulators. I have built an open source rocket simulator in C++ [4], based in part on Bart Massey's original simulator. Work on the simulator is on-going and is documented on the PSAS Wiki site [5]. General Information ------------------- The simulator has been built to compile and run under Debian GNU/Linux [6], kernel 2.6, with GCC 3.3 and the CppUnit [7] packages libcppunit-1.10-0 and libcppunit-dev. In the PSAS CVS repository, the main working directory is c/tools/cppsim. The command make should build both the RocketSim and RocketSimTest projects. Once built, the rocket simulator can be run with the script ./sim, and the unit testing program can be run with the script ./unit. No command-line parameters are required. The configuration file is in c/tools/cppsim/RocketSim/bin and can be edited to change the behavior of the simulator. Currently the simulator runs in a demonstration mode where the rocket is automatically launched after 4 seconds of simulated time, the parachutes are automatically deployed at safe speeds, and the simulation ends when the rocket lands. The default configuration is set to run the simulation at 10x speed. The simulator prints a readable rocket status on stderr, and prints rocket CAN bus messages on stdout. To only see the readable messages redirect stdout to /dev/null. To do this, run the script like this: ./sim > /dev/null Unit Testing ------------ One of the goals of this project was to unit test all code as it was written or as it was ported from earlier projects. After I created the unit testing project and tests for my first two classes it was clear that I would not have time to write tests for all the work that I wanted to complete by December 11th. I have decided to not work on unit testing until I have met the basic requirements for the simulator. I will write the unit tests as part of a thorough code review, rewrite and enhancement pass. Threading Messaging System -------------------------- My goal for the threading and message passing system system was to make it easy to add new threaded elements to the simulator with a minimal amount of work. and in this I was successful. At the core of this system is the encapsulation of POSIX thread, semaphore and mutex functionality with classes. This encapsulation results in easier use, reduces error handling code, and provides base classes that more specific behavior can be extended from. As part of making thread-safe code easier to write, I created the MutexLock class. This class will lock a Mutex upon creation and will unlock it upon destruction. When used within a thread method that is manipulating thread sensitive data, the thread mutex can be locked with this class, and when the method exits, either normally, or because an exception occurred, the Mutex will be unlocked. This helps prevent Mutexes from staying locked when a method exits in an unexpected fashion, and in most code, it can eliminate the need to worry about where the Mutex should be unlocked. The base messaging system is the MessageQueue class. This class allows messages to be posted to a queue, and waited for in a thread-safe way, with the data type of the message provided as a template parameter. This class forms the communication backbone of the entire project. The most common usage of the MessageQueue class is within the MessageThread class. This class creates a thread that messages can be posted to. While running, the thread will wait for messages on the queue, and when they arrive, it will dispatch them to a message handler. Finally the most specific class of this general system is the TypedMessageThread. This class will dispatch arriving messages to different message handlers based on the type of the arriving message. This requires that the template provided message type supports a Type method and a MessageType enum. In the simulator, most threads are derived from TypedMessageThread, with the message type of RocketMessage. RocketMessage is a class that will store many data types in an overloaded fashion and can be extended easily as the needs of the simulator change. Timing System ------------- My goals for the timing system are to provide a high quality 1 to 1 real-time simulation, to provide a speed multiplier so that the simulation can run faster than real time, to allow a speed unrestricted simulation and to support message rates of at least 2500Hz. To provide the high quality 1 to 1 real-time simulation I created a central TimingThread class. This class has a high resolution one ms timing loop which it uses to post TimeVal messages to MessageThreads. Each MessageThread is registered with the timing thread with a specific start time and notification rate. In this mode the TimeVal message is created from the actual system time. To ensure that the TimingThread achieves its maximum real-time rate of 1000 messages per second, the program must be scheduled with a real-time policy. RocketSim will do this if it is run as super-user. To support simulated time multiplication, the TimingThread class supports a time factor which is used to modify the posted TimeVal messages and simulate a faster passage of time. This factor does not change the real-time one ms rate of the timing loop, which is constrained by the kernel. MessageThreads that are scheduled to run at a high frequencies may not run as often with time factors greater than 1.0. To allow speed unrestricted simulations, and to support 2500Hz message rates additional work is required. This can be achieved simply within the timing loop by waiting for all MessageThreads to complete processing of the posted TimeVal message before continuing, and by never sleeping. In this mode the program should not be scheduled under a real-time policy unless the system has more than one processor or the processor is hyper-threaded. Otherwise the program will run to the exclusion of all else, which can make it impossible to kill, or to interact with the process. Configuration System -------------------- My experience with these types of tools has been that an external, text editable configuration file can greatly improve their utility. The configuration system was a top priority for me. The Configuration class consists of a list of ConfigSection. Each section has a name, a description, and contains a list of ConfigVar which are configuration variables. Each variable has a name, description, a list of valid data types, and possibly a default value. At the core of this structure is the ConfigValue, which is a class that will store other data types in an overloaded fashion. At present it will store the string, double, int and bool data types. Valid configurations are defined in code. Each section and variable can be optional or required, and has an IsSet flag which is true if the value was set during parsing. When the configuration file is parsed, all required sections and variables must be provided, and unrecognized sections and variables are not allowed. To do the parsing I enhanced my ParserBase class from an earlier project. This class supports the createion of a finite state machine that is used to parse input. The transitions of this machine will execute callback functions for sophisticated input processing. Although it has many nice features, this class requires too much finesse to be easy to use for complex input like the configuration format. The Configuration class can parse configurations from a stream and can output a parsable configuration into a stream with formatted descriptions included. This allows the program to produce a default configuration file for itself. Rocket Model ------------ My original goal was to create a rather generic physics engine for this project. But by the time I had built enough infrastructure to work on the rocket model, I simply did not have time to explore the possible designs. For now, I have built the physics and world models directly into the RocketModelThread class. The basic representation of the rocket's location, velocity and acceleration all support full 3D flight, however, I have not yet given the rocket a full 3D orientation, so the flight path is strictly vertical. The rocket experiences forces from gravity, from the ground before launch and on final landing (or impact), from air drag on the rocket and parachutes, and from engine burn. The rocket loses mass as the engine burns, the parachutes can rip if the rocket is going too fast when they are deployed, and the model knows if the rocket has safely landed or has crashed. All of the physical rocket values and limits are defined in the configuration file. The rocket tracks its state with a time stamp in a rocket snapshot. This snapshot is held by the RocketModelThread for a time so that other elements of the simulation, such as sensors, can request a recent snapshot for a given time stamp. Rocket Sensors/Nodes and CAN Bus Messages ----------------------------------------- The PSAS rocket has many system nodes, and sensors, each of which needs to be simulated. The highest priority sensors and nodes are the IMU accelerometer and pressure sensors, and the GPS node. On the real rocket, when these sensors make measurements, the raw data from the sensor is bundled into a message that is sent out on the CAN bus. Each of these messages has a time stamp, a header and eight bytes of data that is usually organized into two or four bytes values each stored in network byte order. The header consists of a message ID, a reply or transmit flag and a data length all bundled into the two byte header. To make this message format easier to use within the simulator, I encapsulated it in the CanMessage class. This class gives easy access to the bundled header values, and to the data. The data can be read or written as bytes, shorts (two bytes) or ints (4 bytes), with the multibyte values stored in network byte order, but are returned in host byte order when read. I updated the RocketMessage class to support CanMessage so that these messages could be passed between MessageThreads in the simulator. With the message handling complete I needed to be able to communicate the simulated message to the real rocket flight computer software. There appears to be three methods of doing this, over the network, over a serial bus (when talking to the real hardware), and as formatted text on stdout. I created the class CanMessageOStreamThread, which print any CanMessage posted to it, to a stream. This is the only point in the system where CAN messages are actually output, and it would be easy to create another thread which would output CAN messages across a network or a serial interface instead. With CAN message output complete, it was easy to implement the accelerometer and pressure sensors. The GPS node was much more time consuming due to its very complex data format, and is still incomplete. Currently the simulator produces valid GPS CAN messages, but the calculations to convert between the local tangent plane, and GPS world coordinates have not been done, so the messages contain zeros for the actual values. The final aspect of the CAN message system that I have implemented is message input. This input comes from stdin and uses the same format that I print CAN message with on stdout. I created a single thread for CAN message input CanMessageIStreamThread. This thread parses input from a stream into CanMessages, and dispatches these messages to all other rocket threads. The next priority after GPS is to accept and respond to these messages. Future Work ----------- There are still a few weeks of work remaining to fully simulate all of the rocket elements and to set up a good rocket testing environment. However there are only a few days of work to complete the simulation of GPS and to respond to the critical CAN messages that will direct the behavior of the simulator. This will be enough functionally to do flight testing for the next launch decision, which will be January 2nd, 2005. Core work that must be done includes unit testing and unrestricted speed within the timing thread. In addition to this, there should probably be a single CAN message dispatcher within the system that relays all CAN messages to all MessageThreads. I have also created a list of improvements that I want to make to the general infrastructure. These improvements include fully supporting all pthread functionality within the Thread class, replacing the current parser with some form of a pushdown automata that can be configured with a grammar, and adding enumerated value support to the configuration system. I also intend to continue work on a relatively generic physics engine. My goal for this engine is that object and environment models can easily be added, removed or exchanged to create different testing conditions. Summary ------- In the last two years I have been working on small simulation projects and creating prototype systems. Starting this project, I knew exactly what I wanted the overall structure to look like. This project has given me an opportunity to pull together my ideas and prototypes into a fully functioning system. My prior experience made it possible for me to focus my effort and write a large amount of code quickly. The December 11th launch decision was actually made late on December 10th as no-launch. Too many problems were being encountered in the flight code, and it was also possible that the FAA would not be ready with our flight clearance by launch day. The next launch decision will be made on January 2nd, and my simulator will be part of the process. I have created a well documented and flexible system that is easy to add rocket specific functionality to. I have kept extensive notes on improvements that I wish to make at every level of the code. This project will provide a solid base for ongoing PSAS simulator work, and other projects. Bibliography ------------ 1: http://psas.pdx.edu 2: http://cvs.psas.pdx.edu/c/tools/rocketsim/rocket.5c?rev=1.2 3: http://www.nickle.org 4: http://cvs.psas.pdx.edu/c/tools/cppsim 5: http://psas.pdx.edu/RocketSim 6: http://www.debian.org 7: http://cppunit.sourceforge.net/cgi-bin/moin.cgi Appendix A: RocketSim File List ------------------------------- Tools.h: Simple tools for formatting and parsing strings. ApplicationException.h, ApplicationException.cpp: Defines the class ApplicationException. Core exception class for the application that enhances std::exception. Adds support for a source string, a message string, and an inner exception, as well as providing a Stack() method that returns a string containing the full exception stack, which includes inner exceptions. ErrnoException.h, ErrnoException.cpp: Defines the class ErrnoException. This class enhances ApplicationException to accept and handle errno errors. The errno value is used to lookup a message and append it to the specific exception message. Semaphore.h, Semaphore.cpp: Defines the class Semaphore. Encapsulates the POSIX semaphores, and converts errors to ErrnoExceptions. Mutex.h, Mutex.cpp: Defines the class Mutex. Encapsulates pthread_mutex_t and converts errors to ErrnoExceptions. MutexLock.h, MutexLock.cpp: Defines the class MutexLock. This class is used to maintain a lock on a Mutex until destruction, or an explicit unlock. This class allows a method to explicitly obtain a lock on a Mutex, and then implicitly release that lock when the method exits normally, or due to an exception. This prevents unintended locks on Mutexes beyond the scope of use, and creates a simple model for Mutex use when creating thread-safe code. TimeVal.h, TimeVal.cpp: Defines the class TimeVal. Encapsulates the value returned by gettimeofday() in a class that supports mathematical and conversion operations on the time. The fundamental representation of the time is a double, and is a value of seconds. Time is only represented supported to microseconds. This class also support a static Now() method that returns the TimeVal that represents the value returned from gettimeofday(), and it supports the concept of being set or not. Unset values in mathematical operations propagate the unset status to the result. MessageQueue.h: Defines the class MessageQueue. Template class that defines a thread-safe queue that messages can be posted to and waited on. The template parameter is the message type for the queue. Thread.h, Thread.cpp: Defines the class Thread. Encapsulate pthread_t and converts thread errors to ErrnoExceptions. The class can be used as a base class with a class internal thread method, or as a stand-alone object with an external thread function. MessageThread.h: Defines the class MessageThread. Template class that defines a thread that messages can be posted to. The thread waits for messages and dispatches them to the message handler. The class can be used as a base class with a class internal message method, or as a stand-alone object with an external message function. The template parameter is the message type for the thread. TypedMessageThread.h: Defines the class TypedMessageThread. Template class that defines a thread that "typed" messages can be posted to. The thread waits for messages and dispatches them to type specific message handlers. The template parameter is the message type for the thread. The message type must have a typedef enum of message types named MessageType and must have a public method named Type() that will return the MessageType of the object. The class can be used as a base class with derived classes registering type specific message handlers, or as a stand-alone object with external functions registered as typed message handlers. TimingThread.h: Defines the class TimingThread. Template class that defines a thread which dispatches TimeVal messages to MessageThread. The MessageThreads are registered to receive the TimeVal messages from a given start time and at a specified interval. The template parameter is the message type for the MessageThreads. There must must be an acceptable conversion from a TimeVal to this message type. ConfigBase.h: Defines the class ConfigBase. This is the base class for all configuration elements. It defines that configuration elements will have a name, description, and flags that indicate if it is required and set. ConfigValue.h, ConfigValue.cpp: Defines the class ConfigValue. This class allows a single configuration value to actually represent any number of other data types. The types currently supported are string, double, int and bool. It is easy to add new types. ConfigVar.h, ConfigVar.cpp: Defines the class ConfigVar. This class defines a configuration variable. It enhances ConfigBase by storing a ConfigValue and limiting the allowed types of that value. It is used to both define what an acceptable configuration assignment is, and to store the value. The ConfigValue can be set to provide a default value for the variable. ConfigSection.h, ConfigSection.cpp: Defines the class ConfigSection. This class defines a configuration section. It enhances ConfigBase by storing a list of ConfigVar. It is used to both define what an acceptable configuration section is, and to store the set variables. Configuration.h, Configuration.cpp: Defines the class Configuration. This class defines an entire configuration. It enhances ConfigBase by storing a list of ConfigSection. It is used to both define what an acceptable configuration, and to store the set sections. This class can be inserted into an output stream to produce a base level configuration file, and it support the extraction of the same format from the stream to populate the configuration variable values. ParseException.h, ParseException.cpp: Defines the class ParseException. This class enhances ApplicationException to accept offset, line and column values and it serves as the base exception for all parsing failures. ParserBase.h, ParserBase.cpp: Defines the class ParserBase. This is a base class that support parsing from an input stream. It basically defines a deterministic finite state machine with transitions between states based on characters read from the stream. To add capability and functionality, each transition can also have associated actions which cause action handlers to be called. The handlers can process input, produce parse errors, and change what the next state or action will be. ConfigParser.h, ConfigParser.cpp: Defines the class ConfigParser. This enhances ParserBase to parse a formated configuration from the stream and to populate a Configuration object with the result. If the parsed configuration is invalid, ParseExceptions are thrown indicating the offending location and what the problem is. Vector3d.h, Vector3d.cpp: Defines the class Vector3d. Basic 3D vector type. Uses i, j, k notation for the vector components. Supports many typical mathematical vector operations. Point3d.h, Point3d.cpp: Defines the class Point3d. Basic 3D point type. Uses x, y, z notation for the point coordinates. Support many typical mathematical point operations. The difference of points produces a vector. The addition of a point with a vector produces a point. Line3dException.h, Line3dException.cpp: Defines the classes Line3dException, InvalidLineException, ParallelLineException, and SkewLineException. These are simple name exceptions used by the Line3d class to report calculation errors. Line3d.h, Line3d.cpp: Defines the class Line3d. Basic 3D line type. Allows a line to be treated as an infinite line in space, or as a line segment. Supports some mathematical line operations. Constants.h: Some physical constants used during calculations of the simulated rocket. RocketConfig.h, RocketConfig.cpp: Defines the class RocketConfig. This class enhances Configuration by defining what a valid configuration is for the rocket simulator. CanMessage.h, CanMessage.cpp: Defines the class CanMessage. This is an encapsulation of the information within a CAN message. This class also makes it easy to store data in network byte order, and to read it in host order. CanMessageParser.h, CanMessageParser.cpp: Defines the class CanMessageParser. This enhances ParserBase to parse a formated CAN message from the stream and to populate a CanMessage object with the result. If the parsed CAN message is invalid, the rest of the line is ignored and ParseExceptions are thrown indicating the offending location and what the problem is. RocketMessage.h, RocketMessage.cpp: Defines the class RocketMessage. This is the core message type that is posted between threads. It is a typed message that supports the encapsulation of almost any type of data; new types can be added with very little work. The currently supported types are TimeVal, CanMessage, and RocketCommand (which is an enum). CanMessageIStreamThread.h, CanMessageIStreamThread.cpp: Defines the class CanMessageIStreamThread. This class enhances Thread to parse CAN messages from an input stream and dispatch them to registered MessageThreads that accept the message type RocketMessage. This is the only place where CAN messages are input into the simulator. CanMessageOStreamThread.h, CanMessageOStreamThread.cpp: Defines the class CanMessageOStreamThread. This class enhances TypedMessageThread to wait for RocketMessages that contain CanMessages. These messages are then inserted in the specified output stream in text format. This is the only place where CAN messages are output from the simulator. RocketModelThread.h, RocketModelThread.cpp: Defines the class RocketModelThread. This class enhances TypedMessageThread to wait for RocketMessages. RocketMessages that contain TimeVals initiate the stepped rocket model simulation. This thread remembers the simulated state in a RocketSnapshot for a short time after it was created, which allows other threads to reach into the recent past to perform their tasks. RocketStatusThread.h, RocketStatusThread.cpp: Defines the class RocketStatusThread. This class enhances TypedMessageThread to wait for RocketMessages. RocketMessages that contain TimeVals initiate a reporting to stderr on the rocket state at the triggering TimeVal. This is mostly used as a debugging tool, used to visually monitor the internal simulation state of the rocket. AccelSensorThread.h, AccelSensorThread.cpp: Defines the class AccelSensorThread. This class enhances TypedMessageThread to wait for RocketMessages. RocketMessages that contain TimeVals initiate an accelerometer sensor measurement reporting on the rocket state at the triggering TimeVal by posting CanMessages to the specified CAN message output thread. PressureSensorThread.h, PressureSensorThread.cpp: Defines the class PressureSensorThread. This class enhances TypedMessageThread to wait for RocketMessages. RocketMessages that contain TimeVals initiate a pressure sensor measurement reporting on the rocket state at the triggering TimeVal by posting CanMessages to the specified CAN message output thread. GpsNodeThread.h, GpsNodeThread.cpp: Defines the class GpsNodeThread. This class enhances TypedMessageThread to wait for RocketMessages. RocketMessages that contain TimeVals initiate a GPS measurement reporting on the rocket state at the triggering TimeVal by posting CanMessages to the specified CAN message output thread. RocketSim.cpp: Contains main(). Reads the configuration file and runs the entire rocket simulator. rocket.config: Configuration file for the rocket simulator. Appendix B: RocketSimTest File List ----------------------------------- ApplicationProtector.h, ApplicationProtector.cpp: Defines the class ApplicationProtector. This class is used to catch application specific exceptions during testing. ApplicationExceptionTest.h, ApplicationExceptionTest.cpp: Defines the class ApplicationExceptionTest. Unit tests for ApplicationException. ErrnoExceptionTest.h, ErrnoExceptionTest.cpp: Defines the class ErrnoExceptionTest. Unit tests for ErrnoException. RocketSimTest.cpp Contains main() for the RocketSimTest project. This program runs all of the registered tests.