mirror of
https://github.com/ArduPilot/ardupilot
synced 2025-01-05 23:48:31 -04:00
39fea200ca
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
523 lines
14 KiB
C++
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;
|
|
}
|
|
|