Wiki
Clone wikiroseline / An API for timing information in control design
Roseline API for Control Design
Motivation
The performance of a controller often depends crucially on accurate time data in its sensors and actuators.
In this page we lay out a software architecture for providing a flexible framework for creating, managing, and delivering timestamped sensor and actuator values.
High-level Ideas
-
I should be able to set the frequency with which a sensor value is retrieved and stored.
-
The time of a sensor reading may only be known to lie within an interval. So let's permit time values to be intervals as well.
-
A sensor collects samples in a buffer that can be accessed in a few ways:
- Get the most recent sample
- Get all samples since time
t
- Get the first sample after time
t
- Get all samples since I last polled
- Get all samples in the whole buffer.
-
It seems reasonable to delete old samples once newer ones have been retrieved. Just store them all in a circular buffer?
-
An actuator can be commanded not just with a single value to assign immediately, but rather a whole "schedule" of (time, value) pairs that specify what value to apply and when.
-
If it doesn't matter when exactly the value is applied, the "time" that a value should be applied at may be an interval. This gives the actuator some flexibility in obeying a control schedule.
-
Error reporting: Modules should be able to log or notify their owner if something goes wrong, e.g., if the actuator is unable to stick to its schedule.
-
Joao initially envisioned using Linux character devices to buffer sensor and actuator data. This will permit us to swap hardware (sensors) without recompiling the kernel. Also, modeling time-stamped sensors as a stream of measurements seems to fit well in the Unix model of a device.
An Object-oriented design
class TimeStamp
A precise (scalar) moment in time.
time()
class TimeInterval : public TimeStamp
An interval of time.
min()
max()
time()
returns the center of the interval.
class Sensor
Abstract class for packaging sensor data.
Methods:
time get_most_recent()
vector<time> get_all()
vector<time> get_all_since(time t)
set_buffer_size(int n)
- Set the buffer size of the circular buffer within the character device.
set_read_rate(int hz)
- Set the rate at which the sensor should read new measurements.
class Actuator
Methods:
set_value(v)
set_value_at_time(v,t)
set_value_in_interval(v,t)
A Design Inspired by Character-devices
Sensors that produce streams of measurements and actuators that are driven by streams of control signals are viewed as time-stamped virtual files (TSVFs). These files differ from usual files in that data is aggregated into atomic units of the same size, each one of them tagged with a time stamp. For sensors these files are read-only and each atomic unit correspond to a set of measurements collected by the physical sensor at a particular time instant, whereas for actuators these files are write-only and each atomic unit corresponds to a control value that should be sent to a physical device at a particular time instant.
TSVF share 5 basic operations with regular files -- open
,
close
, read
, write
, and
poll
-- and, like special device files, uses
ioctl
for device-specific interactions with the physical
device.
open
Open a connection to a TSVF.
handle=open(device,parameters)
close
Close a connection to a TSVF.
close(handle)
Time types and functions
exactTime_t
Data structure that stores a time instant.
exactTime_t time;
uncertainTime_t
Data structure that stores the time stamp of an event, with associated uncertainty.
typedef struct uncertainTime_s {
exactTime_t minTime;
exactTime_t maxTime;
} uncertainTime_t;
timeCopy
Copies the content of src
to dest
.
timeCopy(exactTime_t *dest,exactTime_t *src);
timeCopy(uncertainTime_t *dest,uncertainTime_t *src);
timePlus
Computes and stores in t3
the sum of the times stores in
t0
and t1
(i.e.,t3
=t0
+t1
), propagating
uncertainty appropriately.
For the 1st form, it is safe to have t3
equal to
t0
or t1
.
timePlus(exactTime_t *t0,exactTime_t *t1,exactTime_t *t3);
timePlus(uncertainTime_t *t0,exactTime_t *t1,uncertainTime_t *t3);
timePlus(uncertainTime_t *t0,uncertainTime_t *t1,uncertainTime_t *t3);
timeDiff
Computes and stores in dt
the different between the times
stores in t1
and t0
(i.e.,
dt
=t1
-t0
), propagating
uncertainty appropriately. For the 1st form, it is safe to have
dt
equal to t0
or t1
.
timeDiff(exactTime_t *t0,exactTime_t *t1,exactTime_t *dt);
timeDiff(uncertainTime_t *t0,uncertainTime_t *t1,uncertainTime_t *dt);
getTimePre, getTimePost
These commands store in the variable time
the time at which
an event takes place. getTimePre
should be called before
the event and getTimePost
should be called after the
event to facilitate quantifying the uncertainty about the time stamp.
getTimePre(uncertainTime_t *time);
getTimePost(uncertainTime_t *time);
Example
uncertainTime_t timeStamp;
getTimePre(&timeStamp);
// execute code
getTimePost(&timeStamp);
waitUntil
Waits (roughly) until the time specified by time
. Returns 0
is all goes well, or -1 if the call is interrupted by a signal handler.
int waitUntil(exactTime_t *time);
int waitUntil(uncertainTime_t *time);
Sensor data types and functions
Sensor data are stored in circular buffers. The buffers have
circularInt16Buffer_t
Data structure that stores a stream of time-stamped measurements. Each
measurement is an array of int16_t
.
typedef struct circularInt16Buffer_s {
int lengthMeasurement;
int maxMeasurements;
int count; // cumulative number of measurements read to buffer
int validMeasurements; // # of valid measurements in the buffer (redundant)
int oldestFull; // index of oldest unprocessed measurement
int firstEmpty; // index of first empty position in the buffer
int32_t *counts;
uncertainTime_t *timeStamps;
int16_t *data;
} circularInt16Buffer_t;
newCircularBuffer
This function allocates a buffer that can store up to
maxMeasurements
measurements, each consisting of
lengthMeasurement
int16_t
. This function
returns a pointer to the newly allocated buffer or NULL, in case the
memory allocation failed.
circularInt16Buffer_t *newCircularBuffer(int lengthMeasurement,
int maxMeasurements);
deleteCircularBuffer
This function deletes a buffer, freeing the memory allocated by
newCircularBuffer
. It does nothing in case
buffer
is NULL.
void deleteCircularBuffer(circularInt16Buffer_t *buffer);
lenCircularBuffer
This function returns the number data measurements currently in the buffer.
int lenCircularBuffer(circularInt16Buffer_t *buffer);
pushCircularBuffer
This function adds a new measurement to the buffer by coping to the
buffer the measurement in data
. It returns the number of
data measurements left in the buffer if all goes well or -1 if the
buffer was full and an old measurement was discarded to make space for
the new one.
int pushCircularBuffer(
circularInt16Buffer_t *buffer,
uncertainTime_t *timeStamp,
int16_t *data);
popCircularBuffer
This function copies to data
the “oldest” measurement in
the buffer and removes it from the buffer. It returns the number of data
measurements left in the buffer if there was a measurement in the buffer
or -1 if the buffer was empty and no measurement could be removed.
“Oldest” refers to the order in which measurements were added to the
buffer and not to the time stamps.
int popCircularBuffer(
circularInt16Buffer_t *buffer,
int32_t *count,
uncertainTime_t *timeStamp,
int16_t *data);
Low-level functions to access sensing devices
openI2CDevice
This function opens communication with a device in a I2C bus and
initializes n
8-bit registers.
int openI2Cdevice(
int i2cBus,
int deviceAddress,
int n,
uint8 *init);
The input init
points to an array with 2n
elements, of the form
{ register address 1, initialization value 1,
register address 2, initialization value 2,
...
register address n, initialization value n}
The function return the file descriptor that should be used for communication or -1 in case the operation fails.
Updated