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