Motivation

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.

In­halts­ver­zeich­nis

Dateistruktur

Bibliothek UrsSettings

Verwendung

Methodenübersicht

Beispiele

Implementierung

Eigene Einstellungswert-Typen ergänzen

Download

Versionshistorie


Dateistruktur

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.

Bibliothek UrsSettings

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.

Verwendung

Zur Nutzung dieser Bibliothek sind mehrere Schritte notwendig:

Container-Klasse deklarieren

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;

Container-Klasse strukturieren

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:

"MySettings.h".

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

MySettings.cpp

01: // MySettings.cpp
02: 
03: #include "MySettings.h"
04: 
05: MySettingsClass MySettings;
Zeile  Bedeutung
05 Definition der Container-Variablen

Einstellungswerte einlesen

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.).

Methodenübersicht

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. "/".
Wenn die Datei nicht vorhanden ist oder Werte fehlen, werden die vordefinierten Standardwerte aktiv.

Der übergebene Dateiname wird gespeichert.

Fehlerursachen sind:

  • SPIFFS konnte nicht initialisiert werden
  •  Datei ist nicht vorhanden.
uint8_t loadFromFile() Lädt die Einstellungswerte über den bereits eingestellten Dateinamen.
Liefert 1, wenn erfolgreich, 0 bei Fehler.

Fehlerursachen sind:

  • SPIFFS konnte nicht initialisiert werden
  •  Datei ist nicht vorhanden.
  • Dateiname nicht angegeben (per Constructor oder durch vorhergehendes loadFromFile(SettingsFileName).
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:

  • SettingsValueState::Default: ursprünglicher Wert
  • SettingsValueState::Found: in der Datei gefunden
  • SettingsValueState::ValueRead : Wert aus Datei eingelesen

Beispiele

SettingsSample

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!

SettingsServer

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:

SettingsServer Web-Site

Web-Interface

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.

Implementierung

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

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

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.

SettingsXxxValue

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.

Eigene Einstellungswert-Typen ergänzen

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.

Download

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

Versionshistorie

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.