├── .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
--------------------------------------------------------------------------------