ardupilot/libraries/AP_Common/AP_Var.cpp
DrZiplok@gmail.com 39fea200ca I never liked the plan that variable groups could only be statically scoped; it was much too fragile. These changes address that issue by splitting the global variable list into two lists, one for standalone variables (including groups), and one for variables that are members of a group.
Now a variable belonging to a group can safely be constructed before the group it belongs to, and the group can be destroyed before its member variables.  This greatly simplifies the AP_Var constructor(s).

Remove the lookup-by-index and lookup-by-key interfaces to AP_Var and replace them with first/next interfaces for all variables, and for variables belonging to a specific group.  Document their usage.  Add an accessor for the key associated with a variable so that search-by-key can be performed by a consumer.  Throw away the lookup cache implementation, as it's not required anymore.

Re-layout the EEPROM variable header and tweak the EEPROM space allocator so that it's more resistant to interruptions during variable save.

Fix the global constants so that they work.

Add an interface for erasing all variables in EEPROM (only writes one byte).

Fix unit tests so that they work with the changed interfaces.  Also tweak the unit test framework so that it doesn't inline all its code.  This is a WIP - many more tests need to be written still.


git-svn-id: https://arducopter.googlecode.com/svn/trunk@1531 f9c3cf11-9bcb-44bc-f272-b75c42450872
2011-01-23 06:52:59 +00:00

523 lines
14 KiB
C++

