Main Page | Modules | Class Hierarchy | Compound List | File List | Compound Members | File Members | Related Pages

Remote Data Logging Services


Detailed Description

RHexLib logging services are designed to make it simple for an operator to collect periodically sampled data from an operating robot and download the resulting data to a client computer (typically part of the operator control unit or OCU). The service is designed to be minimally invasive on the robot, but to enable the effective collection of large quantities of data. The service is divided into two sections: first is the LogServer class which operates on the robot and is responsible data acquisition and buffering, second the LogClient class operates as part of the user interface and abstracts the underlying communication tasks needed to interact with the LogServer.

Note that while the LogClient and LogServer pair are designed to support multiple client connections to a single server, there are limits in the underlying protocol that prevent certain tasks from being performed ``simultaneously'' by multiple clients. In particular starting a new logging task can involve multiple messages sent between the client and server, and this sequence can be ``interrupted'' by messages from other clients. This said, if multiple clients wish to interact with the robot the best procedure to follow is to ensure that the logging process has been fully started by one client before the second attempts to start its loggin process (specifically startLog calls should not be made simultaniously on multiple clients).

LogClient interface

The LogClient class provides the basic interface between a client program (one that wishes to collect data from the robot) and the LogSever class running on the robot. This class includes various methods for interacting with the LogServer class as well as methods for creating and destroying logging tasks. The typical sequence of operations is to create an instance of the LogClient class, query the remote LogServer class for a list of loggable variables, create a new logging task, add the desired variable to the newly crated task, start the task, receive and process/save/interpret the data from the logging task, stop the logging task, destroy the logging task, and finally finish interaction with the LogServer by destroying the LogClient instance.

Note: most methods accept an option argument msec, this argument can be used to specify the desired timeout that the LogClient will wait for a response from the associated LogSerrver before indicating failure. The default timeout is normally 5 seconds.

The specific interface to LogClient follows:

#include "LogClient.hh"

class LogClient {
public:
  LogClient(CommManager *com, RemoteManager *rmt,
            const com_id_t log_port=DEFAULT_LOG_STREAM);
  LogClient(CommManager *com,
            const char* host, const unsigned short port,
            const com_id_t log_port=DEFAULT_LOG_STREAM);

  ~LogClient();

  // BASIC API -- methods that send simple messages
  bool ping(const unsigned int msec=LOG_CLIENT_TIMEOUT);
  bool reset(const unsigned int msec=LOG_CLIENT_TIMEOUT);

  // VARIABLE MANAGEMENT -- methods for managing avaliable variables
  bool query(const unsigned int msec=LOG_CLIENT_TIMEOUT);
  void clearVars();
  unsigned int listVars();
  log_name_t *getNextVar();
  ListIterator<log_name_t> *varIterator();

  // LOG-JOB MANAGEMENT
  LogTask *newLog();
  bool destroyLog(LogTask *task);
};

The two constructors provide two alternate means to specify the computer to be contacted, either as a pointer to a RemoteManager instance, or by hostname and port number. The final argument specifies the port number of the stream where the LogServer is expecting to be contacted -- by default the LogServer waits on stream port 76. The basic API section includes a ping method, which can be used to verify the health of the remote LogServer -- note that the underlying protocol is unreliable and thus while a successful ping (returning true) indicates that the LogServer is running, a failed ping (returning false) should not be used to conclude that the LogServer has failed (The LogServer replies to all messages by sending a status message to a mailbox since these could be dropped/lost it is general not safe to use any ``failed'' request to conclude that the LogServer has actually failed. Rather, failed requests should be used only to indicate that there may be either network or software problems in the system.). The second method in the group, reset, can be used to force the remote LogServer to fully reset its internal state. This has the effect of terminating all active logging tasks, and breaking all pending connections/communications.

The variable management section provides methods for interrogating the available variables from the LogServer. The LogClient class maintains an internal list of available variables. This list is updated by calling query. When query is called, a list of available variables is retrieved from the LogServer and the contents of this list are merged with the existing internal list. clearVars clears the contents of the internal list. listVars produces an alphabetically sorted list of the available variables, and indexes an iterator to the beginning of this list, getNextVar can then be used to extract the names of each variable (See LogMsg.h) for the definition of log_name_t which includes the variable name as well as type and size information. So a typical listing of the available variables can be generated as follows.

  LogClient* logger = new LogClient(...);

  if (!logger->query()) {
    printf("Unable to query LogServer\n");
  } else {
    unsigned int num_var = logger->listVars();
    printf("%d variables avaliable\n", num_var);

    log_name_t *n;
    while((n=logger->getNextVar()))
      printf("<%s> %s[%d]\n",
             n->name, format_type(n->type), n->array);
  }
  delete logger;

