Beim suchen nach Projektideen mit den LEDs ( WS2812b ) bin ich vor langer Zeit auf eine Wlan Uhr mit WS2812b gestoßen, dieses Projekt wurde in eine herkömmliche Analoge Uhr untergebracht.
Dies gefiel mir aber vom Aussehen und Umsetzung allerdings gar nicht.
Also setzte ich mich hin und realisierte auf dem Papier eine Standuhr aus Holz, inkl Temperaturfühler und einem Meter LED Strip ( WS2812b ), die Temperatur wird visuell auch nochmal auf einen kleinen 7 Segment Display angezeigt, so brauch ich nicht ständig auf Handy schauen wie warm oder kalt mein Wohnzimmer ist 🙂
Zum Einsatz kommt ein NodeMCU V2 ( V3 geht natürlich auch ) Ein DHT22/11 und natürlich ein Meter mit den LEDs WS2812b ( + 220Ohm Widerstand für die Datenleitung ) und ein 5V Netzteil + 1000er Kondensator + 7 Segment Display.
Teileliste:
NodeMCU V2/3
DHT22/DHT11
LEDs ( 60Stück/Meter ) 1 Meter. ( RGB 3 PIN 5V )
Widerstand 220ohm
Kondensator 1000
5V Netzteil ( min 1,5A )
7 Segment Display ( Direktes Anzeigen der Temperatur ) IC2
3 Holzplatten ( Alte Regalböden etc )
1 Holzstab rund
Bisschen Holzfarbe.
Wie wird es gemacht?
Der „Uhrenring“ wurde aus einem Buchen geleimten Stück geschnitten,
ein zweiter Ring mit größeren Durchmesser wurde als Rückwand angebracht.
Ein dritter Ring ( diesmal geschlossen ) wurde für den Boden ausgeschnitten.
Ich habe es ganz einfach mit einem Zirkel und einer Stichsäge umgesetzt.
Für einen Meter LED gilt folgende Formel:
Formeln:
d = 2 r
u = 2 π r
A = π r²
Kreiszahl pi:
π = 3.141592653589793
Ergebnis:
Radius(r): 15,9155 cm
Durchmesser(d): 31,831 cm
Nun werden die Ringe miteinander verklebt ( alternativ Schrauben ) und oben
( ziemlich mittig ) ein 8er Loch gebohrt. ( Kabeldurchführung )
Nun muss der Holzstab angepasst werden, so das er in dem untersten Ring greift, dazu mit einen Stift und Winkel ( Alternativ Lineal ) den Stab teilen, und aussägen.
Kleben oder Schrauben.
Den Boden habe ich verschraubt ( bitte vorbohren, ist sauberer )
Anschließend lackieren wir das ganze in Wunschfarbe.
Ich habe meine Uhr in Schwarz lackiert.
Den NodeMCu und den Sensor: DHT22 werden auf der Rückseite verklebt oder verschraubt.
Scripte und Anweisungen:
Haltet eurer Wlan Passwort und euer Wlan -Name bereit, den nun Pflegen wir die Scripte ein.
Es werden folgende Bibliotheken für den Betrieb benötigt:
<Adafruit_Sensor.h> —– > Im Bibliotheken Manager zu finden
<Adafruit_NeoPixel.h> —– > Im Bibliotheken Manager zu finden
<ESP8266WiFi.h> —– > Im Bibliotheken Manager zu finden
„TimeClient.h“ —– > Ist im Zip Archiv beigefügt.
<DHT.h> —– > Im Bibliotheken Manager zu finden
<TM1637Display.h> —– > Im Bibliotheken Manager zu finden
<PubSubClient.h> —– > Im Bibliotheken Manager zu finden
Wir benötigen folgende Scripte ( Achtung!! ich übertrage die Werte per MQTT an meinen Server, habt Ihr keinen Dienst im Hintergrund laufen, entfernt bitte die MQTT Einträge )
Wlanuhr
#include <Adafruit_Sensor.h> #include <Adafruit_NeoPixel.h> #include <ESP8266WiFi.h> #include "TimeClient.h" #include <DHT.h> #include <TM1637Display.h> #include <PubSubClient.h> #define CLK D3 // Pinbelegung für das 7 Segment Display - ggf. anpassen #define DIO D4 // Pinbelegung für das 7 Segment Display - ggf. anpassen #define PIN D5 // Pinbelegung für den LED Streifen - ggf. anpassen #define DHTPIN D2 // Pinbelegung für den DHT22 Sensor - ggf. anpassen #define DHTTYPE DHT22 DHT dht(DHTPIN, DHTTYPE); char tmp[50]; char hum[50]; int tellstate = 0; long lastUpdate = millis(); long lastSecond = millis(); int ledsInString = 60; String hours, minutes, seconds; int currentSecond, currentMinute, currentHour; int NUMPIXELS = 60; // 1 Meter LED = 60 LED/m int i = 0; int hmark = 1; char ssid[] = "Mein Netzwerkname"; // SSID Name ( Wlan Funkname ) char pass[] = "Mein Wlan Passwort"; // Wlan Passwort const char* mqtt_server = "Broker IP Adresse"; //Broker IP Address const char* mqttUser = "MQTT User"; // MQTT Username const char* mqttPassword = "MQTT Passwort"; // MQTT Server Passwort WiFiClient espClient; PubSubClient client(espClient); TM1637Display display(CLK, DIO); const float UTC_OFFSET = 1; TimeClient timeClient(UTC_OFFSET); Adafruit_NeoPixel strip = Adafruit_NeoPixel(ledsInString, PIN); void connect_to_MQTT() { client.setServer(mqtt_server, 1886);//MQTT Server Details, inkl PORT ( Bitte euren Port dort eintragen ) client.setCallback(callback); if (client.connect("WlanUhr_Temp" , mqttUser, mqttPassword)) { Serial.println("Verbinde zum MQTT Server"); client.subscribe("wlan/sensor"); } else { Serial.println("zu MQTT Server nicht verbunden "); } } void setup() { connect_to_MQTT(); dht.begin(); Serial.begin(115200); Serial.println(); Serial.println(); uint8_t data[] = { 0xff, 0xff, 0xff, 0xff }; display.setBrightness(0x0f); // 7 Segment AN stellen display.setSegments(data); strip.begin(); strip.setBrightness(128); strip.show(); // Netzwerk wird verbunden Serial.print("Connecting to "); Serial.println(ssid); WiFi.begin(ssid, pass); while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); } Serial.println(""); Serial.println("WiFi connected"); Serial.println("IP address: "); Serial.println(WiFi.localIP()); timeClient.updateTime(); updateTime() ; lastUpdate = millis(); lastSecond = millis(); } void loop() { float Luftfeuchtigkeit = dht.readHumidity(); //unter „Luftfeutchtigkeit“ speichern float Temperatur = dht.readTemperature();//unter „Temperatur“ speichern //Serial.print("Temperatur: "); //Serial.print(Temperatur); //Serial.println(" Grad Celsius"); display.showNumberDec(Temperatur,false,4,0); //display.showNumberDec(0x00,false,4,1); //uint8_t segto; //int value = 1244; //segto = 0x80 | display.encodeDigit((value / 100)%10); //display.setSegments(&segto, 1, 1); //display.showNumberDec(grad,true,2,2); updateTime(); if(currentHour > 11) currentHour -= 12; // 12 Stunden, wenn Stunde 13-23, ändere auf 0-11 currentHour = (currentHour*60 + currentMinute) / 12; zeiger(); strip.setPixelColor(currentHour, 0xff0000);strip.setPixelColor(currentHour-1, 0xff0000);strip.setPixelColor(currentHour+1, 0xff0000); // strip.setPixelColor(hourval-2, 0x001010);strip.setPixelColor(hourval+2, 0x001010); strip.setPixelColor(currentMinute, 0x00ff00); // strip.setPixelColor(minuteval-1, 0x200020);strip.setPixelColor(minuteval+1, 0x200020); strip.setPixelColor(currentSecond, 0x0000ff);//strip.setPixelColor(secondval-1, 0x002F00);strip.setPixelColor(secondval+1, 0x002F00); strip.show(); strip.setPixelColor(currentHour, 0x000000);strip.setPixelColor(currentHour-1, 0x000000);strip.setPixelColor(currentHour+1, 0x000000); strip.setPixelColor(currentHour-2, 0x000000);strip.setPixelColor(currentHour+2, 0x000000); strip.setPixelColor(currentMinute, 0x000000);strip.setPixelColor(currentMinute-1, 0x000000);strip.setPixelColor(currentMinute+1, 0x000000); strip.setPixelColor(currentSecond, 0x000000);//strip.setPixelColor(secondval-1, 0x000000);strip.setPixelColor(secondval+1, 0x000000); delay(25); client.loop(); if (!client.connected()) { Serial.println("Keine Verbindung zum MQTT Server...."); connect_to_MQTT(); } if ( (millis() - tellstate) > 60000 ) { getTemperature(); tellstate = millis(); } } void updateTime() { hours = timeClient.getHours(); minutes = timeClient.getMinutes(); seconds = timeClient.getSeconds(); currentHour = hours.toInt(); if (currentHour >= 12) currentHour = currentHour - 12; currentMinute = minutes.toInt(); currentSecond = seconds.toInt(); lastUpdate = millis(); } void zeiger() { if(hmark > 0) { for(int i = 0; i<12; i++) { strip.setPixelColor((5*i), strip.Color(5,5,5)); } } } void callback(char* topic, byte* payload, unsigned intlength) // Reservere { Serial.print("Messageved in topic: "); Serial.println(topic); } void getTemperature() { float h = dht.readHumidity(); // Lese Temperatur in Grad (default) float t = dht.readTemperature(); // Wenn keine Daten vorhanden sind, versuch es nochmal. if (isnan(h) || isnan(t)) { Serial.println("Keine Daten vom DHT Sensor!"); return; } //Temp as string itoa(t,tmp,10); client.publish("wlan/sensor/temperature",tmp); Serial.println(tmp); //Humidity as string itoa(h,hum,10); client.publish("wlan/sensor/humidity",hum); Serial.println(hum); }
Jetzt benötigen wir noch die Zeit Scripte ( TimeClient.h ):
/**The MIT License (MIT) Copyright (c) 2018 by Daniel Eichhorn, ThingPulse Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. See more at https://thingpulse.com */ #pragma once #include <ESP8266WiFi.h> #define NTP_PACKET_SIZE 48 class TimeClient { private: float myUtcOffset = 0; long localEpoc = 0; unsigned long localMillisAtUpdate; const char* ntpServerName = "time.nist.gov"; unsigned int localPort = 2390; byte packetBuffer[ NTP_PACKET_SIZE]; //buffer to hold incoming and outgoing packets public: TimeClient(float utcOffset); void updateTime(); void setUtcOffset(float utcOffset); String getHours(); String getMinutes(); String getSeconds(); String getFormattedTime(); long getCurrentEpoch(); long getCurrentEpochWithUtcOffset(); };
Die TimeClient.h benötigt noch die TimeClient.cpp :
/**The MIT License (MIT) Copyright (c) 2018 by Daniel Eichhorn, ThingPulse Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. See more at https://thingpulse.com */ #include "TimeClient.h" TimeClient::TimeClient(float utcOffset) { myUtcOffset = utcOffset; } void TimeClient::setUtcOffset(float utcOffset) { myUtcOffset = utcOffset; } void TimeClient::updateTime() { WiFiClient client; const int httpPort = 80; if (!client.connect("google.com", httpPort)) { Serial.println("connection failed"); return; } // This will send the request to the server client.print(String("GET / HTTP/1.1\r\n") + String("Host: google.com\r\n") + String("Connection: close\r\n\r\n")); int repeatCounter = 0; while(!client.available() && repeatCounter < 10) { delay(1000); Serial.println("."); repeatCounter++; } String line; int size = 0; client.setNoDelay(false); while(client.available() || client.connected()) { while((size = client.available()) > 0) { line = client.readStringUntil('\n'); line.toUpperCase(); // example: // date: Thu, 19 Nov 2015 20:25:40 GMT if (line.startsWith("DATE: ")) { Serial.println(line.substring(23, 25) + ":" + line.substring(26, 28) + ":" +line.substring(29, 31)); int parsedHours = line.substring(23, 25).toInt(); int parsedMinutes = line.substring(26, 28).toInt(); int parsedSeconds = line.substring(29, 31).toInt(); Serial.println(String(parsedHours) + ":" + String(parsedMinutes) + ":" + String(parsedSeconds)); localEpoc = (parsedHours * 60 * 60 + parsedMinutes * 60 + parsedSeconds); Serial.println(localEpoc); localMillisAtUpdate = millis(); } } } } String TimeClient::getHours() { if (localEpoc == 0) { return "--"; } int hours = ((getCurrentEpochWithUtcOffset() % 86400L) / 3600) % 24; if (hours < 10) { return "0" + String(hours); } return String(hours); // print the hour (86400 equals secs per day) } String TimeClient::getMinutes() { if (localEpoc == 0) { return "--"; } int minutes = ((getCurrentEpochWithUtcOffset() % 3600) / 60); if (minutes < 10 ) { // In the first 10 minutes of each hour, we'll want a leading '0' return "0" + String(minutes); } return String(minutes); } String TimeClient::getSeconds() { if (localEpoc == 0) { return "--"; } int seconds = getCurrentEpochWithUtcOffset() % 60; if ( seconds < 10 ) { // In the first 10 seconds of each minute, we'll want a leading '0' return "0" + String(seconds); } return String(seconds); } String TimeClient::getFormattedTime() { return getHours() + ":" + getMinutes() + ":" + getSeconds(); } long TimeClient::getCurrentEpoch() { return localEpoc + ((millis() - localMillisAtUpdate) / 1000); } long TimeClient::getCurrentEpochWithUtcOffset() { return round(getCurrentEpoch() + 3600 * myUtcOffset + 86400L) % 86400L; }
UPDATE zum SCRIPT
in der TimeClient.cpp muss zwingend folgender Teil geändert werden wenn man den Core 2.5 oder neuer verwendet:
long TimeClient::getCurrentEpochWithUtcOffset() { return round(getCurrentEpoch() + 3600 * myUtcOffset + 86400L) % 86400L; }
in
long TimeClient::getCurrentEpochWithUtcOffset() { return fmod(round(getCurrentEpoch() + 3600 * myUtcOffset + 86400L), 86400L); }
Alles unter 2,5 ( Core 2.4.2 zum Beispiel ) kann die Zeile so belassen werden.
Ansonsten werdet Ihr den Fehler:
invalid operands of types ‚double‘ and ‚long int‘ to binary ‚operator%‘
erhalten.
Flasht alles auf den NodeMCU und testest es „on the fly“, geht alles reibungslos, kann alles verklebt/verschraubt werden.
Anbei paar Bilder:
Hier mal ein kleines Video vom fast fertigen Projekt:
Anbei noch die ZP Datei:
Das Projekt benötigt ein wenig mehr Zeit, alleine die Holzarbeiten benötigen viel Aufmerksamkeit, man möchte ja eine runde Uhr und kein Ei 😉
Wer natürlich auf Professionelle Schneidwerkzeuge zurückgreifen kann, sollte dies auch tun, das Ergebnis dankt es einem 🙂