EEPROM file storage, and argparsing that actually works

This commit is contained in:
John Stäck 2019-06-24 11:56:57 +02:00
parent 3b2f0fa4cf
commit 829c08c031
5 changed files with 4385 additions and 492 deletions

View file

@ -2,6 +2,7 @@
#define __EEPROM_H #define __EEPROM_H
#include <stdint.h> #include <stdint.h>
#include <stdio.h>
struct EEPROMClass struct EEPROMClass
{ {
@ -36,12 +37,19 @@ struct EEPROMClass
// return t; // return t;
// } // }
//Make EEPROM persistent by storing to a file
int16_t setStorage(const char* filename, bool write);
void closeStorage();
private: private:
char someFakeEEPROM_memory[2048]; uint8_t someFakeEEPROM_memory[2048]; //Teensy 3.2 size
FILE *storage;
bool autoUpdate;
}; };
static EEPROMClass EEPROM __attribute__ ((unused)); extern EEPROMClass EEPROM __unused;
#endif #endif

View file

@ -1,468 +0,0 @@
/*
This library comes from https://github.com/hbristow/argparse
Copyright (c) 2017, Hilton Bristow
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef ARGPARSE_HPP_
#define ARGPARSE_HPP_
#if __cplusplus >= 201103L
#include <unordered_map>
typedef std::unordered_map<std::string, size_t> IndexMap;
#else
#include <map>
typedef std::map<std::string, size_t> IndexMap;
#endif
#include <string>
#include <vector>
#include <typeinfo>
#include <stdexcept>
#include <sstream>
#include <iostream>
#include <cassert>
#include <algorithm>
/*! @class ArgumentParser
* @brief A simple command-line argument parser based on the design of
* python's parser of the same name.
*
* ArgumentParser is a simple C++ class that can parse arguments from
* the command-line or any array of strings. The syntax is familiar to
* anyone who has used python's ArgumentParser:
* \code
* // create a parser and add the options
* ArgumentParser parser;
* parser.addArgument("-n", "--name");
* parser.addArgument("--inputs", '+');
*
* // parse the command-line arguments
* parser.parse(argc, argv);
*
* // get the inputs and iterate over them
* string name = parser.retrieve("name");
* vector<string> inputs = parser.retrieve<vector<string>>("inputs");
* \endcode
*
*/
class ArgumentParser {
private:
class Any;
struct Argument;
class PlaceHolder;
class Holder;
typedef std::string String;
typedef std::vector<Any> AnyVector;
typedef std::vector<String> StringVector;
typedef std::vector<Argument> ArgumentVector;
// --------------------------------------------------------------------------
// Type-erasure internal storage
// --------------------------------------------------------------------------
class Any {
public:
// constructor
Any() : content(0) {}
// destructor
~Any() { delete content; }
// INWARD CONVERSIONS
Any(const Any& other) : content(other.content ? other.content->clone() : 0) {}
template <typename ValueType>
Any(const ValueType& other)
: content(new Holder<ValueType>(other)) {}
Any& swap(Any& other) {
std::swap(content, other.content);
return *this;
}
Any& operator=(const Any& rhs) {
Any tmp(rhs);
return swap(tmp);
}
template <typename ValueType>
Any& operator=(const ValueType& rhs) {
Any tmp(rhs);
return swap(tmp);
}
// OUTWARD CONVERSIONS
template <typename ValueType>
ValueType* toPtr() const {
return content->type_info() == typeid(ValueType)
? &static_cast<Holder<ValueType>*>(content)->held_
: 0;
}
template <typename ValueType>
ValueType& castTo() {
if (!toPtr<ValueType>()) throw std::bad_cast();
return *toPtr<ValueType>();
}
template <typename ValueType>
const ValueType& castTo() const {
if (!toPtr<ValueType>()) throw std::bad_cast();
return *toPtr<ValueType>();
}
private:
// Inner placeholder interface
class PlaceHolder {
public:
virtual ~PlaceHolder() {}
virtual const std::type_info& type_info() const = 0;
virtual PlaceHolder* clone() const = 0;
};
// Inner template concrete instantiation of PlaceHolder
template <typename ValueType>
class Holder : public PlaceHolder {
public:
ValueType held_;
Holder(const ValueType& value) : held_(value) {}
virtual const std::type_info& type_info() const { return typeid(ValueType); }
virtual PlaceHolder* clone() const { return new Holder(held_); }
};
PlaceHolder* content;
};
// --------------------------------------------------------------------------
// Argument
// --------------------------------------------------------------------------
static String delimit(const String& name) {
return String(std::min(name.size(), (size_t)2), '-').append(name);
}
static String strip(const String& name) {
size_t begin = 0;
begin += name.size() > 0 ? name[0] == '-' : 0;
begin += name.size() > 3 ? name[1] == '-' : 0;
return name.substr(begin);
}
static String upper(const String& in) {
String out(in);
std::transform(out.begin(), out.end(), out.begin(), ::toupper);
return out;
}
static String escape(const String& in) {
String out(in);
if (in.find(' ') != std::string::npos) out = String("\"").append(out).append("\"");
return out;
}
struct Argument {
Argument() : short_name(""), name(""), optional(true), fixed_nargs(0), fixed(true) {}
Argument(const String& _short_name, const String& _name, bool _optional, char nargs)
: short_name(_short_name), name(_name), optional(_optional) {
if (nargs == '+' || nargs == '*') {
variable_nargs = nargs;
fixed = false;
} else {
fixed_nargs = nargs;
fixed = true;
}
}
String short_name;
String name;
bool optional;
union {
size_t fixed_nargs;
char variable_nargs;
};
bool fixed;
String canonicalName() const { return (name.empty()) ? short_name : name; }
String toString(bool named = true) const {
std::ostringstream s;
String uname = name.empty() ? upper(strip(short_name)) : upper(strip(name));
if (named && optional) s << "[";
if (named) s << canonicalName();
if (fixed) {
size_t N = std::min((size_t)3, fixed_nargs);
for (size_t n = 0; n < N; ++n) s << " " << uname;
if (N < fixed_nargs) s << " ...";
}
if (!fixed) {
s << " ";
if (variable_nargs == '*') s << "[";
s << uname << " ";
if (variable_nargs == '+') s << "[";
s << uname << "...]";
}
if (named && optional) s << "]";
return s.str();
}
};
void insertArgument(const Argument& arg) {
size_t N = arguments_.size();
arguments_.push_back(arg);
if (arg.fixed && arg.fixed_nargs <= 1) {
variables_.push_back(String());
} else {
variables_.push_back(StringVector());
}
if (!arg.short_name.empty()) index_[arg.short_name] = N;
if (!arg.name.empty()) index_[arg.name] = N;
if (!arg.optional) required_++;
}
// --------------------------------------------------------------------------
// Error handling
// --------------------------------------------------------------------------
void argumentError(const std::string& msg, bool show_usage = false) {
if (use_exceptions_) throw std::invalid_argument(msg);
std::cerr << "ArgumentParser error: " << msg << std::endl;
if (show_usage) std::cerr << usage() << std::endl;
exit(-5);
}
// --------------------------------------------------------------------------
// Member variables
// --------------------------------------------------------------------------
IndexMap index_;
bool ignore_first_;
bool use_exceptions_;
size_t required_;
String app_name_;
String final_name_;
ArgumentVector arguments_;
AnyVector variables_;
public:
ArgumentParser() : ignore_first_(true), use_exceptions_(false), required_(0) {}
// --------------------------------------------------------------------------
// addArgument
// --------------------------------------------------------------------------
void appName(const String& name) { app_name_ = name; }
void addArgument(const String& name, char nargs = 0, bool optional = true) {
if (name.size() > 2) {
Argument arg("", verify(name), optional, nargs);
insertArgument(arg);
} else {
Argument arg(verify(name), "", optional, nargs);
insertArgument(arg);
}
}
void addArgument(const String& short_name, const String& name, char nargs = 0,
bool optional = true) {
Argument arg(verify(short_name), verify(name), optional, nargs);
insertArgument(arg);
}
void addFinalArgument(const String& name, char nargs = 1, bool optional = false) {
final_name_ = delimit(name);
Argument arg("", final_name_, optional, nargs);
insertArgument(arg);
}
void ignoreFirstArgument(bool ignore_first) { ignore_first_ = ignore_first; }
String verify(const String& name) {
if (name.empty()) argumentError("argument names must be non-empty");
if ((name.size() == 2 && name[0] != '-') || name.size() == 3)
argumentError(String("invalid argument '")
.append(name)
.append("'. Short names must begin with '-'"));
if (name.size() > 3 && (name[0] != '-' || name[1] != '-'))
argumentError(String("invalid argument '")
.append(name)
.append("'. Multi-character names must begin with '--'"));
return name;
}
// --------------------------------------------------------------------------
// Parse
// --------------------------------------------------------------------------
void parse(size_t argc, const char** argv) { parse(StringVector(argv, argv + argc)); }
void parse(const StringVector& argv) {
// check if the app is named
if (app_name_.empty() && ignore_first_ && !argv.empty()) app_name_ = argv[0];
// set up the working set
Argument active;
Argument final = final_name_.empty() ? Argument() : arguments_[index_[final_name_]];
size_t consumed = 0;
size_t nrequired = final.optional ? required_ : required_ - 1;
size_t nfinal = final.optional ? 0 : (final.fixed ? final.fixed_nargs
: (final.variable_nargs == '+' ? 1 : 0));
// iterate over each element of the array
for (StringVector::const_iterator in = argv.begin() + ignore_first_;
in < argv.end() - nfinal; ++in) {
String active_name = active.canonicalName();
String el = *in;
// check if the element is a key
if (index_.count(el) == 0) {
// input
// is the current active argument expecting more inputs?
if (active.fixed && active.fixed_nargs <= consumed)
argumentError(String("attempt to pass too many inputs to ").append(active_name),
true);
if (active.fixed && active.fixed_nargs == 1) {
variables_[index_[active_name]].castTo<String>() = el;
} else {
variables_[index_[active_name]].castTo<StringVector>().push_back(el);
}
consumed++;
} else {
// new key!
// has the active argument consumed enough elements?
if ((active.fixed && active.fixed_nargs != consumed) ||
(!active.fixed && active.variable_nargs == '+' && consumed < 1))
argumentError(String("encountered argument ")
.append(el)
.append(" when expecting more inputs to ")
.append(active_name),
true);
active = arguments_[index_[el]];
// check if we've satisfied the required arguments
if (active.optional && nrequired > 0)
argumentError(String("encountered optional argument ")
.append(el)
.append(" when expecting more required arguments"),
true);
// are there enough arguments for the new argument to consume?
if ((active.fixed && active.fixed_nargs > (argv.end() - in - nfinal - 1)) ||
(!active.fixed && active.variable_nargs == '+' &&
!(argv.end() - in - nfinal - 1)))
argumentError(String("too few inputs passed to argument ").append(el), true);
if (!active.optional) nrequired--;
consumed = 0;
}
}
for (StringVector::const_iterator in =
std::max(argv.begin() + ignore_first_, argv.end() - nfinal);
in != argv.end(); ++in) {
String el = *in;
// check if we accidentally find an argument specifier
if (index_.count(el))
argumentError(String("encountered argument specifier ")
.append(el)
.append(" while parsing final required inputs"),
true);
if (final.fixed && final.fixed_nargs == 1) {
variables_[index_[final_name_]].castTo<String>() = el;
} else {
variables_[index_[final_name_]].castTo<StringVector>().push_back(el);
}
nfinal--;
}
// check that all of the required arguments have been encountered
if (nrequired > 0 || nfinal > 0)
argumentError(String("too few required arguments passed to ").append(app_name_), true);
}
// --------------------------------------------------------------------------
// Retrieve
// --------------------------------------------------------------------------
template <typename T>
T& retrieve(const String& name) {
if (index_.count(delimit(name)) == 0) throw std::out_of_range("Key not found");
size_t N = index_[delimit(name)];
return variables_[N].castTo<T>();
}
// --------------------------------------------------------------------------
// Properties
// --------------------------------------------------------------------------
String usage() {
// premable app name
std::ostringstream help;
help << "Usage: " << escape(app_name_);
size_t indent = help.str().size();
size_t linelength = 0;
// get the required arguments
for (ArgumentVector::const_iterator it = arguments_.begin(); it != arguments_.end(); ++it) {
Argument arg = *it;
if (arg.optional) continue;
if (arg.name.compare(final_name_) == 0) continue;
help << " ";
String argstr = arg.toString();
if (argstr.size() + linelength > 80) {
help << "\n" << String(indent, ' ');
linelength = 0;
} else {
linelength += argstr.size();
}
help << argstr;
}
// get the optional arguments
for (ArgumentVector::const_iterator it = arguments_.begin(); it != arguments_.end(); ++it) {
Argument arg = *it;
if (!arg.optional) continue;
if (arg.name.compare(final_name_) == 0) continue;
help << " ";
String argstr = arg.toString();
if (argstr.size() + linelength > 80) {
help << "\n" << String(indent, ' ');
linelength = 0;
} else {
linelength += argstr.size();
}
help << argstr;
}
// get the final argument
if (!final_name_.empty()) {
Argument arg = arguments_[index_[final_name_]];
String argstr = arg.toString(false);
if (argstr.size() + linelength > 80) {
help << "\n" << String(indent, ' ');
linelength = 0;
} else {
linelength += argstr.size();
}
help << argstr;
}
return help.str();
}
void useExceptions(bool state) { use_exceptions_ = state; }
bool empty() const { return index_.empty(); }
void clear() {
ignore_first_ = true;
required_ = 0;
index_.clear();
arguments_.clear();
variables_.clear();
}
bool exists(const String& name) const { return index_.count(delimit(name)) > 0; }
size_t count(const String& name) {
// check if the name is an argument
if (index_.count(delimit(name)) == 0) return 0;
size_t N = index_[delimit(name)];
Argument arg = arguments_[N];
Any var = variables_[N];
// check if the argument is a vector
if (arg.fixed) {
return !var.castTo<String>().empty();
} else {
return var.castTo<StringVector>().size();
}
}
};
#endif

