Mutila: Mouse's Utilities for Arduino
Oft-used utilities: debouncing buttons, averaging samples, and so on.
PersistentSetting.h
1 #pragma once
2 
3 #include <Arduino.h>
4 #include <MutilaDebug.h>
5 #include <stdint.h>
6 #include <EEPROM.h>
7 
8 extern uint16_t PersistentSettingOffset;
9 
89 template <class T>
91 public:
102  PersistentSetting(T minimum, T maximum, T defaultValue, uint8_t copies=1, int32_t eepromOffset=-1) :
103  _eepromOffset(eepromOffset),
104  _copies(copies),
105  _minimum(minimum),
106  _maximum(maximum),
107  _defaultValue(defaultValue)
108  {
109  if (eepromOffset < 0) {
110  _eepromOffset = PersistentSettingOffset;
111  } else {
112  _eepromOffset = eepromOffset;
113  }
114  PersistentSettingOffset = _eepromOffset + size();
115  this->load();
116  }
117 
124  bool isValid(T v) {
125  return v >= _minimum && v <= _maximum;
126  }
127 
132  void reset(bool saveIt=false) {
133  _value = _defaultValue;
134  if (saveIt) {
135  save();
136  }
137  }
138 
143  T getValueAt(uint16_t address) {
144 #if defined(ESP8266) or defined(ESP32)
145 #warning ESP EEPROM functionality not yet implemented
146 #else
147  T value;
148  for (uint8_t i=0; i<sizeof(T); i++) {
149  *((uint8_t*)&value+i) = EEPROM.read(address+i);
150  }
151  return value;
152 #endif
153  }
154 
159  uint16_t counterOffset(uint8_t idx) {
160  return _eepromOffset + ((uint16_t)idx * (sizeof(T) + sizeof(uint8_t)));
161  }
162 
163  uint16_t dataOffset(uint8_t idx) {
164  return counterOffset(idx) + (_copies < 2 ? 0 : 1);
165  }
166 
167  uint8_t getCurrentIdx() {
168 #if defined(ESP8266) or defined(ESP32)
169 #warning ESP EEPROM functionality not yet implemented
170 #else
171  uint8_t i = 0;
172  for (uint8_t thisCount = EEPROM.read(counterOffset(i));
173  i < _copies;
174  i++) {
175  uint8_t nextCount = EEPROM.read(counterOffset((i+1)%_copies));
176  if ((uint8_t)(nextCount - thisCount) != 1) {
177  return i;
178  }
179  thisCount = nextCount;
180  }
181  return 0;
182 #endif
183  }
184 
189  T load() {
190  // work out in which wear-levelling slot the last value was written
191  uint8_t idx = getCurrentIdx();
192  _value = getValueAt(dataOffset(idx));
193  if (!isValid(_value)) {
194  _value = _defaultValue;
195  }
196  return _value;
197  }
198 
204  void save()
205  {
206 #if defined(ESP8266) or defined(ESP32)
207 #warning ESP EEPROM functionality not yet implemented
208 #else
209  uint8_t idx = getCurrentIdx();
210  if (getValueAt(dataOffset(idx)) == _value) {
211  return;
212  }
213 
214  uint16_t address;
215  if (_copies < 2) {
216  // no wear levelling counters or any of that stuff, just a value
217  address = _eepromOffset;
218  } else {
219  // handle wear levelling
220  uint8_t counter = EEPROM.read(counterOffset(idx));
221  idx = (idx + 1) % _copies;
222  ++counter;
223  address = counterOffset(idx);
224  EEPROM.update(address++, counter);
225  }
226 
227  //DB(F(" EEPROM write at: "));
228  //DBLN(address);
229  for (uint8_t i=0; i<sizeof(T); i++) {
230  EEPROM.update(address++, *((uint8_t*)&_value+i));
231  }
232 #endif
233  }
234 
237  T get() { return _value; }
238 
241  T getMinimum() { return _minimum; }
242 
245  T getMaximum() { return _maximum; }
246 
249  T getDefault() { return _defaultValue; }
250 
257  T operator=(T v) { if (isValid(v)) { _value = v; } return _value; }
258 
266  bool set(T v, bool saveIt=false) {
267  if (isValid(v)) {
268  _value = v;
269  if (saveIt) {
270  save();
271  }
272  return true;
273  } else {
274  return false;
275  }
276  }
277 
280  uint16_t size() {
281  if (_copies < 2) {
282  /* when we only have 1 instance, we don't store counters at all, just the value. */
283  return sizeof(T);
284  } else {
285  /* we have one counter byte + one data item per wear-level instance. */
286  return ( sizeof(uint8_t) + sizeof(T) ) * _copies;
287  }
288  }
289 
292  void dump() {
293  DB(F("off=0x"));
294  DB(_eepromOffset, HEX);
295  DB(F(" sz="));
296  DB(size());
297  DB(F(" min="));
298  DB(_minimum);
299  DB(F(" max="));
300  DB(_maximum);
301  DB(F(" val="));
302  DBLN(_value);
303  }
304 
305 private:
306  uint16_t _eepromOffset;
307  uint8_t _copies;
308 
309 protected:
310  T _value;
311  T _minimum;
312  T _maximum;
313  T _defaultValue;
314 };
315 
316 /* Sometimes it is nice to have a name attached to a PersistentSetting, but the String
317  * class is a little heavy for many sketches. NamedPersistentSetting adds a name for
318  * a PersistentSetting, with the accociated overhead, so only use this if you need the
319  * name.
320  */
321 template <class T>
323 public:
324  NamedPersistentSetting(T minimum, T maximum, T defaultValue, const char* name, uint8_t copies=1, int32_t eepromOffset=-1) :
325  PersistentSetting<T>(minimum, maximum, defaultValue, copies, eepromOffset),
326  _name(name) {
327  }
328 
329  const String& getName() { return _name; }
330  void dump() {
331  DB(_name);
332  DB(": ");
334  }
335 
336 private:
337  String _name;
338 };
339 
340 
bool set(T v, bool saveIt=false)
EEPROM-backed variables with optional wear levelling functionality.
PersistentSetting(T minimum, T maximum, T defaultValue, uint8_t copies=1, int32_t eepromOffset=-1)
void reset(bool saveIt=false)
uint16_t counterOffset(uint8_t idx)
T getValueAt(uint16_t address)