Wenn man mit einem ESP8266-Modul arbeitet, das ein Dateisystem unterstützt (z.B. ein ESP-12 mit SPIFFS) liegt es nahe, Konfigurationseinstellungen in einer Datei zu hinterlegen. Dies ermöglicht die Anpassung der Einstellungen ohne Neuprogrammierung z.B. per Web-Interface (s. Beispiel-Sketch "SettingsServer" weiter unten). Stellt der ESP8266 dann noch einen Access-Point bereit, kann man über diesen dann auch in der Einstellungsdatei hinterlegte WLAN-Zugangsdaten ändern. Letzteres macht man sinnvoller Weise per Handy oder Tablet, weil deren OS weniger auf einen WLAN-Zugang angewiesen ist als WINDOS und der WLAN-Wechsel dort deshalb einfacher funktioniert.
Die im Folgenden beschriebene Bibliothek bietet eine komfortable Möglichkeit, Einstellungen aus einer Datei einzulesen.
Inhaltsverzeichnis
Der Aufbau der Datei mit den Einstellungsdaten ist recht einfach. Die Datei enthält pro Parameter eine Zeile. Diese enthält den Namen des Parameters direkt am Zeilenanfang. Dann folgt mindestens ein Leerzeichen und dann der Parameter-Wert. Groß- / Kleinschreibung ist relevant! Umlaute sollten vermieden werden. Zeilen die mit "#" beginnen sind Kommentarzeilen und werden ignoriert. Beispiel:
#Kommentar Welcome Hallo Factor 55
Die erste Zeile ist eine Kommentarzeile. Die zweite definiert den Zeichenketten-Parameter mit der Bezeichnung "Welcome" und dem Einstellungswert "Hallo". Die dritte Zeile dient der Definition eines Zahlenwerts.
Einstellungen (Settings) sind applikationsspezifisch. UrsSettings kann deshalb nur einen Rahmen (eine Basis-Klasse) zur Verfügung stellen. Eine in der Applikation definierte, von UrsSettings abgeleitete Klasse nimmt die konkreten Einstellungselemente auf.
Zur Nutzung dieser Bibliothek sind mehrere Schritte notwendig:
Zunächst muss eine applikationsspezifische Container-Klasse für die Aufnahme der Variablen mit den Inhalten der Einstellungswerte von UrsSettingsBaseClass abgeleitet werden. Dies macht man sinnvoller Weise in Arduino-Manier in einer eigenständigen Datei.
Header-Datei ("MySettings.h"):
class MySettingsClass : public UrsSettingsBaseClass {
...
};
extern MySettingsClass MySettings;
Code-Date ("MySettings.cpp"):
MySettingsClass MySettings;
Innerhalb dieser Klasse werden Elemente für die Aufnahme der Einstellungswerte definiert. Für die Einstellungswerte sind spezielle, typenspezifische Klassen hinterlegt, die von SettingsValue abgeleitet sind. In der aktuellen Version stehen vier Klassen zur Verfügung. Weitere Datentypen lassen sich leicht implementieren (s.u.).
Klasse | hinterlegter Datentyp | hinterlegtes Konstruktionsmakro |
---|---|---|
SettingsStringValue | String | StringSetting(name, default) |
SettingsIpValue | IPAddress | IPSetting(name, default) |
SettingsIntValue | int | IntSetting(name, default) |
SettingsFloatValue | float | FloatSetting(name, default) |
Der Konstruktor dieser Typen verlangt die Angabe
SettingsStringValue Welcome = SettingsStringValue(*this, "Welcome", "");
Sind Variablen-Name (z.B. "Welcome" im vorhergehenden Beispiel) und Parameter-Name in der Einstellungsdatei identisch, können die vordefinierten Makros genutzt werden:
StringSetting(Welcome, "");
IntSetting(Factor, 1);
Das folgende Beispiel zeigt das Ganze im Zusammenhang:
01: // MySettings.h
02:
03: #ifndef _MYSETTINGS_h
04: #define _MYSETTINGS_h
05:
06: #include <Arduino.h>
07: #include <UrsSettings.h>
08:
09: class MySettingsClass : public UrsSettingsBaseClass {
10: public:
11: StringSettings(Welcome, "");
12: IntSettings(Factor, 1);
13: };
14:
15: extern MySettingsClass MySettings;
16: #endif
Zeile | Bedeutung |
07 | Einbindung der Bibliothek |
09 | Deklaration der Klasse MySettingsClass abgeleitet von UrsSettingsBaseClass |
11 | Definition des Zeichenketten-Parameters Welcome. Bezeichnung in der Einstellungsdatei ist "Welcome" und der Vorgabewert ist "". |
12 | Definition des Integer-Parameters Factor. Bezeichnung in der Einstellungsdatei ist "Factor" und der Vorgabewert ist "1". |
15 | Deklaration der Variablen MySettings vom Typ MySettingsClass |
01: // MySettings.cpp
02:
03: #include "MySettings.h"
04:
05: MySettingsClass MySettings;
Zeile | Bedeutung |
05 | Definition der Container-Variablen |
Das Einlesen der Einstellungswerte erfolgt dann über die Methode loadFromFile:
MySettings.loadFromFile("/settings.txt");
liest die Einstellungswerte aus der angegebene Datei im SPIFFS.
Der Zugriff auf die einzelnen Einstellungswerter erfolgt über das value-Feld der Einstellungswert-Objekte:
Serial.print(MySettings.welcome.value);
Alternativ können die Werte (ab Version 1.1) über die hinterlegten Konvertierungsoperatoren angerufen werden. Bei Mehrdeutigkeit ist ggf. die Konvertierungsvorschrift explizit anzugeben:
String s1 = MySettings.welcome;
String s2 = (String) MySettings.welcome;
String s3 = static_cast<String>(MySettings.welcome);
In einer zweiten Variante kann dem Settings-Objekt der Dateiname direkt bei der Instanziierung (Konstruktor) übergeben werden:
...
} MySettings("/settings.txt)";
Dann ist es ausreichend loadFromFile ohne Parameter aufzurufen:
MySettings.loadFromFile();
Dies ist dann von Bedeutung, wenn ein unspezifisch (d.h. per Verweis auf UrsSettingsBaseClass) übergebenes Settings-Objekt neu geladen werden muss. Dieses Verfahren wird im Beispiel "SettingsServer" verwendet (s.u.).
Die Basisklasse UrsSettingsBaseClass dient als Container für die einzelnen Einstellungswerte und stellt folgende Methoden zur Verfügung:
Funktion | Beschreibung | Anmerkung |
---|---|---|
UrsSettingsBaseClass() | Initialisiert eine neue Instanz von UrsSettingsBaseClass. | Der Dateiname der Datei mit den Einstellungswerten ist unbestimmt. |
UrsSettingsBaseClass (String SettingsFileName) | Initialisiert eine neue Instanz von UrsSettingsBaseClass. Die Datei mit den Einstellungswerten wird in SettingsFileName übergeben. Der Dateiname wird intern gespeichert. | SPIFFS-Dateien benötigen eine führende Verzeichnis-Angaben, z.B. "/". |
uint8_t loadFromFile (const String &SettingsFileName) | Lädt die Einstellungsdaten aus der Datei mit dem namen SettingsFileName.
Der Name der Datei wird für nachfolgende Aufrufe von loadFromFile()
intern gespeichert. Liefert 1, wenn erfolgreich, 0 bei Fehler. |
SPIFFS-Dateien benötigen eine führende Verzeichnis-Angaben, z.B. "/". Der übergebene Dateiname wird gespeichert. Fehlerursachen sind:
|
uint8_t loadFromFile() | Lädt die Einstellungswerte über den bereits eingestellten Dateinamen. Liefert 1, wenn erfolgreich, 0 bei Fehler. |
Fehlerursachen sind:
|
String getFileName() | Liefert den aktuell eingestellten Dateinamen für die Datei mit den Einstellungswerten. | |
void loadFromStream (Stream& s) | Liest Settings-Daten aus einem Stream. | |
void loadFromString (String& s) | Liest Einstellungswerte aus einem String. | Diese Funktion nutzt eigene Einlesemethode um den Aufruf von yield() zu vermeiden. |
bool isLoaded() | Gibt an, ob die Einstellungswerte aus einer Quelle gelesen wurde. | false: Die Einstellungswerte haben noch die
Default-Werte. true: Die Einstellungswerte wurden aud einer Quelle bezogen |
Die Klassen SettingsXxxValue (mit Xxx = "String", "IP, "Int" oder "Float") zur Aufnahme der Einstellungswerte sind von SettingsValue abgeleitet und besitzen die folgenden öffentlichen Methoden:
Funktion | Beschreibung | Anmerkung |
---|---|---|
SettingsXxxValue (UrsSettingsBaseClass& parent, const String Name, const String defaultValue) | Initialisiert eine neue Instanz von SettingsXxxValue. | Je nach Klasse gibt es weitere Konstruktoren, die z.B. statt der Parameter vom Typ String solche vom Type char* aufweisen. |
XxxSetting(name, default) | Konstruktionsmakro | name ist ein Bezeichner, default ist vom Typ Xxx. |
Xxx value | Erlaubt den Zugriff auf den Einstellungswert. | Das Feld ist öffentlich und kann bei Bedarf von der Applikation überschrieben werden. |
String getName() | Liefert die Bezeichnung des Parameters nach der in der Einstellungsdatei gesucht wird. | Diese Methode wird von der Basisklasse SettingsValue bereit gestellt. |
SettingsValueState getState() | Liefert den aktuellen Zustand des Parameters. |
Mögliche Werte sind:
|
Das folgende Beispiel – SettingsSample – zeigt die Verwendung.
Die Datei mit den Einstellungsdaten hat folgenden Aufbau:
#Nur und Mindestens ein Leerzeichen
#zwischen Name und Parameter
#WLAN
ssid !!! Repace This !!!
pass !!! Repace This !!!
hostName UrsSettings
#Test
TestString Hallo Welt
TestIP 192.168.4.1
TestInt 123
TestFloat 45.321
In der Header-Datei wird die Klasse SettingsSampleClass definiert und die zugehörige Instanz SettingsSample. Die Klasse definiert sieben Einstellungswerte (hostName ... Testfloat).
// SettingsSample.h
#ifndef _SETTINGSSAMPLE_h
#define _SETTINGSSAMPLE_h
#include <UrsSettings.h>
class SettingsSampleClass : public UrsSettingsBaseClass {
public:
StringSetting(hostName, "ESP8266");
StringSetting(ssid, "");
SettingsStringValue password = SettingsStringValue(*this, "pass", ""); // Variablenname und Parametername in der
// Datei sind nicht identisch!
StringSetting(TestString, "");
IPSetting(TestIP, "1.2.3.4");
IntSetting(TestInt, 100);
FloatSetting(TestFloat, 200);
};
extern SettingsSampleClass SettingsSample;
#endif
// SettingsSample.cpp
#include "SettingsSample.h"
SettingsSampleClass SettingsSample;
#endif
Der zughörige Sketch liest die Einstellungsdaten ein und verwendet diese zum Aufbau einer WLAN-Verbindung. Anschließend werden die restlichen Parameter über die serielle Schnittstelle ausgegeben (zu UrsOTA s. ESP8266-OTA: Neues aus der Luft gegriffen).
// Settings Sample
//
// Autor: http://UllisRoboterSeite.de
// Doku: http://ullisroboterseite.de/esp8266-settings.html
#include <ESP8266WiFi.h>
#include <UrsWiFi.h>
#include <UrsOTA.h>
#include "SettingsSample.h"
void setup() {
Serial.begin(115200);
delay(1);
Serial.print("\n\n");
Serial.println("Settings Sample");
SettingsSample.loadFromFile("/settings.txt");
UrsWiFi.connectStation(SettingsSample.ssid.value, SettingsSample.password.value, SettingsSample.hostName.value);
UrsOTA.begin(SettingsSample.hostName.value);
Serial.println();
Serial.print("Dies ist TestInt: "); Serial.println(SettingsSample.TestInt.value);
Serial.print("Dies ist TestString: "); Serial.println(SettingsSample.TestString.value);
Serial.print("Dies ist TestIP: "); Serial.println(SettingsSample.TestIP.value);
Serial.print("Dies ist TestFloat: "); Serial.println(SettingsSample.TestFloat.value, 3);
}
void loop() {
UrsOTA.handle();
delay(10);
}
Nicht vergessen, die Datendatei ins SPIFFS hochzuladen!
Dieses Beispiel ist etwas komplexer. Zusätzlich zu dem o.a. Bespiel wird ein Web-Server eingerichtet, über den die Datei mit den Einstellungsdaten editiert werden kann. Der Code hierzu ist in der Library enthalten. Weitere Tipps zur ESP8266WebServer-Klasse gibt's auf der Seite ESP8266-Webserver: Spinnen leicht gemacht.
Die bereit gestellte Web-Site sieht so aus:
Auf der Seite ESP8266 UrsAsyncWebServer: Basis-Web-Site für alle Projekte ist eine Ableitung des ESPAsyncWebServer beschrieben, der eine verbesserte Version des vorhergehenden Beispiels als Standardfunktionalität bereit stellt.
Die Bibliothek besteht aus den Klassen UrsSettingsBaseClass als Basis-Container-Klasse für Einstellungswerte und SettingsValue als Basis für die Einstellungswerte. Hinzu kommen spezifische Klassen für die unterschiedlichen Typen von Einstellungswerten SettingsXxxValue mit dem Typ Xxx. Sämtliche Elemente, auf die die Applikation kein Zugriff haben soll, sind als private oder protected deklariert. Der Zugriff innerhalb der Bibliothek wird durch entsprechende friend Deklarationen sicher gestellt.
UrsSettingsBaseClass dient als Basis-Klasse für einen Container mit Einstellungswerten, abgeleitet von SettingsValue. Pointer auf die definierten Wert-Variablen werden in einer internen Liste std::list<SettingsValue *> values gehalten. In diese Liste tragen sich die Eigenschaftsvariablen über die Methode add(SettingsValue *setting) im Constructor-Code ein. add() ist virtual und protected, abgeleitete Klassen könne hier also eigene Ergänzungen festlegen.
loadFromFile() liest die angegebene Datei Zeilenweise und vergleicht den Zeilenanfang mit sämtlichen Namen in der Einstellungswerte in der gespeicherten Liste. Bei einem Treffer wird der Status des Einstellungswerts von Default auf Found gesetzt und der Rest der um führende und anhänge Leerzeichen bereinigte Text an die Methode fromString() des Einstellungswert-Objekt übergeben.
Dieses Objekt versucht aus dem Text einen passenden Wert für die Variable value abzuleiten. War dies möglich wird der Wert gespeichert und true zurück geliefert. Der Status des Einstellungswerts wird daraufhin von Found auf ValueRead gesetzt.
loadFromFile() liefert true, wenn die Datei geöffnet und gelesen werden konnte. Dies ist unabhängig davon, die Werte erfolgreich eingelesen werden konnten.
SettingsValue ist virtuell und kann nicht direkt instanziiert werden. Sie dient als Basisklasse für typspezifische Einstellungswerte. Sie beinhaltet die privaten Variablen _Name und _State für die Aufnahme des Suchbegriffs in der Einstellungsdatei und den Status. _Name wird im Constructor mit dem übergegeben Parameternamen belegt. Ebenfalls wird eine Referenz auf den zugehörigen Container, Ableitung von UrsSettingsBaseClass, übergeben (*this in der abgeleiteten Klasse, s. Beispiele oben). Über dessen Methode add() wird die erzeugte Instanz einer Ableitung von SettingsValue in die zugehörige Liste eingetragen.
Weiterhin deklariert sie die virtuelle Methode fromString(String Text), die den übergeben Text in einen typgerechten Wert konvertieren soll. Diese Methode muss von den abgeleiteten Klassen überschrieben werden.
Diese Klassen sind typspezifische Ableitungen von SettingsValue. Sie definieren das Feld value vom Typ Xxx. Außerdem stellen sie eine konkrete Ausprägung der Methode fromString() bereit.
Hierzu ist nicht viel zu tun. Zunächst definiert man eine entsprechende Klasse, die den entsprechenden Einstellungswert-Typ repräsentiert. Im nachfolgen Beispiel wird eine entsprechende Klasse für den Typ uint8_t (Byte) entwickelt.
Zunächst die Klassendefinition. Diese kann man bequem in der Header-Datei für den Einstellungswerte-Container unterbringen. Die anzupassen Code-Sequenzen beziehen sich nur auf den Type:
// MySettings.h
01: class SettingsByteValue : public SettingsValue {
02: protected:
03: virtual bool fromString(String Text) override;
04:
05: public:
06: uint8_t value;
07:
08: SettingsByteValue(UrsSettingsBaseClass& parent, const String Name, const uint8_t defaultValue);
09: SettingsByteValue(UrsSettingsBaseClass& parent, const char* Name, const uint8_t defaultValue);
10:
11: #define ByteSetting(name, default) SettingsByteValue name = SettingsByteValue(*this, #name, default);
12: };
Zeile | Bedeutung |
01 | Definition der Klasse, Ableitung von SettingsValue |
03 | Deklaration von fromString |
06 | Definition des Wertes vom Typ uint8_t |
08 | Constructor I |
09 | Constructor II |
11 | Definition des Konstruktionsmakros |
Dann sind noch die ausstehenden Methoden zu definieren:
// MySettings.cpp
01: SettingsByteValue::SettingsByteValue(UrsSettingsBaseClass& parent, const String name, const uint8_t defaultValue)
02: : SettingsValue(parent, name), value(defaultValue) {
03: }
04:
05: SettingsByteValue::SettingsByteValue(UrsSettingsBaseClass& parent, const char * name, const uint8_t defaultValue)
06: : SettingsValue(parent, name), value(defaultValue) {
07: }
08:
09: bool SettingsByteValue::fromString(String Text) {
10: if (isInteger(Text)) {
11: int v = Text.toInt();
12: if (v <= 255 && v >= 0) {
13: value = v;
14: return true;
15: }
16: }
17: return false;
18: }
In der Code-Datei (.cpp) ist dann zunächst der Constructor zu definieren. Hier werden einfach nur die übergebenen Werte an die Basisklasse weiter gegeben und das Feld value mit dem Default-Wert gefüllt.
Zuletzt muss noch die Methode fromString mit Leben gefüllt werden. isInteger() ist in "UrsSettings.cpp" definiert und prüft, ob ein String komplett in einen Integer gewandelt werden kann.
Das ZIP-Archiv für Bibliothek UrsSettings zum Download. Die entpackten Dateien ins Verzeichnis <user>\Documents\Arduino\libraries kopieren (siehe Installing Additional Arduino Libraries).
Das Archiv enthält die Bibliotheksdateien
Datum | Version | Änderung |
---|---|---|
2017-06-08 | 1.0 | Basisversion |
2017-08-06 | 1.1 | Konvertierungsoperatoren für die Settings-Typen hinzugefügt, z.B. SettingsStringValue -> String |
2017-09-05 | 1.2 | String und Stream als Quelle für Settings-Daten hinzugefügt. |
2017-09-13 | 1.3 | Datenquelle String nutzt eigene
Einlesemethode um den Aufruf von yield() zu vermeiden. AsyncWebServer hat Probleme, wenn innerhalb der asynchronen Methoden yield aufgerufen wird. |
2017-12-06 | 1.4 | isLoaded hinzugefügt. |
2018-01-21 | 1.5 | - SettingsValue von
Printable abgeleitet - Zuweisungsoperatoren für alle Settings-Typen definiert |
2018-01-29 | 1.6 | Der Default-Wert wurde bei IP-Adressen nicht übernommen |
2018-07-08 | 1.7 | Return-Werte bei "loadFromFile" und "virtual size_t printTo(Print& p) ..." wurden nicht weitergereicht. |
2020-07-29 | 1.8 | Copy Constructor und Copy Assingment mit 'delete' versehen. |