4265
simulation/src/args.hxx Normal file

File diff suppressed because it is too large Load diff

View file

@ -12,15 +12,16 @@
#include "GL/gl3w.h" #include "GL/gl3w.h"
#include "examples/imgui_impl_sdl.h" #include "examples/imgui_impl_sdl.h"
#include "examples/imgui_impl_opengl3.h" #include "examples/imgui_impl_opengl3.h"
#include "EEPROM.h"
#include <Arduino.h> #include <Arduino.h>
#include "argparse.hpp" #include "args.hxx"
// Forward declarations // Forward declarations
static void SimQuit(void); static void SimQuit(void);
static int SimInit(void); static int SimInit(void);
static int SimRun(void); static int SimRun(std::string eepromFile, bool eepromWrite, bool factoryReset);
static void SimLoop(std::function<bool()>, std::function<void()>); static void SimLoop(std::function<bool()>, std::function<void()>);
@ -31,6 +32,8 @@ SimWire Wire;
SimSerial Serial; SimSerial Serial;
SimSerial Serial3; //Midi SimSerial Serial3; //Midi
SimUsbMidi usbMIDI; SimUsbMidi usbMIDI;
EEPROMClass EEPROM;
static const int scale = 3; static const int scale = 3;
@ -510,12 +513,20 @@ static int SimRun(std::string eepromFile, bool eepromWrite, bool factoryReset)
{ {
if( 0 != SimInit() ) { return 1; } if( 0 != SimInit() ) { return 1; }
// Dummy to always force full reset of EEPROM, to circumvent bug in NuEVI.ino //Set up EEPROM file storage
digitalInputs[mPin] = 0; if(eepromFile.length()>0) {
digitalInputs[ePin] = 0; EEPROM.setStorage(eepromFile.c_str(), eepromWrite);
}
// Holding down buttons if doing a factory reset on startup
if(factoryReset) {
digitalInputs[mPin] = 0;
digitalInputs[ePin] = 0;
}
setup(); setup();
//Let it go, let it go, not resetting any more
digitalInputs[mPin] = 1; digitalInputs[mPin] = 1;
digitalInputs[ePin] = 1; digitalInputs[ePin] = 1;
@ -585,6 +596,8 @@ static void SimQuit()
{ {
printf("Leaving Sim, see you later!\n"); printf("Leaving Sim, see you later!\n");
EEPROM.closeStorage();
if( window != NULL ) { if( window != NULL ) {
SDL_DestroyWindow( window ); SDL_DestroyWindow( window );
// SDL_FreeSurface( surface ); // SDL_FreeSurface( surface );
@ -597,25 +610,15 @@ static void SimQuit()
int main(int argc, const char** argv) int main(int argc, const char** argv)
{ {
ArgumentParser parser;
parser.addArgument("--eeprom-file", 1); args::ArgumentParser parser("This is a test program.", "This goes after the options.");
parser.addArgument("--eeprom-write");
parser.addArgument("--factory-reset");
parser.parse(argc, argv);
std::string eepromFile;
bool eepromWrite;
bool factoryReset;
eepromFile = parser.retrieve<std::string>("eeprom-file"); args::ValueFlag<std::string> eepromFile(parser, "eeprom-write", "File to use for EEPROM data", {'e', "eeprom-file"});
eepromWrite = parser.exists("eeprom-write"); args::Flag eepromWrite(parser, "eeprom-write", "Write EEPROM changes to file", {'w', "eeprom-write"});
factoryReset = parser.exists("factory-reset"); args::Flag factoryReset(parser, "factory-reset", "Trigger factory reset", {'r', "factory-reset"});
printf("%d: %s\n",eepromWrite, eepromFile.c_str()); parser.ParseCLI(argc, argv);
return 0;
return SimRun(eepromFile, eepromWrite, factoryReset); return SimRun(args::get(eepromFile), args::get(eepromWrite), args::get(factoryReset));
} }

View file

@ -8,6 +8,8 @@
EEPROMClass::EEPROMClass() { EEPROMClass::EEPROMClass() {
memset(someFakeEEPROM_memory, 0xff, sizeof(someFakeEEPROM_memory)); memset(someFakeEEPROM_memory, 0xff, sizeof(someFakeEEPROM_memory));
storage = NULL;
autoUpdate = false;
} }
@ -22,15 +24,98 @@ void EEPROMClass::write( int idx, uint8_t val )
{ {
printf("Writing to EEPROM address %u = %u\n", idx, val); printf("Writing to EEPROM address %u = %u\n", idx, val);
someFakeEEPROM_memory[idx] = val; someFakeEEPROM_memory[idx] = val;
if(autoUpdate && storage)
{
fseek(storage, idx, SEEK_SET);
fputc(val, storage);
fflush(storage);
}
} }
void EEPROMClass::update( int idx, uint8_t val ) void EEPROMClass::update( int idx, uint8_t val )
{ {
write(idx, val); write(idx, val);
} }
uint16_t EEPROMClass::length() uint16_t EEPROMClass::length()
{ {
return sizeof(someFakeEEPROM_memory); return sizeof(someFakeEEPROM_memory);
} }
// TODO: Add missing functioality.. int16_t EEPROMClass::setStorage(const char* filename, bool write)
{
//Close any open storage file
if(storage)
{
fclose(storage);
storage = NULL;
}
autoUpdate = write;
storage = fopen(filename, "rb");
//If only reading, fail if file does not exist (makes no sense otherwise)
if(!storage && !autoUpdate) {
printf("Could not open EEPROM storage file: '%s'\n", filename);
return -1;
}
if(storage)
{
printf("Reading EEPROM storage file: '%s'\n", filename);
rewind(storage);
fread(someFakeEEPROM_memory, sizeof(someFakeEEPROM_memory), 1, storage);
}
if(!autoUpdate)
{
//No need for the file anymore, close it
fclose(storage);
storage = NULL;
}
//Create file if it doesn't exist (so we can write to it)
if(!storage && autoUpdate)
{
storage = fopen(filename, "wb");
if(!storage)
{
printf("Could not create EEPROM storage file: '%s'\n", filename);
autoUpdate = false;
return -2;
}
}
if(storage && autoUpdate)
{
//Reopen file for writing without overwriting it
storage = freopen(filename, "r+b", storage);
if(!storage)
{
printf("Could not access EEPROM storage file for writing: '%s'\n", filename);
autoUpdate = false;
return -3;
}
printf("Writing any EEPROM changes to '%s'\n", filename);
}
return 0;
}
void EEPROMClass::closeStorage() {
if(storage==NULL)
{
return;
}
printf("Closing EEPROM storage\n");
fclose(storage);
storage=NULL;
}