remove AP_Var: deprecated
This commit is contained in:
parent
488fb9750b
commit
925223341d
@ -1,824 +0,0 @@
|
||||
// -*- 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.
|
||||
//
|
||||
|
||||
/// @file AP_Var.cpp
|
||||
/// @brief The AP variable store.
|
||||
|
||||
#define USE_AP_VAR
|
||||
|
||||
#include <AP_Common.h>
|
||||
#include <AP_Var.h>
|
||||
#include <math.h>
|
||||
#include <string.h>
|
||||
|
||||
//#define ENABLE_FASTSERIAL_DEBUG
|
||||
|
||||
#ifdef ENABLE_FASTSERIAL_DEBUG
|
||||
# include <FastSerial.h>
|
||||
# define serialDebug(fmt, args...) if (FastSerial::getInitialized(0)) do {Serial.printf("%s:%d: " fmt "\n", __FUNCTION__, __LINE__ , ##args); delay(0); } while(0)
|
||||
#else
|
||||
# define serialDebug(fmt, args...)
|
||||
#endif
|
||||
|
||||
// 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;
|
||||
uint16_t AP_Var::_bytes_in_use;
|
||||
|
||||
// Constructor for standalone variables
|
||||
//
|
||||
AP_Var::AP_Var(Key p_key, const prog_char_t *name, Flags flags) :
|
||||
_group(NULL),
|
||||
_key(p_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 *pGroup, Key index, const prog_char_t *name, Flags flags) :
|
||||
_group(pGroup),
|
||||
_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;
|
||||
size_t loopCount = 0;
|
||||
while (*vp != NULL) {
|
||||
|
||||
if (loopCount++>k_num_max) return;
|
||||
|
||||
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
|
||||
|
||||
{
|
||||
size_t loopCount = 0;
|
||||
while (*vp) {
|
||||
|
||||
if (loopCount++>k_num_max) return;
|
||||
|
||||
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;
|
||||
size_t loopCount = 0;
|
||||
|
||||
while (*vp) {
|
||||
|
||||
if (loopCount++>k_num_max) return;
|
||||
|
||||
// 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 (_name) {
|
||||
if (_group)
|
||||
_group->copy_name(buffer, buffer_size);
|
||||
strlcat_P(buffer, _name, buffer_size);
|
||||
}
|
||||
}
|
||||
|
||||
// Find a variable by name.
|
||||
//
|
||||
AP_Var *
|
||||
AP_Var::find(const char *name)
|
||||
{
|
||||
AP_Var *vp;
|
||||
|
||||
size_t loopCount = 0;
|
||||
|
||||
for (vp = first(); vp; vp = vp->next()) {
|
||||
|
||||
if (loopCount++>k_num_max) return NULL;
|
||||
|
||||
char name_buffer[32];
|
||||
|
||||
// copy the variable's name into our scratch buffer
|
||||
vp->copy_name(name_buffer, sizeof(name_buffer));
|
||||
|
||||
// compare with the user-supplied name
|
||||
if (!strcmp(name, name_buffer)) {
|
||||
return vp;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Find a variable by key.
|
||||
//
|
||||
AP_Var *
|
||||
AP_Var::find(Key key)
|
||||
{
|
||||
AP_Var *vp;
|
||||
size_t loopCount = 0;
|
||||
for (vp = first(); vp; vp = vp->next()) {
|
||||
if (loopCount++>k_num_max) return NULL;
|
||||
if (key == vp->key()) {
|
||||
return vp;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
// 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();
|
||||
}
|
||||
|
||||
serialDebug("save: %S", _name ? _name : PSTR("??"));
|
||||
|
||||
// locate the variable in EEPROM, allocating space as required
|
||||
if (!_EEPROM_locate(true)) {
|
||||
serialDebug("locate failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
// serialize the variable into the buffer and work out how big it is
|
||||
size = serialize(vbuf, sizeof(vbuf));
|
||||
if (0 == size) {
|
||||
// variable cannot be serialised into the buffer
|
||||
serialDebug("cannot save (too big or not supported)");
|
||||
return false;
|
||||
}
|
||||
|
||||
// if it fit in the buffer, save it to EEPROM
|
||||
if (size <= sizeof(vbuf)) {
|
||||
serialDebug("saving %u to %u", size, _key);
|
||||
// XXX this should use eeprom_update_block if/when Arduino moves to
|
||||
// avr-libc >= 1.7
|
||||
uint8_t *ep = (uint8_t *)_key;
|
||||
for (size_t i = 0; i < size; i++, ep++) {
|
||||
uint8_t newv;
|
||||
// if value needs to change, change it
|
||||
if (eeprom_read_byte(ep) != vbuf[i])
|
||||
eeprom_write_byte(ep, vbuf[i]);
|
||||
|
||||
// now read it back
|
||||
newv = eeprom_read_byte(ep);
|
||||
if (newv != vbuf[i]) {
|
||||
serialDebug("readback failed at offset %p: got %u, expected %u",
|
||||
ep, newv, vbuf[i]);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// 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();
|
||||
}
|
||||
|
||||
serialDebug("load: %S", _name ? _name : PSTR("??"));
|
||||
|
||||
// locate the variable in EEPROM, but do not allocate space
|
||||
if (!_EEPROM_locate(false)) {
|
||||
serialDebug("locate failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
// ask the serializer how big the variable is
|
||||
//
|
||||
// XXX should check size in EEPROM var header too...
|
||||
//
|
||||
size = serialize(NULL, 0);
|
||||
if (0 == size) {
|
||||
serialDebug("cannot load (too big or not supported)");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Read the buffer from EEPROM, now that _EEPROM_locate
|
||||
// has converted _key into an EEPROM address.
|
||||
//
|
||||
if (size <= sizeof(vbuf)) {
|
||||
serialDebug("loading %u from %u", size, _key);
|
||||
eeprom_read_block(vbuf, (void *)_key, size);
|
||||
return unserialize(vbuf, size);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Save all variables that don't opt out.
|
||||
//
|
||||
//
|
||||
bool AP_Var::save_all(void)
|
||||
{
|
||||
bool result = true;
|
||||
AP_Var *vp = _variables;
|
||||
|
||||
size_t loopCount = 0;
|
||||
|
||||
while (vp) {
|
||||
|
||||
if (loopCount++>k_num_max) return false;
|
||||
|
||||
if (!vp->has_flags(k_flag_no_auto_load) && // not opted out of autosave
|
||||
(vp->_key != k_key_none)) { // has a key
|
||||
|
||||
if (!vp->save()) {
|
||||
result = false;
|
||||
}
|
||||
}
|
||||
vp = vp->_link;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// Load all variables that don't opt out.
|
||||
//
|
||||
bool AP_Var::load_all(void)
|
||||
{
|
||||
bool result = true;
|
||||
AP_Var *vp = _variables;
|
||||
|
||||
size_t loopCount = 0;
|
||||
|
||||
while (vp) {
|
||||
|
||||
if (loopCount++>k_num_max) return false;
|
||||
|
||||
if (!vp->has_flags(k_flag_no_auto_load) && // not opted out of autoload
|
||||
(vp->_key != k_key_none)) { // has a key
|
||||
|
||||
if (!vp->load()) {
|
||||
result = false;
|
||||
}
|
||||
}
|
||||
vp = vp->_link;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// Erase all variables in EEPROM.
|
||||
//
|
||||
// We first walk the variable set and recover their key values
|
||||
// from EEPROM, so that we have a chance of saving them later.
|
||||
//
|
||||
void
|
||||
AP_Var::erase_all()
|
||||
{
|
||||
AP_Var *vp;
|
||||
uint16_t i;
|
||||
|
||||
serialDebug("erase EEPROM");
|
||||
|
||||
// Scan the list of variables/groups, fetching their key values and
|
||||
// reverting them to their not-located state.
|
||||
//
|
||||
vp = _variables;
|
||||
|
||||
size_t loopCount = 0;
|
||||
|
||||
while (vp) {
|
||||
|
||||
if (loopCount++>k_num_max) return;
|
||||
|
||||
vp->_key = vp->key() | k_key_not_located;
|
||||
vp = vp->_link;
|
||||
}
|
||||
|
||||
// wipe the whole EEPROM, including waypoints, as we call this
|
||||
// on firmware revison changes, which may include a change to the
|
||||
// waypoint format
|
||||
for (i = 0; i < k_EEPROM_size; i++) {
|
||||
eeprom_write_byte((uint8_t *)i, 0xff);
|
||||
}
|
||||
|
||||
// revert to ignorance about the state of the EEPROM
|
||||
_tail_sentinel = 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, note that _key points to the space
|
||||
// allocated for storage; the header is immediately before.
|
||||
//
|
||||
eeprom_read_block(&var_header, (void *)(_key - sizeof(var_header)), sizeof(var_header));
|
||||
return var_header.key;
|
||||
}
|
||||
|
||||
// Default implementation of cast_to_float, which always fails.
|
||||
//
|
||||
float
|
||||
AP_Var::cast_to_float(void) const
|
||||
{
|
||||
return NAN;
|
||||
}
|
||||
|
||||
// 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;
|
||||
|
||||
serialDebug("seeking %p", group);
|
||||
|
||||
size_t loopCount = 0;
|
||||
|
||||
while (*vp) {
|
||||
|
||||
if (loopCount++>k_num_max) return NULL;
|
||||
|
||||
serialDebug("consider %p with %p", *vp, (*vp)->_group);
|
||||
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;
|
||||
size_t loopCount = 0;
|
||||
|
||||
while (vp) {
|
||||
|
||||
if (loopCount++>k_num_max) return NULL;
|
||||
|
||||
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;
|
||||
uint16_t eeprom_address;
|
||||
|
||||
// Assume that the EEPROM contents are invalid
|
||||
_tail_sentinel = 0;
|
||||
|
||||
// read the header and validate
|
||||
eeprom_address = 0;
|
||||
eeprom_read_block(&ee_header, (void *)eeprom_address, sizeof(ee_header));
|
||||
if ((ee_header.magic != k_EEPROM_magic) ||
|
||||
(ee_header.revision != k_EEPROM_revision)) {
|
||||
|
||||
serialDebug("no header, magic 0x%x revision %u", ee_header.magic, ee_header.revision);
|
||||
return false;
|
||||
}
|
||||
|
||||
// scan the EEPROM
|
||||
//
|
||||
// Avoid trying to read a header when there isn't enough space left.
|
||||
//
|
||||
eeprom_address = sizeof(ee_header);
|
||||
|
||||
size_t loopCount = 0;
|
||||
|
||||
while (eeprom_address < (k_EEPROM_size - sizeof(var_header) - 1)) {
|
||||
|
||||
if (loopCount++>k_num_max) return NULL;
|
||||
|
||||
// Read a variable header
|
||||
//
|
||||
serialDebug("reading header from %u", eeprom_address);
|
||||
eeprom_read_block(&var_header, (void *)eeprom_address, sizeof(var_header));
|
||||
|
||||
// If the header is for the sentinel, scanning is complete
|
||||
//
|
||||
if (var_header.key == k_key_sentinel) {
|
||||
serialDebug("found tail sentinel");
|
||||
break;
|
||||
}
|
||||
|
||||
// Sanity-check the variable header and abort if it looks bad
|
||||
//
|
||||
if (k_EEPROM_size <= (
|
||||
eeprom_address + // current position
|
||||
sizeof(var_header) + // header for this variable
|
||||
var_header.size + 1 + // data for this variable
|
||||
sizeof(var_header))) { // header for sentinel
|
||||
|
||||
serialDebug("header overruns EEPROM");
|
||||
return false;
|
||||
}
|
||||
|
||||
// look for a variable with this key
|
||||
vp = _variables;
|
||||
size_t loopCount2 = 0;
|
||||
while(vp) {
|
||||
if (loopCount2++>k_num_max) return false;
|
||||
if (vp->key() == var_header.key) {
|
||||
// adjust the variable's key to point to this entry
|
||||
vp->_key = eeprom_address + sizeof(var_header);
|
||||
serialDebug("update %p with key %u -> %u", vp, var_header.key, vp->_key);
|
||||
break;
|
||||
}
|
||||
vp = vp->_link;
|
||||
}
|
||||
if (!vp) {
|
||||
serialDebug("key %u not claimed (already scanned or unknown)", var_header.key);
|
||||
}
|
||||
|
||||
// move to the next variable header
|
||||
eeprom_address += sizeof(var_header) + var_header.size + 1;
|
||||
}
|
||||
|
||||
// Mark any variables that weren't assigned addresses as not-allocated,
|
||||
// so that we don't waste time looking for them again later.
|
||||
//
|
||||
// Note that this isn't done when the header is not found on an empty EEPROM.
|
||||
// The first variable written on an empty EEPROM falls out as soon as the
|
||||
// header is not found. The second will scan and find one variable, then
|
||||
// mark all the rest as not allocated.
|
||||
//
|
||||
vp = _variables;
|
||||
size_t loopCount3 = 0;
|
||||
while(vp) {
|
||||
if (loopCount3++>k_num_max) return false;
|
||||
if (vp->_key & k_key_not_located) {
|
||||
vp->_key |= k_key_not_allocated;
|
||||
serialDebug("key %u not allocated", vp->key());
|
||||
}
|
||||
vp = vp->_link;
|
||||
}
|
||||
|
||||
// Scanning is complete
|
||||
serialDebug("scan done");
|
||||
_tail_sentinel = eeprom_address;
|
||||
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)) {
|
||||
serialDebug("not addressable");
|
||||
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
|
||||
}
|
||||
|
||||
// We don't know where this variable belongs. If the variable isn't
|
||||
// marked as already having been looked for and not found in EEPROM,
|
||||
// try scanning to see if we can locate it.
|
||||
//
|
||||
if (!(_key & k_key_not_allocated)) {
|
||||
serialDebug("need scan");
|
||||
_EEPROM_scan();
|
||||
|
||||
// Has the variable now been located?
|
||||
//
|
||||
if (!(_key & k_key_not_located)) {
|
||||
return true; // it has
|
||||
}
|
||||
}
|
||||
|
||||
// If not located and not permitted to allocate, we have failed.
|
||||
//
|
||||
if (!allocate) {
|
||||
return false;
|
||||
}
|
||||
serialDebug("needs allocation");
|
||||
|
||||
// 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)) {
|
||||
serialDebug("size %u out of bounds", 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) {
|
||||
serialDebug("no space in EEPROM");
|
||||
return false;
|
||||
}
|
||||
|
||||
// If there is no data in the EEPROM, write the header and move the
|
||||
// sentinel.
|
||||
//
|
||||
if (0 == _tail_sentinel) {
|
||||
uint8_t pad_size;
|
||||
|
||||
serialDebug("writing header");
|
||||
EEPROM_header ee_header;
|
||||
|
||||
ee_header.magic = k_EEPROM_magic;
|
||||
ee_header.revision = k_EEPROM_revision;
|
||||
ee_header.spare = 0;
|
||||
|
||||
eeprom_write_block(&ee_header, (void *)0, sizeof(ee_header));
|
||||
|
||||
_tail_sentinel = sizeof(ee_header);
|
||||
|
||||
// Write a variable-sized pad header with a reserved key value
|
||||
// to help wear-level the EEPROM a bit.
|
||||
pad_size = (((uint8_t)micros()) % k_size_max) + 1; // should be fairly random
|
||||
var_header.key = k_key_pad;
|
||||
var_header.size = pad_size - 1;
|
||||
var_header.spare = 0;
|
||||
|
||||
eeprom_write_block(&var_header, (void *)_tail_sentinel, sizeof(var_header));
|
||||
_tail_sentinel += sizeof(var_header) + pad_size;
|
||||
}
|
||||
|
||||
// Save the location we are going to insert at, and compute the new
|
||||
// tail sentinel location.
|
||||
//
|
||||
new_location = _tail_sentinel;
|
||||
_tail_sentinel += sizeof(var_header) + size;
|
||||
serialDebug("allocated %u/%u for key %u new sentinel %u", new_location, size, key(), _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;
|
||||
var_header.spare = 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.
|
||||
// Update _key to point to the space allocated for it.
|
||||
//
|
||||
_key = new_location + sizeof(var_header);
|
||||
return true;
|
||||
}
|
||||
|
||||
size_t
|
||||
AP_Var_group::serialize(void *buf, size_t buf_size) const
|
||||
{
|
||||
// We have to cast away the const in order to call _serialize_unserialize,
|
||||
// as it cannot be const due to changing this when called to unserialize.
|
||||
//
|
||||
// XXX it's questionable how much advantage we get from having ::serialize
|
||||
// const in the first place...
|
||||
//
|
||||
return const_cast<AP_Var_group *>(this)->_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;
|
||||
size_t size, total_size;
|
||||
|
||||
// Traverse the list of group members, serializing each in order
|
||||
//
|
||||
vp = first_member(this);
|
||||
serialDebug("starting with %p", vp);
|
||||
total_size = 0;
|
||||
|
||||
size_t loopCount = 0;
|
||||
|
||||
while (vp) {
|
||||
|
||||
if (loopCount++>k_num_max) return false;
|
||||
|
||||
// (un)serialise the group member
|
||||
if (do_serialize) {
|
||||
size = vp->serialize(buf, buf_size);
|
||||
serialDebug("serialize %p -> %u", vp, size);
|
||||
} else {
|
||||
size = vp->unserialize(buf, buf_size);
|
||||
serialDebug("unserialize %p -> %u", vp, size);
|
||||
}
|
||||
|
||||
// Unserialize will return zero if the buffer is too small
|
||||
// Serialize will only return zero if the variable cannot be serialised
|
||||
// Either case is fatal for any operation we might be trying.
|
||||
//
|
||||
if (0 == size) {
|
||||
serialDebug("group (un)serialize failed, buffer too small or not supported");
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
// 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;
|
||||
serialDebug("used %u", total_size);
|
||||
if (size <= buf_size) {
|
||||
// there was space for this one, account for it
|
||||
buf_size -= size;
|
||||
buf = (void *)((uint8_t *)buf + size);
|
||||
}
|
||||
|
||||
vp = vp->next_member();
|
||||
}
|
||||
return total_size;
|
||||
}
|
||||
|
||||
// Static pseudo-constant type IDs for known AP_VarT subclasses.
|
||||
//
|
||||
AP_Meta_class::Type_id AP_Var::k_typeid_float; ///< meta_type_id() value for AP_Float
|
||||
AP_Meta_class::Type_id AP_Var::k_typeid_float16; ///< meta_type_id() value for AP_Float16
|
||||
AP_Meta_class::Type_id AP_Var::k_typeid_int32; ///< meta_type_id() value for AP_Int32
|
||||
AP_Meta_class::Type_id AP_Var::k_typeid_int16; ///< meta_type_id() value for AP_Int16
|
||||
AP_Meta_class::Type_id AP_Var::k_typeid_int8; ///< meta_type_id() value for AP_Int8
|
||||
AP_Meta_class::Type_id AP_Var::k_typeid_group; ///< meta_type_id() value for AP_Var_group
|
||||
|
||||
/// A special class used to initialise the k_typeid_* values that AP_Var exports.
|
||||
///
|
||||
class AP_Var_typesetup
|
||||
{
|
||||
public:
|
||||
/// Constructor
|
||||
///
|
||||
/// This constructor should be run just once by creating a static instance
|
||||
/// of the class. It will initialise the k_typeid_* values for the well-known
|
||||
/// AP_VarT subclasses.
|
||||
///
|
||||
/// When a new subclass is created, a new k_typeid_* constant should also be
|
||||
/// created and the list below should likewise be expanded.
|
||||
///
|
||||
AP_Var_typesetup(void);
|
||||
};
|
||||
|
||||
/// Initialise AP_Var's k_typeid_* values
|
||||
AP_Var_typesetup::AP_Var_typesetup(void)
|
||||
{
|
||||
AP_Var::k_typeid_float = AP_Meta_class::meta_type_id<AP_Float>();
|
||||
AP_Var::k_typeid_float16 = AP_Meta_class::meta_type_id<AP_Float16>();
|
||||
AP_Var::k_typeid_int32 = AP_Meta_class::meta_type_id<AP_Int32>();
|
||||
AP_Var::k_typeid_int16 = AP_Meta_class::meta_type_id<AP_Int16>();
|
||||
AP_Var::k_typeid_int8 = AP_Meta_class::meta_type_id<AP_Int8>();
|
||||
AP_Var::k_typeid_group = AP_Meta_class::meta_type_id<AP_Var_group>();
|
||||
}
|
||||
|
||||
/// Cause the AP_Var_typesetup constructor to be run.
|
||||
///
|
||||
static AP_Var_typesetup _typesetup __attribute__((used));
|
@ -1,977 +0,0 @@
|
||||
// -*- 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.
|
||||
//
|
||||
|
||||
/// @file AP_Var.h
|
||||
/// @brief A system for managing and storing variables that are of
|
||||
/// general interest to the system.
|
||||
|
||||
#ifndef AP_VAR_H
|
||||
#define AP_VAR_H
|
||||
#include <stddef.h>
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <avr/pgmspace.h>
|
||||
#include <avr/eeprom.h>
|
||||
|
||||
#include "AP_MetaClass.h"
|
||||
|
||||
class AP_Var_group;
|
||||
|
||||
/// Base class for variables.
|
||||
///
|
||||
/// Provides naming and lookup services for variables.
|
||||
///
|
||||
class AP_Var : public AP_Meta_class
|
||||
{
|
||||
public:
|
||||
/// EEPROM header
|
||||
///
|
||||
/// This structure is placed at the head of the EEPROM to indicate
|
||||
/// that the ROM is formatted for AP_Var.
|
||||
///
|
||||
/// The EEPROM may thus be cheaply erased by overwriting just one
|
||||
/// byte of the header magic.
|
||||
///
|
||||
struct EEPROM_header {
|
||||
uint16_t magic;
|
||||
uint8_t revision;
|
||||
uint8_t spare;
|
||||
};
|
||||
|
||||
static const uint16_t k_EEPROM_magic = 0x5041; ///< "AP"
|
||||
static const uint16_t k_EEPROM_revision = 2; ///< 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.
|
||||
///
|
||||
/// At creation time, the _key value has the k_key_not_located bit
|
||||
/// set, and its value is an ordinal uniquely identifying the
|
||||
/// variable. Once the address of the variable in EEPROM is known,
|
||||
/// either as a result of scanning or due to allocation of new
|
||||
/// space in the EEPROM, the k_key_not_located bit will be cleared
|
||||
/// and the _key value gives the offset into the EEPROM where
|
||||
/// the variable's data can be found.
|
||||
///
|
||||
typedef uint16_t Key;
|
||||
|
||||
/// This header is prepended to a variable stored in EEPROM.
|
||||
///
|
||||
/// Note that the size value is the first element in the header.
|
||||
/// The sentinel entry marking the end of the EEPROM is always written
|
||||
/// before updating the header of a new variable at the end of EEPROM.
|
||||
/// This provides protection against corruption that might be caused
|
||||
/// if the update process is interrupted.
|
||||
///
|
||||
struct Var_header {
|
||||
/// The size of the variable, minus one.
|
||||
/// This allows a variable or group to be anything from one to 64 bytes long.
|
||||
///
|
||||
uint8_t size:6;
|
||||
|
||||
/// Spare bits, currently unused
|
||||
///
|
||||
/// @todo One could be a parity bit?
|
||||
///
|
||||
uint8_t spare:2;
|
||||
|
||||
/// The key assigned to the variable.
|
||||
///
|
||||
uint8_t key;
|
||||
|
||||
};
|
||||
|
||||
/// A key value 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 (and it's too big to fit the Var_header::key field).
|
||||
///
|
||||
/// This value is normally the default.
|
||||
///
|
||||
static const Key k_key_none = 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_key_not_located = (Key)1 << 15;
|
||||
|
||||
/// A key that has this bit set was not found during a scan of the EEPROM.
|
||||
/// If this bit is set in the key, it's not useful to scan the EEPROM again
|
||||
/// in an attempt to find it.
|
||||
///
|
||||
static const Key k_key_not_allocated = (Key)1 << 14;
|
||||
|
||||
/// Key assigned to the terminal entry in EEPROM.
|
||||
///
|
||||
static const Key k_key_sentinel = 0xff;
|
||||
|
||||
/// Key assigned to the wear-balancing pad entry in EEPROM.
|
||||
///
|
||||
static const Key k_key_pad = 0xfe;
|
||||
|
||||
/// A bitmask that removes any control bits from a key giving just the
|
||||
/// value.
|
||||
///
|
||||
static const Key k_key_mask = (Key)(~(k_key_not_located | k_key_not_allocated));
|
||||
|
||||
/// 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. It should match the maximum size that can
|
||||
/// be encoded in the Var_header::size field.
|
||||
///
|
||||
static const size_t k_size_max = 64;
|
||||
|
||||
/// The maximum number of keys
|
||||
///
|
||||
static const size_t k_num_max = 255;
|
||||
|
||||
/// Optional flags affecting the behavior and usage of the variable.
|
||||
///
|
||||
typedef uint8_t Flags;
|
||||
static const Flags k_flags_none = 0;
|
||||
|
||||
/// 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.
|
||||
static const Flags k_flag_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.
|
||||
static const Flags k_flag_no_import = (1 << 1);
|
||||
|
||||
/// This flag indicates that the variable is really a group; it is normally
|
||||
/// set automatically by the AP_Var_group constructor.
|
||||
///
|
||||
static const Flags k_flag_is_group = (1 << 2);
|
||||
|
||||
/// This flag indicates that the variable wants to opt out of being listed.
|
||||
///
|
||||
static const Flags k_flag_unlisted = (1 << 3);
|
||||
|
||||
/// @todo Flag that indicates variable is non-volatile (auto-saved when set).
|
||||
|
||||
static AP_Meta_class::Type_id k_typeid_float; ///< meta_type_id() value for AP_Float
|
||||
static AP_Meta_class::Type_id k_typeid_float16; ///< meta_type_id() value for AP_Float16
|
||||
static AP_Meta_class::Type_id k_typeid_int32; ///< meta_type_id() value for AP_Int32
|
||||
static AP_Meta_class::Type_id k_typeid_int16; ///< meta_type_id() value for AP_Int16
|
||||
static AP_Meta_class::Type_id k_typeid_int8; ///< meta_type_id() value for AP_Int8
|
||||
static AP_Meta_class::Type_id k_typeid_group; ///< meta_type_id() value for AP_Var_group
|
||||
|
||||
/// Constructor for a freestanding variable
|
||||
///
|
||||
/// @param key The storage key to be associated with this variable.
|
||||
/// @param name An optional name by which the variable may be known.
|
||||
/// @param flags Optional flags which control how the variable behaves.
|
||||
///
|
||||
AP_Var(Key key = k_key_none, const prog_char_t *name = NULL, Flags flags = k_flags_none);
|
||||
|
||||
/// Constructor for variable belonging to a group
|
||||
///
|
||||
/// @param group The group the variable belongs to.
|
||||
/// @param index The position of the variable in the group.
|
||||
/// @param name An optional name by which the variable may be known.
|
||||
/// @param flags Optional flags which control how the variable behaves.
|
||||
///
|
||||
AP_Var(AP_Var_group *group, Key index, const prog_char_t *name, Flags flags = k_flags_none);
|
||||
|
||||
/// Destructor
|
||||
///
|
||||
/// For freestanding variables, this will remove the variable from the global list
|
||||
/// of variables. The list is organised FIFO, so locally-constructed freestanding
|
||||
/// variables are typically cheap to destroy as they tend to be at or very close to
|
||||
/// the head of the list.
|
||||
///
|
||||
/// Destroying a variable that is a group member may be less efficient as the list
|
||||
/// of variables that are group members is sorted by key, requiring a traversal
|
||||
/// of the list up to the index before it can be removed.
|
||||
///
|
||||
/// Destroying a group removes all variables that are members of the group from
|
||||
/// the list, requiring a complete traversal of the list of group-member variables.
|
||||
/// If the group is destroyed before its members, they will also traverse the entire
|
||||
/// list as they attempt to remove themselves.
|
||||
///
|
||||
/// The moral of the story: be careful when creating groups with local scope.
|
||||
///
|
||||
~AP_Var(void);
|
||||
|
||||
/// Copy the variable's name, prefixed by any containing group name, to a buffer.
|
||||
///
|
||||
/// If the variable has no name, the buffer will contain an empty string.
|
||||
///
|
||||
/// Note that if the combination of names is larger than the buffer, the
|
||||
/// result in the buffer will be truncated.
|
||||
///
|
||||
/// @param buffer The destination buffer
|
||||
/// @param bufferSize Total size of the destination buffer.
|
||||
///
|
||||
void copy_name(char *buffer, size_t bufferSize) const;
|
||||
|
||||
/// Find a variable by name.
|
||||
///
|
||||
/// If the variable has no name, it cannot be found by this interface.
|
||||
///
|
||||
/// @param name The full name of the variable to be found.
|
||||
/// @return A pointer to the variable, or NULL if
|
||||
/// it does not exist.
|
||||
///
|
||||
static AP_Var *find(const char *name);
|
||||
|
||||
/// Find a variable by key.
|
||||
///
|
||||
/// @param key The key being looked up.
|
||||
/// @return A pointer to the variable, or NULL if
|
||||
/// it does not exist.
|
||||
///
|
||||
static AP_Var *find(Key key);
|
||||
|
||||
/// Save the current value of the variable to EEPROM.
|
||||
///
|
||||
/// This interface works for any subclass that implements
|
||||
/// AP_Meta_class::serialize.
|
||||
///
|
||||
/// 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
|
||||
/// AP_Meta_class::unserialize.
|
||||
///
|
||||
/// 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
|
||||
///
|
||||
/// 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
|
||||
///
|
||||
/// 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);
|
||||
|
||||
/// Erase all variables in EEPROM.
|
||||
///
|
||||
/// This can be used prior to save_all to ensure that only known variables
|
||||
/// will be present in the EEPROM.
|
||||
///
|
||||
/// It can also be used immediately prior to reset, followed by a save_all,
|
||||
/// to restore all saved variables to their initial value.
|
||||
///
|
||||
static void erase_all(void);
|
||||
|
||||
/// Test for flags that may be set.
|
||||
///
|
||||
/// @param flagval Flag or flags to be tested
|
||||
/// @return True if all of the bits in flagval are set in the flags.
|
||||
///
|
||||
bool has_flags(Flags flagval) const {
|
||||
return (_flags & flagval) == flagval;
|
||||
}
|
||||
|
||||
/// Returns the group that a variable belongs to
|
||||
///
|
||||
/// @return The parent group, or NULL if the variable is not grouped.
|
||||
///
|
||||
AP_Var_group *group(void) {
|
||||
return _group;
|
||||
}
|
||||
|
||||
/// Returns the first variable in the global list.
|
||||
///
|
||||
/// @return The first variable in the global list, or NULL if
|
||||
/// there are none.
|
||||
///
|
||||
static AP_Var *first(void) {
|
||||
return _variables;
|
||||
}
|
||||
|
||||
/// Returns the next variable in the global list.
|
||||
///
|
||||
/// All standalone variables are returned first, then all grouped variables.
|
||||
/// Note that groups themselves are considered standalone variables.
|
||||
///
|
||||
/// A caller not wishing to iterate grouped variables should test the return
|
||||
/// value from this function with ::group, and if non-null, ignore it.
|
||||
///
|
||||
/// XXX how to ignore groups?
|
||||
///
|
||||
/// @return The next variable, either the next group member in order or
|
||||
/// the next variable in an arbitrary order, or NULL if this is
|
||||
/// the last variable in the list.
|
||||
///
|
||||
AP_Var *next(void);
|
||||
|
||||
/// Returns the first variable that is a member of a specific group.
|
||||
///
|
||||
/// @param group The group whose member(s) are sought.
|
||||
/// @return The first variable in the group, or NULL if there are none.
|
||||
///
|
||||
static AP_Var *first_member(AP_Var_group *group);
|
||||
|
||||
/// Returns the next variable that is a member of the same group.
|
||||
///
|
||||
/// This will not behave correctly if called on a variable that is not a group member.
|
||||
///
|
||||
/// @param group The group whose member(s) are sought.
|
||||
/// @return The next variable in the group, or NULL if there are none.
|
||||
///
|
||||
AP_Var *next_member();
|
||||
|
||||
/// Returns the storage key for a variable.
|
||||
///
|
||||
/// Note that group members do not have storage keys - the key is held by the group
|
||||
/// and instead the key indicates the variable's ordering within the group.
|
||||
///
|
||||
/// @return The variable's key, or k_key_none if it does not have a key.
|
||||
///
|
||||
Key key(void);
|
||||
|
||||
/// Casts the value to float, if possible.
|
||||
///
|
||||
/// This is a convenience method for code that would rather not try to
|
||||
/// meta-cast to the various subtypes. It cannot guarantee that a useful value
|
||||
/// will be returned; the caller must check the returned value against NAN
|
||||
/// before using it.
|
||||
///
|
||||
/// @return The value cast to float, or NAN if it cannot be cast.
|
||||
///
|
||||
virtual float cast_to_float() const;
|
||||
|
||||
/// Report the amount of memory being used by AP_Var subclasses.
|
||||
///
|
||||
/// @return The sum of sizeof(*this) for all constructed AP_Var subclass instances.
|
||||
///
|
||||
static uint16_t get_memory_use() {
|
||||
return _bytes_in_use;
|
||||
}
|
||||
|
||||
protected:
|
||||
// Memory statistics
|
||||
static uint16_t _bytes_in_use;
|
||||
|
||||
private:
|
||||
AP_Var_group *_group; ///< Group that the variable may be a member of
|
||||
AP_Var *_link; ///< linked list pointer to next variable
|
||||
Key _key; ///< Storage key; see the discussion of Key above.
|
||||
const prog_char_t *_name; ///< name known to external agents (GCS, etc.)
|
||||
uint8_t _flags; ///< flag bits
|
||||
|
||||
// static state used by ::lookup
|
||||
static AP_Var *_variables; ///< linked list of all freestanding variables
|
||||
static AP_Var *_grouped_variables; ///< linked list of all grouped variables
|
||||
|
||||
// EEPROM space allocation and scanning
|
||||
static uint16_t _tail_sentinel; ///< EEPROM address of the tail sentinel
|
||||
|
||||
static const uint16_t k_EEPROM_size = 4096; ///< XXX avr-libc doesn't consistently export this
|
||||
|
||||
/// 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);
|
||||
|
||||
};
|
||||
|
||||
/// Variable groups.
|
||||
///
|
||||
/// 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 that is a member
|
||||
/// of a group, it will prepend the name of the group; this helps save
|
||||
/// memory.
|
||||
///
|
||||
/// Variables belonging to a group are always sorted into the global
|
||||
/// variable list after the group.
|
||||
///
|
||||
class AP_Var_group : public AP_Var
|
||||
{
|
||||
public:
|
||||
/// Constructor
|
||||
///
|
||||
/// @param key Storage key for the group.
|
||||
/// @param name An optional name prefix for members of the group.
|
||||
///
|
||||
AP_Var_group(Key with_key = k_key_none, const prog_char_t *name = NULL, Flags flags = k_flags_none) :
|
||||
AP_Var(with_key, name, flags | k_flag_is_group)
|
||||
{
|
||||
_bytes_in_use += sizeof(*this);
|
||||
}
|
||||
|
||||
/// Serialize the group.
|
||||
///
|
||||
/// Iteratively serializes the entire group into the supplied buffer.
|
||||
///
|
||||
/// @param buf Buffer into which serialized data should be placed.
|
||||
/// @param buf_size The size of the buffer provided.
|
||||
/// @return The size of the serialized data, even if that data would
|
||||
/// have overflowed the buffer. If the value is less than or
|
||||
/// equal to buf_size, serialization was successful.
|
||||
///
|
||||
virtual size_t serialize(void *buf, size_t buf_size) const;
|
||||
|
||||
/// Unserialize the group.
|
||||
///
|
||||
/// Iteratively unserializes the group from the supplied buffer.
|
||||
///
|
||||
/// @param buf Buffer containing serialized data.
|
||||
/// @param buf_size 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 buf_size, unserialization was successful.
|
||||
///
|
||||
virtual size_t unserialize(void *buf, size_t buf_size);
|
||||
|
||||
private:
|
||||
/// Common implementation of the group member traversal and accounting used
|
||||
/// by serialize/unserialize.
|
||||
///
|
||||
/// @param buf Buffer containing serialized data.
|
||||
/// @param buf_size The size of the buffer.
|
||||
/// @param do_serialize True if the operation should serialize, false if it should
|
||||
/// unserialize.
|
||||
/// @return The number of bytes from the buffer that would be consumed
|
||||
/// operating on the data. If the value is less than or equal
|
||||
/// to buf_size, the operation was successful.
|
||||
///
|
||||
size_t _serialize_unserialize(void *buf, size_t buf_size, bool do_serialize);
|
||||
};
|
||||
|
||||
/// Template class for scalar variables.
|
||||
///
|
||||
/// Objects of this type have a value, and can be treated in many ways as though they
|
||||
/// were the value.
|
||||
///
|
||||
/// @tparam T The scalar type of the variable
|
||||
///
|
||||
template<typename T>
|
||||
class AP_VarT : public AP_Var
|
||||
{
|
||||
public:
|
||||
/// Constructor for non-grouped variable.
|
||||
///
|
||||
/// Initialises a stand-alone variable with optional initial value, storage key, name and flags.
|
||||
///
|
||||
/// @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_key_none
|
||||
/// 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> (const T initial_value = 0,
|
||||
Key with_key = k_key_none,
|
||||
const prog_char_t *name = NULL,
|
||||
Flags flags = k_flags_none) :
|
||||
AP_Var(with_key, name, flags),
|
||||
_value(initial_value)
|
||||
{
|
||||
_bytes_in_use += sizeof(*this);
|
||||
}
|
||||
|
||||
/// 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 *with_group, // XXX maybe make this a ref?
|
||||
Key vindex,
|
||||
T initial_value,
|
||||
const prog_char_t *name = NULL,
|
||||
Flags flags = k_flags_none) :
|
||||
AP_Var(with_group, vindex, name, flags),
|
||||
_value(initial_value)
|
||||
{
|
||||
_bytes_in_use += sizeof(*this);
|
||||
}
|
||||
|
||||
// serialize _value into the buffer, but only if it is big enough.
|
||||
//
|
||||
virtual size_t serialize(void *buf, size_t size) const {
|
||||
if (size >= sizeof(_value)) {
|
||||
*(T *)buf = _value;
|
||||
}
|
||||
return sizeof(_value);
|
||||
}
|
||||
|
||||
// Unserialize from the buffer, but only if it is big enough.
|
||||
//
|
||||
virtual size_t unserialize(void *buf, size_t size) {
|
||||
if (size >= sizeof(_value)) {
|
||||
_value = *(T *)buf;
|
||||
}
|
||||
return sizeof(_value);
|
||||
}
|
||||
|
||||
/// Value getter
|
||||
///
|
||||
T get(void) const {
|
||||
return _value;
|
||||
}
|
||||
|
||||
/// Value setter
|
||||
///
|
||||
void set(T v) {
|
||||
_value = v;
|
||||
}
|
||||
|
||||
/// Combined set and save
|
||||
///
|
||||
bool set_and_save(T v) {
|
||||
set(v);
|
||||
return save();
|
||||
}
|
||||
|
||||
/// Conversion to T returns a reference to the value.
|
||||
///
|
||||
/// This allows the class to be used in many situations where the value would be legal.
|
||||
///
|
||||
operator T &() {
|
||||
return _value;
|
||||
}
|
||||
|
||||
/// Copy assignment from self does nothing.
|
||||
///
|
||||
AP_VarT<T>& operator=(AP_VarT<T>& v) {
|
||||
return v;
|
||||
}
|
||||
|
||||
/// Copy assignment from T is equivalent to ::set.
|
||||
///
|
||||
AP_VarT<T>& operator=(T v) {
|
||||
_value = v;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/// AP_VarT types can implement AP_Var::cast_to_float
|
||||
///
|
||||
virtual float cast_to_float() const;
|
||||
|
||||
protected:
|
||||
T _value;
|
||||
};
|
||||
|
||||
// Implement AP_Var::cast_to_float.
|
||||
//
|
||||
template<typename T>
|
||||
float
|
||||
AP_VarT<T>::cast_to_float() const
|
||||
{
|
||||
return (float)_value;
|
||||
}
|
||||
|
||||
/// Template class for structure variables.
|
||||
///
|
||||
/// Objects created using this variable contain an instance of the type T
|
||||
///
|
||||
/// Note that the size of the structure cannot be greater than AP_Var::k_size_max
|
||||
/// if it is ever to be loaded or saved to EEPROM.
|
||||
/// Unfortunately there is no way to assert this at compile time; a larger
|
||||
/// array will compile correctly and work as expected, except that ::save and
|
||||
/// ::load will always fail.
|
||||
///
|
||||
/// @note The initial value of the fields of the structure are set by
|
||||
/// the type's constructor at the time that the template is constructed.
|
||||
///
|
||||
template<typename T>
|
||||
class AP_VarS : public AP_Var
|
||||
{
|
||||
public:
|
||||
/// Constructor for a non-grouped structure variable.
|
||||
///
|
||||
/// Initializes a stand-alone structure variable with optional storage key, name and flags.
|
||||
///
|
||||
/// @param key Storage key for the variable. If not set, or set to AP_Var::k_key_none
|
||||
/// 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 behavior of the variable.
|
||||
///
|
||||
AP_VarS<T> (Key with_key = k_key_none,
|
||||
const prog_char_t *name = NULL,
|
||||
Flags flags = k_flags_none) :
|
||||
AP_Var(with_key, name, flags)
|
||||
{
|
||||
_bytes_in_use += sizeof(*this);
|
||||
}
|
||||
|
||||
/// Constructor for a grouped structure variable.
|
||||
///
|
||||
/// Initializes 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 name An optional name by which the variable may be known.
|
||||
/// @param flags Optional flags that may affect the behavior of the variable.
|
||||
///
|
||||
AP_VarS<T> (AP_Var_group *with_group, // XXX maybe make this a ref?
|
||||
Key index,
|
||||
const prog_char_t *name = NULL,
|
||||
Flags flags = k_flags_none) :
|
||||
AP_Var(with_group, index, name, flags)
|
||||
{
|
||||
_bytes_in_use += sizeof(*this);
|
||||
}
|
||||
|
||||
// serialize _value into the buffer, but only if it is big enough.
|
||||
//
|
||||
virtual size_t serialize(void *buf, size_t size) const {
|
||||
if (size >= sizeof(_value)) {
|
||||
memcpy(buf, &_value, sizeof(_value));
|
||||
}
|
||||
return sizeof(_value);
|
||||
}
|
||||
|
||||
// Unserialize from the buffer, but only if it is big enough.
|
||||
//
|
||||
virtual size_t unserialize(void *buf, size_t size) {
|
||||
if (size >= sizeof(_value)) {
|
||||
memcpy(&_value, buf, sizeof(_value));
|
||||
}
|
||||
return sizeof(_value);
|
||||
}
|
||||
|
||||
/// Value getter
|
||||
///
|
||||
T& get() {
|
||||
return _value;
|
||||
}
|
||||
|
||||
/// Value getter
|
||||
///
|
||||
const T& get() const {
|
||||
return _value;
|
||||
}
|
||||
|
||||
/// Value setter
|
||||
///
|
||||
void set(T v) {
|
||||
_value = v;
|
||||
}
|
||||
|
||||
/// Combined set and save
|
||||
///
|
||||
void set_and_save(T v) {
|
||||
set(v);
|
||||
save();
|
||||
}
|
||||
|
||||
// Note no attempt to do anything fancy with the assignment or cast operators.
|
||||
// Implementing something that works reliably and produces readable code is not
|
||||
// straightforward, so it's been deferred for now.
|
||||
|
||||
protected:
|
||||
T _value;
|
||||
};
|
||||
|
||||
/// Template class for array variables.
|
||||
///
|
||||
/// Objects created using this template behave like arrays of the type T,
|
||||
/// but are stored like single variables.
|
||||
///
|
||||
/// Note that the size of the array cannot be greater than AP_Var::k_size_max
|
||||
/// if it is ever to be loaded or saved to EEPROM.
|
||||
/// Unfortunately there is no way to assert this at compile time; a larger
|
||||
/// array will compile correctly and work as expected, except that ::save and
|
||||
/// ::load will always fail.
|
||||
///
|
||||
/// For example:
|
||||
///
|
||||
/// AP_VarA<float,4> float_array(some_key, PSTR("some_name"));
|
||||
///
|
||||
/// will create float_array as an array of four floats, stored using the
|
||||
/// key some_key and visible with the name 'some_name'.
|
||||
///
|
||||
/// @note Like regular arrays, the initial value of the members of the
|
||||
/// array is zero if the object is declared with global scope, and
|
||||
/// undefined if declared in a block (function, etc.).
|
||||
///
|
||||
/// @tparam T The scalar type of the variable
|
||||
///
|
||||
template<typename T, uint8_t N>
|
||||
class AP_VarA : public AP_Var
|
||||
{
|
||||
public:
|
||||
/// Constructor for non-grouped array.
|
||||
///
|
||||
/// Initializes a stand-alone array with optional storage key, name and flags.
|
||||
///
|
||||
/// @param key Storage key for the variable. If not set, or set to AP_Var::k_key_none
|
||||
/// 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 behavior of the variable.
|
||||
///
|
||||
AP_VarA<T,N> (Key with_key = k_key_none,
|
||||
const prog_char_t *name = NULL,
|
||||
Flags flags = k_flags_none) :
|
||||
AP_Var(with_key, name, flags)
|
||||
{
|
||||
_bytes_in_use += sizeof(*this);
|
||||
}
|
||||
|
||||
/// Constructor for a grouped array.
|
||||
///
|
||||
/// Initializes 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 name An optional name by which the variable may be known.
|
||||
/// @param flags Optional flags that may affect the behavior of the variable.
|
||||
///
|
||||
AP_VarA<T,N> (AP_Var_group *with_group, // XXX maybe make this a ref?
|
||||
Key index,
|
||||
const prog_char_t *name = NULL,
|
||||
Flags flags = k_flags_none) :
|
||||
AP_Var(with_group, index, name, flags)
|
||||
{
|
||||
_bytes_in_use += sizeof(*this);
|
||||
}
|
||||
|
||||
// serialize _value into the buffer, but only if it is big enough.
|
||||
//
|
||||
virtual size_t serialize(void *buf, size_t size) const {
|
||||
if (size >= sizeof(_value)) {
|
||||
memcpy(buf, &_value[0], sizeof(_value));
|
||||
}
|
||||
return sizeof(_value);
|
||||
}
|
||||
|
||||
// Unserialize from the buffer, but only if it is big enough.
|
||||
//
|
||||
virtual size_t unserialize(void *buf, size_t size) {
|
||||
if (size >= sizeof(_value)) {
|
||||
memcpy(&_value[0], buf, sizeof(_value));
|
||||
}
|
||||
return sizeof(_value);
|
||||
}
|
||||
|
||||
/// Array operator accesses members.
|
||||
///
|
||||
/// @note It would be nice to range-check i here, but then what would we return?
|
||||
///
|
||||
T &operator [](uint8_t i) {
|
||||
return _value[i];
|
||||
}
|
||||
|
||||
/// Value getter
|
||||
///
|
||||
/// @note Returns zero for index values out of range.
|
||||
///
|
||||
T get(uint8_t i) const {
|
||||
if (i < N) {
|
||||
return _value[i];
|
||||
} else {
|
||||
return (T)0;
|
||||
}
|
||||
}
|
||||
|
||||
/// Value setter
|
||||
///
|
||||
/// @note Attempts to set an index out of range are discarded.
|
||||
///
|
||||
void set(uint8_t i, T v) {
|
||||
if (i < N) {
|
||||
_value[i] = v;
|
||||
}
|
||||
}
|
||||
|
||||
/// Copy assignment from self does nothing.
|
||||
///
|
||||
AP_VarA<T,N>& operator=(AP_VarA<T,N>& v) {
|
||||
return v;
|
||||
}
|
||||
|
||||
protected:
|
||||
T _value[N];
|
||||
};
|
||||
|
||||
/// Convenience macro for defining instances of the AP_VarT template.
|
||||
///
|
||||
#define AP_VARDEF(_t, _n) typedef AP_VarT<_t> AP_##_n;
|
||||
AP_VARDEF(float, Float); // defines AP_Float
|
||||
AP_VARDEF(int8_t, Int8); // defines AP_Int8
|
||||
AP_VARDEF(int16_t, Int16); // defines AP_Int16
|
||||
AP_VARDEF(int32_t, Int32); // defines AP_Int32
|
||||
|
||||
/// Rely on built in casting for other variable types
|
||||
/// to minimize template creation and save memory
|
||||
#define AP_Uint8 AP_Int8
|
||||
#define AP_Uint16 AP_Int16
|
||||
#define AP_Uint32 AP_Int32
|
||||
#define AP_Bool AP_Int8
|
||||
|
||||
/// Many float values can be saved as 16-bit fixed-point values, reducing EEPROM
|
||||
/// consumption. AP_Float16 subclasses AP_Float and overloads serialize/unserialize
|
||||
/// 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),
|
||||
/// giving an effective range of approximately +/-31.999
|
||||
///
|
||||
class AP_Float16 : public AP_Float
|
||||
{
|
||||
public:
|
||||
/// Constructors mimic AP_Float::AP_Float()
|
||||
///
|
||||
AP_Float16(float initial_value = 0,
|
||||
Key with_key = k_key_none,
|
||||
const prog_char_t *name = NULL,
|
||||
Flags flags = k_flags_none) :
|
||||
AP_Float(initial_value, with_key, name, flags)
|
||||
{
|
||||
_bytes_in_use += sizeof(*this);
|
||||
}
|
||||
|
||||
AP_Float16(AP_Var_group *with_group,
|
||||
Key index,
|
||||
float initial_value = 0,
|
||||
const prog_char_t *name = NULL,
|
||||
Flags flags = k_flags_none) :
|
||||
AP_Float(with_group, index, initial_value, name, flags)
|
||||
{
|
||||
_bytes_in_use += sizeof(*this);
|
||||
}
|
||||
|
||||
|
||||
// Serialize _value as Q5.10.
|
||||
//
|
||||
virtual size_t serialize(void *buf, size_t size) const {
|
||||
uint16_t *sval = (uint16_t *)buf;
|
||||
|
||||
if (size >= sizeof(*sval)) {
|
||||
*sval = _value * 1024.0; // scale by power of 2, may be more efficient
|
||||
}
|
||||
return sizeof(*sval);
|
||||
}
|
||||
|
||||
// Unserialize _value from Q5.10.
|
||||
//
|
||||
virtual size_t unserialize(void *buf, size_t size) {
|
||||
uint16_t *sval = (uint16_t *)buf;
|
||||
|
||||
if (size >= sizeof(*sval)) {
|
||||
_value = (float)*sval / 1024.0; // scale by power of 2, may be more efficient
|
||||
return sizeof(*sval);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// copy operators must be redefined in subclasses to get correct behavior
|
||||
AP_Float16 &operator=(AP_Float16 &v) {
|
||||
return v;
|
||||
}
|
||||
AP_Float16 &operator=(float v) {
|
||||
_value = v;
|
||||
return *this;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
/// Some convenient constant AP_Vars.
|
||||
///
|
||||
/// @todo Work out why these can't be const and fix if possible.
|
||||
///
|
||||
/// @todo Work out how to get these into a namespace and name them properly.
|
||||
///
|
||||
extern AP_Float AP_Float_unity;
|
||||
extern AP_Float AP_Float_negative_unity;
|
||||
extern AP_Float AP_Float_zero;
|
||||
|
||||
|
||||
/// Print the value of an AP_Var
|
||||
///
|
||||
/// This function knows about the types listed in the AP_Var::k_typeid_* constants,
|
||||
/// and it will print their value using Serial, which is assumed to be a BetterStream
|
||||
/// serial port (e.g. FastSerial).
|
||||
///
|
||||
/// @param vp The variable to print.
|
||||
///
|
||||
extern void AP_Var_print(AP_Var *vp);
|
||||
|
||||
#ifndef __AP_COMMON_MENU_H
|
||||
#include "include/menu.h"
|
||||
#endif
|
||||
|
||||
/// Menu function for setting an AP_Var.
|
||||
///
|
||||
/// This function can be directly called from a Menu. It expects two args, the
|
||||
/// first is the full name of a variable, the second is the value to set the variable
|
||||
/// to.
|
||||
///
|
||||
/// This function knows about the types listed in the AP_Var::k_typeid_* constants.
|
||||
///
|
||||
/// @param argc The number of arguments; must be 3.
|
||||
/// @param argv Arguments. argv[1] must contain a variable name, argv[2].f and .i
|
||||
/// are consulted when setting the value.
|
||||
/// @return Zero if the variable was set successfully, -1 if it could not be set.
|
||||
///
|
||||
extern int8_t AP_Var_menu_set(uint8_t argc, const Menu::arg *argv);
|
||||
|
||||
/// Menu function for displaying AP_Vars.
|
||||
///
|
||||
/// This function can be directly called from a Menu. It expects zero or one argument.
|
||||
/// If no arguments are supplied, all known variables are printed. If an argument is
|
||||
/// supplied, it is expected to be the name of a variable and that variable will be
|
||||
/// printed.
|
||||
///
|
||||
/// @param argc The number of arguments, must be 1 or 2.
|
||||
/// @param argv Argument array; argv[1] may be set to the name of a variable to show.
|
||||
///
|
||||
extern int8_t AP_Var_menu_show(uint8_t argc, const Menu::arg *argv);
|
||||
|
||||
|
||||
#endif // AP_VAR_H
|
@ -1,348 +0,0 @@
|
||||
//
|
||||
// Unit tests for the AP_Meta_class and AP_Var classes.
|
||||
//
|
||||
|
||||
#define USE_AP_VAR
|
||||
#include <FastSerial.h>
|
||||
#include <AP_Common.h>
|
||||
#include <AP_Test.h>
|
||||
#include <AP_Var.h>
|
||||
#include <AP_Math.h>
|
||||
#include <string.h>
|
||||
|
||||
// we need to do this, even though normally it's a bad idea
|
||||
#pragma GCC diagnostic ignored "-Wfloat-equal"
|
||||
|
||||
FastSerialPort(Serial, 0);
|
||||
|
||||
//
|
||||
// Unit tests
|
||||
//
|
||||
void
|
||||
setup(void)
|
||||
{
|
||||
Serial.begin(115200);
|
||||
Serial.println("AP_Var unit tests.\n");
|
||||
|
||||
// MetaClass: test type ID
|
||||
{
|
||||
TEST(meta_type_id);
|
||||
|
||||
AP_Float f1(0);
|
||||
AP_Float f2(0);
|
||||
AP_Int8 i1(0);
|
||||
|
||||
uint16_t m1 = f1.meta_type_id();
|
||||
uint16_t m2 = f2.meta_type_id();
|
||||
uint16_t m3 = i1.meta_type_id();
|
||||
uint16_t m4 = AP_Meta_class::meta_type_id<AP_Float>();
|
||||
|
||||
REQUIRE(m1 != 0);
|
||||
REQUIRE(m1 == m2);
|
||||
REQUIRE(m1 != m3);
|
||||
REQUIRE(m1 == m4);
|
||||
}
|
||||
|
||||
// MetaClass: meta_type_equivalent
|
||||
{
|
||||
TEST(meta_type_equivalent);
|
||||
|
||||
AP_Float f1;
|
||||
AP_Float f2;
|
||||
AP_Int8 i1;
|
||||
|
||||
REQUIRE(AP_Meta_class::meta_type_equivalent(&f1, &f2));
|
||||
REQUIRE(!AP_Meta_class::meta_type_equivalent(&f1, &i1));
|
||||
}
|
||||
|
||||
|
||||
// MetaClass: external handles
|
||||
{
|
||||
TEST(meta_handle);
|
||||
|
||||
AP_Float f(0);
|
||||
AP_Meta_class::Meta_handle h = f.meta_get_handle();
|
||||
|
||||
REQUIRE(0 != h);
|
||||
REQUIRE(NULL != AP_Meta_class::meta_validate_handle(h));
|
||||
REQUIRE(NULL == AP_Meta_class::meta_validate_handle(h + 1));
|
||||
}
|
||||
|
||||
// MetaClass: test meta_cast
|
||||
{
|
||||
TEST(meta_cast);
|
||||
|
||||
AP_Float f(0);
|
||||
|
||||
REQUIRE(NULL != AP_Meta_class::meta_cast<AP_Float>(&f));
|
||||
REQUIRE(NULL == AP_Meta_class::meta_cast<AP_Int8>(&f));
|
||||
}
|
||||
|
||||
// MetaClass: ... insert tests here ...
|
||||
|
||||
// AP_Var: constants
|
||||
{
|
||||
TEST(var_constants);
|
||||
|
||||
REQUIRE(AP_Float_zero == 0);
|
||||
REQUIRE(AP_Float_unity == 1.0);
|
||||
REQUIRE(AP_Float_negative_unity = -1.0);
|
||||
}
|
||||
|
||||
// AP_Var: type IDs
|
||||
{
|
||||
TEST(var_type_ids);
|
||||
|
||||
AP_Float f;
|
||||
AP_Float16 fs;
|
||||
AP_Int32 l;
|
||||
AP_Int16 s;
|
||||
AP_Int8 b;
|
||||
|
||||
REQUIRE(f.meta_type_id() == AP_Var::k_typeid_float);
|
||||
REQUIRE(fs.meta_type_id() == AP_Var::k_typeid_float16);
|
||||
REQUIRE(l.meta_type_id() == AP_Var::k_typeid_int32);
|
||||
REQUIRE(s.meta_type_id() == AP_Var::k_typeid_int16);
|
||||
REQUIRE(b.meta_type_id() == AP_Var::k_typeid_int8);
|
||||
|
||||
REQUIRE(AP_Var::k_typeid_float != AP_Var::k_typeid_int32);
|
||||
}
|
||||
|
||||
// AP_Var: initial value
|
||||
{
|
||||
TEST(var_initial_value);
|
||||
|
||||
AP_Float f1(12.345);
|
||||
AP_Float f2;
|
||||
|
||||
REQUIRE(f1 == 12.345);
|
||||
REQUIRE(f2 == 0);
|
||||
}
|
||||
|
||||
// AP_Var: set, get, assignment
|
||||
{
|
||||
TEST(var_set_get);
|
||||
|
||||
AP_Float f(1.0);
|
||||
|
||||
REQUIRE(f == 1.0);
|
||||
REQUIRE(f.get() == 1.0);
|
||||
|
||||
f.set(10.0);
|
||||
REQUIRE(f == 10.0);
|
||||
REQUIRE(f.get() == 10.0);
|
||||
}
|
||||
|
||||
// AP_Var: cast to type
|
||||
{
|
||||
TEST(var_cast_to_type);
|
||||
|
||||
AP_Float f(1.0);
|
||||
|
||||
f *= 2.0;
|
||||
REQUIRE(f == 2.0);
|
||||
f /= 4;
|
||||
REQUIRE(f == 0.5);
|
||||
f += f;
|
||||
REQUIRE(f == 1.0);
|
||||
}
|
||||
|
||||
// AP_Var: equality
|
||||
{
|
||||
TEST(var_equality);
|
||||
|
||||
AP_Float f1(1.0);
|
||||
AP_Float f2(1.0);
|
||||
AP_Float f3(2.0);
|
||||
|
||||
REQUIRE(f1 == f2);
|
||||
REQUIRE(f2 != f3);
|
||||
}
|
||||
|
||||
// AP_Var: naming
|
||||
{
|
||||
TEST(var_naming);
|
||||
|
||||
AP_Float f(0, AP_Var::k_key_none, PSTR("test"));
|
||||
char name_buffer[16];
|
||||
|
||||
f.copy_name(name_buffer, sizeof(name_buffer));
|
||||
REQUIRE(!strcmp(name_buffer, "test"));
|
||||
}
|
||||
|
||||
// AP_Var: arrays
|
||||
{
|
||||
TEST(var_array);
|
||||
|
||||
AP_VarA<float,4> fa;
|
||||
|
||||
fa[0] = 1.0;
|
||||
fa[1] = 10.0;
|
||||
fa.set(2, 100.0);
|
||||
fa[3] = -1000.0;
|
||||
|
||||
REQUIRE(fa.get(0) == 1.0);
|
||||
REQUIRE(fa.get(1) == 10.0);
|
||||
REQUIRE(fa.get(2) == 100.0);
|
||||
REQUIRE(fa.get(3) == -1000.0);
|
||||
}
|
||||
|
||||
// AP_Var: serialize
|
||||
// note that this presumes serialisation to the native in-memory format
|
||||
{
|
||||
TEST(var_serialize);
|
||||
|
||||
float b = 0;
|
||||
AP_Float f(10.0);
|
||||
size_t s;
|
||||
|
||||
s = f.serialize(&b, sizeof(b));
|
||||
REQUIRE(s == sizeof(b));
|
||||
REQUIRE(b == 10.0);
|
||||
}
|
||||
|
||||
// AP_Var: unserialize
|
||||
{
|
||||
TEST(var_unserialize);
|
||||
|
||||
float b = 10;
|
||||
AP_Float f(0);
|
||||
size_t s;
|
||||
|
||||
s = f.unserialize(&b, sizeof(b));
|
||||
REQUIRE(s == sizeof(b));
|
||||
REQUIRE(f == 10);
|
||||
}
|
||||
|
||||
// AP_Var: groups and names
|
||||
{
|
||||
TEST(group_names);
|
||||
|
||||
AP_Var_group group(AP_Var::k_key_none, 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"));
|
||||
}
|
||||
|
||||
// AP_Var: enumeration
|
||||
{
|
||||
TEST(empty_variables);
|
||||
|
||||
REQUIRE(AP_Var::first() == NULL);
|
||||
}
|
||||
|
||||
{
|
||||
TEST(enumerate_variables);
|
||||
|
||||
AP_Float f1;
|
||||
|
||||
REQUIRE(AP_Var::first() == &f1);
|
||||
|
||||
{
|
||||
AP_Var_group group;
|
||||
AP_Var f2(&group, 0, 0);
|
||||
AP_Var f3(&group, 1, 0);
|
||||
AP_Var *vp;
|
||||
|
||||
vp = AP_Var::first();
|
||||
REQUIRE(vp == &group); // XXX presumes FIFO insertion
|
||||
vp = vp->next();
|
||||
REQUIRE(vp == &f1); // XXX presumes FIFO insertion
|
||||
vp = vp->next();
|
||||
REQUIRE(vp == &f2); // first variable in the grouped list
|
||||
|
||||
vp = AP_Var::first_member(&group);
|
||||
REQUIRE(vp == &f2);
|
||||
vp = vp->next_member();
|
||||
REQUIRE(vp == &f3);
|
||||
}
|
||||
}
|
||||
|
||||
// AP_Var: save and load
|
||||
{
|
||||
TEST(var_save_load);
|
||||
|
||||
AP_Float f1(10.0, 1);
|
||||
AP_Float16 f2(1.23, 2);
|
||||
|
||||
AP_Var::erase_all();
|
||||
REQUIRE(true == f1.save());
|
||||
REQUIRE(f1 == 10.0);
|
||||
f1 = 0;
|
||||
REQUIRE(true == f1.load());
|
||||
REQUIRE(f1 == 10.0);
|
||||
|
||||
REQUIRE(true == f2.save());
|
||||
REQUIRE(f2 == 1.23);
|
||||
f2 = 0;
|
||||
REQUIRE(true == f2.load());
|
||||
REQUIRE(f2 == 1.23);
|
||||
|
||||
}
|
||||
|
||||
// AP_Var: reload
|
||||
{
|
||||
TEST(var_reload);
|
||||
|
||||
AP_Float f1(0, 1);
|
||||
|
||||
REQUIRE(true == f1.load());
|
||||
REQUIRE(f1 == 10.0);
|
||||
|
||||
AP_Var::erase_all();
|
||||
}
|
||||
|
||||
// AP_Var: save/load all
|
||||
{
|
||||
TEST(var_save_load_all);
|
||||
|
||||
AP_Float f1(10.0, 1);
|
||||
AP_Float f2(123.0, 2);
|
||||
AP_Int8 i(17, 3);
|
||||
|
||||
REQUIRE(true == AP_Var::save_all());
|
||||
f1 = 0;
|
||||
f2 = 0;
|
||||
i = 0;
|
||||
REQUIRE(true == AP_Var::load_all());
|
||||
REQUIRE(f1 == 10.0);
|
||||
REQUIRE(f2 == 123.0);
|
||||
REQUIRE(i == 17);
|
||||
|
||||
AP_Var::erase_all();
|
||||
}
|
||||
|
||||
// AP_Var: group load/save
|
||||
{
|
||||
TEST(var_group_save_load);
|
||||
|
||||
AP_Var_group group(10);
|
||||
AP_Float f1(&group, 0, 10.0);
|
||||
AP_Float f2(&group, 1, 123.0);
|
||||
AP_Float f3(-1.0);
|
||||
AP_Float16 f4(&group, 2, 7);
|
||||
|
||||
REQUIRE(true == group.save());
|
||||
f1 = 0;
|
||||
f2 = 0;
|
||||
f3 = 0;
|
||||
f4 = 0;
|
||||
REQUIRE(true == group.load());
|
||||
REQUIRE(f1 == 10.0);
|
||||
REQUIRE(f2 == 123.0);
|
||||
REQUIRE(f3 == 0);
|
||||
REQUIRE(f4 == 7);
|
||||
|
||||
AP_Var::erase_all();
|
||||
}
|
||||
|
||||
|
||||
Test::report();
|
||||
}
|
||||
|
||||
void
|
||||
loop(void)
|
||||
{
|
||||
}
|
@ -1 +0,0 @@
|
||||
include ../../../AP_Common/Arduino.mk
|
Loading…
Reference in New Issue
Block a user