RoboCar Device Level Design The Device class and all its helpers are in the package: com.etantdonnes.robocar.dev This class and its contents describe a specific kind of robot device, e.g., RoboCar, Lego, Descartes, etal. It is responsible for translating and communicating commands and status information between the common data representation of the higher levels and the device specific needs of the robot hardware and/or I/O Processor. The devCommand class is used to pass commands in, and get status back from the Device. devCommand Class This class holds all of the possible settings for any robot device in a common format (CM, degrees, etal). A field usually maps to a field in the ControlData classes described below. A devCommand object is used to pass all the data necessary to execute a Device Level command, and is also used to pass back all the results. Any value set to InterpreterLevel.NoChange will not be used in a command. public class devCommand extends Command { public int speed; // Drive.speed -- cm/sec public int distance; // Drive.distance -- +/- cm public int radius; // Turn.radius -- cm from center public int headchg; // Turn.headchg -- +/- degrees public int endstate; // stuff to make it stop or status of what did public int time; // timeout or execution time status public int bumpers; // Bumper.bumper return status public int locId; // device ID sent or received public int locdata; // locator status to send or message received public int locstr; // strength of received locator signal public int pir; // status of PIR detector public int posX; // calculated X position in cm. public int posY; // calculated Y position in cm. public int heading; // calculated Forward facing direction in degrees. } devCommand also contains methods to initialize, set, and update the values as well as mechanisms to parse commands from various sources -- the code is the documentation... Device Class The Device class extends the InterpreterLevel definition such that all upperlevel operations, command execution, etal, can be implemented without knowledge of the level or internals. Device contains methods necessary for common operations, but is an abstract class which must be extended by each kind of physical robot car. Each extended Device class adds fields necessary to interpret the specific kinds of data that are expected from the physical robot car. The added fields are instances of ControlData classes that describe all the possible control and data input options that can be used. Then the abstract methods must be implemented to reference all the ControlData fields for the actual robot. Device also contains a DataIO implementation specific to the communication properties of this type of robot. The abstract methods allow transparent handling of each kind of robot's output control and input status. // Encapsulates all of the information about the capabilities of the // actual hardware robot. The constructor should initialize the // relevant ControlData elements. And each abstract method should // process the full list of initialized elements. // It's all here to keep the device specific information in one place. // public abstract class Device extends InterpreterLevel { // All I/O controls -- the full set. Each one extends ControlData. // Will be initialized to null to start. Should be set in the // sub-classes for the actual device capabilities. // Output controls to robot public Drive myDrive; // drive motors public Turn myTurn; // steering public Locator myLocator; // broadcast Locator Signal // Input status from robot public Bumpers myBumpers; // bumper contacts public Locator theirLocator; // input LocatorSignal public Pir myPir; // PIR sensor public Position myPosition; // estimated position in environment public TimeTick myTime; // timeout or elapsed time for command public EndState myState; // bit map of state which ended command public DataIO dio; // low level IO object // Called from I/O thread to parse input data from the robot // into the controls private storage. Returns the endstate // value (what changed), to determine if the command is done. public abstract int parseInput(); // Called just prior to calling the I/O thread to write output data. // Convert and format the ControlData values for the robot itself. // Makes a ControlData Output telemetry record. public abstract int formatOutput(); // Called just after getting the final command result to move // ControlData private data into public values for access. public abstract int saveData(); // write all control fields telemetry using 'head' as the wrapper public abstract void dioTele( String head ); // from InterpreterLevel -- do whatever necessary to quit public int shutdown() // from InterpreterLevel -- stop command in progress public int wakeup( Command cmd ) // from InterpreterLevel -- set controls and execute command public int doCommand( Command c ) } // end'o'Device ControlData Class ControlData is an abstract class used to wrap each kind of output control or input status that can be used in a robot system. It provides a common interface and value range for each type of robot data. Every type of control extendeds this class and implements the functions necessary to convert the common data description to and from the robot's internal representation. Each extended control will define a set of public fields to keep the robot's actual data, being public they can be accessed directly. However, ControlData maintains a 'newdata' flag in order to determine if any changes have been made since the last command cycle. Some of the methods manipulate this flag at appropriate times, so care should be taken to keep this flag valid when accessing data elements. Note: The public fields in the Controls contain the values that will be sent to the robot device itself. They are converted to and from the common format by the get/setValue() calls. There are also a set of shadow private values in which the parseData() method puts its results. The private values are copied to the public area when a 'real' command result is achieved. This prevents the public data from changing as intermediate results happen in the I/O thread. // Wrapper for all kinds of robot data, both direct and calculated. // Each data type should implement its own version, with a constructor // that sets the public data elements appropriately and saves Device. // Note1: The Device reference here is in case one data type needs // to communicate with another, i.e., Drive and Turn may // be interdependent.... // Note2: The public data values in these objects are the actual // values that go to/from the robot. They get converted to // common format in the devCommand by get/setValue(). // public abstract class ControlData { protected boolean newdata; // are the contents new? protected Device device; // the Device to which we belong // private xxDataIO dio; // set in constructor to avoid casting etc. // convert the local data into common format in the devCommand fields, // then toggle the newdata flag off public abstract void getValue( devCommand ); // get the current common format values for this control // from the Device Command object and convert to local data // then toggle the newdata flag on public abstract void setValue( devCommand ); // Format current local field values into physical format // for this Control in DataIO 'stream'. // Set newdata to false so we can don't try to do it again. // Uses the local reference to the DataIO object. public abstract void formatOutput(); // Parse input from the DataIO object into private fields // for this Control on this Device. // Set newdata on if it's new data. // Return Device endstate flags indicating what changed. // Uses the local reference to the DataIO object. public abstract int parseInput(); // Copy the private input values to the public local values // for this Control on this Device. public abstract void saveData(); // Format an ascii XML telemetry string for this Control's value // If newdata is false, just return a null. public abstract String toTele(); // Format a debugging string for this Control's value // public String toString() -- defined in Object so can't override } // end'o'ControlData The classes that extend ControlData, and their public data fields, are: Battery (abstract) -- Information about the state of the robot's power. level -- Voltage level. charge -- (Dis)charging current, positive means battery is charging. If the level or charge fall below parameterized values the endstate flags will be set appropriately when the current command returns. Bumpers (abstract) -- The state of the collision bumpers. bumpers -- Bit mapped bumper state, one is active. If any bumpers are hit during a command cycle the BumperDetect bit will be set in the endstate indicator. If any bumpers are released during a command the UnBumpDetect endstate will be set. Drive (abstract) -- Drive motor settings. Will be updated to actuals. distance -- Distance to travel in CM, negative means backwards speed -- Speed to travel, may be adjusted to reflect capabilities Also defines some speed and distance values for convenience. ElapsedTime -- The command's timeout and the execution time. time -- The number of milliseconds. EndState -- The command's desired results and the actual completion. endstate -- A bit mask of completion states as defined in the InterpreterLevel class. Locator (abstract) -- The broadcast locator signal. Either the robot's own signal for output, in which case the data is the robot's own ID and status along with any message it wants to send. Or another device's received signal, in which case the ID will be the other device's, and the data will be the status and message sent by that device. locId -- Device ID, either our own, or the other device's. locstat -- Status information. locmsg -- Data to send or that was received. locstr -- Signal strength, significant for input only. Pir (abstract) -- Passive InfraRed sensor state. pir -- int triggered or not...could be modified to have strength. Position -- Calculated device position. posX -- X position in cm. posY -- Y position in cm. heading -- Forward facing direction in degrees. Note: All the values are relative to the device's starting position, at least until we come up with some way to synchronize them with the actual environment. These values can be updated externally to set them to 'reality'. Turn (abstract) -- Steering settings. Will be updated to actuals. headchg -- Heading change in degrees, positive means clockwise. radius -- Radius of turn in CM. Zero is in place, may be adjusted to reflect device's capabilities. Also defines some heading and radii values for convenience. Note: If a radius is specified without a heading change nothing will happen and the command will be ignored. The last Drive. speed value will be used for non-zero radius turns. If both Turn and Drive commands are issued, the specified heading change will occur using the Drive.speed and then any Drive. distance not used in making the radius turn will be completed on the new heading. The classes labeled (abstract) above need to be extended by each type of device. For convenience, they are not actually java abstract classes but they only implement the simplest of 'lower half' functions. However they do implement the most useful 'upper half' functions necessary for each kind of data. DataIO Class The DataIO class implements the Runnable interface in order to be able to start an independent thread to handle the actual in/output operations with the robot's I/O Processor. It also contains the thread synchronization methods needed to pass control between the main command thread and the I/O thread. DataIO is an abstract class which must be extended by each kind of robot to implement the functions necessary to communicate with the robot car's hardware or internal processor. The 'upper half' of the functionality, thread management, etal, is already implemented, but the 'lower half' communication is device dependent. // Defines the I/O thread and actual robot communication functions. // Each kind of robot device should extend this, implement the // abstract methods, and add a constructor that sets the device // reference, initializes the right I/O streams, buffers, etal, // and then starts the I/O thread. // public abstract class DataIO implements Runnable { public int elapsedTime; // Ticks elapsed during last WaitForChange() public int wakestate; // endstate mask that woke us up this time public byte[] inbuf; // i/o buffers to be accessed by Controls public byte[] outbuf; protected Device device; // the device to which we belong private boolean writeReady; // true if data ready to send to robot // write data to robot.... public abstract void writeData() throws Exception; // read data from robot.... public abstract void readData() throws Exception; // return TRUE if there is data to read from robot's low level controller public abstract boolean inAvailable() throws Exception; // clear and reset to stop current command, or maybe quit // just used by initialize, wakeup(), and shutdown() private abstract void resetOutput() throws Exception; // shutdown and cleanup the system to QUIT public void shutdown() {} // interrupt and cleanup command to wakeup the Car public void wakeup() {} // cleanup after an external wakeup() public void intack() {} // Calls device.formatOutput() to setup output data for robot. // Makes a telemetry record of the new ControlData settings. // Gets the timeout and endstate information from the device fields. // May return immediately if it looks like the desired results // have already happened...(a little hazy on that concept still...) // Or else, signals the IO thread to start writing the command data. public int putOutputData() {} // Wait for change in state triggered by previous putOutputData(). // Blocks in wait() until the desired results or errors occur. // Gets woken up by DataIO rd/wr thread in run() from notifyChange(). // When done, the new robot status and controls will be parsed // into their Controls and a telemetry record will be sent. // Returns the endstate flags that woke us up. public synchronized int waitForChange() {} // Notify the main thread if the change we want has occurred. // Wakes up DataIO object in waitForchange(), only used internally. // Arguments: state -- wakeup state that triggered this. private synchronized void notifyChange( int newstate ) {} // From the Runnable interface // The actual I/O Processor read&write thread.... public void run() {} } // end'o'DataIO Execution PseudoCode Initialization 1. The main program instantiates Params and Telemetry objects of the required type and then passes them to the constructor of the robot's Device class. 2. The Device constructor: a. Saves the Params and Telemetry references. b. Instantiates the right kind of DataIO object for this robot. c. Instantiates any Controls used by this device and sets the appropriate Device.my* fields. 3. The DataIO constructor: a. Opens the necessary I/O ports and streams. b. Allocates and initializes any I/O buffers it may need. c. Creates and starts the "IOThread" using DataIO.start(). 4. The ControlData objects initialize themselves however necessary. Execution 1. A devCommand is passed to Device.doCommand(). 2. Device.doCommand(): a. Create a devCommand telemetry Command record. b. Pass the devCommand to all output ControlData.setValue(). i. The output controls initialize their local values from the devCommand. (note: 'local value' means the value the robot itself wants, converted from the common data format in the devCommand) c. Call DataIO.putOutputData() to send the command to the robot. d. Call DataIO.waitForChange() to wait for results. e. Pass the original devCommand to all the in and out ControlData.getValue() to be updated with actual results. f. Call Device.dioTele() to make an ControlData input telemetry record. g. Create a devCommand telemetry Result record. h. Post the glob of telemetry data to the Mothership. i. Return the endstate bit mask. 3. DataIO.putOutputData(): a. Check to see if the command's requested endstate is already true. If so: i. Call Device.saveData(): saveData(): x. Call each Control.saveData() to move private input states to accessible local values. ii. Reset the DataIO state and return the endstate we have. If not: i. Setup DataIO state for command cycle. ii. Call Device.formatOutput() to setup data to be sent. A. formatOutput(): x. Make a ControlData output telemetry record. y. Call each output ControlData.formatOutput() to convert their local values into actual robot commands. iii. Set the 'writeReady' flag to true for the IOthread. iv. Return InterpreterLevel.NoDetect because nothing has happened yet. 4. DataIO.waitForChange(): a. Check to see if the command's requested endstate is already true. If so (like above...): i. Call Device.saveData() to move Control states to accessible values. ii. Reset the DataIO state and return the endstate we have. If not: i. Setup and call wait( timeout ); ii. Return the endstate we got. 5. The IOthread DataIO.run() loop: a. while 'runme' is true (so we have some chance of shutting down)... b. If DataIO.inAvailable() is true i. Call DataIO.readData(). ii. Call Device.parseInput(). A. Call each ControlData.parseInput() -- returns state flag. B. OR together all the state results and return. iii. Call DataIO.notifyChange( state ): A. Check the state flags against what the command asked for. B. If we got it.... x. call Device.saveData() -- to save Control values to locals Y. call notify() to wakeup main thread in waitForChange() c. if DataIO.writeReady is true i. Call DataIO.writeData() -- send command to robot ii. reset writeReady. d. loop forever..... That's all folks....