For convenience, access is also given to this sorted list via a ListIterator -- note that the elements pointed to on this list are internal to the LogClient class, and must not be changed by the user.

Finally there are two methods for creating and destroying instances of the class LogTask. A LogTask instance represent a logging task, specifying wich variables are to be collected, how frequently they should be sampled, and for what period that sampling should continue. Since a logging task is inexorably intertwined with its associated LogClient instances of LogTask are created and destroyed respectively with the newLog and destroyLog methods of LogClient.

LogTask interface

The LogTask class provides an interface through which a client program can specify and control the execution of a particular logging task. A single instance of LogClient will support and essentially arbitrary number of LogTasks. After creation of an instance of Logtask (as described above) typically a program ``adds'' variable to be logged to the task, then issues a ``start'' command, specifying the period, duration, and buffering behavior for the task. The specific class definition includes the following methods:

#include "LogClient.hh"

class LogTask {
public:
  bool addVar(const char *name);
  unsigned int startLog(const unsigned int period, const unsigned int length,
                        const unsigned int buf_size=1000,
                        const unsigned int msec=LOG_CLIENT_TIMEOUT);
  bool stopLog(const unsigned int msec=LOG_CLIENT_TIMEOUT);
  bool abortLog(const unsigned int msec=LOG_CLIENT_TIMEOUT);

  log_line_t *getData(const unsigned int msec=LOG_CLIENT_TIMEOUT);

  ListIterator<log_name_t> *varIterator();
  List<log_name_t> *varList();

  unsigned int totalData() const { return _data_size; };
  unsigned int bufferSize() const { return _buf_size; };
  unsigned int winSize() const { return _win_size; };
  bool isDone() const { return _data_done; };

Note that the constructor and destructor for this class are private, since an instance of LogTask without an associated LogClient is meaningless. The method addVar is used to specify (by name) the variables that should be logged for this task, note that every logging task automatically timestamps all data collected with a 64-bit integer, representing microseconds since the robot began operation. startLog is used to actually begin the logging process, here the logging period (in miliseconds), the length or duration of the logging task (in number of samples to be collected), the maximum buffer size (in units of approximately 1kB), and the timeout. Specifying a length of 0 results in a task that will run until it is manually stopped. Once a job is started, getData can be used to retrieve data from the task. Each call to getData will return one line of data (the value of each variable at one instant in time). The format of this data is defined in LogMsg.hh as

typedef struct {
  unsigned long long time;                        // timestamp
                                                  // -1 here indicates lost data
  unsigned char data[4];                          // start of the data
} log_line_t;

The time field is the timestamp for this line of data, and the data field marks the beginning of the actual data. The variables collected will overrun the data field and are packed tightly. in their native format. If either the end of the data stream has been reached or a timeout occurs getData will return NULL, in either case the status of the buffers should be checked (as described below) to determine that cause. In the event that the LogServer ran out of buffer space an timestamp of -1 will be used to indicate where data was discarded.

Two methods for stopping a LogTask are included. The first, stopLog, causes an orderly shutdown of the logging task by sending a message requesting that the LogServer cease collecting data, any data already collected is buffered and returned to the client so that it may be received via calls to getData. The alternate method, abortLog, causes an immediate stop of the logging task, all buffers are discarded and the server is notified that the task should be discarded.

Four functions are included to facilitate monitoring of a logging task. the most basic is isDone, this method returns true if all of the data associated with the task has been retrieved from the LogServer. isDone returning true in conjunction with getData returning NULL indicates that all data has been received and processed. The methods totalData, bufferSize, and winSize indicate respectively the total amount of data received by the client process, the amount of buffered data waiting to be processed by getData, and the amount of mis-ordered data waiting to be sorted and buffered. It is the last of these three that deserves some addition explanation. Since the underlying communications protocol is unreliable, the LogTask class makes an effort to ensure that the received data is presented to a user in the order it was acquired, this requires extra ``sorting'' of the incoming data if message packets are lost in transmission. Basically a ``window buffer'' is used to store packets that are out of order until the intervening packets arrive at which point they can be safely appended to the end of the buffer. The result is a reliable ordered stream of logging data.

Finally two means of gaining access to the list of variables associated with the logging task are provided. The first return an iterator for the internal list, while the second returns a pointer to the list itself. In either case it is critical that the contents of these lists not be modified in any way.


Compounds

class  LogServer
 Basic logging services for use on the robot. More...


RHexLib Reference Documentation