WiFi Schaltuhr mit PCF8574 und 8-fach Relais

  • Hallo zusammen,
    ich bastle gerade mal wieder neues Equipment für meinen Bruder.
    Diesmal ist es ein ESP8266-01 mit einer 8-fach Relaiskarte, die über einen PCF8574 angesteuert wird.
    Als Anhaltspunkt für die Stromversorgung diente -> dieses Projekt <-.


    Was wird benötigt?

    • 1 x ESP8266-01 Modul, es geht aber auch ein anderes Modul
    • 1 x 8-fach Relaiskarte, z.B. -> so was hier <- ...


    für die Stromversorgung:

    • 1 x Elko 1200 µF 6V3
    • 1 x Elko 47 µF 25 V
    • 1 x PCF8574
    • 1 x AMS1117 3V3
    • 1 x Steckernetzteil 5V 1A


    ausserdem:

    • einige Kabel
    • Buchsenleiste 2 x 4 abhängig vom verwendeten ESP-Modul


    So, ich hoffe, ich hab' jetzt nichts vergessen.
    Da die Schaltung für die Spannungsversorgung auf der oben verlinkten Seite http://esp8266-server.de/8-I2C-WiFi-Relais.html ausführlich beschrieben ist, spare ich mir an dieser Stelle weitere Kommentare dazu.


    Beschreibung:


    Elektronik:
    Mit einem ESP-Modul - möglichst einem ESP8266-01, weil davon noch einige auf Halde liegen - und einer 8-fach-Relaiskarte soll eine Art Zeitschalt-Uhr realisiert werden. Als Treiberbaustein für die Relais-Karte wird ein PCF8574 verwendet.
    Durch den Treiberbaustein wird das ESP8266-01 Modul um acht Ausgänge erweitert. Die Ansteuerung des PCF8574 erfolgt über I2C. Dazu werden Pin #0 des ESP8266-01 als SCL- und Pin #2 des ESP8266-01 als SDA-Leitung des I2C Bus verwendet.
    Die Verwendung des Pin #0 (GPIO00) des ESP ist in diesem Fall unproblematisch, da dieser Pin zum Einleiten der Flash-Sequenz dient und während eines Restart des ESP konstant auf GND gelegt werden muss.


    Software:
    Für jeden Ausgang des PCF8574 soll es möglich sein, zwei verschiedene Ein- und Ausschalt-Zeiten zu definieren. Da das ESP-Modul keine RTC besitzt, soll die jeweils aktuelle Zeit über NTP ermittelt werden.
    Die Eingabe dieser Zeiten erfolgt über ein HTML-Formular, das vom ESP8266 im HTTP-Server-Modus zur Verfügung gestellt und ausgewertet wird. Dazu ist im Formular eine Tabelle mit acht Zeilen enthalten, in die jeweils zwei Ein- und zwei Aus-Schaltzeiten eingegeben werden können.
    Jede Ein-/Aus-Schaltkombination hat einen zusätzliches Merker, der gesetzt werden kann um den Modus für die jeweilge Schaltsequenz festzulegen.
    Jeder Ausgang hat drei Modi:
    Im Modus ON oder OFF wird der Ausgang, unabhängig vom aktuellen Schaltstatus, ein- bzw. ausgeschaltet.
    Im dritten Modus, AUTO, greifen die Schaltzeiten und der jeweilige Ausgang wird entsprechend der definierten Schaltzyklen ein- oder ausgeschaltet wenn der entsprechende Merker aktiviert ist.
    Für jeden Ausgang gibt es zudem zwei weitere Merker die mit "Ext 1" und "Ext 2" bezeichnet sind. Diese beiden Merker kennzeichnen, ob der zugehörige Schaltzyklus von einer externen Quelle übersteuert werden kann. Hintergrund ist hier, dass es z.B. keinen Sinn macht das geschlossene Gartentor im Falle eines Alarms zyklisch zu öffnen und zu schliessen.
    Diese Einstellungen werden im EEPROM des ESP gespeichert und bei jedem Neustart des Moduls aus dem EEPROM gelesen.


    Die Übersteuerungen erfolgen, soweit möglich, über das Eingabeformular oder über die Web-API des auf dem ESP laufenden HTTP-Servers. Eine detaillierte Beschreibung der Web-API erfolgt zu einem späteren Zeitpunkt.


    Mit seinen vier Schaltflächen stellt das Formular folgende Funktionen zur Verfügung: CANCEL macht alle Änderungen rückgängig. SAVE speichert die Änderungen im EEPROM des ESP. Alle vorgenommenen Änderungen werden zudem sofort wirksam. Mit dem RESET-Button können alle Werte auf ihre Grundeinstellungen und die Schaltzeiten auf 00:00 Uhr gesetzt werden, wobei alle Schaltzyklen deaktivert sind.
    Mit dem Button REBOOT wird ein Software-Reset des ESP-Moduls durchgeführt - d.h. das Modul wird neu gestartet und die Einstellungen aus dem EEPROM gelesen.


    [hr]


    Projekt-Verlauf:


    16.11.2016: Ich hatte da noch einen Bug in der eeprom-Library ... der ist mittlerweile gefixed, das Attachment ist aktualisiert, github ebenfalls
    17.11.2016: Ausführliche Doku für die EEPROM-Bibliothek erstellen.
    17.11.2016: Attachments mit der EEPROM- und der Log-Bibliothek entfernt. Bitte ab jetzt nur noch die im github Repository veröffentlichten Versionen verwenden.
    05.01.2017: Attachment mit sketch entfernt. Bitte nur noch von github herunterladen. Web-API als letzter Punkt wurde Ende 2016 bereits implementiert. Projekt erst einmal erledigt.



    ... tbc


    [hr]


    TODO-Liste:
    Design und Erstellen des HTML-Formulars für die Eingaben (07.11.2016)
    Vereinfachte EEPROM-Zugriffe (endlich) mal in eine eigene Library packen (07.11.2016)
    NTP-Zugriffe implementieren (07.11.2016)
    HTML-Formular Vorlage posten (07.11.2016)
    EEPROM-Library posten (07.11.2016)
    Log-Library posten (07.11.2016)
    Fix in der EEPROM Lib (10.11.2016)
    NTP- und Zeitzonen Code kommentieren und posten (10.11.2016)
    Doku zur EEPROM Lib auf deutsch erstellen und veröffentlichen (16.11.2016)
    Doku zur Logging-Bibliothek auf englisch erstellen und auf github einpflegen (17.11.2016)
    An diesen Beitrag angehängte Versionen der EEPROM- und der SimpleLog-Bibliothek entfernen. Hinweis auf das jeweilge Repository auf github einfügen (17.11.2016)
    Definieren system- und funktionsrelevante Daten (22.11.2016)
    System-Daten in das EEPROM schreiben (22.11.2016)
    Funktions-Daten (Schalttabelle) in das EEPROM schreiben (22.11.2016)
    EEPROM bei jedem Neustart auslesen (22.11.2016)
    Allgemeines setup des sketch implementieren (22.11.2016)
    Zwischenfunktionen implementieren, um die existierenden Code-Fragmente sinnvoll zu nutzen (22.11.2016)
    Doku zur EEPROM Lib auf englisch erstellen und auf github einpflegen (22.11.2016 -> in eigenes Projekt ausgelagert)
    Doku zur Logging-Bibliothek auf deutsch erstellen und im Notiznbuch veröffentlichen (22.11.2016 -> in eigenes Projekt ausgelagert)
    EEPROM Lib und Logging-Bibliothek erweitern und jeweils einen Release erstellen (22.11.2016 -> in eigenes Projekt ausgelagert)
    Für beide Bibliotheken ein oder zwei einfache Beispiele erstellen und auf github einpflegen (22.11.2016 -> in eigenes Projekt ausgelagert)
    Die zugehörigen Dokus aktualisieren (22.11.2016 -> in eigenes Projekt ausgelagert)
    Neues Eingabe-Formular erstellt und eingepflegt (22.11.2016)
    Erster Prototyp - nur Datenverwaltung (22.11.2016)
    Erster kompletter Prototyp (27.11.2016)
    Erster Release fertigstellen (Zieltermin: spätestens 1. Advent) (18.12.2016)
    Kosmetik (18.12.2016)
    Sketch-Grösse optimieren auf max. 50% eines ESP-01 (wegen OTA-Update) (18.12.2016)
    Einfache Prüfungen der Werte (18.12.2016)
    Schaltlogik vereinfachen (18.12.2016)
    Unterstützung für direktes Schalten zweier Relais über GPIO00/GPIO02 und Treiber (18.12.2016)
    OTA (Over The Air) -Update (18.12.2016)
    Implementieren der WEB-API (05.01.2017)


    Nächste Schritte:


    Für die Zukunft:
    In Version 2 unbedingt eine RTC verbauen



    [hr]
    Den Sourcecode des sketch findet ihr ab sofort in meinem -> github-Repository <-.
    Es ist einfacher, den Sourcecode dort zu verwalten.
    Die beiden Libraries (für den vereinfachten EEPROM-Zugriff und das Logging) sind dort ebenfalls in eigenen Repos zu finden.
    Die vereinfachte Formular-Vorlage findet ihr im Anhang.


    Ich hoffe, ich konnte das einigermassen verständlich beschreiben. Wenn irgendwas unklar ist, einfach hier melden ;)



    cu,
    -ds-

  • Also ... ich mach' das jetzt mal Schritt für Schritt in jeweils einem eigenen Baitrag für alle, die etwas in dieser Richtung nachbauen möchten.
    Am Schluß gibt's dann den kompletten sketch sowohl auf gibthub als auch im Eröffnungs-Beitrag.


    Zunächst mal die Zeit-Funktionalität.
    Da wir keine RTC haben ist die Vorgabe, die Uhrzeit über NTP zu aktualisieren. Das kann aber auch nur eine Zwischenlösung sein, denn bei einem Ausfall des WLAN haben wir auch keine aktuelle Zeit mehr. In einer zweiten Stufe wird dann wohl auch eine RTC (z.B. eine DS3231) verbaut.


    Um das NTP (Network Time Protocol) abzuhandeln benötigen wir UDP (User Datagram Protocol)-Unterstützung.
    Ausserdem wird eine sog. "time keeping" Funktionalität benötigt. Das ist eine Art "Unterbau", der sich das aktuelle Datum und die aktuelle Uhrzeit "merkt". Somit stehen diese Daten innerhalb des sketch jederzeit zur Verfügung.
    Schliesslich fehlt noch das korrigieren der Uhrzeit auf die aktuelle Zeitzone. Das hat zudem Auswirkungen auf die Sommer- bzw. Winterzeit.


    Um die UDP-Unterstützung zu integrieren müssen wir die entsprechende Header-Datei des ESP einbinden:

    Code
    1. #include <WiFiUdp.h>            // udp for network time


    Als Bibliothek für das "time-keeping" habe ich mich für Paul Stoffregens TimeLib entschieden (https://github.com/PaulStoffregen/Time). Paul ist imho bekannt für seinen stabilen und sauberen Code. Ich denke, da kann man nichts falsch machen.
    Diese Library müsst ihr zuerst noch installieren, und anschliessend die Header-Datei im sketch einbinden:

    Code
    1. #include <TimeLib.h>            // time keeping feature


    Schliesslich noch die Umrechnung des Datums und der Uhrzeit abhängig von der aktuellen Zeitzone. Die wird unter anderem benötigt, weil dein NTP-Server in der Regel die koordinierte Weltzeit (-> UTC<-) zurücklifert. Hier habe ich erst mal eine Weile gesucht und mich dann für die ZimeZone-Library von Jack Christensen entschieden. Die machte auch mich den besten und vor allem simpelsten Eindruck. Diese Library müsst ihr ebenfalls installieren (https://github.com/JChristensen/Timezone) und anschliessend den Header in den sketch einbinden, so dass sich damit folgender Include-Block ergibt:


    Den zugehörigen Code habe ich "geklaut" und erstmal nur geringfügig angepasst.
    Als Grundlage für die Kommunikation mit dem NTP-server habe ich Paul Stoffregens Beispiel "TimeNTP_ESP8266WiFi" (https://github.com/PaulStoffre…mples/TimeNTP_ESP8266WiFi) abgespeckt, so dass nur folgender Code übrig geblieben ist:




    Die obigen Variablen dienen dem Zugriff auf den NTP-Server. Im setup() des sketch müssen diese noch zugewiesen werden:


    Code
    1. ...
    2. ntpServerName  = DEAULT_NTP_SERVERNAME;
    3. Udp.begin(localUDPPort);
    4. ...


    Für das Handling des NTP benötigen wir ausserdem zwei Funktionen, getNtpTime() und sendNTPpacket().
    Die beiden Funktionen habe ich ebenfalls aus dem Beispiel übernommen und die Serial-Ausgaben durch meine Logging-Funktion ersetzt. Das Ganze sieht dann so aus:



    Im setup() müssen wir jetzt noch die synchronisierung aktivieren und das UDP initialisieren. Dazu fügen wir folgenden Code in setup ein:


    Code
    1. ...
    2.  Udp.begin(localUDPPort);
    3. ...
    4.  // after syncing set initial time
    5.  setSyncProvider(getNtpTime);
    6.  setSyncInterval(SECS_PER_HOUR);
    7.  setTime(now());
    8. ...


    Udp.begin() intialisiert den UDP-Stack, mit setSyncProvider() wird der callback definiert, der zur Snchroniserung verwendet wird und mit setSyncInterval() wird das Intervall festgelegt, in dem die Synchronisierung erfolgen soll. In unserem Fall SECS_PER_HOUR also einmal pro Stunde.



    Kommen wir zum letzten Punkt in diesem Abschnitt: das Umrechnen für die Zeitzone unter Berücksichtigung von Sommer- und Winter-Zeit.
    Zu diesem Thema hatte ich eine Menge Beispiele ausgegraben, aber die waren mir entweder zu umfangreich, zu unflexibel oder einfach nur suspekt ;) ...
    Bei Jack Christensen binn ich dann fündig geworden. Das Ganze ist gut gelöst, leicht verständlich und daher optimal für meine Zwecke geeignet.
    Sein WorldClock-Beispiel (https://github.com/JChristense…aster/examples/WorldClock) beinhaltet alles, was wir brauchen. Das sind zunächst mal folgende globale Variablen:


    Damit ist schon fast alles erledigt. Das korrigierte Datum und die passende Uhrzeit könnt ihr nun mit

    Code
    1. ...
    2.      printTime(CE.toLocal(utc, &tcr), tcr -> abbrev, "Paris");
    3. ...


    ausgeben.
    Die Funktion printTime() sowie die zugehörigen weiteren Funktionen könnt ihr dem WorldClock-Beispiel entnehmen.
    Da ich keine Ausgaben der Unrzeit geplant habe, habe ich das allerdings weggelassen.
    CE.toLocal() gibt einen time_t Wert zurück, der den für unsere Zeitzone gültigen Wert enthält.
    Ihr könnt sogar die Zeitzonen-Einstellung ins EEPROM schreiben. Dazu gibt es die Funktion

    Code
    1. CE.writeRules(100); //write rules to EEPROM address 100


    Aber Achtung!
    Denkt daran, dass wir später die gesamten Einstellungen des ESP inkl. der Schalt-Tabelle ins EEPROM schreiben. Wählt also für die Zeitzone eine entsprechend hohe Adresse oder behaltet im Hinterkopf, die anderen Einstellungen entsprechend nach hinten zu schieben.
    Notwendig ist das Abspeichern der Zeitzone nicht.


    Das war jetzt mal der erste Teil ... das NTP-Handling.
    Falls noch Fragen sind: her damit. Ansonsten geht's demnächst hier weiter ...
    Anmerkung aus diesem Teil: für eine zweite Version sollte unbedingt eine RTC verbaut werden.


    Wichtiger Hinweis: Sourcen poste ich, der Einfachheit halber, alle im -> Eingangs-Beitrag <-. Schaut also dort also hin und wieder rein, denn dort sind auch fehlerbereinigte Sourcen zu finden. So hatte die Library für das EEPROM Handling noch einen Bug. Näheres dazu, wie gesagt, -> im Eingangs-Post <-.




    ciao,
    -ds-

  • In diesem Teil geht es um die Daten, die wir halten und speichern müssen.
    Diese können wir schon mal in systemrelevante und funktionsrelevante Daten unterteilen. Systemrelevant sind z.B. die SSID des WLAN, die Passphrase zum anmelden usw..
    Funktionsrelevant sind die Schaltzeiten, die wir über das Formular eingeben und ändern können.


    Systemrelevante Daten:
    Hier haben wir zunächst mal die SSID und die Passphrase zur Anmeldung am WLAN. die wir uns im EEPROM merken. Ausserdem berücksichtigen wir gleich die Weiter-Entwicklung dieses Projekts.
    Wie sich herausgestellt hat, ist es sinnvoll, die Einstellungen über ein Passwort zu schützen. Dabei geht es weniger um "Hacker" als darum, dass der 3-jährige Sohnemann nicht zufällig irgendwelche Einstellungen ändert :) ...


    Also kommen noch hinzu: das Administrator-Passwort und der eindeutige Node-Name des ESPs.


    Für die NTP-Zugriffe werden wir den Server und den Port ebenfalls variable halten. Also speichern wir beide Variablen ebenfalls ins EEPROM.


    Damit wären wir wohl für alle Eventualitäten gewappnet. Unsere System-Variablen sind jetzt:


    Code
    1. static String ntpServerName;
    2. static String wlanSSID;
    3. static String wlanPassphrase;
    4. static bool useDhcp;
    5. static String wwwServerIP;
    6. static String wwwServerPort;
    7. static String nodeName;
    8. static String adminPasswd;
    9. static unsigned int localUDPPort;


    Jetzt gibt es einige Besonderheit in der EEPROM-Library: die Positionen und Längen einige System-Variablen sind bereits vordefiniert. Ihr müsst diese Vorgaben nicht nutzen, ich für meinen Teil fand das Feature einfach praktisch.
    Allerdings decken sich die vordefinierten System-Variablen nicht mit denen, die wir oben festgelegt haben. In der dsEeprom.h sind die Positionen und Löngen für folgende Systemvariablen vordefiniert:

    Code
    1. wlanSSID
    2. wlanPassphrase
    3. wwwServerIP
    4. wwwServerPort
    5. nodeName
    6. adminPasswd


    In der Headerdatei dsEeprom.h findet ihr diese vordefinierten Längen- und Positions-Angaben. Sie sollten ausreichend gross dimensioniert sein, aber ihr könnt sie ggf. anpassen:


    Weiter möchte ich an dieser Stelle jetzt gar nicht auf die Library eingehen. Das erledige ich, inkl. der Beschreibung der Logging Library, in einem eigenen Kapitel.


    Da ich die Library so wie sie ist verwende, ist die für mich erste, relevante Datenposition EEPROM_STD_DATA_END oder EEPROM_EXT_DATA_BEGIN. Die sind beide gleich ... ihr habt also die Wahl. Ich verwende hier EEPROM_STD_DATA_END.


    Jetzt kommen unsere Gedanken vom Anfang ins Spiel:
    Neben den obigen Daten wollten wir als weitere Systemdaten den NTP-Server und der Port, der verwendet werden soll, abspeichern.
    Um das Ganze übersichtlich zu halten, definieren wir erst mal einen Startpunkt:

    Code
    1. #define EEPROM_ADD_SYS_VARS_BEGIN  EEPROM_STD_DATA_END


    Als nächsten legen wir die Länge der Einträge fest. Für den NTP-Server benötigen wir mindestens 15 Zeichen ( IPV4-Adresse ). Legen wir also noch eine Handvoll drauf und legen die maximale Länge des Eintrags mit 20 Zeichen fest. Der Port ( max. 65536 ) ergibt sich von selbst. Auch diese Daten werden als String hinterlegt:

    Code
    1. #define EEPROM_MAXLEN_NTP_SERVER_NAME    20
    2. #define EEPROM_MAXLEN_NTP_SERVER_PORT     5


    Somit ergeben sich als Positionen:

    Code
    1. #define EEPROM_POS_NTP_SERVER_NAME       (EEPROM_ADD_SYS_VARS_BEGIN)
    2. #define EEPROM_POS_NTP_SERVER_PORT       (EEPROM_POS_NTP_SERVER_NAME  + EEPROM_MAXLEN_NTP_SERVER_NAME   + EEPROM_LEADING_LENGTH)
    3. [code]
    4. Schliesslich setzen wir noch einen Merker auf das Ende der effektiven Systemdaten und definieren den Anfang der externen Daten neu. Das muss nicht sein, dient aber der Übersichtlichkeit:
    5. [code]
    6. #define EEPROM_ADD_SYS_VARS_END          (EEPROM_POS_NTP_SERVER_PORT  + EEPROM_MAXLEN_NTP_SERVER_PORT + EEPROM_LEADING_LENGTH)
    7. #define EEPROM_EXT_DATA_BEGIN             EEPROM_ADD_SYS_VARS_END



    Unsere Daten für die Schaltvorgänge entnehmen wir der Vorgabe. Wir brauchen eine Tabelle mit acht Zeilen. Jede Zeile enthält einen Namen sowie zwei Schaltzyklen für jeweils einen Ausgang. Ausserdem die beiden Flags für externe Übersteuerung, jeweils ein Markierungsflag pro Zeit und einen Modus für den Ausgang.


    Ausser den beiden Flags sind alle Felder der Einfachheit halber als String definiert. Das Ganze ergibt dann folgende Struktur:


    Dazu definieren wir uns die Anzahl der Zeilen:

    Code
    1. #define MAX_ACTION_TABLE_LINES   8


    und legen anschliessend die Tabelle als globale Variable an:

    Code
    1. struct _action_entry tblEntry[MAX_ACTION_TABLE_LINES];


    In einem "normalen" Programm würde man möglichst vermeiden, globale Variablen zu verwenden. Das gilt allgemein als schlechter Programmierstil.
    Bei µControllern sieht das anders aus. Hier ist gerade das RAM meist sehr knapp bemessen. Da Parameter über den Stack übergeben werden benötigen siejeweils mehr oder zumindest genaus so viel Speicher zusätzlich wie die Instanz der Variablen selbst. Hinzu kommt, dass das Holen der Variablen vom Stack u.U. zusätzliche (wertvolle) Arbeitstakte benötigt.
    Deshalb ist es von Vorteil bei µControllern die Variablen möglichst global zu halten.


    Um die Daten abzulegen und auszulesen fehlt jetzt noch die Länge. Hierzu definieren wir:

    Code
    1. #define EEPROM_MAXLEN_TBL_ROW_NAME    15
    2. #define EEPROM_MAXLEN_TBL_ROW_MODE     5
    3. #define EEPROM_MAXLEN_TBL_ENABLED      EEPROM_MAXLEN_BOOLEAN
    4. #define EEPROM_MAXLEN_TBL_HR_FROM      2
    5. #define EEPROM_MAXLEN_TBL_MIN_FROM     2
    6. #define EEPROM_MAXLEN_TBL_HR_TO        2
    7. #define EEPROM_MAXLEN_TBL_MIN_TO       2
    8. #define EEPROM_MAXLEN_TBL_EXT_ENABLED  EEPROM_MAXLEN_BOOLEAN


    Für den frei definierbaren Namen einer Tabellenzeile ( eines Ausgangs ) legen wir mal 15 Zeichen als Maximum fest. Das sollte ausreichen. Den Modus legen wir mit 5 Zeichen fest - mehr als "on", "off", "auto" werden wir hier nicht ablegen.
    Die Stunden und Minuten sind jeweils zweistellig.
    BOOLEAN für die Flags ist bereits in der Library definiert.


    Fehlen noch die Positionen. Das ist jetzt etwas schwierig, denn die Anzahl der Zeilen wollen wir ja möglichst flexibel halten. Mein Ansatz dazu: wir definieren nur die Positionen des ersten Datensatzes ( der ersten Zeile ) und rechnen von da ausgehen die Positionen der anderen Zeilen hoch.


    Wie weiter oben schon erwähnt fangen unsere Daten ab Position EEPROM_EXT_DATA_BEGIN an. Somit ergibt sich folgendes Layout:


    Um die gesamte Grösse der Tabelle im EEPROM zu erhalten, brauchen wir die Länge eines Datensatzes. Diese erhalten wir durch:

    Code
    1. #define EEPROM_ACTION_TBL_ENTRY_LENGTH (EEPROM_ACTION_TBL_ENTRY_END - EEPROM_ACTION_TBL_ENTRY_START)


    Multiplizieren wir diesen Wert mit der Anzahl der Tabellen-Zeilen und addieren diesen Wert auf den Anfang der Tabelle im EEPROM, erhalten wir die Endposition der Daten im EEPROM:

    Code
    1. #define EEPROM_EXT_DATA_END            (EEPROM_ACTION_TBL_ENTRY_START + (EEPROM_ACTION_TBL_ENTRY_LENGTH * MAX_ACTION_TABLE_LINES))


    Was jetzt noch fehlt sind die Funktionen zum Lesen und Schreiben der Daten. Diese Funktionen nutzen die API der dsEeprom-Klasse. Weitere Informationen dazu sind in der Beschreibung der dsEeprom-Library zu finden.
    Deshalb liste ich die Funktionen einfach mal ohne weiteren Kommentar hier auf.



    Das wäre jetzt mal der Part mit den Daten gewesen. Die Beschreibung der Libraries folgt dann als Nächstes.
    Im letzten Schritt werden wir dann versuchen aus den ganzen Code-Fragmenten und weiteren Funktionen was sinnvolles zusammenzubauen.


    Bid dahin viel Spass beim Nachbauen,
    -ds-


    [hr]
    Eine relativ umfangreiche deutsche Beschreibung der EEPROM-Bibliothek für ESP8266 und Arduino ist erst einmal aktuell und fertig. Sie ist derzeit unter:
    http://dreamshader.bplaced.net…ff-auf-das-onchip-eeprom/ erreichbar.

  • Hallo zusammen,
    es ist Zeit für den ersten Prototypen :) ...


    Im Eingabeformular sind die einzelnen Auswahl-Listen nun durch einfache Text-Eingabefelder ersetzt worden. Unsr -> Tell <- hat mir da den passenden Tipp gegeben. Die Felder waren ursprünglich viel zu groß und das Ganze sah entsprechend aus ... "eine Prise css" brachte da den gewünschten Erfolg.
    Das Eingabeformular ist im Ausgangspost bereits geändert und heisst jetzt Home.htm.


    Das Generieren des Eingabeformulars ist als erster Prototyp abgeschlossen. Es wurde in mehrere Funktionen aufgeteilt, die ich hier kurz vorstellen möchte.
    Da wäre die Funktion setupPage(). Hier wird der Inhalt des Dokuments zusammengestellt:


    Innerhalb dieser Funktion wird doCreateLine() für jede Zeile der Tabelle (jeden Ausgang) aufgerufen. Hier geschieht folgendes:



    Es werden die einzelnen Datenfelder positioniert und, so weit möglich, mit den Variablen gefüllt. Das ist oben mal exemplarisch für die Checkbox "Zeit 1" und das Optionsfeld "Modus" aufgezeigt. Im Grunde nur reine Fleissarbeit ...
    Für jedes Feld, das eine Uhrzeit beinhaltet wird zudem doTimeSelect() aufgerufen:



    Da die Eingabefelder für die Zeiten alle nach dem selben Schema behandelt werden, ruft doTimeSelect() wiederum eine Funktion auf, die die eigentlichen Felder für Stunde und Minute erzeugt:



    Damit wäre das Formular aufgebaut. Es muss jetzt noch "scharfgeschaltet" werden. Ausserdem müssen wir noch die Daten aus dem EEPROM lesen.
    Dazu brauchen wir noch ein paar kleine Ergänzungen im Source-Code.
    In setup() kommen die Lesefunktionen für das EEPROM hinzu:



    Anschliessend wird der HTTP-Server gestartet und ein Handler auf die Index-Seite gesetzt:



    Schliesslich muss in loop() noch das Abarbeiten der Server-Requests eingebaut werden. Das geschieht folgendermassen:


    Code
    1. // ************************************************************************
    2. //
    3. void loop()
    4. {
    5.  server.handleClient();
    6. }


    Das ist im Übrigen auch schon der vorerst gesamte Inhalt von loop().
    Im Prinzip wäre es das schon ... aber es fehlt noch ein wichtiges Feature: das speichern der Daten.
    Das erfolgt im Seiten-Handler, also der Funktion setupPage():




    Um mir das Leben etwas einfacher zu gestalten, habe ich die Namen der Formular-Variablen in einem Array abgelegt. Dadurch ist es möglich mit Schleifen zu arbeiten. Das wiederum macht den Code übersichtlicher.
    Ok, Zuweisungen wie:


    Code
    1. tblEntry[i].minuteTo_2   = server.arg( _form_keywords_[i][KW_IDX_MTO_2] );
    2. oder
    3. tblEntry[i].enabled_1   = server.arg( _form_keywords_[i][ KW_IDX_ENABLED_1] ).equalsIgnoreCase("aktiv") ? true : false ;


    mögen etwas cryptisch erscheinen. Ist aber imho alles halb so wild und m.E. einfacher als ellenlange Zuweisungen mit Formular-Variablen, die zudem noch eine Quelle für Schreibfehler darstellen.
    Das Array steht am Anfang des Source und sieht so aus:



    Das wäre jetzt mal der komplette Prototyp zur Datenverwaltung.
    Was im nächsten Schritt hinzukommt ist die Schaltfunktionalität. Ausserdem fehlt noch einiges an Kommentaren und sonstiger Kosmetik.
    Die Web-API wird derzeit noch nicht benötigt und wird später beschrieben und implementiert.


    Der Sourcecode ist bereits im -> Eingangs-Beitrag <- als Anhang verfügbar. Die Aktualisierung von github folgt ...




    Dann erst mal gutes Gelingen beim Nachbau und bis zum nächsten Schritt,
    -ds-

  • Auf zum Endspurt ...
    Um die Relais am Portexpander zu schalten, habe ich mir eine Library aus dem Internet besorgt. Ihr findet die Sourcen hier -> http://playground.arduino.cc/Main/PCF8574Class <-. Dort die PCF8574.h und PCF8574.cpp herunterladen, ein Verzeichnis PCF8574 unter ~/sketchbook/libraries anlegen und die beiden Dateien dort speichern.
    Ich habe diese kleine Lib getestet ... sie funktioniert einwandfrei, so dass ich gar nicht mehr nach anderen Bibliotheken gesucht habe.


    Für die Schaltlogik habe ich jetzt die Zeiten einfach in Minuten seit 00:00 Uhr umgerechnet ... das erschien mir am einfachsten.
    Aus dieser Idee sind zunächst mal die folgenden Funktionen entstanden.
    Achtung! Es ist noch keine Konsistenz-Prüfung der Daten eingebaut!
    Die Schalterei funktioniert jedenfalls schon mal und scheint auch zu stimmen.
    Die diversen Flags (weil abhängig von der WEB-API) werden noch nicht ausgewertet.


    [hr]


    startupActions() - klappert alle Schaltzeiten ab und überprüft, ob der Ausgang geschaltet werden muss oder nicht. Diese Funktion wird z.B. beim Initialisieren (setup) des sketch oder auch nach einer Änderung der Schaltzeiten aufgerufen, um einen gültigen Zustand zum aktuellen Zeitunkt herzustellen.
    Eine Besonderheit ist der Fall eines time-wrap ... hier ist die Ausschalt-Zeit kleiner als die Einschaltzeit, weil der zugehörige Schaltzustand über Mitternacht andauert ( z.B. 23:00 Uhr bis 01:15 Uhr ).



    [hr]


    Dann die Funktion check4Action() ...
    Hier wird die aktuelle Zeit (in Minuten seit Mitternacht) mit den Start- und Endzeiten aus der Tabelle verglichen und die passenden Ausgänge ein- oder ausgeschaltet.



    [hr]


    Und schliesslich die loop() Funktion ändern. Ich hab' einfach den Aufruf der Prüf-Funktion und einen Vergleich der aktuellen Minuten mit einer Merker-Variablen (statisch!) eingebaut, damit die Prüfung nur einmal pro Minute stattfindet.



    [hr]


    Nun denn, das war jetzt mal, ganz unbürokratisch, der erste Wurf für den WiFi Schalter.
    Der sketch ist jetzt auch auf github eingepflegt.
    Der Eingangs-Beitrag ist ebenfalls aktualisiert und enthält jetzt den neuen sketch.
    Es folgen noch ein paar kosmetische Maßnahmen als auch Konsistenz-Prüfungen für die eingegebenen Daten.
    Als letzten Schritt definieren wir beim nächsten Mal die Web-API und implementieren das Behandeln der Flags ...
    Das werde ich demnächst noch posten. Ansonsten ist der sketch von der Logik her ok und fertig.


    //EDIT: wie ich gerade festgestellt habe, ist in der startupActions()-Funktion beim Auswerten der Schaltzeiten noch ein Denkfehler drin. Das poste ich selbstverständlich noch, sobald es korrigiert ist.


    Viel Spass beim Nachbauen,
    -ds-

  • Hallo zusammen,
    ich habe eine Weile hier nichts mehr aktualisiert, weil es einige Änderungen gab, die ich zunächst mal implementieren wollte.
    Der sketch ist mittlerweile, bis auf die Web-API, fertig und schon im Test-Einsatz. Ausserdem sind einige Features hinzugekommen:


    • Der gesamte Sourcecode wurde kosmetisch aufbereitet und ausführlicher dokumentiert.
    • Die Eingabewerte der Schaltzeiten werden jetzt überprüft bevor sie übernommen werden.
    • Das Thema restart ( herstellen des richtigen Status nach einem reboot ) wurde optimiert und funktioniert zuverlässig.
    • Das direkte Schalten zweier Relais über GPIO00 und GPIO02 über Treiber ist jetzt auch möglich.
    • Die Grösse des sketch ist jetzt kleiner als 50% des Speichers eines ESP-01 um ein OTA-Update zu ermöglichen.
    • OTA (Over The Air) - Updates der Firmware werden jetzt unterstützt.
    • Das Problem mit dem WiFi.begin(), das -> joh.raspi hier <- gepostet hatte, wird berücksichtigt.


    Sowohl -> mein github-Repo <- als auch -> der Eingangspost <- sind auf dem aktuellsten Stand.


    Wie immer: bei Fragen, Ideen, Kritik - immer her damit.


    Bis demnächst,
    -ds-

  • Hallo
    Da ich sehr an dieser Wifi Schaltuhr intressiert bin möchte ich mir dafür eine Platine
    anfertigen. Einen Entwurf mir Sprint Layout wurde dafür schon entworfen.
    Die Platine kann direkt auf das 8fach Relaisboard gesteckt werden.
    Da ich aber leider keine grossen Kenntnisse im Gebiet software habe bräuchte ich
    Unterstützung beim Programmieren des ESP01.
    So kleinere Handhabungen wie einen DS18, DHT22 und BMP habe ich schon mit
    vorgefertigten Skripten hinbekommen.

  • Hallo dreamshader
    Mein Problem ist, dass ich nicht genau weiss was in den Skript so rein muss.
    Habe mal folgendes eingetragen aber es erscheint eine Fehlermeldung im
    Arduino IDE.
    Der ESP01 sitzt im Programmer.
    Als Firmware wurde "ESP_8266_v0.9.2.2 AT" vorher geflasht.
    Unter Libraries ein Verzeichnis PCF8574 erstellt und mit
    PCF8574.cpp, PCF8574.h, Timelib.h befüllt.
    Automatisch zusammengefügt:
    [hr]

  • Da scheint aber irgendwas mit Deiner Umgebung nicht zu stimmen.
    Das sieht so aus, als wäre die ESP-Plattform nicht in die IDE integriert.
    Hast Du schon mal was für den ESP mit der IDE erstellt?
    Da fehlt vermutlich der Eintrag

    Code
    1. http://arduino.esp8266.com/stable/package_esp8266com_index.json


    unter Voreinstellungen -> zusätzliche Boardverwalter-URLs


    Ansonsten findest Du zum Relais-Projekt ausführlichere Info -> hier <- ...


    cu,
    -ds-

  • Hm ... er sagt aber, dass das generic Board des ESP unbekannt ist.
    Probier mal über die Boardverwaltung eine Aktualisierung zu machen ...
    Ansonsten würde ich an Deiner Stelle die IDE zu löschen und neu zu installieren.
    Darin nach irgendwelchen Fehlern/Inkosistenzen zu suchen lohnt imho nicht.
    cu,
    -ds-

  • Ja servus,
    schmeiss mal Deine time Geschichten weg. -> Auf meiner Webseite <- steht, welche Lib verwendet wird:

    Zitat


    Als Bibliothek für das „time-keeping“ habe ich mich für Paul Stoffregens TimeLib entschieden (https://github.com/PaulStoffregen/Time). Paul ist imho bekannt für seinen stabilen und sauberen Code. Ich denke, da kann man nichts falsch machen.
    Diese Library müsst ihr zuerst noch installieren, und anschliessend die Header-Datei im sketch einbinden:


    na und dann seh'n wir mal weiter ...
    cu,
    -ds-

  • Naja ... die Hinweise auf doppelt vorhandene Libs sind imho nicht weiter tragisch.
    Er findet halt die EEPROM.h nicht ... und das ist sonderbar, denn die ist in der IDE-Installation bereits enthalten.
    Hast Du die evtl. wieder irgendwie da "reingezaubert" ...
    Schau mal, ob Du nicht vielleicht die .h oder die .cpp der EEPROM Lib umbenennen musst ...


    cu,
    -ds-

  • Hmm ... also irgendwie verbiegst Du Dir da dauernd die Umgebung der IDE :s
    Noch dazu Windows ...


    Na gut, kannst Du mal eine saubere Installation der aktuellen IDE (so um 1.6.13) machen, ohne irgendwelche Bibliotheken irgendwo hinzukopieren? Und vielleicht schaust Du sicherheitshalber vorher mal nach, ob in Deinem Home-Verzeichnis auch was von der IDE installiert wird und benamst das Verzeichnis mal um.


    Nur die IDE ... sonst nix ...
    Und dann in "Voreinstellungen -> Zusätzliche Boardverwalter"

    Code
    1. http://arduino.esp8266.com/stable/package_esp8266com_index.json


    Und nur diesen Eintrag ... sonst absolut nichts ändern ...
    Müsste doch mit Beelzebub zugehen.
    Bei mir funktionierts ja auch mit verschiedenen Umgebungen.
    So bekommst Du jedenfalls keinen sketch verlässlich übersetzt.


    cu,
    -ds-

  • Ah ja ... das sieht schon besser aus.
    Die Dateien für den PCF8574 musst Du selbst anlegen.
    Ein Verzeichnis in libraries mit dem Namen PCF8574 erstellen. Darin dann eine PCF8574.h und eine PCF8574.cpp anlegen (Achtung! Auf die Schreibweise des Namens achten). Den Quellcode bekommst Du hier: http://playground.arduino.cc/Main/PCF8574Class
    Lass Dich nicht irritieren, dass dort die Dateinamen klein geschrieben sind.


    Und dann sehen wir mal weiter ...
    -ds-