Passend zur bevorstehenden Adventszeit habe ich mich für den Bau eines Adventssterns mit 50 RGB-LED LPD6803 und einem ESP8266 als Steuerteil entschieden. Zusätzlich kommt noch ein LDR (Photowiderstand) zum Einsatz, damit der Stern zusätzlich zur Zeit erst einschaltet, wenn es dunkel genug ist.
Weitere Kriterien waren:
- Zeit per NTP (einer von den 4 NTP-Pool-Servern in DE)
- Ein-/Ausschalten nach Stunde (16/22 Uhr)
- grundlegende Konfiguration (Zeiten, Durchläufe) per Webinterface (AJAX) änderbar
- wahlweise speichern der geänderten Konfiguration persistent auf SPIFFS
- lesen der Konfiguration beim Boot vom SPIFFS
Das Ganze wurde auf einer 2mm dicken 50cm x 50 cm Platte aus "Bastlerglas" aufgebaut und sieht so aus (Video mp4).
Hier einige Auszüge der Codes. Das komplette ZIP im Anhang.
Grundsketch:
Code:
// webserver mit externen Dateien im SPIFFS
// FS-Tool in Sketchbook/tools https://github.com/esp8266/arduino-esp8266fs-plugin/releases/download/0.2.0/ESP8266FS-0.2.0.zip
// Basis ist der Webserver auf http://bienonline.magix.net/public/esp8266-webserver-klasse.html
// Angepasste LPD 6803-Lib https://github.com/area3001/esp8266-arduino-lpd6803 - Störfestigkeit erhöht
/*****************************************************************
Hardware: WEMOS D1 mini pro, 50 RGB-LED mit LPD 6803
A0 ein LDR nach 3,3V und ein 22 kOhm nach GND (Umgebungsgelligkeit)
D5 = GPIO 14 = CLOCK = grün
D7 = GPOI 13 = DATA = weiss / gelb
Versorgung über USB
Speicheraufteilung 3MB Sketch 1MB SPIFFS (die kompletten 16MB werden noch nicht unterstützt)
******************************************************************
Copyright (c) 2017 Thomas Kühnert. All rights reserved.
This file is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This file is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
*******************************************************************/
#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>
// Die beiden Files müssen für SPIFFS eingebunden werden
#include <FS.h> // muss vor <detail\RequestHandlersImpl.h> stehen
#include <detail\RequestHandlersImpl.h>
void setup() {
uint16_t res1, res2;
int counter = 0; // Counter für Verbindung zum WLAN
seedRandom32(A0);
sbeg(115200);
spn("\nStart\n");
// Betrieb als Station
WiFi.mode(WIFI_STA);
// Eigene Config, besonders IP
WiFi.config(myIP, gateway, subnet, dns);
WiFi.begin(ssid, password);
// Wait for connection
while (WiFi.status() != WL_CONNECTED) {
delay (500);
sp (".");
counter++;
if (counter > 100) {
spn("Kein WLAN. Restart!");
ESP.restart();
}
}
sp("\nConnected to ");
spn( ssid );
sp("IP address: ");
spn(WiFi.localIP());
Dieser basiert auf einem normalen Webserver, in den zusätzlich die SPIFFS-Verwaltung, OTA und einige andere Teile eingehängt wurden.
Die Effekte wurden ausgelagert.
Code:
/******************************************************************
Copyright (c) 2017 Thomas Kühnert. All rights reserved.
This file is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This file is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
*******************************************************************/
// Eine zufällige Länge mit einer zufälligen Farbe initialisieren
void shiftRandInit() {
uint8_t l;
color = randColor();
l = random(10,20);
for(uint8_t i=0; i<l;i++) buf[i] = color;
idx++;
strip.show();
}
// umlaufen lassen
void shiftRand() {
uint8_t l;
if (!dir) shiftLeft1();
else shiftRight1();
idx++;
if (idx >= numLeds) {
idx = 0;
count++;
clear();
l = random(10,20);
color = randColor();
for(uint8_t i=0; i<l;i++) buf[i] = color;
}
strip.show();
}
// Regenbogen mit 96 Farben
void rainbow32() {
for (int i=0; i < numLeds; i++) {
strip.setPixelColor(i, Wheel( (idx+i) % 96,32));
}
idx++;
if (idx >= 96) {
idx = 0;
count++;
// sp("Count: ");spn(count);
}
strip.show(); // write all the pixels out
// spn("Rainbow32");
}
// Regenbogen mit 48 Farben füllen
void rainbow16Init() {
for (int i=0; i < numLeds; i++) {
strip.setPixelColor(i, Wheel( (i) % 48, 16));
}
idx++;
strip.show();
}
// umlaufen lassen
void rainbow16() {
if (!dir) shiftLeft1();
else shiftRight1();
if (idx++ >= 48) {
idx = 0;
count++;
}
strip.show(); // write all the pixels out
// spn("Rainbow16");
}
// Hilfsfunktion Regenbogen aus LPD 6803-Lib-Examples angepasst
// Input a value 0 to 127 to get a color value.
// The colours are a transition r - g -b - back to r
unsigned int Wheel(byte WheelPos, byte laenge) {
byte r,g,b,shift = 4;
int c;
if (laenge ==32) shift++;
switch(WheelPos >> shift)
{
case 0:
r=(laenge - 1)- WheelPos % laenge; //Red down
g=WheelPos % laenge; // Green up
b=0; //blue off
break;
case 1:
g=(laenge - 1)- WheelPos % laenge; //green down
b=WheelPos % laenge; //blue up
r=0; //red off
break;
case 2:
b=(laenge - 1)- WheelPos % laenge; //blue down
r=WheelPos % laenge; //red up
g=0; //green off
break;
}
c = Color(r,g,b);
// sp("Rot: ");sp(r); sp(" Gruen: "); sp(g);
// sp(" Blau: ");sp(b); sp(" Color: ");spn(c);
return(c);
}
// Hilfsfunktion Regenbogen aus LPD 6803-Lib-Examples angepasst
// Create a 15 bit color value from R,G,B
unsigned int Color(byte r, byte g, byte b)
{
//Take the lowest 5 bits of each value and append them end to end
return( (((unsigned int)r & 0x1F )<<10 | ((unsigned int)g & 0x1F)<<5 | (unsigned int)b & 0x1F) | 0x8000);
}
// Verschieben aller LED
// linksrum ist einfach, weil es der Speicherrichtung entspricht
void shiftLeft1() {
// Ziel, Quelle (In Array-Elementen!), Länge (in Byte!);
memmove(buf, buf+1, numLeds*sizeof(uint16_t));
}
// Die müssen in den Hauptsketch, wenn sie dort verwendet werden sollen
// char lastSyncStr[] = "00:00:00 00.00.0000";
// char actualTime[] = "00:00:00 00.00.0000";
// den 5 gibt es nicht, der ist nur zum Fehlertest drin
static const char ntpServerName[][20] = {"1.de.pool.ntp.org","2.de.pool.ntp.org", "3.de.pool.ntp.org","4.de.pool.ntp.org","5.de.pool.ntp.org"};
time_t lastSyncTime;
time_t prevDisplay = 0; // when the digital clock was displayed
const int timeZone = 1; // Central European Time
boolean newSync = false;
WiFiUDP Udp;
unsigned int localPort = 8888; // local port to listen for UDP packets
void timeSetup() {
spn("Starting UDP");
Udp.begin(localPort);
sp("Local port: ");
spn(Udp.localPort());
setSyncProvider(getNtpTime);
setSyncInterval(3600); // einmal pro Stunde
}
void timeActions() {
if (timeStatus() != timeNotSet) {
if (newSync) {
timeToStr(lastSyncTime,lastSyncStr);
newSync = false;
}
if (now() != prevDisplay) { //update the display only if time has changed
prevDisplay = now();
timeToStr(prevDisplay,actualTime);
if (einStd == hour() && progStatus == 0) { // Einschalten?
if (config[0].param4 == 0) { // kein LDR-Wert definiert
progStatus = 3; // Ein
}
else {
progStatus = 2; // Ein, wenn dunkel
}
}
if (ausStd == hour()) progStatus = 0; // aus
// sp("Time: ");spn(actualTime);sp(" LastSync: ");spn(lastSyncStr);
}
}
}
/*-------- NTP code ----------*/
const int NTP_PACKET_SIZE = 48; // NTP time is in the first 48 bytes of message
byte packetBuffer[NTP_PACKET_SIZE]; //buffer to hold incoming & outgoing packets
time_t getNtpTime(){
IPAddress ntpServerIP; // NTP server's ip address
static uint8_t num = 0; // Nummer des NTP-Pool-Servers 4 für Fehlertest
while (Udp.parsePacket() > 0) ; // discard any previously received packets
spn("Transmit NTP Request");
// get a random server from the pool
spn(num);
WiFi.hostByName(ntpServerName[num], ntpServerIP);
sp(ntpServerName[num]);
sp(": ");
spn(ntpServerIP);
sendNTPpacket(ntpServerIP);
uint32_t beginWait = millis();
while (millis() - beginWait < 1500) {
int size = Udp.parsePacket();
if (size >= NTP_PACKET_SIZE) {
spn("Receive NTP Response");
Udp.read(packetBuffer, NTP_PACKET_SIZE); // read packet into the buffer
unsigned long secsSince1900;
// convert four bytes starting at location 40 to a long integer
secsSince1900 = (unsigned long)packetBuffer[40] << 24;
secsSince1900 |= (unsigned long)packetBuffer[41] << 16;
secsSince1900 |= (unsigned long)packetBuffer[42] << 8;
secsSince1900 |= (unsigned long)packetBuffer[43];
lastSyncTime = secsSince1900 - 2208988800UL + timeZone * SECS_PER_HOUR;
newSync = true;
return lastSyncTime;
}
}
spn("No NTP Response :-(");
num = ++num %4;
now();
return 0; // return 0 if unable to get the time
}
// send an NTP request to the time server at the given address
void sendNTPpacket(IPAddress &address){
// set all bytes in the buffer to 0
memset(packetBuffer, 0, NTP_PACKET_SIZE);
// Initialize values needed to form NTP request
// (see URL above for details on the packets)
packetBuffer[0] = 0b11100011; // LI, Version, Mode
packetBuffer[1] = 0; // Stratum, or type of clock
packetBuffer[2] = 6; // Polling Interval
packetBuffer[3] = 0xEC; // Peer Clock Precision
// 8 bytes of zero for Root Delay & Root Dispersion
packetBuffer[12] = 49;
packetBuffer[13] = 0x4E;
packetBuffer[14] = 49;
packetBuffer[15] = 52;
// all NTP fields have been given values, now
// you can send a packet requesting a timestamp:
Udp.beginPacket(address, 123); //NTP requests are to port 123
Udp.write(packetBuffer, NTP_PACKET_SIZE);
Udp.endPacket();
}
Die anderen Funktionalitäten habe ich hier schon irgendwo veröffentlicht. Die sind dann nur im ZIP enthalten.
Die LPD6803 sind zwar schon etwas älter aber mechanisch für großflächigere Darstellungen gut geeignet. Von FastLED werden sie nicht mehr unterstützt.
Wenn Ihr das evtl. als Anregung für eigene Basteleien sehen wollt, könnt Ihr beliebige Teile Übernehmen.
Gruß Tommy
"Wer den schnellen Erfolg sucht, sollte nicht programmieren, sondern Holz hacken." (Quelle unbekannt)
Alle von mir veröffentlichten Codes unterliegen der GPL Version 3
Beiträge: 4.786
Registriert seit: May 2013
Bewertung 24
RE: [Vorstellung] Weihnachtsstern mit LPD6803
WOW Tommy,
da hast dich aber mächtig ins Zeug gelegt bei der Mustergenerierung....das kostet Zeit...
lgbk
An alle Neuankömmlinge hier, wenn ihr Code(Sketch) hier posten wollt dann liest euch bitte diese Anleitung durch.
Und benutzt vor dem Beitrag absenden den "Vorschau" Button.
Ich spreche fließend Schwäbisch, Deutsch das Notwendigste und für die Begriffsstutzigen erprobtes Tacheles mit direkten Hinweisen ohne Schnörkel...
Dont feed the Troll's
1+1 = 10 ...und ich bin hier nicht der Suchmaschinen-Ersatz ...nur mal so als genereller Tipp..
(26.11.2017 07:56)Bitklopfer schrieb: WOW Tommy,
da hast dich aber mächtig ins Zeug gelegt bei der Mustergenerierung....das kostet Zeit...
lgbk
Das Schlimmste war die Ideen zu finden. Das geht ja schon mehr ins Künstlerische und das ist nicht so meine Ecke.
Gruß Tommy
"Wer den schnellen Erfolg sucht, sollte nicht programmieren, sondern Holz hacken." (Quelle unbekannt)
Alle von mir veröffentlichten Codes unterliegen der GPL Version 3
(26.11.2017 15:24)Chopp schrieb: Hallo,
Klasse Arbeit.
Ist das Plexiglas, Acryl oder Macrolon ?
Gruß, Markus
Das ist sogenanntes Hobbyglas 0,2 cm Transparent 50 cm x 50 cm vom OBI für 5,99€. Genauer weiß ich es nicht.
Gruß Tommy
"Wer den schnellen Erfolg sucht, sollte nicht programmieren, sondern Holz hacken." (Quelle unbekannt)
Alle von mir veröffentlichten Codes unterliegen der GPL Version 3
(26.11.2017 19:02)hotsystems schrieb: Hallo Tommy,
sieht sehr schön aus, gefällt mir sehr gut.
Dann kann die Adventszeit ja kommen.
Wo wird der Stern denn hängen ?
Der hängt im Fenster vom Arbeitszimmer direkt neben meinem Arbeitsplatz.
Gruß Tommy
"Wer den schnellen Erfolg sucht, sollte nicht programmieren, sondern Holz hacken." (Quelle unbekannt)
Alle von mir veröffentlichten Codes unterliegen der GPL Version 3