// -*- tab-width: 4; Mode: C++; c-basic-offset: 4; indent-tabs-mode: nil -*-
//
// This is free software; you can redistribute it and/or modify it under
// the terms of the GNU Lesser General Public License as published by the
// Free Software Foundation; either version 2.1 of the License, or (at
// your option) any later version.
//
/// The AP variable interface. This allows different types
/// of variables to be passed to blocks for floating point
/// math, memory management, etc.
#include "AP_Var.h"
// Global constants exported for general use.
//
AP_Float AP_Float_unity ( 1.0, AP_Var::k_key_none, NULL, AP_Var::k_flag_unlisted);
AP_Float AP_Float_negative_unity(-1.0, AP_Var::k_key_none, NULL, AP_Var::k_flag_unlisted);
AP_Float AP_Float_zero ( 0.0, AP_Var::k_key_none, NULL, AP_Var::k_flag_unlisted);
// Static member variables for AP_Var.
//
AP_Var *AP_Var::_variables;
AP_Var *AP_Var::_grouped_variables;
uint16_t AP_Var::_tail_sentinel;
bool AP_Var::_EEPROM_scanned;
// Constructor for standalone variables
//
AP_Var::AP_Var(Key key, const prog_char *name, Flags flags) :
_group(NULL),
_key(key | k_key_not_located),
_name(name),
_flags(flags)
{
// Insert the variable or group into the list of known variables, unless
// it wants to be unlisted.
//
if (!has_flags(k_flag_unlisted)) {
_link = _variables;
_variables = this;
}
}
// Constructor for variables in a group
//
AP_Var::AP_Var(AP_Var_group *group, Key index, const prog_char *name, Flags flags) :
_group(group),
_key(index),
_name(name),
_flags(flags)
{
AP_Var **vp;
// Sort the variable into the list of group-member variables.
//
// This list is kept sorted so that groups can traverse forwards along
// it in order to enumerate their members in key order.
//
// We use a pointer-to-pointer insertion technique here; vp points
// to the pointer to the node that we are considering inserting in front of.
//
vp = &_grouped_variables;
while (*vp != NULL) {
if ((*vp)->_key > _key) {
break;
}
vp = &((*vp)->_link);
}
_link = *vp;
*vp = this;
}
// Destructor
//
AP_Var::~AP_Var(void)
{
AP_Var **vp;
// Determine which list the variable may be in.
// If the variable is a group member and the group has already
// been destroyed, it may not be in any list.
//
if (_group) {
vp = &_grouped_variables;
} else {
vp = &_variables;
}
// Scan the list and remove this if we find it
while (*vp) {
if (*vp == this) {
*vp = _link;
break;
}
vp = &((*vp)->_link);
}
// If we are destroying a group, remove all its variables from the list
//
if (has_flags(k_flag_is_group)) {
// Scan the list and remove any variable that has this as its group
vp = &_grouped_variables;
while (*vp) {
// Does the variable claim us as its group?
if ((*vp)->_group == this) {
*vp = (*vp)->_link;
continue;
}
vp = &((*vp)->_link);
}
}
}
// Copy the variable's whole name to the supplied buffer.
//
// If the variable is a group member, prepend the group name.
//
void AP_Var::copy_name(char *buffer, size_t buffer_size) const
{
buffer[0] = '\0';
if (_group)
_group->copy_name(buffer, buffer_size);
strlcat_P(buffer, _name, buffer_size);
}
// Save the variable to EEPROM, if supported
//
bool AP_Var::save(void)
{
uint8_t vbuf[k_size_max];
size_t size;
// if the variable is a group member, save the group
if (_group) {
return _group->save();
}
// locate the variable in EEPROM, allocating space as required
if (!_EEPROM_locate(true)) {
return false;
}
// serialize the variable into the buffer and work out how big it is
size = serialize(vbuf, sizeof(vbuf));
// if it fit in the buffer, save it to EEPROM
if (size <= sizeof(vbuf)) {
eeprom_write_block(vbuf, (void *)_key, size);
}
return true;
}
// Load the variable from EEPROM, if supported
//
bool AP_Var::load(void)
{
uint8_t vbuf[k_size_max];
size_t size;
// if the variable is a group member, load the group
if (_group) {
return _group->load();
}
// locate the variable in EEPROM, but do not allocate space
if (!_EEPROM_locate(false)) {
return false;
}
// ask the unserializer how big the variable is
//
// XXX should check size in EEPROM var header too...
//
size = unserialize(NULL, 0);
// Read the buffer from EEPROM, now that _EEPROM_locate
// has converted _key into an EEPROM address.
//
if (size <= sizeof(vbuf)) {
eeprom_read_block(vbuf, (void *)_key, size);
unserialize(vbuf, size);
}
return true;
}
// Save all variables that don't opt out.
//
//
bool AP_Var::save_all(void)
{
bool result = true;
AP_Var *p = _variables;
while (p) {
if (!p->has_flags(k_flag_no_auto_load) && // not opted out
!(p->_group)) { // not saved with a group
if (!p->save()) {
result = false;
}
}
p = p->_link;
}
return result;
}
// Load all variables that don't opt out.
//
bool AP_Var::load_all(void)
{
bool result;
AP_Var *p = _variables;
while (p) {
if (!p->has_flags(k_flag_no_auto_load) && // not opted out
!(p->_group)) { // not loaded with a group
if (!p->load()) {
result = false;
}
}
p = p->_link;
}
return result;
}
// Erase all variables in EEPROM.
//
void
AP_Var::erase_all()
{
uint8_t c;
// overwrite the first byte of the header, invalidating the EEPROM
//
c = 0xff;
eeprom_write_byte(&c, 0);
}
// Return the key for a variable.
//
AP_Var::Key
AP_Var::key(void)
{
Var_header var_header;
if (_group) { // group members don't have keys
return k_key_none;
}
if (_key && k_key_not_located) { // if not located, key is in memory
return _key & k_key_mask;
}
// read key from EEPROM
eeprom_read_block(&var_header, (void *)_key, sizeof(var_header));
return var_header.key;
}
// Return the next variable in the global list.
//
AP_Var *
AP_Var::next(void)
{
// If there is a variable after this one, return it.
//
if (_link)
return _link;
// If we are at the end of the _variables list, _group will be NULL; in that
// case, move to the _grouped_variables list.
//
if (!_group) {
return _grouped_variables;
}
// We must be at the end of the _grouped_variables list, nothing remains.
//
return NULL;
}
// Return the first variable that is a member of the group.
//
AP_Var *
AP_Var::first_member(AP_Var_group *group)
{
AP_Var **vp;
vp = &_grouped_variables;
while (*vp) {
if ((*vp)->_group == group) {
return *vp;
}
vp = &((*vp)->_link);
}
return NULL;
}
// Return the next variable that is a member of the same group.
AP_Var *
AP_Var::next_member()
{
AP_Var *vp;
vp = _link;
while (vp) {
if (vp->_group == _group) {
return vp;
}
vp = vp->_link;
}
return NULL;
}
// Scan the EEPROM and assign addresses to all the variables that
// are known and found therein.
//
bool AP_Var::_EEPROM_scan(void)
{
struct EEPROM_header ee_header;
struct Var_header var_header;
AP_Var *vp;
// assume that the EEPROM is empty
_tail_sentinel = 0;
// read the header and validate
eeprom_read_block(0, &ee_header, sizeof(ee_header));
if ((ee_header.magic != k_EEPROM_magic) ||
(ee_header.revision != k_EEPROM_revision))
return false;
// scan the EEPROM
//
// Avoid trying to read a header when there isn't enough space left.
//
_tail_sentinel = sizeof(ee_header);
while (_tail_sentinel < (k_EEPROM_size - sizeof(var_header) - 1)) {
// read a variable header
eeprom_read_block(&var_header, (void *)_tail_sentinel, sizeof(var_header));
// if the header is for the sentinel, scanning is complete
if (var_header.key == k_key_sentinel)
break;
// if the variable plus the sentinel would extend past the end of EEPROM, we are done
if (k_EEPROM_size <= (
_tail_sentinel + // current position
sizeof(ee_header) + // header for this variable
var_header.size + 1 + // data for this variable
sizeof(ee_header))) { // header for sentinel
break;
}
// look for a variable with this key
vp = _variables;
while (vp) {
if (vp->key() == var_header.key) {
// adjust the variable's key to point to this entry
vp->_key = _tail_sentinel;
break;
}
vp = vp->_link;
}
// move to the next variable header
_tail_sentinel += sizeof(var_header) + var_header.size + 1;
}
// Scanning is complete
_EEPROM_scanned = true;
return true;
}
// Locate a variable in EEPROM, allocating space if required.
//
bool AP_Var::_EEPROM_locate(bool allocate)
{
Var_header var_header;
Key new_location;
size_t size;
// Is it a group member, or does it have a no-location key?
//
if (_group || (_key == k_key_none)) {
return false; // it is/does, and thus it has no location
}
// Has the variable already been located?
//
if (!(_key & k_key_not_located)) {
return true; // it has
}
// If the EEPROM has not been scanned, try that now.
//
if (!_EEPROM_scanned) {
_EEPROM_scan();
}
// If not located and not permitted to allocate, we have failed.
//
if ((_key & k_key_not_located) && !allocate) {
return false;
}
// Ask the serializer for the size of the thing we are allocating, and fail
// if it is too large or if it has no size, as we will not be able to allocate
// space for it.
//
size = serialize(NULL, 0);
if ((size == 0) || (size > k_size_max))
return false;
// Make sure there will be space in the EEPROM for the variable, its
// header and the new tail sentinel.
//
if ((_tail_sentinel + size + sizeof(Var_header) * 2) > k_EEPROM_size)
return false;
// If there is no data in the EEPROM, write the header and move the
// sentinel.
//
if (0 == _tail_sentinel) {
EEPROM_header ee_header;
ee_header.magic = k_EEPROM_magic;
ee_header.revision = k_EEPROM_revision;
ee_header.spare = 0;
eeprom_write_block(0, &ee_header, sizeof(ee_header));
_tail_sentinel = sizeof(ee_header);
}
// Save the location we are going to insert at, and compute the new
// tail sentinel location.
//
_tail_sentinel += sizeof(Var_header) + size;
new_location = _tail_sentinel;
// Write the new sentinel first. If we are interrupted during this operation
// the old sentinel will still correctly terminate the EEPROM image.
//
var_header.key = k_key_sentinel;
var_header.size = 0;
eeprom_write_block(&var_header, (void *)_tail_sentinel, sizeof(var_header));
// Write the header for the block we have just located, claiming the EEPROM space.
//
var_header.key = key();
var_header.size = size - 1;
eeprom_write_block(&var_header, (void *)new_location, sizeof(Var_header));
// We have successfully allocated space and thus located the variable.
//
_key = new_location;
return true;
}
size_t
AP_Var_group::serialize(void *buf, size_t buf_size)
{
return _serialize_unserialize(buf, buf_size, true);
}
size_t
AP_Var_group::unserialize(void *buf, size_t buf_size)
{
return _serialize_unserialize(buf, buf_size, false);
}
size_t
AP_Var_group::_serialize_unserialize(void *buf, size_t buf_size, bool do_serialize)
{
AP_Var *vp;
uint8_t *bp;
size_t size, total_size, resid;
// Traverse the list of group members, serializing each in order
//
vp = first_member(this);
bp = (uint8_t *)buf;
resid = buf_size;
total_size = 0;
while (vp) {
// (un)serialise the group member
if (do_serialize) {
size = vp->serialize(bp, buf_size);
} else {
size = vp->unserialize(bp, buf_size);
}
// Account for the space that this variable consumes in the buffer
//
// We always count the total size, and we always advance the buffer pointer
// if there was room for the variable. This does mean that in the case where
// the buffer was too small for a variable in the middle of the group, that
// a smaller variable after it in the group may still be serialised into
// the buffer. Since that's a rare case it's not worth optimising for - in
// either case this function will return a size greater than the buffer size
// and the calling function will have to treat it as an error.
//
total_size += size;
if (size <= resid) {
// there was space for this one, account for it
resid -= size;
bp += size;
}
vp = vp->next_member();
}
return total_size;
}