mirror of https://github.com/ArduPilot/ardupilot
Implement a pure data serialisation load/save model for AP_Var.
The format is a simple TLV format; a tag identifying the variable, a length for the variable, and then the actual data. This format could ultimately be extended to include waypoint/commands as well. This allows us to load and save groups of variables to EEPROM without having to have a static EEPROM map. Still needs unit tests and (probably) bugfixing. git-svn-id: https://arducopter.googlecode.com/svn/trunk@1522 f9c3cf11-9bcb-44bc-f272-b75c42450872
This commit is contained in:
parent
64d061a79f
commit
d53844c52d
|
@ -24,10 +24,14 @@
|
|||
#include <inttypes.h>
|
||||
|
||||
#include <avr/io.h> // for RAMEND
|
||||
///
|
||||
/// Basic meta-class from which other AP_* classes can derive.
|
||||
///
|
||||
/// Functions that form the public API to the metaclass are prefixed meta_.
|
||||
///
|
||||
/// Note that classes inheriting from AP_Meta_class *must* have a default
|
||||
/// constructor and destructor in order for meta_cast to work.
|
||||
///
|
||||
class AP_Meta_class
|
||||
{
|
||||
public:
|
||||
|
|
|
@ -22,16 +22,74 @@ AP_Float AP_Float_zero(0);
|
|||
AP_Var *AP_Var::_variables = NULL;
|
||||
AP_Var *AP_Var::_lookup_hint = NULL;
|
||||
int AP_Var::_lookup_hint_index = 0;
|
||||
uint16_t AP_Var::_tail_sentinel;
|
||||
bool AP_Var::_EEPROM_scanned;
|
||||
|
||||
AP_Var_group::AP_Var_group()
|
||||
{
|
||||
}
|
||||
|
||||
AP_Var_group::~AP_Var_group()
|
||||
{
|
||||
}
|
||||
|
||||
// Constructor
|
||||
//
|
||||
AP_Var::AP_Var(Address address, const prog_char *name, const AP_Var_scope *scope, Flags flags) :
|
||||
_address(address), _name(name), _scope(scope), _flags(flags)
|
||||
AP_Var::AP_Var(Key key, const prog_char *name, AP_Var_group *group, Flags flags) :
|
||||
_group(group),
|
||||
_key(key | k_not_located),
|
||||
_name(name),
|
||||
_flags(flags)
|
||||
{
|
||||
// link the variable into the list of known variables
|
||||
_link = _variables;
|
||||
_variables = this;
|
||||
AP_Var *vp;
|
||||
|
||||
// Insert the variable or group into the list of known variables.
|
||||
//
|
||||
// Variables belonging to a group are inserted into the list following the group,
|
||||
// which may not itself yet be in the global list. Thus groups must be
|
||||
// statically constructed (which guarantees _link will be zero due to the BSS
|
||||
// being cleared).
|
||||
//
|
||||
|
||||
if (group) {
|
||||
|
||||
// Sort the variable into the list of variables following the group itself.
|
||||
vp = group;
|
||||
|
||||
for (;;) {
|
||||
// if there are no more entries, we insert at the end
|
||||
if (vp->_link == NULL)
|
||||
break;
|
||||
|
||||
// if the next entry in the list is a group, insert before it
|
||||
if (meta_type_equivalent(this, vp->_link))
|
||||
break;
|
||||
|
||||
// if the next entry has a higher index, insert before it
|
||||
if ((vp->_link->_key & k_key_mask) > key)
|
||||
break;
|
||||
vp = vp->_link;
|
||||
}
|
||||
|
||||
// insert into the group's list
|
||||
_link = vp->_link;
|
||||
vp->_link = this;
|
||||
|
||||
} else {
|
||||
// Insert directly at the head of the list. Take into account the possibility
|
||||
// that what is being inserted is a group that already has variables sorted after it.
|
||||
//
|
||||
vp = this;
|
||||
if (meta_cast<AP_Var_group>(this) != NULL) {
|
||||
// we are inserting a group, scan to the end of any list of pre-attached variables
|
||||
while (vp->_link != NULL)
|
||||
vp = vp->_link;
|
||||
}
|
||||
|
||||
// insert at the head of the global list
|
||||
vp->_link = _variables;
|
||||
_variables = this;
|
||||
}
|
||||
|
||||
// reset the lookup cache
|
||||
_lookup_hint_index = 0;
|
||||
|
@ -43,116 +101,104 @@ AP_Var::AP_Var(Address address, const prog_char *name, const AP_Var_scope *scope
|
|||
//
|
||||
AP_Var::~AP_Var(void)
|
||||
{
|
||||
// if we are at the head of the list - for variables
|
||||
// recently constructed this is usually the case
|
||||
if (_variables == this) {
|
||||
// remove us from the head of the list
|
||||
_variables = _link;
|
||||
AP_Var **vp;
|
||||
|
||||
} else {
|
||||
// traverse the list looking for the entry that points to us
|
||||
AP_Var *p = _variables;
|
||||
// Groups can only be destroyed when they have no members.
|
||||
//
|
||||
// If this is a group with one or more members, _link is not NULL
|
||||
// and _link->group is this.
|
||||
//
|
||||
if ((_link != NULL) && (_link->_group == this))
|
||||
return;
|
||||
|
||||
while (p) {
|
||||
// is it pointing at us?
|
||||
if (p->_link == this) {
|
||||
// make it point at what we point at, and done
|
||||
p->_link = _link;
|
||||
break;
|
||||
}
|
||||
// try the next one
|
||||
p = p->_link;
|
||||
// Walk the list and remove this when we find it.
|
||||
//
|
||||
vp = &_variables;
|
||||
while (*vp != NULL) {
|
||||
// pointer pointing at this?
|
||||
if (*vp == this) {
|
||||
*vp = _link;
|
||||
break;
|
||||
}
|
||||
|
||||
// address of next entry's link pointer
|
||||
vp = &((*vp)->_link);
|
||||
}
|
||||
|
||||
// reset the lookup cache
|
||||
_lookup_hint_index = 0;
|
||||
}
|
||||
|
||||
void AP_Var::set_default(void)
|
||||
{
|
||||
// Default implementation of ::set_default does nothing
|
||||
}
|
||||
|
||||
// Copy the variable name to the provided buffer.
|
||||
// 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 (_scope) {
|
||||
_scope->copy_name(buffer, buffer_size);
|
||||
}
|
||||
if (_group)
|
||||
_group->copy_name(buffer, buffer_size);
|
||||
strlcat_P(buffer, _name, buffer_size);
|
||||
}
|
||||
|
||||
// Compute any offsets that should be applied to a variable in this scope.
|
||||
//
|
||||
// Note that this depends on the default _address value for scopes being zero.
|
||||
//
|
||||
AP_Var::Address AP_Var_scope::get_address(void) const
|
||||
{
|
||||
AP_Var::Address addr = _address;
|
||||
|
||||
if (_parent) {
|
||||
addr += _parent->get_address();
|
||||
}
|
||||
|
||||
return addr;
|
||||
}
|
||||
|
||||
// Compute the address in EEPROM where the variable is stored
|
||||
//
|
||||
AP_Var::Address AP_Var::_get_address(void) const
|
||||
{
|
||||
Address addr = _address;
|
||||
|
||||
// if we have an address at all
|
||||
if ((addr != k_no_address) && _scope) {
|
||||
addr += _scope->get_address();
|
||||
}
|
||||
|
||||
return addr;
|
||||
}
|
||||
|
||||
// Save the variable to EEPROM, if supported
|
||||
//
|
||||
void AP_Var::save(void) const
|
||||
bool AP_Var::save(void)
|
||||
{
|
||||
Address addr = _get_address();
|
||||
uint8_t vbuf[k_max_size];
|
||||
size_t size;
|
||||
|
||||
if (addr != k_no_address) {
|
||||
uint8_t vbuf[k_max_size];
|
||||
size_t size;
|
||||
|
||||
// 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 *)addr, 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
|
||||
//
|
||||
void AP_Var::load(void)
|
||||
bool AP_Var::load(void)
|
||||
{
|
||||
Address addr = _get_address();
|
||||
uint8_t vbuf[k_max_size];
|
||||
size_t size;
|
||||
|
||||
if (addr != k_no_address) {
|
||||
uint8_t vbuf[k_max_size];
|
||||
size_t size;
|
||||
|
||||
// ask the unserializer how big the variable is
|
||||
size = unserialize(NULL, 0);
|
||||
|
||||
// read the buffer from EEPROM
|
||||
if (size <= sizeof(vbuf)) {
|
||||
eeprom_read_block(vbuf, (void *)addr, size);
|
||||
unserialize(vbuf, 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;
|
||||
}
|
||||
|
||||
//
|
||||
|
@ -160,25 +206,26 @@ void AP_Var::load(void)
|
|||
//
|
||||
|
||||
AP_Var *
|
||||
AP_Var::lookup(int index)
|
||||
AP_Var::lookup_by_index(int index)
|
||||
{
|
||||
AP_Var *p;
|
||||
int i;
|
||||
|
||||
// establish initial search state
|
||||
if (_lookup_hint_index && // we have a cached hint
|
||||
(index >= _lookup_hint_index)) { // the desired index is at or after the hint
|
||||
//
|
||||
if (_lookup_hint_index && // we have a cached hint (cannot use a hint index of zero)
|
||||
(index >= _lookup_hint_index)) { // the desired index is at or after the hint
|
||||
|
||||
p = _lookup_hint; // start at the hint point
|
||||
i = index - _lookup_hint_index; // count only the distance from the hint to the index
|
||||
p = _lookup_hint; // start at the hint point
|
||||
i = index - _lookup_hint_index; // count only the distance from the hint to the index
|
||||
} else {
|
||||
|
||||
p = _variables; // start at the beginning of the list
|
||||
i = index; // count to the index
|
||||
p = _variables; // start at the beginning of the list
|
||||
i = index; // count to the index
|
||||
}
|
||||
|
||||
// search
|
||||
while (i-- && p) { // count until we hit the index or the end of the list
|
||||
while (p && i--) { // count until we hit the index or the end of the list
|
||||
p = p->_link;
|
||||
}
|
||||
|
||||
|
@ -187,34 +234,211 @@ AP_Var::lookup(int index)
|
|||
_lookup_hint_index = index;
|
||||
_lookup_hint = p;
|
||||
}
|
||||
|
||||
return (p);
|
||||
return p;
|
||||
}
|
||||
|
||||
// Save all variables that have an identity.
|
||||
// Save all variables that don't opt out.
|
||||
//
|
||||
void AP_Var::save_all(void)
|
||||
//
|
||||
bool AP_Var::save_all(void)
|
||||
{
|
||||
bool result = true;
|
||||
AP_Var *p = _variables;
|
||||
|
||||
while (p) {
|
||||
if (!p->has_flags(k_no_auto_load)) {
|
||||
p->save();
|
||||
if (!p->has_flags(k_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 have an identity.
|
||||
// Load all variables that don't opt out.
|
||||
//
|
||||
void AP_Var::load_all(void)
|
||||
bool AP_Var::load_all(void)
|
||||
{
|
||||
bool result;
|
||||
AP_Var *p = _variables;
|
||||
|
||||
while (p) {
|
||||
if (!p->has_flags(k_no_auto_load)) {
|
||||
p->load();
|
||||
if (!p->has_flags(k_no_auto_load) && // not opted out
|
||||
!(p->_group)) { // not loaded with a group
|
||||
|
||||
if (!p->load()) {
|
||||
result = false;
|
||||
}
|
||||
}
|
||||
p = p->_link;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// Scan the list of variables for a matching key.
|
||||
//
|
||||
AP_Var *
|
||||
AP_Var::_lookup_by_key(Key key)
|
||||
{
|
||||
AP_Var *p;
|
||||
Key nl_key;
|
||||
Var_header var_header;
|
||||
|
||||
nl_key = key | k_not_located; // key to expect in memory
|
||||
|
||||
// scan the list of variables
|
||||
p = _variables;
|
||||
while (p) {
|
||||
|
||||
// if the variable is a group member, it cannot be found by key search
|
||||
if (p->_group) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// has this variable been located?
|
||||
if (p->_key & k_not_located) {
|
||||
// does the variable have the non-located form of the key?
|
||||
if (p->_key == nl_key)
|
||||
return p; // found it
|
||||
} else {
|
||||
// read the header from EEPROM and compare it with the search key
|
||||
eeprom_read_block(&var_header, (void *)p->_key, sizeof(var_header));
|
||||
if (var_header.key == key)
|
||||
return p;
|
||||
}
|
||||
|
||||
// try the next variable
|
||||
p = p->_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_tail_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 = _lookup_by_key(var_header.key);
|
||||
if (vp) {
|
||||
// adjust the variable's key to point to this entry
|
||||
vp->_key = _tail_sentinel;
|
||||
}
|
||||
|
||||
// move to the next variable header
|
||||
_tail_sentinel += sizeof(var_header) + var_header.size + 1;
|
||||
}
|
||||
_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;
|
||||
size_t size;
|
||||
|
||||
// Has the variable already been located?
|
||||
if (!(_key & k_not_located)) {
|
||||
return true; // it has
|
||||
}
|
||||
|
||||
// Does it have a no-key key?
|
||||
if (_key == k_no_key) {
|
||||
return false; // it does, and thus it has no location
|
||||
}
|
||||
|
||||
// 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_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
|
||||
size = serialize(NULL, 0);
|
||||
if ((0 == size) || (size > k_max_size))
|
||||
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);
|
||||
}
|
||||
|
||||
// Write a new sentinel first
|
||||
var_header.key = k_tail_sentinel;
|
||||
var_header.size = 0;
|
||||
eeprom_write_block(&var_header, (void *)(_tail_sentinel + sizeof(Var_header) + size), sizeof(var_header));
|
||||
|
||||
// Write the header for the block we have just located
|
||||
var_header.key = _key & k_key_mask;
|
||||
var_header.size = size - 1;
|
||||
eeprom_write_block(&var_header, (void *)_tail_sentinel, sizeof(Var_header));
|
||||
|
||||
// Save the located address for the variable
|
||||
_key = _tail_sentinel + sizeof(Var_header);
|
||||
|
||||
// Update to the new tail sentinel
|
||||
_tail_sentinel += sizeof(Var_header) + size;
|
||||
|
||||
// We have successfully allocated space and thus located the variable
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -27,7 +27,7 @@
|
|||
|
||||
#include "AP_MetaClass.h"
|
||||
|
||||
class AP_Var_scope;
|
||||
class AP_Var_group;
|
||||
|
||||
/// Base class for variables.
|
||||
///
|
||||
|
@ -36,50 +36,111 @@ class AP_Var_scope;
|
|||
class AP_Var : public AP_Meta_class
|
||||
{
|
||||
public:
|
||||
/// Storage address for variables that can be saved to EEPROM
|
||||
/// EEPROM header
|
||||
///
|
||||
/// If the variable is contained within a scope, then the address
|
||||
/// is relative to the scope.
|
||||
/// This structure is placed at the head of the EEPROM to indicate
|
||||
/// that the ROM is formatted for AP_Var.
|
||||
///
|
||||
/// @todo This might be used as a token for mass serialisation,
|
||||
/// but for now it's just the address of the variable's backing
|
||||
/// store in EEPROM.
|
||||
/// The EEPROM may thus be cheaply erased by overwriting just one
|
||||
/// byte of the header magic.
|
||||
///
|
||||
typedef uint16_t Address;
|
||||
struct EEPROM_header {
|
||||
uint16_t magic;
|
||||
uint8_t revision;
|
||||
uint8_t spare;
|
||||
};
|
||||
|
||||
/// An address value that indicates that a variable is not to be saved to EEPROM.
|
||||
static const uint16_t k_EEPROM_magic = 0x5041; ///< "AP"
|
||||
static const uint16_t k_EEPROM_revision = 1; ///< current format revision
|
||||
|
||||
/// Storage key for variables that are saved in EEPROM.
|
||||
///
|
||||
/// The key is used to associate a known variable in memory
|
||||
/// with storage for the variable in the EEPROM. When the
|
||||
/// variable's storage is located in EEPROM, the key value is
|
||||
/// replaced with an EEPROM address.
|
||||
///
|
||||
/// When the variable is saved to EEPROM, a header is attached
|
||||
/// which contains the key along with other details.
|
||||
///
|
||||
typedef uint16_t Key;
|
||||
|
||||
/// The header prepended to a variable stored in EEPROM
|
||||
///
|
||||
struct Var_header {
|
||||
/// The size of the variable, minus one.
|
||||
/// This allows a variable to be anything from one to 32 bytes long.
|
||||
///
|
||||
uint16_t size:5;
|
||||
|
||||
/// The key assigned to the variable.
|
||||
///
|
||||
uint16_t key:8;
|
||||
|
||||
/// Spare bits, currently unused
|
||||
///
|
||||
/// @todo One could be a parity bit?
|
||||
///
|
||||
uint16_t spare:3;
|
||||
};
|
||||
|
||||
/// An key that indicates that a variable is not to be saved to EEPROM.
|
||||
///
|
||||
/// As the value has all bits set to 1, it's not a legal EEPROM address for any
|
||||
/// EEPROM smaller than 64K.
|
||||
/// EEPROM smaller than 64K (and it's too big to fit the Var_header::key field).
|
||||
///
|
||||
/// This value is normally the default.
|
||||
///
|
||||
static const Address k_no_address = ~(Address)0;
|
||||
static const Key k_no_key = 0xffff;
|
||||
|
||||
/// A key that has this bit set is still a key; if it has been cleared then
|
||||
/// the key's value is actually an address in EEPROM.
|
||||
///
|
||||
static const Key k_not_located = (Key)1 << 15;
|
||||
|
||||
/// Key assigned to the terminal entry in EEPROM.
|
||||
///
|
||||
static const Key k_tail_sentinel = 0xff;
|
||||
|
||||
/// A bitmask that removes any control bits from a key giving just the
|
||||
/// value.
|
||||
///
|
||||
static const Key k_key_mask = ~(k_not_located);
|
||||
|
||||
/// The largest variable that will be saved to EEPROM.
|
||||
/// This affects the amount of stack space that is required by the ::save, ::load,
|
||||
/// ::save_all and ::load_all functions.
|
||||
/// ::save_all and ::load_all functions. It should match the maximum size that can
|
||||
/// be encoded in the Var_header::size field.
|
||||
///
|
||||
static const size_t k_max_size = 16;
|
||||
static const size_t k_max_size = 32;
|
||||
|
||||
/// Optional flags affecting the behavior and usage of the variable.
|
||||
///
|
||||
enum Flags {
|
||||
k_no_flags = 0,
|
||||
k_no_auto_load = (1 << 0), ///< will not be loaded by ::load_all or saved by ::save_all
|
||||
k_no_import = (1 << 1) ///< new values must not be imported from e.g. a GCS
|
||||
|
||||
/// The variable will not be loaded by ::load_all or saved by ::save_all, but it
|
||||
/// has a key and can be loaded/saved manually.
|
||||
k_no_auto_load = (1 << 0),
|
||||
|
||||
/// This flag is advisory; it indicates that the variable's value should not be
|
||||
/// imported from outside, e.g. from a GCS.
|
||||
k_no_import = (1 << 1)
|
||||
};
|
||||
|
||||
AP_Var() {}
|
||||
|
||||
/// Constructor
|
||||
///
|
||||
/// @param key The storage key to be associated with this variable.
|
||||
/// @param name An optional name by which the variable may be known.
|
||||
/// This name may be looked up via the ::lookup function.
|
||||
/// @param scope An optional scope that the variable may be a contained within.
|
||||
/// @param group An optional group to which the variable may belong.
|
||||
/// The scope's name will be prepended to the variable name
|
||||
/// by ::copy_name.
|
||||
/// @param flags Optional flags which control how the variable behaves.
|
||||
///
|
||||
AP_Var(Address address = k_no_address, const prog_char *name = NULL, const AP_Var_scope *scope = NULL,
|
||||
Flags flags = k_no_flags);
|
||||
AP_Var(Key key, const prog_char *name, AP_Var_group *group, Flags flags);
|
||||
|
||||
/// Destructor
|
||||
///
|
||||
|
@ -91,10 +152,6 @@ public:
|
|||
///
|
||||
~AP_Var(void);
|
||||
|
||||
/// Set the variable to its default value
|
||||
///
|
||||
virtual void set_default(void);
|
||||
|
||||
/// Copy the variable's name, prefixed by any parent class names, to a buffer.
|
||||
///
|
||||
/// If the variable has no name, the buffer will contain an empty string.
|
||||
|
@ -109,122 +166,184 @@ public:
|
|||
|
||||
/// Return a pointer to the n'th known variable.
|
||||
///
|
||||
/// This function is used to iterate the set of variables that are considered
|
||||
/// interesting; i.e. those that may be saved to EEPROM, or that have a name.
|
||||
/// This function is used to iterate the set of variables, and is optimised
|
||||
/// for the case where the index argument is increased sequentially from one
|
||||
/// call to the next.
|
||||
///
|
||||
/// Note that variable index numbers are not constant, they depend on the
|
||||
/// the static construction order.
|
||||
/// Note that variable index numbers are not constant; they will vary
|
||||
/// from build to build.
|
||||
///
|
||||
/// @param index enumerator for the variable to be returned
|
||||
/// @param index Enumerator for the variable to be returned
|
||||
/// @return Pointer to the index'th variable, or NULL if
|
||||
/// the set of variables has been exhausted.
|
||||
///
|
||||
static AP_Var *lookup(int index);
|
||||
static AP_Var *lookup_by_index(int index);
|
||||
|
||||
/// Save the current value of the variable to EEPROM.
|
||||
///
|
||||
/// This interface works for any subclass that implements
|
||||
/// serialize.
|
||||
/// AP_Meta_class::serialize.
|
||||
///
|
||||
void save(void) const;
|
||||
/// Note that invoking this method on a variable that is a group
|
||||
/// member will cause the entire group to be saved.
|
||||
///
|
||||
/// @return True if the variable was saved successfully.
|
||||
///
|
||||
bool save(void);
|
||||
|
||||
/// Load the variable from EEPROM.
|
||||
///
|
||||
/// This interface works for any subclass that implements
|
||||
/// unserialize.
|
||||
/// AP_Meta_class::unserialize.
|
||||
///
|
||||
void load(void);
|
||||
/// If the variable has not previously been saved to EEPROM, this
|
||||
/// routine will return failure.
|
||||
///
|
||||
/// Note that invoking this method on a variable that is a group
|
||||
/// member will cause the entire group to be loaded.
|
||||
///
|
||||
/// @return True if the variable was loaded successfully.
|
||||
///
|
||||
bool load(void);
|
||||
|
||||
/// Save all variables to EEPROM
|
||||
///
|
||||
static void save_all(void);
|
||||
/// This routine performs a best-efforts attempt to save all
|
||||
/// of the variables to EEPROM. If some fail to save, the others
|
||||
/// that succeed will still all be saved.
|
||||
///
|
||||
/// @return False if any variable failed to save.
|
||||
///
|
||||
static bool save_all(void);
|
||||
|
||||
/// Load all variables from EEPROM
|
||||
///
|
||||
static void load_all(void);
|
||||
/// This function performs a best-efforts attempt to load all
|
||||
/// of the variables from EEPROM. If some fail to load, their
|
||||
/// values will remain as they are.
|
||||
///
|
||||
/// @return False if any variable failed to load. Note
|
||||
/// That this may be caused by a variable not having
|
||||
/// previously been saved.
|
||||
///
|
||||
static bool load_all(void);
|
||||
|
||||
/// Test for flags that may be set.
|
||||
///
|
||||
/// @param flagval Flag or flags to be tested
|
||||
/// @returns True if all of the bits in flagval are set in the flags.
|
||||
/// @return True if all of the bits in flagval are set in the flags.
|
||||
///
|
||||
bool has_flags(Flags flagval) const {
|
||||
return (_flags & flagval) == flagval;
|
||||
}
|
||||
|
||||
private:
|
||||
const Address _address;
|
||||
const prog_char *_name;
|
||||
const AP_Var_scope *const _scope;
|
||||
AP_Var *_link;
|
||||
uint8_t _flags;
|
||||
|
||||
/// Do the arithmetic required to compute the variable's address in EEPROM
|
||||
///
|
||||
/// @returns The address at which the variable is stored in EEPROM,
|
||||
/// or k_no_address if it is not saved.
|
||||
///
|
||||
Address _get_address(void) const;
|
||||
AP_Var_group *_group; ///< Group that the variable may be a member of
|
||||
Key _key; ///< storage key
|
||||
const prog_char *_name; ///< name known to external agents (GCS, etc.)
|
||||
AP_Var *_link; ///< linked list pointer to next variable
|
||||
uint8_t _flags; ///< flag bits
|
||||
|
||||
// static state used by ::lookup
|
||||
static AP_Var *_variables;
|
||||
static AP_Var *_lookup_hint; /// pointer to the last variable that was looked up by ::lookup
|
||||
static int _lookup_hint_index; /// index of the last variable that was looked up by ::lookup
|
||||
static AP_Var *_variables; ///< linked list of all variables
|
||||
static AP_Var *_lookup_hint; ///< pointer to the last variable that was looked up by ::lookup
|
||||
static int _lookup_hint_index; ///< index of the last variable that was looked up by ::lookup
|
||||
|
||||
|
||||
// EEPROM space allocation and scanning
|
||||
static uint16_t _tail_sentinel; ///< EEPROM address of the tail sentinel
|
||||
static bool _EEPROM_scanned; ///< true once the EEPROM has been scanned and addresses assigned
|
||||
|
||||
static const uint16_t k_EEPROM_size = 4096; ///< XXX avr-libc doesn't consistently export this
|
||||
|
||||
|
||||
/// Return a pointer to the variable with a given key.
|
||||
///
|
||||
/// This function is used to search the set of variables for
|
||||
/// one with a given key.
|
||||
///
|
||||
/// Note that this search will fail (XXX should it?) if the variable
|
||||
/// assigned this key has been located in EEPROM.
|
||||
///
|
||||
/// @param key The key to search for.
|
||||
/// @return Pointer to the variable assigned the key,
|
||||
/// or NULL if no variable owns up to it.
|
||||
///
|
||||
static AP_Var *_lookup_by_key(Key key);
|
||||
|
||||
/// Scan the EEPROM and assign addresses to any known variables
|
||||
/// that have entries there.
|
||||
///
|
||||
/// @return True if the EEPROM was scanned successfully.
|
||||
///
|
||||
static bool _EEPROM_scan(void);
|
||||
|
||||
/// Locate this variable in the EEPROM.
|
||||
///
|
||||
/// @param allocate If true, and the variable does not already have
|
||||
/// space allocated in EEPROM, allocate it.
|
||||
/// @return True if the _key field is a valid EEPROM address with
|
||||
/// space reserved for the variable to be saved. False
|
||||
/// if the variable does not have a key, or space could not
|
||||
/// be allocated and the variable does not already exist in
|
||||
/// EEPROM.
|
||||
///
|
||||
bool _EEPROM_locate(bool allocate);
|
||||
|
||||
};
|
||||
|
||||
/// Nestable scopes for variable names.
|
||||
/// Variable groups.
|
||||
///
|
||||
/// This provides a mechanism for scoping variable names and their
|
||||
/// EEPROM addresses.
|
||||
/// Grouped variables are treated as a single variable when loaded from
|
||||
/// or saved to EEPROM. The size limits for variables apply to the entire
|
||||
/// group; thus a group cannot be larger than the largest legal variable.
|
||||
///
|
||||
/// When AP_Var is asked for the name of a variable, it will
|
||||
/// prepend the names of all enclosing scopes. This provides a way
|
||||
/// of grouping variables and saving memory when many share a large
|
||||
/// common prefix.
|
||||
/// When AP_Var is asked for the name of a variable that is a member
|
||||
/// of a group, it will prepend the name of the group; this helps save
|
||||
/// memory.
|
||||
///
|
||||
/// When AP_var computes the address of a variable, it will take
|
||||
/// into account the address offsets of each of the variable's
|
||||
/// enclosing scopes.
|
||||
/// Variables belonging to a group are always sorted into the global
|
||||
/// variable list after the group.
|
||||
///
|
||||
class AP_Var_scope
|
||||
class AP_Var_group : public AP_Var
|
||||
{
|
||||
public:
|
||||
AP_Var_group();
|
||||
virtual ~AP_Var_group();
|
||||
|
||||
/// Constructor
|
||||
///
|
||||
/// @param name The name of the scope.
|
||||
/// @param address An EEPROM address offset to be added to the address assigned to
|
||||
/// any variables within the scope.
|
||||
/// @param parent Optional parent scope to nest within.
|
||||
/// @param key Storage key for the group.
|
||||
/// @param name An optional name prefix for members of the group.
|
||||
///
|
||||
AP_Var_scope(const prog_char *name, AP_Var::Address address = 0, AP_Var_scope *parent = NULL) :
|
||||
_name(name), _parent(parent), _address(address) {
|
||||
AP_Var_group(Key key, const prog_char *name = NULL) :
|
||||
AP_Var(key, name, NULL, k_no_flags)
|
||||
{
|
||||
}
|
||||
|
||||
/// Copy the scope name, prefixed by any parent scope names, to a buffer.
|
||||
/// Serialize the group.
|
||||
///
|
||||
/// Note that if the combination of names is larger than the buffer, the
|
||||
/// result in the buffer will be truncated.
|
||||
/// Iteratively serializes the entire group into the supplied buffer.
|
||||
///
|
||||
/// @param buffer The destination buffer
|
||||
/// @param bufferSize Total size of the destination buffer.
|
||||
/// @param buf Buffer into which serialized data should be placed.
|
||||
/// @param bufSize The size of the buffer provided.
|
||||
/// @return The size of the serialized data, even if that data would
|
||||
/// have overflowed the buffer.
|
||||
///
|
||||
void copy_name(char *buffer, size_t bufferSize) const {
|
||||
if (_parent) {
|
||||
_parent->copy_name(buffer, bufferSize);
|
||||
}
|
||||
strlcat_P(buffer, _name, bufferSize);
|
||||
}
|
||||
virtual size_t serialize(void *buf, size_t bufSize) const;
|
||||
|
||||
/// Compute the address offset that this and any parent scope might apply
|
||||
/// to variables inside the scope.
|
||||
/// Unserialize the group.
|
||||
///
|
||||
/// This provides a way for variables to be grouped into collections whose
|
||||
/// EEPROM addresses can be more easily managed.
|
||||
/// Iteratively unserializes the group from the supplied buffer.
|
||||
///
|
||||
AP_Var::Address get_address(void) const;
|
||||
/// @param buf Buffer containing serialized data.
|
||||
/// @param bufSize The size of the buffer.
|
||||
/// @return The number of bytes from the buffer that would be consumed
|
||||
/// unserializing the data. If the value is less than or equal
|
||||
/// to bufSize, unserialization was successful.
|
||||
///
|
||||
virtual size_t unserialize(void *buf, size_t bufSize);
|
||||
|
||||
private:
|
||||
const prog_char *_name; /// pointer to the scope name in program memory
|
||||
AP_Var_scope *_parent; /// pointer to a parent scope, if one exists
|
||||
AP_Var::Address _address; /// container base address, offsets contents
|
||||
};
|
||||
|
||||
/// Template class for scalar variables.
|
||||
|
@ -238,23 +357,45 @@ template<typename T>
|
|||
class AP_VarT : public AP_Var
|
||||
{
|
||||
public:
|
||||
/// Constructor
|
||||
/// Constructor for non-grouped variable.
|
||||
///
|
||||
/// @note Constructors for AP_Var are specialised so that they can
|
||||
/// pass the correct typeCode argument to the AP_Var ctor.
|
||||
/// Initialises a stand-alone variable with optional initial value, storage key, name and flags.
|
||||
///
|
||||
/// @param initialValue Value the variable should have at startup.
|
||||
/// @param identity A unique token used when saving the variable to EEPROM.
|
||||
/// Note that this token must be unique, and should only be
|
||||
/// changed for a variable if the EEPROM version is updated
|
||||
/// to prevent the accidental unserialisation of old data
|
||||
/// into the wrong variable.
|
||||
/// @param name An optional name by which the variable may be known.
|
||||
/// @param varClass An optional class that the variable may be a member of.
|
||||
/// @param default_value Value the variable should have at startup.
|
||||
/// @param key Storage key for the variable. If not set, or set to AP_Var::k_no_key
|
||||
/// the variable cannot be loaded from or saved to EEPROM.
|
||||
/// @param name An optional name by which the variable may be known.
|
||||
/// @param flags Optional flags that may affect the behaviour of the variable.
|
||||
///
|
||||
AP_VarT<T> (T default_value = 0, Address address = k_no_address, const prog_char *name = NULL,
|
||||
AP_Var_scope *scope = NULL, Flags flags = k_no_flags) :
|
||||
AP_Var(address, name, scope, flags), _value(default_value), _default_value(default_value) {
|
||||
AP_VarT<T> (T initial_value = 0,
|
||||
Key key = k_no_key,
|
||||
const prog_char *name = NULL,
|
||||
Flags flags = k_no_flags) :
|
||||
AP_Var(key, name, NULL, flags),
|
||||
_value(initial_value)
|
||||
{
|
||||
}
|
||||
|
||||
/// Constructor for grouped variable.
|
||||
///
|
||||
/// Initialises a variable that is a member of a group with optional initial value, name and flags.
|
||||
///
|
||||
/// @param group The group that this variable belongs to.
|
||||
/// @param index The variable's index within the group. Index values must be unique
|
||||
/// within the group, as they ensure that the group's layout in EEPROM
|
||||
/// is consistent.
|
||||
/// @param initial_value The value the variable takes at startup.
|
||||
/// @param name An optional name by which the variable may be known.
|
||||
/// @param flags Optional flags that may affect the behaviour of the variable.
|
||||
///
|
||||
AP_VarT<T> (AP_Var_group *group,
|
||||
Key index,
|
||||
T initial_value = 0,
|
||||
const prog_char *name = NULL,
|
||||
Flags flags = k_no_flags) :
|
||||
AP_Var(index, name, group, flags),
|
||||
_value(initial_value)
|
||||
{
|
||||
}
|
||||
|
||||
// serialize _value into the buffer, but only if it is big enough.
|
||||
|
@ -275,11 +416,6 @@ public:
|
|||
return sizeof(T);
|
||||
}
|
||||
|
||||
/// Restore the variable to its default value
|
||||
virtual void set_default(void) {
|
||||
_value = _default_value;
|
||||
}
|
||||
|
||||
/// Value getter
|
||||
///
|
||||
T get(void) const {
|
||||
|
@ -306,7 +442,8 @@ public:
|
|||
return v;
|
||||
}
|
||||
|
||||
/// Copy assignment from T
|
||||
/// Copy assignment from T is equivalent to ::set.
|
||||
///
|
||||
AP_VarT<T>& operator=(T v) {
|
||||
_value = v;
|
||||
return *this;
|
||||
|
@ -314,8 +451,8 @@ public:
|
|||
|
||||
protected:
|
||||
T _value;
|
||||
T _default_value;
|
||||
};
|
||||
|
||||
/// Convenience macro for defining instances of the AP_Var template
|
||||
///
|
||||
#define AP_VARDEF(_t, _n) typedef AP_VarT<_t> AP_##_n;
|
||||
|
@ -336,21 +473,32 @@ AP_VARDEF(int32_t, Int32); // defines AP_Int32
|
|||
/// to achieve this.
|
||||
///
|
||||
/// Note that any protocol transporting serialized data should be aware that the
|
||||
/// encoding used is effectively Q5.10 (one sign bit, 5 integer bits, 10 fractional bits).
|
||||
/// encoding used is effectively Q5.10 (one sign bit, 5 integer bits, 10 fractional bits),
|
||||
/// giving an effective range of approximately +/-31.999
|
||||
///
|
||||
class AP_Float16 : public AP_Float
|
||||
{
|
||||
public:
|
||||
/// Constructor mimics AP_Float::AP_Float()
|
||||
/// Constructors mimic AP_Float::AP_Float()
|
||||
///
|
||||
AP_Float16(float default_value = 0,
|
||||
Address address = k_no_address,
|
||||
AP_Float16(float initial_value = 0,
|
||||
Key key = k_no_key,
|
||||
const prog_char *name = NULL,
|
||||
AP_Var_scope *scope = NULL,
|
||||
Flags flags = k_no_flags) :
|
||||
AP_Float(default_value, address, name, scope, flags) {
|
||||
AP_Float(initial_value, key, name, flags)
|
||||
{
|
||||
}
|
||||
|
||||
AP_Float16(AP_Var_group *group,
|
||||
Key index,
|
||||
float initial_value = 0,
|
||||
const prog_char *name = NULL,
|
||||
Flags flags = k_no_flags) :
|
||||
AP_Float(group, index, initial_value, name, flags)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
// Serialize _value as Q5.10.
|
||||
//
|
||||
virtual size_t serialize(void *buf, size_t size) const {
|
||||
|
|
|
@ -112,6 +112,15 @@ setup(void)
|
|||
REQUIRE(AP_Float_negative_unity = -1.0);
|
||||
}
|
||||
|
||||
// AP_Var: initial value
|
||||
{
|
||||
TEST(var_initial_value);
|
||||
|
||||
AP_Float f(12.345);
|
||||
|
||||
REQUIRE(f == 12.345);
|
||||
}
|
||||
|
||||
// AP_Var: set, get, assignment
|
||||
{
|
||||
TEST(var_set_get);
|
||||
|
@ -126,19 +135,6 @@ setup(void)
|
|||
REQUIRE(f.get() == 10.0);
|
||||
}
|
||||
|
||||
// AP_Var: default value
|
||||
{
|
||||
TEST(var_default_value);
|
||||
|
||||
AP_Float f(10.0);
|
||||
|
||||
REQUIRE(f == 10.0);
|
||||
f = 0;
|
||||
REQUIRE(f == 0);
|
||||
f.set_default();
|
||||
REQUIRE(f == 10.0);
|
||||
}
|
||||
|
||||
// AP_Var: cast to type
|
||||
{
|
||||
TEST(var_cast_to_type);
|
||||
|
@ -169,7 +165,7 @@ setup(void)
|
|||
{
|
||||
TEST(var_naming);
|
||||
|
||||
AP_Float f(0, AP_Var::k_no_address, PSTR("test"));
|
||||
AP_Float f(0, AP_Var::k_no_key, PSTR("test"));
|
||||
char name_buffer[16];
|
||||
|
||||
f.copy_name(name_buffer, sizeof(name_buffer));
|
||||
|
@ -177,6 +173,7 @@ setup(void)
|
|||
}
|
||||
|
||||
// AP_Var: serialize
|
||||
// note that this presumes serialisation to the native in-memory format
|
||||
{
|
||||
TEST(var_serialize);
|
||||
|
||||
|
@ -202,12 +199,58 @@ setup(void)
|
|||
REQUIRE(f == 10);
|
||||
}
|
||||
|
||||
// AP_Var: enumeration
|
||||
// note that this test presumes the singly-linked list implementation of the list
|
||||
{
|
||||
TEST(var_enumeration);
|
||||
|
||||
AP_Var *v = AP_Var::lookup_by_index(0);
|
||||
|
||||
// test basic enumeration
|
||||
AP_Float f1(0, AP_Var::k_no_key, PSTR("test1"));
|
||||
REQUIRE(AP_Var::lookup_by_index(0) == &f1);
|
||||
REQUIRE(AP_Var::lookup_by_index(1) == v);
|
||||
|
||||
// test that new entries arrive in order
|
||||
{
|
||||
AP_Float f2(0, AP_Var::k_no_key, PSTR("test2"));
|
||||
REQUIRE(AP_Var::lookup_by_index(0) == &f2);
|
||||
REQUIRE(AP_Var::lookup_by_index(1) == &f1);
|
||||
REQUIRE(AP_Var::lookup_by_index(2) == v);
|
||||
|
||||
{
|
||||
AP_Float f3(0, AP_Var::k_no_key, PSTR("test3"));
|
||||
REQUIRE(AP_Var::lookup_by_index(0) == &f3);
|
||||
REQUIRE(AP_Var::lookup_by_index(1) == &f2);
|
||||
REQUIRE(AP_Var::lookup_by_index(2) == &f1);
|
||||
REQUIRE(AP_Var::lookup_by_index(3) == v);
|
||||
}
|
||||
}
|
||||
|
||||
// test that destruction removes from the list
|
||||
REQUIRE(AP_Var::lookup_by_index(0) == &f1);
|
||||
REQUIRE(AP_Var::lookup_by_index(1) == v);
|
||||
}
|
||||
|
||||
// AP_Var: group names
|
||||
{
|
||||
TEST(group_names);
|
||||
|
||||
AP_Var_group group(AP_Var::k_no_key, PSTR("group_"));
|
||||
AP_Float f(&group, 1, 1.0, PSTR("test"));
|
||||
char name_buffer[16];
|
||||
|
||||
f.copy_name(name_buffer, sizeof(name_buffer));
|
||||
REQUIRE(!strcmp(name_buffer, "group_test"));
|
||||
}
|
||||
|
||||
#if SAVE
|
||||
// AP_Var: load and save
|
||||
{
|
||||
TEST(var_load_save);
|
||||
|
||||
AP_Float f1(10, 4);
|
||||
AP_Float f2(0, 4);
|
||||
AP_Float f1(10, 4);
|
||||
AP_Float f2(0, 4);
|
||||
|
||||
f2.save();
|
||||
f2 = 1.0;
|
||||
|
@ -219,58 +262,13 @@ setup(void)
|
|||
REQUIRE(f2 == 10);
|
||||
}
|
||||
|
||||
// AP_Var: enumeration
|
||||
// note that this test presumes the singly-linked list implementation of the list
|
||||
// AP_Var: group load/save
|
||||
{
|
||||
TEST(var_enumeration);
|
||||
TEST(var_group_loadsave);
|
||||
|
||||
AP_Var *v = AP_Var::lookup(0);
|
||||
|
||||
// test basic enumeration
|
||||
AP_Float f1(0, AP_Var::k_no_address, PSTR("test1"));
|
||||
REQUIRE(AP_Var::lookup(0) == &f1);
|
||||
REQUIRE(AP_Var::lookup(1) == v);
|
||||
|
||||
// test that new entries arrive in order
|
||||
{
|
||||
AP_Float f2(0, AP_Var::k_no_address, PSTR("test2"));
|
||||
REQUIRE(AP_Var::lookup(0) == &f2);
|
||||
REQUIRE(AP_Var::lookup(1) == &f1);
|
||||
REQUIRE(AP_Var::lookup(2) == v);
|
||||
|
||||
{
|
||||
AP_Float f3(0, AP_Var::k_no_address, PSTR("test3"));
|
||||
REQUIRE(AP_Var::lookup(0) == &f3);
|
||||
REQUIRE(AP_Var::lookup(1) == &f2);
|
||||
REQUIRE(AP_Var::lookup(2) == &f1);
|
||||
REQUIRE(AP_Var::lookup(3) == v);
|
||||
}
|
||||
}
|
||||
|
||||
// test that destruction removes from the list
|
||||
REQUIRE(AP_Var::lookup(0) == &f1);
|
||||
REQUIRE(AP_Var::lookup(1) == v);
|
||||
}
|
||||
|
||||
// AP_Var: scope names
|
||||
{
|
||||
TEST(var_scope_names);
|
||||
|
||||
AP_Var_scope scope(PSTR("scope_"));
|
||||
AP_Float f(1.0, AP_Var::k_no_address, PSTR("test"), &scope);
|
||||
char name_buffer[16];
|
||||
|
||||
f.copy_name(name_buffer, sizeof(name_buffer));
|
||||
REQUIRE(!strcmp(name_buffer, "scope_test"));
|
||||
}
|
||||
|
||||
// AP_Var: scope address offsets
|
||||
{
|
||||
TEST(var_scope_addressing);
|
||||
|
||||
AP_Var_scope scope(PSTR("scope"), 4);
|
||||
AP_Var_group group(PSTR("group_"), 4);
|
||||
AP_Float f1(10.0, 8);
|
||||
AP_Float f2(1.0, 4, PSTR("var"), &scope);
|
||||
AP_Float f2(1.0, 4, PSTR("var"), &group);
|
||||
|
||||
f1.save();
|
||||
f1.load();
|
||||
|
@ -283,6 +281,7 @@ setup(void)
|
|||
f1.load();
|
||||
REQUIRE(f1 == 1);
|
||||
}
|
||||
#endif
|
||||
|
||||
// AP_Var: derived types
|
||||
{
|
||||
|
|
Loading…
Reference in New Issue