Immer mehr Geräte werden „smart“, warum nicht auch der Weihnachtsbaum. Dieses Projekt befasst sich mit der Steuerung der Weihnachtsbaum-Lichterkette über Browser und Alexa. Im Browser und über LED´s auf der Hardware kann zusätzlich der Wasserstand im Weihnachtsbaumfuß überwacht werden. Natürlich kann auch ein Timer konfiguriert werden, um die Beleuchtung regelmäßig ein- und auszuschalten. Die gesamte Konfiguration erfolgt im Browser. Also los, worauf warten Sie noch!
Benötigte Hardware
Es werden zwei Varianten beschrieben.
Freier Aufbau ohne Gehäuse:
Anzahl |
Bauteil |
Anmerkung |
---|---|---|
1 |
|
|
1 |
|
|
1 |
Für Wasserstand Sensor |
|
3 |
Für Wasserstand Sensor |
|
4 |
Für Wasserstand Sensor |
Die Bauteile für den Wasserstand Sensor sind optional, wenn dieses Feature genutzt werden soll.
Aufbau auf Dual-Base Shield mit Gehäuse aus dem 3D-Drucker:
Anzahl |
Bauteil |
Anmerkung |
---|---|---|
1 |
|
|
1 |
|
|
1 |
|
|
1 |
Für Wasserstand Sensor |
|
1 |
Für Wasserstand Sensor |
|
3 |
Für Wasserstand Sensor |
|
4 |
Für Wasserstand Sensor |
|
1 |
Abgewinkelte Stiftleiste 3-polig |
Für Wasserstand Sensor |
1 |
|
|
1 |
|
|
1 |
Für Wasserstand Sensor |
|
1 |
Für Wasserstand Sensor |
|
1 |
Für Wasserstand Sensor |
Die Bauteile für den Wasserstand Sensor sind optional, wenn dieses Feature genutzt werden soll.
Wir haben auch ein Video zum Blog vorbereitet:
Die Schaltung
Freier Aufbau ohne Gehäuse:
Mit Dual-Bas Shield und Relaisshield:
Aufbau der Variante 2
Wenn die Option Wasserstand-Sensor verwendet werden soll, muss der D1-Mini mit den kombinierten Stift- und Federleisten bestückt werden, damit dann der Sensor-Shield draufgesteckt werden kann. Beim Relais Shield ist nichts zu tun. Das Dual Base Shield wird mit den beiliegenden Federleisten bestückt.
Das Sensor Shield wird auf dem D1-Mini Prototyping Shield aufgebaut. Zuerst sollten die Widerstände, dann die Stiftleisten und zuletzt die Leuchtdioden eingebaut werden. Die Anordnung der Bauteile wurde so gewählt, dass die Verdrahtung mit blanken Drähten erfolgen kann. Lediglich für die Masseleitung (in der folgenden Abbildung lila dargestellt), muss ein isolierter Draht verwendet werden.
Software
Damit der Sketch kompiliert werden kann, muss die Arduino IDE entsprechend vorbereitet werden. Die Arduino IDE unterstützt standardmäßig eine große Anzahl von Boards mit unterschiedlichen Mikrocontrollern, nicht aber den ESP8266. Damit man Programme für den ESP8266 erstellen und hochladen kann, muss daher ein Softwarepaket für die Unterstützung des ESP8266 installiert werden.
Zuerst müssen Sie der Arduino-IDE mitteilen, wo sie die zusätzlich benötigten Daten für den ESP8266 findet. Dazu öffnen Sie im Menü Datei den Punkt Voreinstellungen. Im Voreinstellungs-Fenster gibt es das Eingabefeld mit der Bezeichnung „Zusätzliche Boardverwalter URLs“. Wenn Sie auf das Ikon rechts neben dem Eingabefeld klicken, öffnet sich ein Fenster in dem Sie die URL
https://arduino.esp8266.com/stable/package_esp8266com_index.json eingeben können.
Nun wählen Sie in der Arduino IDE unter Werkzeug → Board die Boardverwaltung.
Es öffnet sich ein Fenster, in dem alle zur Verfügung stehenden Pakete aufgelistet werden. Um die Liste einzugrenzen, gibt man im Suchfeld „esp8266“ ein. Dann erhält man nur noch einen Eintrag in der Liste.
Für das Alexa Interface benötigen Sie zwei Bibliotheken, die nicht über die Bibliotheksverwaltung installiert werden können. Diese müssen zuerst als ZIP Datei heruntergeladen werden. Der asynchrone Webserver von https://github.com/me-no-dev/ESPAsyncWebServer und Asynchron TCP von https://github.com/me-no-dev/ESPAsyncTCP. Zum Herunterladen einfach auf den grünen Knopf Code klicken und „Download ZIP“ auswählen.
Um die heruntergeladenen ZIP-Dateien in der Arduino IDE zu installieren, rufen Sie im Menü Sketch -> Bibliothek einbinden -> .ZIP Bibliothek hinzufügen auf. Es erscheint ein Datei-Auswahl-Dialog, indem Sie die heruntergeladenen Dateien auswählen. Mehr ist nicht zu tun.
Schließlich werden noch zwei weitere Bibliotheken benötigt, die über die Arduino Bibliotheksverwaltung installiert werden können. Das ist die Bibliothek „ESPFauxmo“, die das Interface zu Alexa implementiert und die Bibliothek „AsyncWebConfig“, die die Konfiguration über den Browser implementiert.
Wenn alle Bibliotheken installiert sind, kann der Sketch kompiliert und auf die Hardware hochgeladen werden.
Der Sketch
#include <ESP8266WiFi.h> //WiFi support #include <ESP8266mDNS.h> //For name server to use names in place of IP #include <TZ.h> //Timezone dfinitions for clock #include <AsyncWebConfig.h> //Library for configuration #include <ESPAsyncWebServer.h> //Asynchron web server #include <fauxmoESP.h> //Interface for Alexa //Pins used for LED and relais #define PINRELAIS D1 #define PINEMPTY D2 #define PINHALF D3 #define PINFULL D4 #define LVLFULL 300 #define LVLEMPTY 600 #define TIMEZONE TZ_Europe_Berlin #define RECONNECT 300 //seconds to wait for reconnect after disconnect //Form for configuration, JSON formatted String params = "[" "{" "'name':'ssid'," "'label':'Name des WLAN'," "'type':"+String(INPUTTEXT)+"," "'default':''" "}," "{" "'name':'pwd'," "'label':'WLAN Passwort'," "'type':"+String(INPUTPASSWORD)+"," "'default':''" "}," "{" "'name':'alexaname'," "'label':'Name für Alexa'," "'type':"+String(INPUTTEXT)+"," "'default':'Weihnachtsbaum'" "}," "{" "'name':'ntpserver'," "'label':'NTP Server'," "'type':"+String(INPUTTEXT)+"," "'default':'pool.ntp.org'" "}," "{" "'name':'showlevel'," "'label':'Wasserstand anzeigen'," "'type':"+String(INPUTCHECKBOX)+"," "'default':'1'" "}," "{" "'name':'usetimer'," "'label':'Timer verwenden'," "'type':"+String(INPUTCHECKBOX)+"," "'default':'0'" "}," "{" "'name':'starttime'," "'label':'Einschaltezeit'," "'type':"+String(INPUTTIME)+"," "'default':'18:30'" "}," "{" "'name':'stoptime'," "'label':'Abschaltezeit'," "'type':"+String(INPUTTIME)+"," "'default':'23:00'" "}" "]"; const char HTML[] PROGMEM = "<!DOCTYPE HTML>\n" "<html lang='de'>\n" "<head>\n" "<meta http-equiv='Content-Type' content='text/html; charset=utf-8'>\n" "<meta name='viewport' content='width=320' />\n" "<meta http-equiv='refresh' content='5'>\n" "<title>Konfiguration</title>\n" "<style>\n" "body {\n" "background-color: #ecc361;\n" "font-family: Arial, Helvetica, Sans-Serif;\n" "Color: #000000;\n" "font-size:12pt;\n" "width:320px;\n" "}\n" ".titel {\n" "font-weight:bold;\n" "text-align:center;\n" "width:100%%;\n" "padding:5px;\n" "}\n" ".zeile {\n" "text-align: center;\n" "}\n" ".btn {\n" "width:150px;\n" "border-radius:10px;\n" "background-color:%s;\n" "}\n" ".bild {\n" "width:320px;\n" "opacity:%f;\n" "}\n" ".full, .half, .empty {\n" "width:30px;\n" "height:30px;\n" "border-radius:15px;\n" "border:2px solid black;\n" "display:inline-block;\n" "}\n" ".full {\n" "background-color:%s;\n" "margin-left:86px;\n" "}\n" ".half {\n" "background-color:%s;\n" "margin-left:18px;\n" "}\n" ".empty {\n" "background-color:%s;\n" "margin-left:18px;\n" "}\n" ".showlevel {\n" "visibility:%s;\n" "}\n" ".conf {\n" "background-color: lightgray;\n" "border: 2px solid black;\n" "width: 150px;\n" "padding: 1px 0px 1px 0px;\n" "text-align: center;\n" "text-decoration: none;\n" "display: inline-block;\n" "border-radius: 15px;\n" "margin-top: 10px;\n" "cursor: pointer;\n" "}\n" "</style>\n" "</head>\n" "<body>\n" "<div id='main_div'>\n" "<img src='https://cdn.pixabay.com/photo/2021/10/05/21/42/christmas-6683805_960_720.png' class='bild'/>\n" "<form method='post'>\n" "<div class='zeile'><button class='btn' type='submit' name='%s'>%s</button></div>\n" "<div class='showlevel'>\n" "<div class='titel'>Wasserstand</div>\n" "<div class='full'> </div>\n" "<div class='half'> </div>\n" "<div class='empty'> </div></div>\n" "<div class='zeile'><a href='./config' target='blank' class='conf'>Konfiguration</a></div>\n" "</form>\n" "</body>\n" "</html>\n"; //Global variables AsyncWebServer server(80); AsyncWebConfig conf; fauxmoESP fauxmo; uint32_t last = 0; //used for timed actions boolean timeron = true; //flag if timer has switched on boolean connected = false; //true if we have a router connection uint16_t nextTry = 0; //second counter for reconnect //current colors for water level const char *bgfull; const char *bghalf; const char *bgempty; const char* bgbtn; const char *btnname; const char *btnlabel; const char *showlevel; float opac; //char buf[2048]; //constants to be used for web page const char bgbtnon[] = "aquamarine"; const char btnnameon[] = "on"; const char btnlabelon[] = "ON"; const char bgbtnoff[] = "lightgray"; const char btnnameoff[] = "off"; const char btnlabeloff[] = "OFF"; const char colfull[] = "green"; const char colhalf[] = "yellow"; const char colempty[] = "red"; const char colnone[] = "lightgray"; const char showit[] = "visible"; const char hideit[] = "hidden"; //Initiate WiFi connection boolean initWiFi() { boolean connected = false; WiFi.mode(WIFI_STA); Serial.print("Connect to "); Serial.println(conf.getValue("ssid")); //if SSID was configured we try to establish a connection if (conf.getValue("ssid") != "") { WiFi.begin(conf.values[0].c_str(),conf.values[1].c_str()); uint8_t cnt = 0; //wait 10 seconds while ((WiFi.status() != WL_CONNECTED) && (cnt<20)){ delay(500); Serial.print("."); cnt++; } Serial.println(); //if successful connected the IP address will be printed if (WiFi.status() == WL_CONNECTED) { Serial.print("IP-Adresse = "); Serial.println(WiFi.localIP()); connected = true; } } //if no SSID was configured or the connection could not be established //we start an accesspoint to make configuration if (!connected) { WiFi.mode(WIFI_AP); WiFi.softAP(conf.getApName(),"",1); nextTry = 0; } return connected; } //get minutes from a time string formatted hh:mm uint16_t getMinutes(String tim) { uint16_t h = tim.substring(0,2).toInt(); uint16_t m = tim.substring(3).toInt(); return h*60+m; } //check if timer is in range and switch relais if necessary void checkTimer() { //get minutes for now time_t now = time(nullptr); //aktuelle Zeit struct tm * s_time = localtime(&now); //Zeitstruktur aufgesplittet in Jahr, Monat, Tag, Stunden, Minuten und Sekunden uint16_t minutes = s_time->tm_hour * 60 + s_time->tm_min; //get minutes for start and stop uint16_t starttime = getMinutes(conf.getString("starttime")); uint16_t stoptime = getMinutes(conf.getString("stoptime")); if (starttime <= stoptime) { //relais on inside the range if (((minutes >= starttime) && (minutes <= stoptime)) && (!timeron)) { timeron = true; setRelay(true); } if (((minutes < starttime) || (minutes > stoptime)) && (timeron)) { timeron = false; setRelay(false); } } else { //relais on outside the range if (((minutes >= starttime) && (minutes <= stoptime)) && (timeron)) { timeron = false; setRelay(false); } if (((minutes < starttime) || (minutes > stoptime)) && (!timeron)) { timeron = true; setRelay(true); } } } //switch the relais on or off void setRelay(boolean on) { digitalWrite(PINRELAIS,on); if (on) { bgbtn = bgbtnon; btnname = btnnameon; btnlabel = btnlabelon; opac = 1; } else { bgbtn = bgbtnoff; btnname = btnnameoff; btnlabel = btnlabeloff; opac = 0.2; } } //check fluid level and set correesponding LEDs void setLEDs(){ //read from moisture sensor //low value if wet int x = analogRead(A0); Serial.printf("Analog read: %i\n",x); //output low to switch LED on bgempty = colnone; bghalf = colnone; bgfull = colnone; if (x > LVLEMPTY) { digitalWrite(PINEMPTY,0); digitalWrite(PINHALF,1); digitalWrite(PINFULL,1); bgempty = colempty; } else if ((x <= LVLEMPTY) && (x >= LVLFULL)) { digitalWrite(PINEMPTY,1); digitalWrite(PINHALF,0); digitalWrite(PINFULL,1); bghalf = colhalf; } else if (x < LVLFULL) { digitalWrite(PINEMPTY,1); digitalWrite(PINHALF,1); digitalWrite(PINFULL,0); bgfull = colfull; } } //*****callback functions for web server void handleRoot(AsyncWebServerRequest *request){ //if we are not connected we show the config page if (!connected) { handleConfig(request); } else { //we show the hompage if (request->hasArg("on")) setRelay(false); if (request->hasArg("off")) setRelay(true); char buf[2048]; sprintf(buf,HTML,bgbtn,opac,bgfull,bghalf,bgempty,showlevel,btnname,btnlabel); request->send(200,"text/html",buf); } } void handleConfig(AsyncWebServerRequest *request){ conf.handleFormRequest(request); if (request->hasArg("SAVE")) { showlevel = hideit; if (conf.getBool("showlevel")) showlevel = showit; } } void handleNotFound(AsyncWebServerRequest *request){ //for fauxmo String body = (request->hasParam("body", true)) ? request->getParam("body", true)->value() : String(); if (fauxmo.process(request->client(), request->method() == HTTP_GET, request->url(), body)) return; // Handle other not found requests here... } void handleBodyRequest(AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total){ Serial.println(request->url()); if (fauxmo.process(request->client(), request->method() == HTTP_GET, request->url(), String((char *)data))) return; // Handle any other body request here... } void handleAlexa(unsigned char device_id, const char * device_name, bool state, unsigned char value) { Serial.printf("[MAIN] Device #%d (%s) state: %s value: %d\n", device_id, device_name, state ? "ON" : "OFF", value); setRelay(state); } //init the Alexa interface void initAlexa() { Serial.println("Init Alexa"); // Set fauxmoESP to not create an internal TCP server and redirect requests to the server on the defined port // The TCP port must be 80 for gen3 devices (default is 1901) // This has to be done before the call to enable() fauxmo.createServer(false); fauxmo.setPort(80); // This is required for gen3 devices // You have to call enable(true) once you have a WiFi connection // You can enable or disable the library at any moment // Disabling it will prevent the devices from being discovered and switched fauxmo.enable(true); // Add virtual devices fauxmo.addDevice(conf.getValue("alexaname")); fauxmo.onSetState(handleAlexa); }
void setup() { Serial.begin(74880); //All used pins as output pinMode(PINRELAIS,OUTPUT); pinMode(PINFULL,OUTPUT); pinMode(PINHALF,OUTPUT); pinMode(PINEMPTY,OUTPUT); //prepare configuration form and read existing configuration conf.setDescription(params); conf.readConfig(); showlevel = hideit; if (conf.getBool("showlevel")) showlevel = showit; connected = initWiFi(); //we start the web server independent if we have an internet //connection or an accesspoint server.on("/",handleRoot); server.on("/config",handleConfig); server.onRequestBody(handleBodyRequest); server.onNotFound(handleNotFound); server.begin(); if (connected) { //if we have an internet connection initAlexa(); //init the internal clock configTime(TIMEZONE, conf.getValue("ntpserver")); } //reset timed event last = millis(); setRelay(false); setLEDs(); MDNS.begin(String(conf.getApName())+".local"); } void loop() { fauxmo.handle(); //action every second if ((millis() - last) > 1000) { if (WiFi.status() != WL_CONNECTED) { if (++nextTry > RECONNECT) { initWiFi(); } else { nextTry = 0; } } last = millis(); setLEDs(); if (conf.getBool("usetimer")) checkTimer(); } MDNS.update(); }
Achtung!!
Wird statt der einzelnen Leuchdioden, die kleine Ampel mit gemeinsamer Masseleitung verwendet, muss die Funktion setLeds() geändert werden:
void setLEDs(){ //read from moisture sensor //low value if wet int x = analogRead(A0); Serial.printf("Analog read: %i\n",x); //output low to switch LED on bgempty = colnone; bghalf = colnone; bgfull = colnone; if (x > LVLEMPTY) { digitalWrite(PINEMPTY,1); digitalWrite(PINHALF,0); digitalWrite(PINFULL,0); bgempty = colempty; } else if ((x <= LVLEMPTY) && (x >= LVLFULL)) { digitalWrite(PINEMPTY,0); digitalWrite(PINHALF,1); digitalWrite(PINFULL,0); bghalf = colhalf; } else if (x < LVLFULL) { digitalWrite(PINEMPTY,0); digitalWrite(PINHALF,0); digitalWrite(PINFULL,1); bgfull = colfull; } }
In Betrieb nehmen
Wenn der Sketch ohne Fehler kompiliert und hochgeladen wurde, startet das Programm. Da noch keine Konfigurationsdaten vorhanden sind, wird ein Accesspoint gestartet. Die SSID wird aus der MAC-Adresse des D1-Minis gebildet. Mit einem Smartphone oder einem anderen WLAN-fähigen Computer kann jetzt eine Verbindung zu diesem Accesspoint hergestellt werden. Der Zugriff ist offen, es ist also kein Passwort erforderlich. Nachdem die WLAN-Verbindung hergestellt ist, kann man im Browser die Adresse 192.168.4.1 aufrufen. Die Konfigurationsseite wird dargestellt.
Der Name des Accesspoints wird später als DNS-Name verwendet. Es folgen die Zugangsdaten zum WLAN.
Der Name für Alexa ist der Gerätename, der in der Alexa App verwendet werden soll.
Der NTP-Server wird zur Synchronisation der internen Uhr verwendet. Hier könnte z.B. auch fritz.box stehen, wenn die Fritz-Box als Zeitserver verwendet werden soll.
Mit der Checkbox Wasserstand anzeigen wird gesteuert, ob der aktuelle Wasserstand auf der Homepage angezeigt wird oder nicht.
Es folgt eine weitere Checkbox zum Aktivieren des Timers. Sollte der Timer verwendet werden, muss auch eine Einschalt- und eine Abschaltzeit gesetzt werden.
Mit dem Button „Save“ wird die Konfiguration im Flash-Filesystem des D1-Minis gespeichert.
Mit dem Button „Restart“ wird die Konfiguration ebenfalls gespeichert und dann der D1-Mini neu gestartet.
Die Erstkonfiguration sollte mit „Restart“ beendet werden, da sich der D1-Mini nach dem Neustart mit dem WLAN verbinden sollte. Ist die Verbindung erfolgreich, wird kein Accesspoint gestartet.
Es sollte jetzt möglich sein, die Homepage mit der URL <nameDesAccesspoint>.local also im dargestellten Beispiel mit Weihnachtsbaum.local aufzurufen. Wenn Ihr Router mDNS nicht unterstützt, müssen Sie die IP-Adresse, die über den seriellen Monitor ausgegeben wurde, verwenden.
Alexa aktivieren
Dazu müssen Sie auf Ihrem Smartphone die Alexa App starten. Tippen Sie unten in der Symbolleiste auf Geräte und auf der jetzt erscheinenden Geräte-Seite auf das Plus Symbol rechts oben.
Jetzt wählen Sie Gerät hinzufügen.
Es erscheint die Auswahl des Geräte-Typs. Gehen Sie hier ganz nach unten und wählen Sie „Sonstiges“ aus. Auf der nächsten Seite können Sie die Gerätesuche starten. Überprüfen Sie vorher, ob der D1-Mini richtig gestartet hat und die Homepage anzeigen kann.
Nach einiger Zeit sollte die Alexa-App anzeigen, dass eine Lampe gefunden und verbunden wurde. Lampe wird angezeigt, da die Bibliothek eine Philips HUE Lampe emuliert.
Tippen Sie auf Gerät einrichten. Sie können nun das neue Gerät einer Gruppe zuordnen.
Zum Schluss erhalten Sie die Meldung „Weihnachtsbaum ist eingerichtet und bereit zur Verwendung“. Falls Sie einen anderen Namen konfiguriert haben, steht natürlich dieser Name und nicht „Weihnachtsbaum“ in der Meldung.
Nun kann die Beleuchtung mit dem Sprachbefehl “Alexa, Weihnachtsbaum ein“ eingeschaltet werden.
Anschluss der Lichterkette
Es kann jede Lichterkette, die ein zweiadriges Anschlusskabel und mit Spannungen unter 40V arbeitet, verwendet werden. Zum Anschluss wird eine der beiden Adern durchtrennt und das Relais dazwischengeschaltet. Zur Stromversorgung des D1-Minis ist ein USB-Kabel erforderlich.
Viel Spaß beim Nachbauen und eine angenehme Adventszeit!
1 comment
Jochen
Hallo zusammen,
zugegeben, Adventszeit ist rum, Weihnachtsbaum schon wieder weg, aber dennoch:
Ich habe versucht das Teil nachzubauen, ist ja “eigentlich” kein Hexenwerk, leider versagt der am wenigsten durchsichtige Step:
Das Hinzufügen zur AlexaApp funktioniert nicht. Selbst wenn ich in der FauxmoESp die Debugs anmache, sehe ich, dass Interaktion mit dem Device stattfindet. Die App meldet aber immer, dass kein Device gefunden wurde…
Hab ich was übersehen?
Danke und Grüße
Jochen