├── .github └── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── CODE_OF_CONDUCT.md ├── LICENSE ├── README.md ├── RTCVars.cpp ├── RTCVars.h ├── _config.yml ├── examples ├── FullRTCVars │ └── FullRTCVars.ino └── MinimalRTCVars │ └── MinimalRTCVars.ino ├── keywords.txt ├── library.json └── library.properties /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Compile '...' 16 | 2. Run '....' 17 | 3. Enter '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Compiler Messages** 24 | If applicable, add compiler output. 25 | 26 | **Arduino environment: (please complete the following information):** 27 | - Device: [e.g. ESP8266] 28 | - IDE Version: [e.g. 1.8.6] 29 | - Board Library version [e.g. 2.4.0] 30 | - Version of other used libs [e.g. FastLED 3.2.1] 31 | 32 | **Additional context** 33 | Add any other context about the problem here. 34 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at friedrichs.lars+rtcvars@gmail.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 highno 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | RTCVars 2 | ======= 3 | This library eases the storage of variables in reset-safe RTC memory. Variables stored there survive all kinds of resets as long as there is no hard reset. It provides boilerplate code to securely store relevant state data in RTC memory so it may survive (unexpected) reboots or deep sleeps. Supports ESP only at this time, will change in the future. 4 | 5 | Aim of the library is to make an integration into existing projects as easy as possible. 6 | 7 | # Help wanted 8 | My own main target is the ESP8266 platform. I am happy owner of a brand new ESP32 so expect support for it soon. Other plattforms can be supported too, but since its basic function relies on a real time clock (RTC) with additional RAM, it would require a plattform including such a real time clock or using an external module for it. I don't have that at home right now, so it would help a lot, if you own an rtc module, if you contact me or develop the integration yourself and make a pull request. 9 | 10 | # Installation 11 | At the moment, this library is not part of the "official" repository within the arduino gui. Therefore you need to download the library as a .zip-file and install it by hand using the arduino gui. 12 | 13 | # Quick examples 14 | ## Pseudo code 15 | The major aim of the library is seamless integration but it still requires a little change to your existing programming pattern. In pseudo code it would look like this: 16 | 17 | // pseudo code example 18 | global variable definition: a, b, c 19 | 20 | setup { 21 | registerVariablesForStorageInRTC(a); 22 | if tryToLoadFromRTC==true then 23 | do some work to recreate/enter saved state 24 | else 25 | do "cold boot" work 26 | endif 27 | } 28 | 29 | loop { 30 | do loop work 31 | if stateRelevantChangeOf(a) then 32 | storeVarsIntoRTC 33 | endif 34 | } 35 | 36 | The magic lies in the first if statement. If the loading of a valid set of values works, you will (most likely) need to add some code, so that your look will enter the correct state. 37 | 38 | ## Basic example 39 | Let's see some real action: 40 | 41 | #include 42 | RTCVars state; // create the state object 43 | 44 | int reset_counter; // we want to keep these values after reset 45 | int program_step; 46 | 47 | void setup() { 48 | Serial.begin(115200); // allow debug output 49 | 50 | state.registerVar( &reset_counter ); // we send a pointer to each of our variables 51 | state.registerVar( &program_step ); 52 | 53 | if (state.loadFromRTC()) { // we load the values from rtc memory back into the registered variables 54 | reset_counter++; 55 | Serial.println("This is reset no. " + (String)reset_counter); 56 | state.saveToRTC(); // since we changed a state relevant variable, we store the new values 57 | } else { 58 | reset_counter = 0; // cold boot part 59 | Serial.println("This seems to be a cold boot. We don't have a valid state on RTC memory"); 60 | program_step = 0; 61 | } 62 | } 63 | 64 | void loop() { 65 | // do your work here - try to reset your chip externally or internally but don't power off... 66 | Serial.println("Current state is " + (String)program_step); 67 | program_step = (program_step == 7) ? 1 : program_step + 1; 68 | Serial.println("New state is " + (String)program_step); 69 | state.saveToRTC(); // there are no parameters because it only needs the vars' definiton once 70 | delay(1000); 71 | } 72 | 73 | First the library is included by #include . Then an RTCVars object named state is created via RTCVars state;. This object now is the interface to all functionality. Be sure to register all variables before reading the old state from RTC memory. The state is invalid if the registered variables differ in total size to the saved state. It seems good practice to use globally defined vars only and register all of them in the setup() function. If you really need to keep track of different, state-specific variables, look at the advanced usage. 74 | 75 | Registering does nothing but keeping track of where the variables are to find in memory. Of course reading the state from RTC memory would require to save the values in the corresponding variables. Therefore you need to register them even before you call state.loadFromRTC(). If everything works out, the call returns true. If not there are problems with the state or it has been a cold boot. See advanced usage for further information on error handling. 76 | 77 | Later on, everytime a change is made to some of the vars registered and have a consistent state is esablished, state.saveToRTC() is called to push these values to RTC memory. 78 | 79 | ## Supported types and size 80 | Please note, that there are two limits in this lib: memory and the number of managed variables. Due to the fact how storage of vars is organized, there is a fixed upper boundry other than memory. By default, the limit is 32 variables, the types are byte, char, int, long, float. Since these types do not exceed 8 bytes each, the RTC memory is not the limit here (512 bytes - 28 starting offset - 7 header/checksum = 477 bytes). See advanced usage for more functionality to control these settings. 81 | 82 | # Advanced Usage 83 | This part will show some functionality seldomly used (e.g. different states) or typically integrated at a later time (e.g. error handling). See the "fullblown" example at the end. 84 | 85 | ## Error handling 86 | The same goes here... 87 | 88 | ## Multiple sets of state vars 89 | Basically it is a bad idea to use different sets of variables to store in RTC memory. Typically this is not needed but due to memory constraints sometimes there is the need for it. Problem is, to setting up a state starts by registering the vars. Restoring these only works if vars are registered with the same type in the same order. What if these are different between two states: state one needs to store 30 ints and state two 12 floats. It's needed to know before setting up vars, which state is to be restored. This is what /state id/s are for: 90 | To keep two (or more) distinct sets of vars for different states, instantiate an additional RTCVars object and register different vars. Initally all RTCVars objects have the state_id 1. This can be changed by calling the objects method setStateId(byte new_state_id). 91 | Using different ids is essential for a correct recovery after reset. The state_id of an RTCVars object is returned when calling getStateId() function. 92 | In a program's setup() method, after creating the RTCVars object, getStateIdFromRTC() function should be called. It reads the state_id of the state in RTC memory ahead of other checks or the need to register vars. Be aware though, that this call might fail (see error handling) because of wrong checksum or other reasons (e.g. cold boot). 93 | Check the returned state_id against valid states and register the vars relevant for this state. Afterwards it is needed to set the object's state_id to the one in the RTC (for safety reasons). You can then load the state from RTC via loadFromRTC(). 94 | 95 | ## Memory and variable accounting 96 | As stated in Supported types and size, there are two limits to check for. 97 | First one is the RTC memory. This is a fixed size depending on the RTC type used. On the ESP8266 e.g. it is 512 bytes with a known good offset to use the memory freely of 28 bytes. Additionally the library uses 7 bytes for management purposes. At any time available RTC memory can be checked by using the getFreeRTCMem() call of the RTCVars instance. It returns the number of free bytes as int. 98 | Second limit is the number of registered vars which is set within the lib as 32. To change that limit, a change in the RTCVars.h file is needed for now, as this limit is set by a #define statement. Use the getFreeRTCVars() function of the RTCVars instance. It returns the number of unused slots for registered vars. 99 | 100 | ## Advanced example 101 | Aim of this example is to show all features developed so far: 102 | extern "C" { 103 | #include 104 | } 105 | #include 106 | 107 | // By default, the maximum number of variables is 32, it can be set to a higher value if needed 108 | // More needs more RAM, (about 6 Bytes per Variable), is a definition in lib's .h file 109 | // number is limited by total RTC memory 110 | // Use getFreeRTCMem() and getFreeRTCMem() to check at runtime 111 | 112 | #include "RTCVars.h" 113 | 114 | RTCVars state; 115 | time_t oldTime; 116 | 117 | // test variables 118 | // variables for RTC memory should be global or valid at any time saveToRTC() is called 119 | int i = 1234; 120 | int j = 23456; 121 | byte k = 0; 122 | long l = 123456789; 123 | float m = 3.141592683; 124 | int int_array[] = {1, 2, 0}; 125 | 126 | int autoreset = 20; // this will count down to zero and reset the esp 127 | 128 | void setup() { 129 | // let's inform the user about what type of reset has happened 130 | rst_info *resetInfo; 131 | resetInfo = ESP.getResetInfoPtr(); 132 | 133 | Serial.begin(115200); 134 | Serial.println("Booting..."); 135 | Serial.print("Reset Reason was: "); 136 | Serial.println(resetInfo->reason); 137 | 138 | // register each variable by sending a pointer to it. valid types are: byte, char, int, long, float 139 | // register preferably in setup call 140 | state.registerVar(&i); 141 | state.registerVar(&j); 142 | state.registerVar(&k); 143 | state.registerVar(&l); 144 | state.registerVar(&m); 145 | // Note: 146 | // Registered vars cannot be "thrown away" (e.g. unregistered). 147 | // If you really need a different set of vars to be saved in RTC mem, create another RTCVars object 148 | // and throw away the first one. These different sets have different signatures so to recover those 149 | // you need to identify the state set with a byte number: 150 | // state.setStateID(3); 151 | 152 | // Since recovery needs to know the kind of state in memory, request the saved ID first: 153 | // byte id_in_rtc = state.getStateIDFromRTC(); 154 | // Afterwards you can register this state's variables and read all from RTC. 155 | // Be aware that you need to set the state ID (setStateID) to the ID in RTC memory, otherwise the 156 | // call to loadFromRTC() will fail. 157 | byte id_in_rtc = state.getStateIDFromRTC(); // =255 if no valid signature is found 158 | Serial.print("The RTCVars state set in RTC memory has the id "); 159 | Serial.println(id_in_rtc); 160 | // since we change the id for demonstration purpose (!) every boot, let's accept the id in RTC memory 161 | if (id_in_rtc != 255) state.setStateID(id_in_rtc); 162 | 163 | j = 0; 164 | // arrays can be inserted by registering all elements in a loop 165 | for (int n = 0; n < 3; n++) state.registerVar(&(int_array[n])); 166 | // double registering is senseless (it is the same memory address!) but possible 167 | state.registerVar(&i); 168 | 169 | // debug output 170 | Serial.println("This is the set of vars BEFORE trying to load these from RTC"); 171 | state.debugOutputRTCVars(); 172 | 173 | // try to load from RTC memory 174 | // be aware that you should keep registering the same variables in the same order between to resets 175 | // otherwise you'll end up with data written in wrong variables 176 | if (state.loadFromRTC()) { 177 | Serial.println(F("Data successfully read from RTC")); 178 | } else { 179 | switch (state.getReadError()) { 180 | Serial.print(F("Couldn't load an old state because of ")); 181 | case RTC_ERROR_MAGIC_BYTES: 182 | Serial.println(F("wrong magic bytes.")); 183 | break; 184 | case RTC_ERROR_SIZE: 185 | Serial.println(F("a different state size than expected.")); 186 | break; 187 | case RTC_ERROR_READING_FAILED: 188 | Serial.println(F("errors while reading RTC memory.")); 189 | break; 190 | case RTC_ERROR_CHECKSUM: 191 | Serial.println(F("a wrong checksum.")); 192 | break; 193 | case RTC_ERROR_STATE_ID: 194 | Serial.println(F("a different state id.")); 195 | break; 196 | case RTC_ERROR_OTHER: 197 | Serial.println(F("unknown reasons.")); 198 | break; 199 | } 200 | } 201 | 202 | // once again let's do some debug output to see what we loaded here 203 | Serial.println(); 204 | Serial.println("And this is the set of vars AFTER trying to load these from RTC"); 205 | state.debugOutputRTCVars(); 206 | 207 | // once per reboot action 208 | // some fiddling in the array 209 | for (int n = 0; n < 3; n++) { 210 | // next element of fibonacci sequence with every reboot 211 | int_array[int_array[2]] = int_array[0] + int_array[1]; 212 | int_array[2] = (int_array[2] + 1) & 0x01; 213 | } 214 | 215 | // simulate different state ids 216 | // note: don't do this at home. only use different ids if you use a different setup of vars 217 | k = (k + 1) & 0x7f; 218 | state.setStateID(k); 219 | } 220 | 221 | void loop() { 222 | // do some action every second 223 | if (now()!=oldTime) { 224 | oldTime = now(); 225 | 226 | // let us change one of the saved vars 227 | j += 1; 228 | 229 | // show results 230 | Serial.print(F("This counter will reset soon: ")); 231 | Serial.print(now()); 232 | Serial.print(F(", this won't: ")); 233 | Serial.println(j); 234 | 235 | // make sure to call this function after each essential change of state variables 236 | // if you don't the RTC memory does not reflect the latest changes 237 | state.saveToRTC(); 238 | 239 | // perform a reset after defined number of seconds 240 | if (autoreset--<=0) ESP.reset(); 241 | } 242 | 243 | // this one changes approx. 20 times per second 244 | i += 1; 245 | delay(50); 246 | } 247 | 248 | Obviously the use of the state_id does not make any sense here but its basic handling is still correct. 249 | -------------------------------------------------------------------------------- /RTCVars.cpp: -------------------------------------------------------------------------------- 1 | #include "Arduino.h" 2 | #include "RTCVars.h" 3 | 4 | #define RTC_BASE 28 // this is a known good offset for unused RTC memory 5 | #define RTC_STATE_HEADER_SIZE 6 // 3 bytes signature, 1 byte state_id, 2 byte 6 | #define RTC_MAX_SIZE (511 - RTC_BASE) // 512 - RTC_BASE - 1 for checksum 7 | 8 | #define RTC_STATE_TYPE_NONE 0 9 | #define RTC_STATE_TYPE_INT 1 10 | #define RTC_STATE_TYPE_LONG 2 11 | #define RTC_STATE_TYPE_FLOAT 3 12 | #define RTC_STATE_TYPE_BYTE 4 13 | #define RTC_STATE_TYPE_CHAR 5 14 | 15 | RTCVars::RTCVars() { 16 | _state_size = RTC_STATE_HEADER_SIZE; 17 | _state_variables_counter = 0; 18 | _state_id = 0; 19 | _last_read_state_id = RTC_STATE_ID_INVALID; 20 | _last_read_status = RTC_OK; 21 | } 22 | 23 | bool RTCVars::registerVar(char *v) { 24 | return _checkAndReserve((uintptr_t)v, RTC_STATE_TYPE_CHAR); 25 | } 26 | 27 | bool RTCVars::registerVar(byte *v) { 28 | return _checkAndReserve((uintptr_t)v, RTC_STATE_TYPE_BYTE); 29 | } 30 | 31 | bool RTCVars::registerVar(int *v) { 32 | return _checkAndReserve((uintptr_t)v, RTC_STATE_TYPE_INT); 33 | } 34 | 35 | bool RTCVars::registerVar(long *v) { 36 | return _checkAndReserve((uintptr_t)v, RTC_STATE_TYPE_LONG); 37 | } 38 | 39 | bool RTCVars::registerVar(float *v) { 40 | return _checkAndReserve((uintptr_t)v, RTC_STATE_TYPE_FLOAT); 41 | } 42 | 43 | bool RTCVars::_checkAndReserve(uintptr_t v, byte type_of_var) { 44 | // check if there is enough room for this var 45 | if ((_state_variables_counter >= RTC_MAX_VARIABLES) || (_state_variable_size[type_of_var] + _state_size >= RTC_MAX_SIZE)) return false; 46 | // keep the pointer to the var 47 | _state_variables_ptr[_state_variables_counter] = v; 48 | // keep the type of var so we copy the correct number of bytes 49 | _state_variables_type[_state_variables_counter] = type_of_var; 50 | // remove these bytes from the free mem counter 51 | _state_size += _state_variable_size[type_of_var]; 52 | // up to the next one 53 | _state_variables_counter++; 54 | return true; 55 | } 56 | 57 | void RTCVars::debugOutputRTCVars() { 58 | byte *vb; 59 | char *vc; 60 | int *vi; 61 | long *vl; 62 | float *vf; 63 | Serial.println(F("debug output of all RTC variables:")); 64 | for (int i = 0; i < _state_variables_counter; i++) { 65 | Serial.print(F("Variable ")); 66 | Serial.print(i); 67 | switch (_state_variables_type[i]) { 68 | case RTC_STATE_TYPE_CHAR: 69 | vc = reinterpret_cast(_state_variables_ptr[i]); 70 | Serial.print(F(" is type char: ")); 71 | Serial.println(*vc); 72 | break; 73 | case RTC_STATE_TYPE_BYTE: 74 | vb = reinterpret_cast(_state_variables_ptr[i]); 75 | Serial.print(F(" is type byte: ")); 76 | Serial.println(*vb); 77 | break; 78 | case RTC_STATE_TYPE_INT: 79 | vi = reinterpret_cast(_state_variables_ptr[i]); 80 | Serial.print(F(" is type int: ")); 81 | Serial.println(*vi); 82 | break; 83 | case RTC_STATE_TYPE_LONG: 84 | vl = reinterpret_cast(_state_variables_ptr[i]); 85 | Serial.print(F(" is type long: ")); 86 | Serial.println(*vl); 87 | break; 88 | case RTC_STATE_TYPE_FLOAT: 89 | vf = reinterpret_cast(_state_variables_ptr[i]); 90 | Serial.print(F(" is type float: ")); 91 | Serial.println(*vf); 92 | break; 93 | } 94 | } 95 | Serial.print(F("Overall size of RTC variables: ")); 96 | Serial.println(_state_size); 97 | Serial.print(F("Free Memory in RTC: ")); 98 | Serial.print(getFreeRTCMem()); 99 | Serial.println(F(" bytes")); 100 | Serial.print(F("Free number of variables in RTC: ")); 101 | Serial.print(getFreeRTCVars()); 102 | Serial.println(F(" vars - adjust by #define RTC_MAX_VARIABLES x")); 103 | } 104 | 105 | bool RTCVars::saveToRTC() { 106 | unsigned char buf[_state_size + 1]; 107 | int p = RTC_STATE_HEADER_SIZE; 108 | int s = 0; 109 | 110 | // migic bytes signature 111 | buf[0] = 'M'; 112 | buf[1] = 'G'; 113 | buf[2] = 'C'; 114 | buf[3] = _state_id; 115 | buf[4]=(unsigned char)((_state_size >> 8) & 0xff); 116 | buf[5]=(unsigned char)(_state_size & 0xff); 117 | 118 | // copy the values from the local variables' memory places into buffer 119 | for (int i = 0; i<_state_variables_counter; i++) { 120 | s = _state_variable_size[_state_variables_type[i]]; 121 | memcpy(&buf[p], reinterpret_cast(_state_variables_ptr[i]), s); 122 | p += s; 123 | } 124 | 125 | buf[_state_size] = 0; 126 | for (int j = 0; j < _state_size; j++) { 127 | buf[_state_size]+=buf[j]; // simple checksum 128 | } 129 | return ESP.rtcUserMemoryWrite(RTC_BASE, (uint32_t*) &buf, _state_size + 1); 130 | } 131 | 132 | bool RTCVars::loadFromRTC() { 133 | DPRINTLN(F(">> call to loadFromRTC")); 134 | if (!_checkValidRTCData()) return false; 135 | _last_read_status = RTC_ERROR_READING_FAILED; 136 | unsigned char buf[_state_size + 1]; 137 | if (!ESP.rtcUserMemoryRead(RTC_BASE, (uint32_t*) &buf, _state_size + 1)) return false; 138 | 139 | // check if state id is ok 140 | _last_read_status = RTC_ERROR_STATE_ID; 141 | if (_last_read_state_id != _state_id) return false; 142 | // finally check if state sizes are equal 143 | _last_read_status = RTC_ERROR_SIZE; 144 | int size_in_rtc = (int)(buf[4] * 256 + buf[5]); 145 | if (size_in_rtc != _state_size) return false; 146 | 147 | // copy the values into the local variables' memory places 148 | _last_read_status = RTC_ERROR_OTHER; 149 | int p = RTC_STATE_HEADER_SIZE; 150 | int s = 0; 151 | for (int i=0; i < _state_variables_counter; i++) { 152 | s = _state_variable_size[_state_variables_type[i]]; 153 | memcpy(reinterpret_cast(_state_variables_ptr[i]), &buf[p], s); 154 | p += s; 155 | } 156 | _last_read_status = RTC_OK; 157 | DPRINTLN(F(">> loadFromRTC: all vars restored")); 158 | return true; 159 | } 160 | 161 | bool RTCVars::_checkValidRTCData() { 162 | // load header only from RTC 163 | _last_read_status = RTC_ERROR_READING_FAILED; 164 | unsigned char buf_head[RTC_STATE_HEADER_SIZE]; 165 | if (!ESP.rtcUserMemoryRead(RTC_BASE, (uint32_t*) &buf_head, RTC_STATE_HEADER_SIZE)) return false; 166 | 167 | _last_read_status = RTC_ERROR_MAGIC_BYTES; 168 | // check if magic bytes are ok 169 | if (buf_head[0] != 'M') return false; 170 | if (buf_head[1] != 'G') return false; 171 | if (buf_head[2] != 'C') return false; 172 | 173 | _last_read_status = RTC_ERROR_SIZE; 174 | // check for valid size 175 | int size_in_rtc = (int)(buf_head[4] * 256 + buf_head[5]); 176 | if (size_in_rtc > RTC_MAX_SIZE) return false; 177 | 178 | _last_read_status = RTC_ERROR_READING_FAILED; 179 | // load the full state from RTC 180 | unsigned char buf[size_in_rtc + 1]; 181 | if (!ESP.rtcUserMemoryRead(RTC_BASE, (uint32_t*) &buf, size_in_rtc + 1)) return false; 182 | 183 | _last_read_status = RTC_ERROR_CHECKSUM; 184 | // check for checksum 185 | unsigned char temp = 0; 186 | for (int j = 0; j < size_in_rtc; j++) temp += buf[j]; //checksum 187 | if (temp != buf[size_in_rtc]) return false; 188 | 189 | _last_read_status = RTC_OK; 190 | _last_read_state_id = buf[3]; 191 | return true; 192 | } 193 | 194 | 195 | 196 | int RTCVars::getFreeRTCMem() { 197 | return RTC_MAX_SIZE - _state_size; 198 | } 199 | 200 | int RTCVars::getFreeRTCVars() { 201 | return RTC_MAX_VARIABLES - _state_variables_counter; 202 | } 203 | 204 | byte RTCVars::getStateID() { 205 | return _state_id; 206 | } 207 | 208 | byte RTCVars::getStateIDFromRTC() { 209 | if (!_checkValidRTCData()) return RTC_STATE_ID_INVALID; 210 | return _last_read_state_id; 211 | } 212 | 213 | void RTCVars::setStateID(byte new_state_id) { 214 | if (new_state_id != RTC_STATE_ID_INVALID) _state_id = new_state_id; 215 | } 216 | 217 | byte RTCVars::getReadError() { 218 | return _last_read_status; 219 | } 220 | -------------------------------------------------------------------------------- /RTCVars.h: -------------------------------------------------------------------------------- 1 | #ifndef ___LarsHelperESP_h___ 2 | #define ___LarsHelperESP_h___ 3 | 4 | #include "Arduino.h" 5 | 6 | #ifndef RTC_MAX_VARIABLES 7 | #define RTC_MAX_VARIABLES 32 8 | #endif 9 | 10 | #ifdef DEBUG //Macros are usually in all capital letters. 11 | #define DPRINT(...) Serial.print(__VA_ARGS__) //DPRINT is a macro, debug print 12 | #define DPRINTLN(...) Serial.println(__VA_ARGS__) //DPRINTLN is a macro, debug print with new line 13 | #else 14 | #define DPRINT(...) //now defines a blank line 15 | #define DPRINTLN(...) //now defines a blank line 16 | #endif 17 | 18 | 19 | const static byte RTC_OK = 0; 20 | const static byte RTC_ERROR_MAGIC_BYTES = 1; 21 | const static byte RTC_ERROR_SIZE = 2; 22 | const static byte RTC_ERROR_READING_FAILED = 3; 23 | const static byte RTC_ERROR_CHECKSUM = 4; 24 | const static byte RTC_ERROR_STATE_ID = 5; 25 | const static byte RTC_ERROR_OTHER = 99; 26 | const static byte RTC_STATE_ID_INVALID = 255; 27 | 28 | 29 | class RTCVars { 30 | public: 31 | RTCVars(); 32 | bool registerVar(char *v); 33 | bool registerVar(byte *v); 34 | bool registerVar(int *v); 35 | bool registerVar(long *v); 36 | bool registerVar(float *v); 37 | void debugOutputRTCVars(); 38 | bool saveToRTC(); 39 | bool loadFromRTC(); 40 | int getFreeRTCMem(); 41 | int getFreeRTCVars(); 42 | byte getStateID(); 43 | byte getStateIDFromRTC(); 44 | void setStateID(byte new_state_id); 45 | byte getReadError(); 46 | private: 47 | byte _state_id; 48 | byte _last_read_state_id; 49 | byte _last_read_status; 50 | int _state_size; 51 | int _state_variables_counter; 52 | uintptr_t _state_variables_ptr[RTC_MAX_VARIABLES]; 53 | byte _state_variables_type[RTC_MAX_VARIABLES]; 54 | const byte _state_variable_size[6] = {0, sizeof(int), sizeof(long), sizeof(float), sizeof(byte), sizeof(char)}; 55 | bool _checkAndReserve(uintptr_t v, byte type_of_var); 56 | bool _checkValidRTCData(); 57 | }; 58 | 59 | #endif 60 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-slate -------------------------------------------------------------------------------- /examples/FullRTCVars/FullRTCVars.ino: -------------------------------------------------------------------------------- 1 | extern "C" { 2 | #include 3 | } 4 | #include 5 | 6 | // By default, the maximum number of variables is 32, it can be set to a higher value if needed 7 | // More needs more RAM, (about 6 Bytes per Variable), is a definition in lib's .h file 8 | // number is limited by total RTC memory 9 | // Use getFreeRTCMem() and getFreeRTCMem() to check at runtime 10 | 11 | #include "RTCVars.h" 12 | 13 | RTCVars state; 14 | time_t oldTime; 15 | 16 | // test variables 17 | // variables for RTC memory should be global or valid at any time saveToRTC() is called 18 | int i = 1234; 19 | int j = 23456; 20 | byte k = 0; 21 | long l = 123456789; 22 | float m = 3.141592683; 23 | int int_array[] = {1, 2, 0}; 24 | 25 | int autoreset = 20; // this will count down to zero and reset the esp 26 | 27 | void setup() { 28 | // let's inform the user about what type of reset has happened 29 | rst_info *resetInfo; 30 | resetInfo = ESP.getResetInfoPtr(); 31 | 32 | Serial.begin(115200); 33 | Serial.println("Booting..."); 34 | Serial.print("Reset Reason was: "); 35 | Serial.println(resetInfo->reason); 36 | 37 | // register each variable by sending a pointer to it. valid types are: byte, char, int, long, float 38 | // register preferably in setup call 39 | state.registerVar(&i); 40 | state.registerVar(&j); 41 | state.registerVar(&k); 42 | state.registerVar(&l); 43 | state.registerVar(&m); 44 | // Note: 45 | // Registered vars cannot be "thrown away" (e.g. unregistered). 46 | // If you really need a different set of vars to be saved in RTC mem, create another RTCVars object 47 | // and throw away the first one. These different sets have different signatures so to recover those 48 | // you need to identify the state set with a byte number: 49 | // state.setStateID(3); 50 | 51 | // Since recovery needs to know the kind of state in memory, request the saved ID first: 52 | // byte id_in_rtc = state.getStateIDFromRTC(); 53 | // Afterwards you can register this state's variables and read all from RTC. 54 | // Be aware that you need to set the state ID (setStateID) to the ID in RTC memory, otherwise the 55 | // call to loadFromRTC() will fail. 56 | byte id_in_rtc = state.getStateIDFromRTC(); // =255 if no valid signature is found 57 | Serial.print("The RTCVars state set in RTC memory has the id "); 58 | Serial.println(id_in_rtc); 59 | // since we change the id for demonstration purpose (!) every boot, let's accept the id in RTC memory 60 | if (id_in_rtc != 255) state.setStateID(id_in_rtc); 61 | 62 | j = 0; 63 | // arrays can be inserted by registering all elements in a loop 64 | for (int n = 0; n < 3; n++) state.registerVar(&(int_array[n])); 65 | // double registering is senseless (it is the same memory address!) but possible 66 | state.registerVar(&i); 67 | 68 | // debug output 69 | Serial.println("This is the set of vars BEFORE trying to load these from RTC"); 70 | state.debugOutputRTCVars(); 71 | 72 | // try to load from RTC memory 73 | // be aware that you should keep registering the same variables in the same order between to resets 74 | // otherwise you'll end up with data written in wrong variables 75 | if (state.loadFromRTC()) { 76 | DPRINTLN(F("Data successfully read from RTC")); 77 | } else { 78 | switch (state.getReadError()) { 79 | Serial.print("Couldn't load an old state because of "); 80 | case RTC_ERROR_MAGIC_BYTES: 81 | Serial.println("wrong magic bytes."); 82 | break; 83 | case RTC_ERROR_SIZE: 84 | Serial.println("a different state size than expected."); 85 | break; 86 | case RTC_ERROR_READING_FAILED: 87 | Serial.println("errors while reading RTC memory."); 88 | break; 89 | case RTC_ERROR_CHECKSUM: 90 | Serial.println("a wrong checksum."); 91 | break; 92 | case RTC_ERROR_STATE_ID: 93 | Serial.println("a different state id."); 94 | break; 95 | case RTC_ERROR_OTHER: 96 | Serial.println("unknown reasons."); 97 | break; 98 | } 99 | } 100 | 101 | // once again let's do some debug output to see what we loaded here 102 | Serial.println(); 103 | Serial.println("And this is the set of vars AFTER trying to load these from RTC"); 104 | state.debugOutputRTCVars(); 105 | 106 | // once per reboot action 107 | // some fiddling in the array 108 | for (int n = 0; n < 3; n++) { 109 | // next element of fibonacci sequence with every reboot 110 | int_array[int_array[2]] = int_array[0] + int_array[1]; 111 | int_array[2] = (int_array[2] + 1) & 0x01; 112 | } 113 | 114 | // simulate different state ids 115 | // note: don't do this at home. only use different ids if you use a different setup of vars 116 | k = (k + 1) & 0x7f; 117 | state.setStateID(k); 118 | } 119 | 120 | void loop() { 121 | // do some action every second 122 | if (now()!=oldTime) { 123 | oldTime = now(); 124 | 125 | // let us change one of the saved vars 126 | j += 1; 127 | 128 | // show results 129 | Serial.print(F("This counter will reset soon: ")); 130 | Serial.print(now()); 131 | Serial.print(F(", this won't: ")); 132 | Serial.println(j); 133 | 134 | // make sure to call this function after each essential change of state variables 135 | // if you don't the RTC memory does not reflect the latest changes 136 | state.saveToRTC(); 137 | 138 | // perform a reset after defined number of seconds 139 | if (autoreset--<=0) ESP.reset(); 140 | } 141 | 142 | // this one changes approx. 20 times per second 143 | i += 1; 144 | delay(50); 145 | } 146 | -------------------------------------------------------------------------------- /examples/MinimalRTCVars/MinimalRTCVars.ino: -------------------------------------------------------------------------------- 1 | #include 2 | RTCVars state; // create the state object 3 | 4 | int reset_counter; // we want to keep these values after reset 5 | int program_step; 6 | 7 | void setup() { 8 | Serial.begin(115200); // allow debug output 9 | 10 | state.registerVar( &reset_counter ); // we send a pointer to each of our variables 11 | state.registerVar( &program_step ); 12 | 13 | if (state.loadFromRTC()) { // we load the values from rtc memory back into the registered variables 14 | reset_counter++; 15 | Serial.println("This is reset no. " + (String)reset_counter); 16 | state.saveToRTC(); // since we changed a state relevant variable, we store the new values 17 | } else { 18 | reset_counter = 0; // cold boot part 19 | Serial.println("This seems to be a cold boot. We don't have a valid state on RTC memory"); 20 | program_step = 0; 21 | } 22 | } 23 | 24 | void loop() { 25 | // do your work here - try to reset your chip externally or internally but don't power off... 26 | Serial.println("Current state is " + (String)program_step); 27 | program_step = (program_step == 7) ? 1 : program_step + 1; 28 | Serial.println("New state is " + (String)program_step); 29 | state.saveToRTC(); // there are no parameters because it only needs the vars' definiton once 30 | delay(1000); 31 | } 32 | -------------------------------------------------------------------------------- /keywords.txt: -------------------------------------------------------------------------------- 1 | # data types KEYWORD1 2 | RTCVars KEYWORD1 3 | # methods KEYWORD2 4 | registerVar KEYWORD2 5 | debugOutputRTCVars KEYWORD2 6 | saveToRTC KEYWORD2 7 | loadFromRTC KEYWORD2 8 | getFreeRTCMem KEYWORD2 9 | getFreeRTCVars KEYWORD2 10 | getStateID KEYWORD2 11 | getStateIDFromRTC KEYWORD2 12 | setStateID KEYWORD2 13 | getReadError KEYWORD2 14 | # constants LITERAL1 15 | RTC_OK LITERAL1 16 | RTC_ERROR_MAGIC_BYTES LITERAL1 17 | RTC_ERROR_SIZE LITERAL1 18 | RTC_ERROR_READING_FAILED LITERAL1 19 | RTC_ERROR_CHECKSUM LITERAL1 20 | RTC_ERROR_STATE_ID LITERAL1 21 | RTC_ERROR_OTHER LITERAL1 22 | RTC_STATE_ID_INVALID LITERAL1 23 | 24 | 25 | -------------------------------------------------------------------------------- /library.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "RTCVars", 3 | "frameworks": "Arduino", 4 | "keywords": "RTC, reset, ESP, reboot, deep sleep, vars, variables, save, keep, state", 5 | "description": "This library eases the storage of variables in reset-safe RTC memory. Variables stored there survive all kinds of resets as long as there is no hard reset. It provides boilerplate code to securely store relevant state data in RTC memory so it may survive (unexpected) reboots or deep sleeps. Supports ESP only at this time, will change in the future. ", 6 | "authors": 7 | [ 8 | { 9 | "name": "Lars Friedrichs", 10 | "email": "friedrichs.lars+rtcvars@gmail.com", 11 | "maintainer": true 12 | } 13 | ], 14 | "repository": 15 | { 16 | "type": "git", 17 | "url": "https://github.com/highno/rtcvars" 18 | } 19 | } 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /library.properties: -------------------------------------------------------------------------------- 1 | name=RTCVars 2 | version=0.1.1 3 | author=Lars Friedrichs 4 | maintainer=Lars Friedrichs 5 | sentence=This library eases the storage of variables in reset-safe RTC memory. 6 | paragraph=Variables stored there survive all kinds of resets as long as there is no hard reset. It provides boilerplate code to securely store relevant state data in RTC memory so it may survive (unexpected) reboots or deep sleeps. Supports ESP only at this time, will change in the future. 7 | category=Data Storage 8 | url=https://github.com/highno/rtcvars 9 | architectures=esp8266 --------------------------------------------------------------------------------