OOKwiz
on/off-keying for ESP32 and a variety of supported radio modules
Settings.cpp
Go to the documentation of this file.
1 #include "Settings.h"
2 #include "config.h" // provides factorySettings()
3 #include "serial_output.h"
4 #include "FS.h"
5 #include "SPIFFS.h"
6 #include "tools.h"
7 
8 std::map<String, String> Settings::store;
9 
10 // Constructor sets the defaults from config.cpp, see 'dummy' at end
13 }
14 
15 /// @brief Save settings to file in SPIFFS
16 /// @param filename The actual filename in SPIFFS will have SPIFFS_PREFIX from config.h preprended
17 /// @return `true` if it worked, displays error and returns `false` if not.
18 bool Settings::save(const String filename) {
19  if (!validName(filename)) {
20  return false;
21  }
22  String actual_filename = QUOTE(SPIFFS_PREFIX/) + filename;
23  if (!SPIFFS.begin(true)) {
24  ERROR("ERROR: Could not open SPIFFS filesystem.\n");
25  return false;
26  }
27  File file = SPIFFS.open(actual_filename, FILE_WRITE);
28  if (!file) {
29  ERROR("ERROR: Could not open file '%s' for writing.\n", filename.c_str(), FILE_WRITE);
30  return false;
31  }
32  String contents = list() + "\n";
33  if (!file.print(contents)) {
34  ERROR("ERROR: Could not save settings to flash.\n");
35  return false;
36  }
37  INFO("Saved settings to file '%s'.\n", filename.c_str());
38  return true;
39 }
40 
41 /// @brief Load settings from file in SPIFFS
42 /// @param filename The actual filename in SPIFFS will have SPIFFS_PREFIX from config.h preprended
43 /// @return `true` if it worked, displays error and returns `false` if not.
44 bool Settings::load(const String filename) {
45  if (!validName(filename)) {
46  return false;
47  }
48  String actual_filename = QUOTE(SPIFFS_PREFIX/) + filename;
49  if (!SPIFFS.begin(true)) {
50  ERROR("ERROR: Could not open SPIFFS filesystem.\n");
51  return false;
52  }
53  File file = SPIFFS.open(actual_filename);
54  if (!file || !SPIFFS.exists(actual_filename)) {
55  ERROR("ERROR: Could not open file '%s'.\n", filename.c_str());
56  return false;
57  }
58  String contents;
59  while(file.available()) {
60  contents += char(file.read());
61  }
62  INFO ("Loaded settings from file '%s'.\n", filename.c_str());
63  return fromList(contents);
64 }
65 
66 /// @brief Shows all files in the location SPIFFS_PREFIX in SPIFFS.
67 /// @return `true` if it worked, displays error and returns `false` if not.
68 bool Settings::ls() {
69  if (!SPIFFS.begin(true)) {
70  ERROR("ERROR: Could not open SPIFFS filesystem.\n");
71  return false;
72  }
73  File root = SPIFFS.open(QUOTE(SPIFFS_PREFIX));
74  File file = root.openNextFile();
75  while(file){
76  INFO("%s\n", file.name());
77  file = root.openNextFile();
78  }
79  return true;
80 }
81 
82 /// @brief Deletes file from SPIFFS
83 /// @param filename The actual filename in SPIFFS will have SPIFFS_PREFIX from config.h preprended
84 /// @return `true` if it worked, displays error and returns `false` if not.
85 bool Settings::rm(const String filename) {
86  String actual_filename = QUOTE(SPIFFS_PREFIX/) + filename;
87  if (!SPIFFS.begin(true)) {
88  ERROR("ERROR: Could not open SPIFFS filesystem.\n");
89  return false;
90  }
91  if (SPIFFS.remove(actual_filename)) {
92  INFO("File '%s' deleted.\n", filename.c_str());
93  return true;
94  }
95  ERROR("ERROR: rm '%s': file not found.\n", filename);
96  return false;
97 }
98 
99 /// @brief See if given file exists in SPIFFS
100 /// @param filename The actual filename in SPIFFS will have SPIFFS_PREFIX from config.h preprended
101 /// @return `true` if file exists, `false` if not.
102 bool Settings::fileExists(const String filename) {
103  String actual_filename = QUOTE(SPIFFS_PREFIX/) + filename;
104  if (!validName(filename)) {
105  return false;
106  }
107  if (!SPIFFS.begin(true)) {
108  ERROR("ERROR: Could not open SPIFFS filesystem. On Arduino IDE, check Tools/Partition Scheme to make sure\n you are set up to have SPIFFS.\n");
109  return false;
110  }
111  return SPIFFS.exists(actual_filename);
112 }
113 
114 /// @brief Deletes all settings from memory
115 void Settings::zap() {
116  store.clear();
117 }
118 
119 /// @brief Stores all settings from a String into memory
120 /// @param in String that contains name=value<lf>name=value<lf>...
121 /// @return `true` if it worked, displays error and returns `false` if not.
122 bool Settings::fromList(String in) {
123  zap();
124  while (true) {
125  int lf = in.indexOf("\n");
126  if (lf == -1) {
127  break;
128  }
129  String this_one = in.substring(0, lf);
130  int equals_sign = this_one.indexOf("=");
131  if (equals_sign != -1) {
132  store[this_one.substring(0, equals_sign)] = this_one.substring(equals_sign + 1);
133  } else {
134  store[this_one.substring(0, equals_sign)] = "";
135  }
136  in = in.substring(lf + 1);
137  }
138  return true;
139 }
140 
141 
142 /// @brief `true` if name is a valid name for a setting.
143 bool Settings::validName(const String &name) {
144  if (name.length() == 0) {
145  ERROR("ERROR: name cannot be empty.\n");
146  return false;
147  }
148  for (int n = 0; n < name.length(); n++) {
149  if (!isAlphaNumeric(name.charAt(n)) && name.charAt(n) != '_') {
150  ERROR("ERROR: name '%s' contains illegal character.\n", name.c_str());
151  return false;
152  }
153  }
154  return true;
155 }
156 
157 /// @brief List of values in memory
158 /// @return String with name=value<lf>name=value<lf>...
159 String Settings::list() {
160  String res;
161  for (const auto& pair: store) {
162  // Remove the ending '=' for settings that are merely set, no value.
163  if (pair.second == "") {
164  res += pair.first;
165  } else {
166  res += pair.first;
167  res += "=";
168  res += pair.second;
169  }
170  res += "\n";
171  }
172  // cut off last lf
173  res = res.substring(0, res.length() - 1);
174  return res;
175 }
176 
177 /// @brief Find out if a key with given name exists in memory
178 /// @param name Setting name
179 /// @return `true` if that name is set
180 bool Settings::isSet(const String &name) {
181  return store.count(name);
182 }
183 
184 /// @brief Set a value
185 /// @param name name of the key to be set
186 /// @param value value to be set as an Arduino String
187 /// @return
188 bool Settings::set(const String &name, const String &value) {
189  if (!validName(name)) {
190  return false;
191  }
192  store[name] = value;
193  return true;
194 }
195 
196 /// @brief Removes key with given name from memory
197 /// @param name name of key
198 /// @return `true` if removed, `false` if name not valid or key not set.
199 bool Settings::unset(const String &name) {
200  if (!validName(name) || !isSet(name)) {
201  return false;
202  }
203  store.erase(name);
204  return true;
205 }
206 
207 /// @brief Get a value from memory as String
208 /// @param name name of the key
209 /// @param value String variable that will hold the value on return
210 /// @return `true` if value found, `false` if not
211 bool Settings::get(const String &name, String &value) {
212  if (isSet(name)) {
213  value = store[name];
214  return true;
215  }
216  return false;
217 }
218 
219 /// @brief Get a value from memory as float
220 /// @param name name of the key
221 /// @param value `float` variable that will hold the value on return
222 /// @return `true` if value found, `false` if not
223 bool Settings::get(const String &name, float &value) {
224  String val_string;
225  if (get(name, val_string)) {
226  value = val_string.toFloat();
227  return true;
228  } else {
229  return false;
230  }
231 }
232 
233 /// @brief Get a value from memory as int
234 /// @param name name of the key
235 /// @param value `int` variable that will hold the value on return
236 /// @return `true` if value found, `false` if not
237 bool Settings::get(const String &name, int &value) {
238  String val_string;
239  if (get(name, val_string)) {
240  value = val_string.toInt();
241  return true;
242  } else {
243  return false;
244  }
245 }
246 
247 /// @brief Get a value from memory as long
248 /// @param name name of the key
249 /// @param value `long` variable that will hold the value on return
250 /// @return `true` if value found, `false` if not
251 bool Settings::get(const String &name, long &value) {
252  String val_string;
253  if (get(name, val_string)) {
254  value = val_string.toInt(); // String's .toInt() actually returns a long
255  return true;
256  } else {
257  return false;
258  }
259 }
260 
261 /// @brief Get a value from memory as String with default
262 /// @param name name of the key
263 /// @param dflt [optional] Default returned if key not found in memory or "" if no default specified.
264 /// @return String with value found, or default
265 String Settings::getString(const String &name, const String dflt) {
266  if (isSet(name)) {
267  return store[name];
268  }
269  return dflt;
270 }
271 
272 /// @brief Get a value from memory as int with default
273 /// @param name name of the key
274 /// @param dflt [optional] Default returned if key not found in memory or -1 if no default specified.
275 /// @return int with value found, or default
276 int Settings::getInt(const String &name, const long dflt) {
277  if (isSet(name)) {
278  return store[name].toInt();
279  }
280  return dflt;
281 }
282 
283 /// @brief Get a value from memory as long with default
284 /// @param name name of the key
285 /// @param dflt [optional] Default returned if key not found in memory or -1 if no default specified.
286 /// @return long with value found, or default
287 long Settings::getLong(const String &name, const long dflt) {
288  if (isSet(name)) {
289  return store[name].toInt();
290  }
291  return dflt;
292 }
293 
294 /// @brief Get a value from memory as float with default
295 /// @param name name of the key
296 /// @param dflt [optional] Default returned if key not found in memory or -1 if no default specified.
297 /// @return float with value found, or default
298 float Settings:: getFloat(const String &name, const float dflt) {
299  if (isSet(name)) {
300  return store[name].toFloat();
301  }
302  return dflt;
303 }
304 
305 Settings dummy; // so the constructor runs once for the default settings