Moin Das habe ich noch nicht ganz begriffen. Macht das nicht schon das Adminmenü?
Konfiguration für Dein Treppenlicht, z.B. Helligkeit, Zeitsteuerung usw.
Gruß Tommy
Hin und wieder ein Brett vorm Kopp. Ich versuche mich mal dran
Ja, so in diese Richtung. Ganz genau können wir das noch nicht wissen, da der Teil ja noch programmiert werden möchte, wenn die LEDs eingetroffen sind!
Gestern Abend habe ich das langersehnte Paket im Briefkasten entdeckt. Leider fehlen noch die PIR-Sensoren und kommen wohl erst übernächste Woche. Damit ich schon nächste Woche etwas basteln kann, habe ich mir nochmal zwei bestellt, die voraussichtlich Donnerstag kommen - einen habe ich noch in meiner Grabbelkiste.
Ich habe bis auf die zwei fehlenden PIR-Sensoren und die Kondensatoren alles verbunden und einen Testlauf gestartet. die Streifen leuchten, Intervall habe ich leicht angepasst.
Interessant fand ich, dass bei einem Wackler des OLED auf dem Board, die Streifen inaktiv sind. Erst mit Starten des OLED fingen auch die LED-Streifen zu wandern. Mit dem einen PIR Sensor habe ich versucht alle drei zu simulieren, indem ich den Stecker nacheinander umgesteckt habe. Dazwischen fangen die Streifen an, Bunt zu faden
Noch ist das so, dass der Streifen nach den definierten 3sek. von vorne beginnt. Ich bin mir nicht sicher, ob das an dem PIR Sensor oder an dem Code liegt.
Ich verstehe den Code "Beleuchtung" folgendermaßen:
"bool rauf" und "bool runter" ist der Abschnitt, mit dem das "Wandern" beschrieben wird. Die Zuordnung der PIR_Sensoren ist unter "void loop_Beleuchtung". Bei dem nächsten Abschnitt ist der Code mit den Pausen und da steige ich noch nicht ganz durch. Hilfe!
switch (schritt) {
case Schritt::WARTEN:
//animation_Regenbogen();
animation_TwinkleFOX();
if (oben_akt) {
vorhin = jetzt;
anzeigeOLED("oben aktiv");
schritt = Schritt::OBEN_AKTIV;
}
if (unten_akt) {
anzeigeOLED("unten aktiv");
schritt = Schritt::UNTEN_AKTIV;
}
break;
Das ist der Ausgangszustand, der ESP wartet, bis der pir oben oder unten aktiviert wird - richtig?
case Schritt::OBEN_AKTIV:
if (runter(true)) {
vorhin = jetzt;
schritt = Schritt::OBEN_AN;
}
break;
case Schritt::OBEN_AN:
if (jetzt - vorhin > ZEIT_AN) {
anzeigeOLED("warten");
schritt = Schritt::WARTEN;
}
break;
case Schritt::UNTEN_AKTIV:
if (rauf(true)) {
vorhin = jetzt;
schritt = Schritt::UNTEN_AN;
}
break;
case Schritt::UNTEN_AN:
if (jetzt - vorhin > ZEIT_AN) {
anzeigeOLED("warten");
schritt = Schritt::WARTEN;
}
break;
In dem Abschnitt wird quasi die Zeit gezählt bis Pause, wenn man einen der beiden Bewegungsmelder (oben/unten) aktiviert, oder? Warum startet dieser dann neu?
Wenn ich das richtig verstehe, fehlt noch die Logik, dass rauf/runter nur einmal in einem gewissen Zeitraum aktiviert werden soll und das umgekehrte "Wandern" der Streifen, sobald der Bewegungsmelder in der Mitte aktiviert wurde.
Rein von der Logik her müsste das doch folgendermaßen aussehen:
Wenn PIR_oben wahr - bool runter (für STRIP_O, ein Durchlauf, aktiv für 5sek)
Wenn innerhalb der nächsten 5sek PIR_mitte wahr - bool runter (für STRIP_U, ein Durchlauf, aktiv für 3sek)
Wenn PIR_unten wahr - bool rauf für STRIP_U (ein Durchlauf, 3sek aktiv).
Wenn innerhalb der nächsten 3sek PIR_mitte wahr - bool rauf für STRIP_O (ein Durchlauf, 5sek aktiv).
Damit die LED's sich nicht überlagern, sind die Werte mit "static" statisch gesetzt, damit diese nicht ausgehen, wenn Personen aus beiden Richtungen kommen, habe ich das richtig verstanden?
Bringt bitte etwas Licht ins Dunkle, damit ich überhaupt weiß, ob ich auf dem richtigen Weg bin Danke!
Schönen Restsonntag!
Die gewählten Eingänge haben keinen internen PullUp- oder PullDown-Widerstand. Unbeschaltet haben sie einen zufälligen Zustand. Beschalte bitte alle die PIR-Eingänge mit externen PullDown-Widerständen 1 bis 10 kΩ. Zusätzlich kannst Du dann noch den PIR-Sensor anschließen. Der sollte ein kurzes Signal kleiner 1 Sekunde abgeben.
Besser?
Ja, nennt sich Funktion. Die Funktion gibt einen Wert vom Typ bool
zurück, wichtig bei if (rauf(true)) {
.
... mit der Schrittkette.
Ja.
Lauflicht ausführen, bis am Ziel angekommen, also Treppe weiß leuchtet.
Warten, bis von Weiß auf Pausen-Animation geschaltet wird.
Nicht berücksichtigt ist der PIR in der Mitte.
Nein, durch static
wird eine Variable am Ende der Funktion nicht gelöscht, sondern steht beim nächsten Aufruf der Funktion wieder zur Verfügung. Der Wert wird also gemerkt.
Noch nicht berücksichtigt. Mir ging es ja eigentlich um die Einbindung der Pausen-Animation
Bist Du
Moin, moin!
Ich bin mir nicht sicher, ob ich das richtig gemacht habe. Ich habe den Eingangspin mit GND verbunden und dazwischen ein 10KO Widerstand geschaltet. Dadurch hat sich jedoch nichts geändert.
Die Kinder werden sich freuen Die Animation lief bisher aber nur, wenn kein PIR angeschlossen war. Ich könnte mir dafür ein bestimmtes Muster zum aktivieren vorstellen. Z.B. PIR_unten-PIR_mitte 3x nacheinander innerhalb von 10sek aktivieren, damit die Animation für 1min aktiviert wird.
LG Walli
Was für einen Ausgang hat der PIR?
- aktiv HIGH
- aktiv LOW
- open collektor
Wie lang ist das Signal?
Wenn ich alle drei Eingänge für PIRs mit GND verbinde, sehe ich die Pausen-Animation. Wenn ich dann den Eingang für oben oder unten rausziehe und mit dem Finger berühre, wird die Treppe weiß.
Wichtig für Dich ist, die Fehlersuche zu lernen. Verbinde einen Eingang mit einem Ausgang mit LED, dann siehst Du, was der Eingang macht. Oder nutze das OLED, dafür ist es da.
Das ist ein HC-SR501. Auf deine Nachfrage habe ich nochmals auf das Datenblatt geschaut. Die Spannung ist mit 4,5-20V angegeben und da liegt der Hund wahrscheinlich begraben
Das mit der LED habe ich verstanden aber wie identifiziere ich Fehler mit dem OLED? Momentan zeigt das OLED abwechselnd Warten-bereit (genau habe ich es nicht im Kopf, bin nicht zu Hause) an.
"Delay time 5-300 S"
Ich habe "Time Delay Adjust" ganz nach links auf minimale Zeit gedreht, dann geht es bei mir.
Einzeln:
anzeigeOLED(oben_akt);
Drei auf einen Streich:
char buf[30] = {'\0'};
snprintf( buf, sizeof(buf), "O %d M %d U %d", oben_akt, mitte_akt, unten_akt );
anzeigeOLED(buf);
Komfort:
Wenn Du nicht alle Varianten von Arduinos sehen möchtest, kannst Du die Datei
\arduino-1.8.19\hardware\arduino\avr\boards.local.txt
anlegen. Beispiel:
# alle mit Kommentarzeichen werden in der IDE angezeigt
yun.hide=
#uno.hide=
diecimila.hide=
#nano.hide=
leonardo.hide=
megaADK.hide=
#mega.hide=
leonardoeth.hide=
micro.hide=
esplora.hide=
mini.hide=
ethernet.hide=
fio.hide=
bt.hide=
LilyPadUSB.hide=
lilypad.hide=
#pro.hide=
atmegang.hide=
robotControl.hide=
robotMotor.hide=
gemma.hide=
circuitplay32u4cat.hide=
yunmini.hide=
chiwawa.hide=
one.hide=
unowifi.hide=
Wenn Du nicht alle Varianten von Arduinos sehen möchtest, kannst Du die Datei
c:\Users\User\AppData\Local\Arduino15\packages\esp32\hardware\esp32\1.0.6\boards.local.txt
anlegen. Beispiel:
# alle mit Kommentarzeichen werden in der IDE angezeigt
#esp32.hide=
esp32wrover.hide=
pico32.hide=
tinypico.hide=
S_ODI_Ultra.hide=
magicbit.hide=
turta_iot_node.hide=
ttgo-lora32-v1.hide=
ttgo-t1.hide=
ttgo-t7-v13-mini32.hide=
ttgo-t7-v14-mini32.hide=
cw02.hide=
esp32thing.hide=
esp32thing_plus.hide=
nina_w10.hide=
widora-air.hide=
esp320.hide=
nano32.hide=
d32.hide=
d32_pro.hide=
lolin32.hide=
lolin32-lite.hide=
pocket_32.hide=
WeMosBat.hide=
espea32.hide=
quantum.hide=
node32s.hide=
hornbill32dev.hide=
hornbill32minima.hide=
firebeetle32.hide=
intorobot-fig.hide=
onehorse32dev.hide=
featheresp32.hide=
nodemcu-32s.hide=
mhetesp32devkit.hide=
mhetesp32minikit.hide=
esp32vn-iot-uno.hide=
esp32doit-devkit-v1.hide=
esp32doit-espduino.hide=
esp32-evb.hide=
esp32-gateway.hide=
esp32-poe.hide=
esp32-poe-iso.hide=
esp32-DevKitLipo.hide=
espino32.hide=
m5stack-core-esp32.hide=
m5stack-fire.hide=
m5stick-c.hide=
m5stack-atom.hide=
m5stack-core2.hide=
m5stack-timer-cam.hide=
m5stack-coreink.hide=
odroid_esp32.hide=
heltec_wifi_kit_32.hide=
heltec_wifi_lora_32.hide=
heltec_wifi_lora_32_V2.hide=
heltec_wireless_stick.hide=
heltec_wireless_stick_lite.hide=
espectro32.hide=
CoreESP32.hide=
alksesp32.hide=
wipy3.hide=
bpi-bit.hide=
wesp32.hide=
t-beam.hide=
d-duino-32.hide=
lopy.hide=
lopy4.hide=
oroca_edubot.hide=
fm-devkit.hide=
frogboard.hide=
esp32cam.hide=
sparkfun_lora_gateway_1-channel.hide=
twatch.hide=
d1_mini32.hide=
gpy.hide=
vintlabs-devkit-v1.hide=
honeylemon.hide=
mgbot-iotik32a.hide=
mgbot-iotik32b.hide=
piranha_esp-32.hide=
metro_esp-32.hide=
sensesiot_weizen.hide=
kits-edu.hide=
mPython.hide=
OpenKB.hide=
wifiduino32.hide=
ttgo-lora32-v21new.hide=
ttgo-lora32-v21new.hide=
imbrios-logsens-v1p1.hide=
healthypi4.hide=
ET-Board.hide=
Bei neuen Cores müssen diese Dateien möglicherweise ergänzt werden.
Connect:
Bei mir habe ich ssid und password durch router_ssid und router_password ersetzt, um diese Zugangsdaten von denen des ESP32 als Access Point zu unterscheiden.
Beleuchtung:
Zum Testen verwende ich unterschiedliche LED-Streifentypen. Du hingegen verwendest zwei Stücke des selben Typs.
FastLED.addLeds<WS2812B, PIN_STRIP_U, RGB>(leds, 0, NUM_PIXEL_STRIP_U);
FastLED.addLeds<NEOPIXEL, PIN_STRIP_O>(leds, NUM_PIXEL_STRIP_U, NUM_PIXEL_STRIP_O);
Du hingegen verwendest zwei Stücke des selben Typs. Vermutlich daher so:
FastLED.addLeds<NEOPIXEL, PIN_STRIP_U>(leds, 0, NUM_PIXEL_STRIP_U);
FastLED.addLeds<NEOPIXEL, PIN_STRIP_O>(leds, NUM_PIXEL_STRIP_U, NUM_PIXEL_STRIP_O);
Ich habe jetzt auf zwei WS2815(B) umgestellt.
const int NUM_PIXEL_STRIP_U = {60};
const int NUM_PIXEL_STRIP_O = {120};
Qualität:
Bitte überprüfe Deine LED-Streifen auf einwandfreie Funktion, leider wird auch mal Schrott geliefert.
Moin,
gestern die Ersatzsensoren angekommen, angeklemmt und es funktioniert. Einer ist allerdings ziemlich heiß geworden - ich hoffe, das ist mit den endgültigen Sensoren besser.
Weiß funktioniert durchgängig
Ich habe mich versucht, die Logik von dem Beitrag weiter oben in ein Code umzusetzen:
void loop() {
if (digitalRead(pirPinOben) == high && (digitalRead(pirPinMitte) == low)) {
// LED-Strip oben startet von Oben bis Mitte
digitalWrite(pirPinOben, high);
delay(5000);
digitalWrite(pirPinOben, low);
// LED-Strip oben geht aus von Oben nach Unten
}
if (digitalRead(pirPinOben) == high && (digitalRead(pirPinMitte) == high)) {
digitalWrite(pirPinOben, low);
digitalWrite(pirPinMitte, high);
// LED-Strip startet von Mitte bis Unten
delay(3000);
digitalWrite(pirPinMitte, low);
// LED-Strip geht aus von Mitte bis Unten
}
if (digitalRead(pirPinUnten) == high && (digitalRead(pirPinMitte) == low)) {
// LED-Strip startet von Unten bis Mitte
digitalWrite(pirPinUnten, high); :see_no_evil:
delay(3000);
digitalWrite(pirPinUnten, low);
// LED-Strip geht aus von Mitte bis Unten
}
if (digitalRead(pirPinUnten) == high && (digitalRead(pirPinMitte) == high)) {
digitalWrite(pirPinUnten, low);
digitalWrite(pirPinMitte, high);
// LED-Strip startet von Mitte bis Oben
delay(5000);
digitalWrite(pirPinMitte, low);
// LED-Strip geht aus von Mitte bis Oben
}
Die Kommentare muss ich noch durch die rauf/runter Funktion ergänzen. Da bin ich allerdings noch nicht ganz durchgestiegen, wie das funktioniert
@agmue Schritt::OBEN_AKTIV (/UNTEN_AKTIV) kann ich da so nicht einpflegen. Haste einen Tipp, wie ich das bis Mitte/ab Mitte lösen kann?
Liebe Grüße
Walli
Sind das nicht Eingänge?
Bis Mitte LEDs einschalten, dann weiter, wenn der mittlere PIR anspricht. Passiert das nach einer gewissen Zeit nicht, alle LEDs wieder aus. Die Zeit muß von den äußeren PIRs erneut getriggert werden (retriggerbares Monoflop).
Stimmt. Den Wert kann ich dann nicht "künstlich" auf high setzen, um mir weitere Variablen sparen zu können?
Die Idee ist, dass innerhalb dieser Zeit der PIR in der Mitte anspringen muss, quasi wie du es beschrieben hast.
Während delay() springt nichts an. Der schläft einfach.
Gruß Tommy
Also auch nicht in der parallelen Wenn-Abfrage?
Beantwortest Du Fragen, während Du schläfst?
Was soll auf einem System mit 1 Prozessor parallel nebenher laufen?
Gruß Tommy
Streu Salz in die Wunde
Das Problem ist, dass ich vieles von dem Programmierten nachvollziehen und anpassen, aber nicht selbst auf diesem Niveau programmieren kann. Ich lege das erstmal auf Eis und widme mich dem Renovieren der Treppe - das kann ich
Vielen Dank allen fürs Helfen und die vielen Tipps!
LG Walli
Bevor dieses Thema vom System geschlossen wird, wollte ich noch die von mir vorbereitete Antwort zeigen. Möglicherweise hilft es ja irgendwie Irgendjemandem
Ja, habe ich, wobei nur die Kinder auf dem unteren Treppenabschnitt berücksichtigt sind:
ESP_Treppe.ino:
#include <WebServer.h>
#include <ArduinoOTA.h>
#include <SPIFFS.h>
//#define DEBUGGING // Einkommentieren für die Serielle Ausgabe
#define DEBUG OLED // Einkommentieren für die Ausgabe auf einem OLED-Display
#ifdef DEBUGGING
#define DEBUG_B(...) Serial.begin(__VA_ARGS__)
#define DEBUG_P(...) Serial.println(__VA_ARGS__)
#define DEBUG_F(...) Serial.printf(__VA_ARGS__)
#else
#define DEBUG_B(...)
#define DEBUG_P(...)
#define DEBUG_F(...)
#endif
WebServer server(80);
#include <FastLED.h>
const int PIN_STRIP_U = {14};
const int PIN_STRIP_O = {12};
const int NUM_PIXEL_STRIP_U = {60};
const int NUM_PIXEL_STRIP_O = {120};
const int NUM_PIXEL = NUM_PIXEL_STRIP_U + NUM_PIXEL_STRIP_O;
CRGBArray<NUM_PIXEL> leds;
const int BRIGHTNESS = {50};
void setup() {
DEBUG_B(115200);
DEBUG_F("\nSketchname: %s\nBuild: %s\t\tIDE: %d.%d.%d\n\n", __FILE__, __TIMESTAMP__, ARDUINO / 10000, ARDUINO % 10000 / 100, ARDUINO % 100 / 10 ? ARDUINO % 100 : ARDUINO % 10);
setup_OLED();
spiffs();
admin();
Connect();
setupTime();
setupTimerSwitch();
setup_Beleuchtung();
setup_TwinkleFOX();
ArduinoOTA.begin();
server.begin();
DEBUG_P("HTTP Server gestartet\n\n");
anzeigeOLED("HTTP Server gestartet");
}
void loop() {
ArduinoOTA.handle();
server.handleClient();
timerSwitch();
loop_Beleuchtung();
}
A_TwinkleFox.ino:
//---------------------------------------------------------------------
// TwinkleFOX: Twinkling 'holiday' lights that fade in and out.
// Colors are chosen from a palette; a few palettes are provided.
//
// This December 2015 implementation improves on the December 2014 version
// in several ways:
// - smoother fading, compatible with any colors and any palettes
// - easier control of twinkle speed and twinkle density
// - supports an optional 'background color'
// - takes even less RAM: zero RAM overhead per pixel
// - illustrates a couple of interesting techniques (uh oh...)
//
// The idea behind this (new) implementation is that there's one
// basic, repeating pattern that each pixel follows like a waveform:
// The brightness rises from 0..255 and then falls back down to 0.
// The brightness at any given point in time can be determined as
// as a function of time, for example:
// brightness = sine( time ); // a sine wave of brightness over time
//
// So the way this implementation works is that every pixel follows
// the exact same wave function over time. In this particular case,
// I chose a sawtooth triangle wave (triwave8) rather than a sine wave,
// but the idea is the same: brightness = triwave8( time ).
//
// Of course, if all the pixels used the exact same wave form, and
// if they all used the exact same 'clock' for their 'time base', all
// the pixels would brighten and dim at once -- which does not look
// like twinkling at all.
//
// So to achieve random-looking twinkling, each pixel is given a
// slightly different 'clock' signal. Some of the clocks run faster,
// some run slower, and each 'clock' also has a random offset from zero.
// The net result is that the 'clocks' for all the pixels are always out
// of sync from each other, producing a nice random distribution
// of twinkles.
//
// The 'clock speed adjustment' and 'time offset' for each pixel
// are generated randomly. One (normal) approach to implementing that
// would be to randomly generate the clock parameters for each pixel
// at startup, and store them in some arrays. However, that consumes
// a great deal of precious RAM, and it turns out to be totally
// unnessary! If the random number generate is 'seeded' with the
// same starting value every time, it will generate the same sequence
// of values every time. So the clock adjustment parameters for each
// pixel are 'stored' in a pseudo-random number generator! The PRNG
// is reset, and then the first numbers out of it are the clock
// adjustment parameters for the first pixel, the second numbers out
// of it are the parameters for the second pixel, and so on.
// In this way, we can 'store' a stable sequence of thousands of
// random clock adjustment parameters in literally two bytes of RAM.
//
// There's a little bit of fixed-point math involved in applying the
// clock speed adjustments, which are expressed in eighths. Each pixel's
// clock speed ranges from 8/8ths of the system clock (i.e. 1x) to
// 23/8ths of the system clock (i.e. nearly 3x).
//
// On a basic Arduino Uno or Leonardo, this code can twinkle 300+ pixels
// smoothly at over 50 updates per seond.
//
// -Mark Kriegsman, December 2015
//---------------------------------------------------------------------
//
// Overall twinkle speed.
// 0 (VERY slow) to 8 (VERY fast).
// 4, 5, and 6 are recommended, default is 4.
int TF_Blinkgeschwindigkeit = 4;
// Overall twinkle density.
// 0 (NONE lit) to 8 (ALL lit at once).
// Default is 5.
int TF_Blinkdichte = 5;
// How often to change color palettes.
int TF_Farbwechselzeit = 30;
// Also: toward the bottom of the file is an array
// called "ActivePaletteList" which controls which color
// palettes are used; you can add or remove color palettes
// from there freely.
// Background color for 'unlit' pixels
// Can be set to CRGB::Black if desired.
CRGB gBackgroundColor = CRGB(1, 0, 2);
// Example of dim incandescent fairy light background color
// CRGB gBackgroundColor = CRGB(CRGB::FairyLight).nscale8_video(16);
// If AUTO_SELECT_BACKGROUND_COLOR is set to 1,
// then for any palette where the first two entries
// are the same, a dimmed version of that color will
// automatically be used as the background color.
#define AUTO_SELECT_BACKGROUND_COLOR 0
// If COOL_LIKE_INCANDESCENT is set to 1, colors will
// fade out slighted 'reddened', similar to how
// incandescent bulbs change color as they get dim down.
#define COOL_LIKE_INCANDESCENT 1
CRGBPalette16 gCurrentPalette;
CRGBPalette16 gTargetPalette;
//----------------------------------
// This function loops over each pixel, calculates the
// adjusted 'clock' that this pixel should use, and calls
// "CalculateOneTwinkle" on each pixel. It then displays
// either the twinkle color of the background color,
// whichever is brighter.
void drawTwinkles( CRGBSet& L)
{
// "PRNG16" is the pseudorandom number generator
// It MUST be reset to the same starting value each time
// this function is called, so that the sequence of 'random'
// numbers that it generates is (paradoxically) stable.
uint16_t PRNG16 = 11337;
uint32_t clock32 = millis();
// Set up the background color, "bg".
// if AUTO_SELECT_BACKGROUND_COLOR == 1, and the first two colors of
// the current palette are identical, then a deeply faded version of
// that color is used for the background color
CRGB bg;
if ( (AUTO_SELECT_BACKGROUND_COLOR == 1) &&
(gCurrentPalette[0] == gCurrentPalette[1] )) {
bg = gCurrentPalette[0];
uint8_t bglight = bg.getAverageLight();
if ( bglight > 64) {
bg.nscale8_video( 16); // very bright, so scale to 1/16th
} else if ( bglight > 16) {
bg.nscale8_video( 64); // not that bright, so scale to 1/4th
} else {
bg.nscale8_video( 86); // dim, scale to 1/3rd.
}
} else {
bg = gBackgroundColor; // just use the explicitly defined background color
}
uint8_t backgroundBrightness = bg.getAverageLight();
for ( CRGB& pixel : L) {
PRNG16 = (uint16_t)(PRNG16 * 2053) + 1384; // next 'random' number
uint16_t myclockoffset16 = PRNG16; // use that number as clock offset
PRNG16 = (uint16_t)(PRNG16 * 2053) + 1384; // next 'random' number
// use that number as clock speed adjustment factor (in 8ths, from 8/8ths to 23/8ths)
uint8_t myspeedmultiplierQ5_3 = ((((PRNG16 & 0xFF) >> 4) + (PRNG16 & 0x0F)) & 0x0F) + 0x08;
uint32_t myclock30 = (uint32_t)((clock32 * myspeedmultiplierQ5_3) >> 3) + myclockoffset16;
uint8_t myunique8 = PRNG16 >> 8; // get 'salt' value for this pixel
// We now have the adjusted 'clock' for this pixel, now we call
// the function that computes what color the pixel should be based
// on the "brightness = f( time )" idea.
CRGB c = computeOneTwinkle( myclock30, myunique8);
uint8_t cbright = c.getAverageLight();
int16_t deltabright = cbright - backgroundBrightness;
if ( deltabright >= 32 || (!bg)) {
// If the new pixel is significantly brighter than the background color,
// use the new color.
pixel = c;
} else if ( deltabright > 0 ) {
// If the new pixel is just slightly brighter than the background color,
// mix a blend of the new color and the background color
pixel = blend( bg, c, deltabright * 8);
} else {
// if the new pixel is not at all brighter than the background color,
// just use the background color.
pixel = bg;
}
}
}
// This function takes a time in pseudo-milliseconds,
// figures out brightness = f( time ), and also hue = f( time )
// The 'low digits' of the millisecond time are used as
// input to the brightness wave function.
// The 'high digits' are used to select a color, so that the color
// does not change over the course of the fade-in, fade-out
// of one cycle of the brightness wave function.
// The 'high digits' are also used to determine whether this pixel
// should light at all during this cycle, based on the TWINKLE_DENSITY.
CRGB computeOneTwinkle( uint32_t ms, uint8_t salt)
{
uint16_t ticks = ms >> (8 - TF_Blinkgeschwindigkeit);
uint8_t fastcycle8 = ticks;
uint16_t slowcycle16 = (ticks >> 8) + salt;
slowcycle16 += sin8( slowcycle16);
slowcycle16 = (slowcycle16 * 2053) + 1384;
uint8_t slowcycle8 = (slowcycle16 & 0xFF) + (slowcycle16 >> 8);
uint8_t bright = 0;
if ( ((slowcycle8 & 0x0E) / 2) < TF_Blinkdichte) {
bright = attackDecayWave8( fastcycle8);
}
uint8_t hue = slowcycle8 - salt;
CRGB c;
if ( bright > 0) {
c = ColorFromPalette( gCurrentPalette, hue, bright, NOBLEND);
if ( COOL_LIKE_INCANDESCENT == 1 ) {
coolLikeIncandescent( c, fastcycle8);
}
} else {
c = CRGB::Black;
}
return c;
}
// This function is like 'triwave8', which produces a
// symmetrical up-and-down triangle sawtooth waveform, except that this
// function produces a triangle wave with a faster attack and a slower decay:
//
//
uint8_t attackDecayWave8( uint8_t i)
{
if ( i < 86) {
return i * 3;
} else {
i -= 86;
return 255 - (i + (i / 2));
}
}
// This function takes a pixel, and if its in the 'fading down'
// part of the cycle, it adjusts the color a little bit like the
// way that incandescent bulbs fade toward 'red' as they dim.
void coolLikeIncandescent( CRGB& c, uint8_t phase)
{
if ( phase < 128) return;
uint8_t cooling = (phase - 128) >> 4;
c.g = qsub8( c.g, cooling);
c.b = qsub8( c.b, cooling * 2);
}
// A mostly red palette with green accents and white trim.
// "CRGB::Gray" is used as white to keep the brightness more uniform.
const TProgmemRGBPalette16 RedGreenWhite_p FL_PROGMEM =
{ CRGB::Red, CRGB::Red, CRGB::Red, CRGB::Red,
CRGB::Red, CRGB::Red, CRGB::Red, CRGB::Red,
CRGB::Red, CRGB::Red, CRGB::Gray, CRGB::Gray,
CRGB::Green, CRGB::Green, CRGB::Green, CRGB::Green
};
// A mostly (dark) green palette with red berries.
#define Holly_Green 0x00580c
#define Holly_Red 0xB00402
const TProgmemRGBPalette16 Holly_p FL_PROGMEM =
{ Holly_Green, Holly_Green, Holly_Green, Holly_Green,
Holly_Green, Holly_Green, Holly_Green, Holly_Green,
Holly_Green, Holly_Green, Holly_Green, Holly_Green,
Holly_Green, Holly_Green, Holly_Green, Holly_Red
};
// A red and white striped palette
// "CRGB::Gray" is used as white to keep the brightness more uniform.
const TProgmemRGBPalette16 RedWhite_p FL_PROGMEM =
{ CRGB::Red, CRGB::Red, CRGB::Red, CRGB::Red,
CRGB::Gray, CRGB::Gray, CRGB::Gray, CRGB::Gray,
CRGB::Red, CRGB::Red, CRGB::Red, CRGB::Red,
CRGB::Gray, CRGB::Gray, CRGB::Gray, CRGB::Gray
};
// A mostly blue palette with white accents.
// "CRGB::Gray" is used as white to keep the brightness more uniform.
const TProgmemRGBPalette16 BlueWhite_p FL_PROGMEM =
{ CRGB::Blue, CRGB::Blue, CRGB::Blue, CRGB::Blue,
CRGB::Blue, CRGB::Blue, CRGB::Blue, CRGB::Blue,
CRGB::Blue, CRGB::Blue, CRGB::Blue, CRGB::Blue,
CRGB::Blue, CRGB::Gray, CRGB::Gray, CRGB::Gray
};
// A pure "fairy light" palette with some brightness variations
#define HALFFAIRY ((CRGB::FairyLight & 0xFEFEFE) / 2)
#define QUARTERFAIRY ((CRGB::FairyLight & 0xFCFCFC) / 4)
const TProgmemRGBPalette16 FairyLight_p FL_PROGMEM =
{ CRGB::FairyLight, CRGB::FairyLight, CRGB::FairyLight, CRGB::FairyLight,
HALFFAIRY, HALFFAIRY, CRGB::FairyLight, CRGB::FairyLight,
QUARTERFAIRY, QUARTERFAIRY, CRGB::FairyLight, CRGB::FairyLight,
CRGB::FairyLight, CRGB::FairyLight, CRGB::FairyLight, CRGB::FairyLight
};
// A palette of soft snowflakes with the occasional bright one
const TProgmemRGBPalette16 Snow_p FL_PROGMEM =
{ 0x304048, 0x304048, 0x304048, 0x304048,
0x304048, 0x304048, 0x304048, 0x304048,
0x304048, 0x304048, 0x304048, 0x304048,
0x304048, 0x304048, 0x304048, 0xE0F0FF
};
// A palette reminiscent of large 'old-school' C9-size tree lights
// in the five classic colors: red, orange, green, blue, and white.
#define C9_Red 0xB80400
#define C9_Orange 0x902C02
#define C9_Green 0x046002
#define C9_Blue 0x070758
#define C9_White 0x606820
const TProgmemRGBPalette16 RetroC9_p FL_PROGMEM =
{ C9_Red, C9_Orange, C9_Red, C9_Orange,
C9_Orange, C9_Red, C9_Orange, C9_Red,
C9_Green, C9_Green, C9_Green, C9_Green,
C9_Blue, C9_Blue, C9_Blue,
C9_White
};
// A cold, icy pale blue palette
#define Ice_Blue1 0x0C1040
#define Ice_Blue2 0x182080
#define Ice_Blue3 0x5080C0
const TProgmemRGBPalette16 Ice_p FL_PROGMEM =
{
Ice_Blue1, Ice_Blue1, Ice_Blue1, Ice_Blue1,
Ice_Blue1, Ice_Blue1, Ice_Blue1, Ice_Blue1,
Ice_Blue1, Ice_Blue1, Ice_Blue1, Ice_Blue1,
Ice_Blue2, Ice_Blue2, Ice_Blue2, Ice_Blue3
};
// Add or remove palette names from this list to control which color
// palettes are used, and in what order.
const TProgmemRGBPalette16* ActivePaletteList[] = {
&RetroC9_p,
&BlueWhite_p,
&RainbowColors_p,
&FairyLight_p,
&RedGreenWhite_p,
&PartyColors_p,
&RedWhite_p,
&Snow_p,
&Holly_p,
&Ice_p
};
// Advance to the next color palette in the list (above).
void chooseNextColorPalette( CRGBPalette16& pal)
{
const uint8_t numberOfPalettes = sizeof(ActivePaletteList) / sizeof(ActivePaletteList[0]);
static uint8_t whichPalette = -1;
whichPalette = addmod8( whichPalette, 1, numberOfPalettes);
pal = *(ActivePaletteList[whichPalette]);
}
void setup_TwinkleFOX() {
chooseNextColorPalette(gTargetPalette);
}
void animation_TwinkleFOX() {
EVERY_N_SECONDS( TF_Farbwechselzeit ) {
chooseNextColorPalette( gTargetPalette );
}
EVERY_N_MILLISECONDS( 10 ) {
nblendPaletteTowardPalette( gCurrentPalette, gTargetPalette, 12);
}
drawTwinkles( leds);
FastLED.show();;
}
Admin.ino
#include <rom/rtc.h>
const char* const PROGMEM flashChipMode[] = {"QIO", "QOUT", "DIO", "DOUT", "Unbekannt"};
const char* const PROGMEM resetReason[] = {"ERR", "Power on", "Unknown", "Software", "Watch dog", "Deep Sleep", "SLC module", "Timer Group 0", "Timer Group 1",
"RTC Watch dog", "Instrusion", "Time Group CPU", "Software CPU", "RTC Watch dog CPU", "Extern CPU", "Voltage not stable", "RTC Watch dog RTC"};
void admin() { // Funktionsaufruf "admin();" muss im Setup eingebunden werden
WiFi.mode(WIFI_STA);
File file = SPIFFS.open("/config.json");
if (file) {
String Hostname = file.readStringUntil('\n');
if (Hostname != "") {
WiFi.setHostname(Hostname.substring(2, Hostname.length() - 2).c_str());
ArduinoOTA.setHostname(WiFi.getHostname());
}
}
file.close();
server.on("/admin/renew", handlerenew);
server.on("/admin/once", handleonce);
server.on("/reconnect", []() {
server.send(304, "message/http");
WiFi.reconnect();
});
server.on("/restart", []() {
server.send(304, "message/http");
//save(); //Einkommentieren wenn Werte vor dem Neustart gesichert werden sollen
ESP.restart();
});
}
void handlerenew() {
server.send(200, "application/json", R"([")" + runtime() + R"(",")" + temperatureRead() + R"(",")" + WiFi.RSSI() + R"("])"); // Json als Array
}
void handleonce() {
if (server.arg(0) != "") {
WiFi.setHostname(server.arg(0).c_str());
File file = SPIFFS.open("/config.json", FILE_WRITE);
file.printf("[\"%s\"]", WiFi.getHostname());
file.close();
}
String fname = String(__FILE__).substring( 3, String(__FILE__).lastIndexOf ('\\'));
String temp = R"({"File":")" + fname.substring(fname.lastIndexOf ('\\') + 1, fname.length()) + R"(", "Build":")" + (String)__DATE__ + " " + (String)__TIME__ +
R"(", "SketchSize":")" + formatBytes(ESP.getSketchSize()) + R"(", "SketchSpace":")" + formatBytes(ESP.getFreeSketchSpace()) +
R"(", "LocalIP":")" + WiFi.localIP().toString() + R"(", "Hostname":")" + WiFi.getHostname() + R"(", "SSID":")" + WiFi.SSID() +
R"(", "GatewayIP":")" + WiFi.gatewayIP().toString() + R"(", "Channel":")" + WiFi.channel() + R"(", "MacAddress":")" + WiFi.macAddress() +
R"(", "SubnetMask":")" + WiFi.subnetMask().toString() + R"(", "BSSID":")" + WiFi.BSSIDstr() + R"(", "ClientIP":")" + server.client().remoteIP().toString() +
R"(", "DnsIP":")" + WiFi.dnsIP().toString() + R"(", "ChipModel":")" + ESP.getChipModel() + R"(", "Reset1":")" + resetReason[rtc_get_reset_reason(0)] +
R"(", "Reset2":")" + resetReason[rtc_get_reset_reason(1)] + R"(", "CpuFreqMHz":")" + ESP.getCpuFreqMHz() + R"(", "HeapSize":")" + formatBytes(ESP.getHeapSize()) +
R"(", "FreeHeap":")" + formatBytes(ESP.getFreeHeap()) + R"(", "MinFreeHeap":")" + formatBytes(ESP.getMinFreeHeap()) +
R"(", "ChipSize":")" + formatBytes(ESP.getFlashChipSize()) + R"(", "ChipSpeed":")" + ESP.getFlashChipSpeed() / 1000000 +
R"(", "ChipMode":")" + flashChipMode[ESP.getFlashChipMode()] + R"(", "IdeVersion":")" + ARDUINO + R"(", "SdkVersion":")" + ESP.getSdkVersion() + R"("})";
server.send(200, "application/json", temp); // Json als Objekt
}
String runtime() {
static uint8_t rolloverCounter;
static uint32_t previousMillis;
uint32_t currentMillis {millis()};
if (currentMillis < previousMillis) {
rolloverCounter++; // prüft Millis Überlauf
DEBUG_P("Millis Überlauf");
}
previousMillis = currentMillis;
uint32_t sec {(0xFFFFFFFF / 1000) * rolloverCounter + (currentMillis / 1000)};
char buf[20];
snprintf(buf, sizeof(buf), "%d Tag%s %02d:%02d:%02d", sec / 86400, sec < 86400 || sec >= 172800 ? "e" : "", sec / 3600 % 24, sec / 60 % 60, sec % 60);
return buf;
}
Beleuchtung.ino
const int pirPinOben = {36}; // VP
const int pirPinMitte = {39}; // VN
const int pirPinUnten = {34}; // 34
const uint32_t ZEIT_AN = {3000}; // in Millisekunden
const uint32_t ZEIT_WARTEN_MITTE = {3000}; // in Millisekunden
const uint32_t ZEIT_LAUFLICHT = {20}; // Intervall für Lauflicht
void setup_Beleuchtung() {
FastLED.addLeds<NEOPIXEL, PIN_STRIP_U>(leds, 0, NUM_PIXEL_STRIP_U);
FastLED.addLeds<NEOPIXEL, PIN_STRIP_O>(leds, NUM_PIXEL_STRIP_U, NUM_PIXEL_STRIP_O);
FastLED.setBrightness( BRIGHTNESS );
leds[richtung(0)] = CRGB::Red;
leds[richtung(1)] = CRGB::Green;
leds[richtung(2)] = CRGB::Blue;
leds[richtung(NUM_PIXEL_STRIP_U + 0)] = CRGB::Red;
leds[richtung(NUM_PIXEL_STRIP_U + 1)] = CRGB::Green;
leds[richtung(NUM_PIXEL_STRIP_U + 2)] = CRGB::Blue;
FastLED.show();
delay(3000); // nur zum Testen
anzeigeOLED("Beleuchtung bereit");
pinMode(pirPinOben, INPUT);
pinMode(pirPinMitte, INPUT);
pinMode(pirPinUnten, INPUT);
}
int richtung(int pix) {
if (pix < NUM_PIXEL_STRIP_U) {
return NUM_PIXEL_STRIP_U - 1 - pix;
} else {
return pix;
}
}
bool runter(bool an) {
uint32_t jetzt = millis();
static uint32_t vorhin = {0};
static uint16_t pix = NUM_PIXEL - 1;
bool fertig = false;
if (jetzt - vorhin >= ZEIT_LAUFLICHT) {
vorhin = jetzt;
if (an) {
leds[richtung(pix)] = CRGB::White;
} else {
leds[richtung(pix)] = CRGB::Black;
}
FastLED.show();
if (pix > 0) {
pix--;
} else {
pix = NUM_PIXEL - 1;
fertig = true;
}
}
return fertig;
}
bool rauf(const bool an, int &pix, const int bis_pixel = NUM_PIXEL) {
uint32_t jetzt = millis();
static uint32_t vorhin = {0};
bool fertig = false;
if (jetzt - vorhin >= ZEIT_LAUFLICHT) {
vorhin = jetzt;
if (an) {
leds[richtung(pix)] = CRGB::White;
} else {
leds[richtung(pix)] = CRGB::Black;
}
FastLED.show();
pix++;
if (pix >= bis_pixel) {
fertig = true;
}
}
return fertig;
}
void animation_Regenbogen() {
uint32_t jetzt = millis();
static uint32_t vorhin = {0};
static byte startfarbe = 0;
if (jetzt - vorhin >= ZEIT_LAUFLICHT) {
vorhin = jetzt;
for (byte pix = 0; pix < NUM_PIXEL; pix++) {
byte farbwinkel = startfarbe + pix * 3;
leds[richtung(pix)] = CHSV(farbwinkel, 255, 255);
}
startfarbe++;
FastLED.show();
}
}
void loop_Beleuchtung() {
uint32_t jetzt = millis();
static uint32_t vorhin = {0};
static int pix = 0;
enum class Schritt : byte {WARTEN, OBEN_AKTIV, OBEN_AN, UNTEN_AKTIV, UNTEN_AN, WARTEN_MITTE, MITTE_AKTIV};
static Schritt schritt = Schritt::WARTEN;
bool oben_akt = digitalRead(pirPinOben);
bool mitte_akt = digitalRead(pirPinMitte);
bool unten_akt = digitalRead(pirPinUnten);
switch (schritt) {
case Schritt::WARTEN:
//animation_Regenbogen();
animation_TwinkleFOX();
if (oben_akt) {
anzeigeOLED("oben aktiv");
schritt = Schritt::OBEN_AKTIV;
}
if (unten_akt) {
anzeigeOLED("unten aktiv");
pix = 0;
schritt = Schritt::UNTEN_AKTIV;
}
break;
case Schritt::OBEN_AKTIV:
if (runter(true)) {
anzeigeOLED("oben an");
vorhin = jetzt;
schritt = Schritt::OBEN_AN;
}
break;
case Schritt::OBEN_AN:
if (oben_akt || mitte_akt || unten_akt) {
anzeigeOLED("oben nachgetriggert");
vorhin = jetzt; // nachtriggern
}
if (jetzt - vorhin > ZEIT_AN) {
anzeigeOLED("oben an nach warten");
schritt = Schritt::WARTEN;
}
break;
case Schritt::UNTEN_AKTIV:
if (rauf(true, pix, NUM_PIXEL_STRIP_U)) {
anzeigeOLED("warten mitte");
vorhin = jetzt;
schritt = Schritt::WARTEN_MITTE;
}
break;
case Schritt::WARTEN_MITTE:
if (unten_akt) {
anzeigeOLED("unten nachgetriggert");
vorhin = jetzt; // nachtriggern
}
if (jetzt - vorhin > ZEIT_WARTEN_MITTE) {
anzeigeOLED("mitte nicht erreicht");
schritt = Schritt::WARTEN;
}
if (oben_akt) {
anzeigeOLED("oben aktiv");
schritt = Schritt::OBEN_AKTIV;
}
if (mitte_akt) {
anzeigeOLED("mitte aktiv");
vorhin = jetzt;
schritt = Schritt::MITTE_AKTIV;
}
break;
case Schritt::MITTE_AKTIV:
if (rauf(true, pix)) {
anzeigeOLED("unten an");
vorhin = jetzt;
schritt = Schritt::UNTEN_AN;
}
break;
case Schritt::UNTEN_AN:
if (oben_akt || mitte_akt || unten_akt) {
anzeigeOLED("unten nachgetriggert");
vorhin = jetzt; // nachtriggern
}
if (jetzt - vorhin > ZEIT_AN) {
anzeigeOLED("unten an nach warten");
schritt = Schritt::WARTEN;
}
break;
default:
schritt = Schritt::WARTEN;
}
}
Connect.ino:
const char* router_ssid = "RouterSSID"; // << kann bis zu 32 Zeichen haben
const char* router_password = "RouterKennwort"; // << mindestens 8 Zeichen jedoch nicht länger als 64 Zeichen
#define LED_BUILTIN 13
void Connect() { // Funktionsaufruf "Connect();" muss im Setup nach "spiffs();" eingebunden werden
pinMode(LED_BUILTIN, OUTPUT); // OnBoardLed ESP32 Dev Module
WiFi.mode(WIFI_STA);
WiFi.begin(router_ssid, router_password);
while (WiFi.status() != WL_CONNECTED) {
digitalWrite(LED_BUILTIN, 1);
delay(250);
digitalWrite(LED_BUILTIN, 0);
delay(250);
DEBUG_F(".");
if (millis() > 10000) {
DEBUG_P("\nVerbindung zum AP fehlgeschlagen\n\n");
ESP.restart();
}
}
DEBUG_P("\nVerbunden mit: " + WiFi.SSID());
DEBUG_P("Esp32 IP: " + WiFi.localIP().toString() + "\n");
anzeigeOLED("Verbunden mit:");
anzeigeOLED(WiFi.SSID().c_str());
char buf[30] = {'\0'};
snprintf( buf, sizeof(buf), "IP: %s", WiFi.localIP().toString().c_str() );
anzeigeOLED(buf);
}
Lokalzeit.ino:
#include "time.h"
struct tm tm;
const char* const PROGMEM ntpServer[] = {"fritz.box", "de.pool.ntp.org", "at.pool.ntp.org", "ch.pool.ntp.org", "ptbtime1.ptb.de", "europe.pool.ntp.org"};
const char* const PROGMEM dayNames[] = {"Sonntag", "Montag", "Dienstag", "Mittwoch", "Donnerstag", "Freitag", "Samstag"};
const char* const PROGMEM dayShortNames[] = {"So", "Mo", "Di", "Mi", "Do", "Fr", "Sa"};
const char* const PROGMEM monthNames[] = {"Januar", "Februar", "März", "April", "Mai", "Juni", "Juli", "August", "September", "Oktober", "November", "Dezember"};
const char* const PROGMEM monthShortNames[] = {"Jan", "Feb", "Mrz", "Apr", "Mai", "Jun", "Jul", "Aug", "Sep", "Okt", "Nov", "Dez"};
bool getTime() { // Zeitzone einstellen https://github.com/nayarsystems/posix_tz_db/blob/master/zones.csv
configTzTime("CET-1CEST,M3.5.0/02,M10.5.0/03", ntpServer[0]); // deinen NTP Server einstellen (von 0 - 5 aus obiger Liste)
if (!getLocalTime(&tm)) return false;
return true;
}
void setupTime() {
if (!getTime()) {
DEBUG_P("Zeit konnte nicht geholt werden\n");
} else {
getLocalTime(&tm);
DEBUG_P(&tm, "Programmstart: %A, %B %d %Y %H:%M:%S");
//DEBUG_P(dayNames[tm.tm_wday]); // druckt den aktuellen Tag
//DEBUG_P(monthNames[tm.tm_mon]); // druckt den aktuellen Monat
//DEBUG_P(dayShortNames[tm.tm_wday]); // druckt den aktuellen Tag (Abk.)
//DEBUG_P(monthShortNames[tm.tm_mon]); // druckt den aktuellen Monat (Abk.)
//DEBUG_P(tm.tm_isdst ? "Sommerzeit" : "Normalzeit");
}
server.on("/zeit", []() {
server.send(200, "application/json", "\"" + (String)localTime() + "\"");
});
}
char* localTime() {
static char buf[20]; // je nach Format von "strftime" eventuell die Größe anpassen
static time_t lastsec {0};
getLocalTime(&tm, 50);
if (tm.tm_sec != lastsec) {
lastsec = tm.tm_sec;
strftime (buf, sizeof(buf), "%d.%m.%Y %T ", &tm); // http://www.cplusplus.com/reference/ctime/strftime/
time_t now;
if (!(time(&now) % 86400)) getTime(); // einmal am Tag die Zeit vom NTP Server holen o. jede Stunde "% 3600" aller zwei "% 7200"
}
return buf;
}
OLED.ino:
#if defined(DEBUG) && (DEBUG == OLED)
#include <Wire.h>
#include <U8g2lib.h>
U8G2_SSD1306_128X64_NONAME_F_HW_I2C oled(U8G2_R0, /* reset=*/ U8X8_PIN_NONE);
#endif
void setup_OLED() {
#if defined(DEBUG) && (DEBUG == OLED)
oled.begin();
#endif
}
void anzeigeOLED(const char * text) {
#if defined(DEBUG) && (DEBUG == OLED)
const byte ZEILEMAX = 8;
const byte SPALTEMAX = 22;
static char buf[ZEILEMAX][SPALTEMAX] = {" ", " ", " ", " ", " ", " ", " ", " "};
oled.clearBuffer(); // clear the internal memory
oled.setFont(u8g2_font_6x12_tr); // choose a suitable font
for (byte zeile = 0; zeile < ZEILEMAX; zeile++) {
oled.setCursor(0, ((zeile + 1) * 8) - 1);
for (byte spalte = 0; spalte < (SPALTEMAX - 1); spalte++) {
if (zeile < (ZEILEMAX - 1)) {
buf[zeile][spalte] = buf[zeile + 1][spalte];
if (buf[zeile][spalte] == '\0') {
break;
}
} else {
buf[zeile][spalte] = text[spalte];
buf[zeile][spalte + 1] = '\0';
if (buf[zeile][spalte] == '\0') {
break;
}
}
oled.print(buf[zeile][spalte]);
}
}
oled.sendBuffer(); // transfer internal memory to the display
#endif
}
Spiffs.ino:
#include <detail/RequestHandlersImpl.h>
#include <list>
const char HELPER[] PROGMEM = R"(<form method="POST" action="/upload" enctype="multipart/form-data">
<input type="file" name="[]" multiple><button>Upload</button></form>Lade die spiffs.html hoch.)";
void spiffs() { // Funktionsaufruf "spiffs();" muss im Setup eingebunden werden
SPIFFS.begin(true);
server.on("/list", handleList);
server.on("/format", formatSpiffs);
server.on("/upload", HTTP_POST, sendResponce, handleFileUpload);
server.onNotFound([]() {
if (!handleFile(server.urlDecode(server.uri())))
server.send(404, "text/plain", "FileNotFound");
});
}
void handleList() { // Senden aller Daten an den Client
File root = SPIFFS.open("/");
typedef std::pair<String, int> prop;
std::list<prop> dirList; // Liste anlegen
while (File f = root.openNextFile()) dirList.emplace_back(f.name(), f.size()); // Liste füllen
dirList.sort([](const prop & f, const prop & l) { // Liste sortieren
if (server.arg(0) == "1") {
return f.second > l.second;
} else {
for (uint8_t i = 0; i < 31; i++) {
if (tolower(f.first[i]) < tolower(l.first[i])) return true;
else if (tolower(f.first[i]) > tolower(l.first[i])) return false;
}
return false;
}
});
String temp = "[";
for (auto& p : dirList) {
if (temp != "[") temp += ',';
temp += "{\"name\":\"" + p.first.substring(1) + "\",\"size\":\"" + formatBytes(p.second) + "\"}";
}
temp += R"(,{"usedBytes":")" + formatBytes(SPIFFS.usedBytes() * 1.05) + R"(",)" + // Berechnet den verwendeten Speicherplatz + 5% Sicherheitsaufschlag
R"("totalBytes":")" + formatBytes(SPIFFS.totalBytes()) + R"(","freeBytes":")" + // Zeigt die Größe des Speichers
(SPIFFS.totalBytes() - (SPIFFS.usedBytes() * 1.05)) + R"("}])"; // Berechnet den freien Speicherplatz + 5% Sicherheitsaufschlag
server.send(200, "application/json", temp);
}
bool handleFile(String&& path) {
if (server.hasArg("delete")) {
SPIFFS.remove(server.arg("delete")); // Datei löschen
sendResponce();
return true;
}
if (!SPIFFS.exists("/spiffs.html"))server.send(200, "text/html", HELPER); // ermöglicht das hochladen der spiffs.html
if (path.endsWith("/")) path += "index.html";
return SPIFFS.exists(path) ? ({File f = SPIFFS.open(path); server.streamFile(f, StaticRequestHandler::getContentType(path)); f.close(); true;}) : false;
}
void handleFileUpload() { // Dateien vom Rechnenknecht oder Klingelkasten ins SPIFFS schreiben
static File fsUploadFile;
HTTPUpload& upload = server.upload();
if (upload.status == UPLOAD_FILE_START) {
if (upload.filename.length() > 30) {
upload.filename = upload.filename.substring(upload.filename.length() - 30, upload.filename.length()); // Dateinamen auf 30 Zeichen kürzen
}
DEBUG_F("handleFileUpload Name: /%s\n", upload.filename.c_str());
fsUploadFile = SPIFFS.open("/" + server.urlDecode(upload.filename), "w");
} else if (upload.status == UPLOAD_FILE_WRITE) {
DEBUG_F("handleFileUpload Data: %u\n", upload.currentSize);
if (fsUploadFile)
fsUploadFile.write(upload.buf, upload.currentSize);
} else if (upload.status == UPLOAD_FILE_END) {
if (fsUploadFile)
fsUploadFile.close();
DEBUG_F("handleFileUpload Size: %u\n", upload.totalSize);
}
}
void formatSpiffs() { //Formatiert den Speicher
SPIFFS.format();
sendResponce();
}
void sendResponce() {
server.sendHeader("Location", "spiffs.html");
server.send(303, "message/http");
}
const String formatBytes(size_t const& bytes) { // lesbare Anzeige der Speichergrößen
return bytes < 1024 ? static_cast<String>(bytes) + " Byte" : bytes < 1048576 ? static_cast<String>(bytes / 1024.0) + " KB" : static_cast<String>(bytes / 1048576.0) + " MB";
}
bool freeSpace(uint16_t const& printsize) { // Funktion um beim speichern in Logdateien zu prüfen ob noch genügend freier Platz verfügbar ist.
DEBUG_F("Funktion: %s meldet in Zeile: %d FreeSpace: %s\n", __PRETTY_FUNCTION__, __LINE__, formatBytes(SPIFFS.totalBytes() - (SPIFFS.usedBytes() * 1.05)).c_str());
return (SPIFFS.totalBytes() - (SPIFFS.usedBytes() * 1.05) > printsize) ? true : false;
}
Zeitschaltuhr.ino:
#include <EEPROM.h>
const auto aktiv = HIGH; // LOW für LOW aktive Relais oder HIGH für HIGH aktive (zB. SSR, Mosfet) einstellen
const uint8_t relPin = 27; // Pin für Relais einstellen
const auto count = 8; // Anzahl Schaltzeiten (analog Html Dokument) einstellen 1 bis 100
char switchTime[count * 2][6];
byte switchActive[count], wday[count];
bool fixed, relState {!aktiv};
void setupTimerSwitch() {
digitalWrite(relPin, !aktiv);
pinMode(relPin, OUTPUT);
EEPROM.begin(4);
EEPROM.get(1, fixed);
fixed = fixed > 0 ? 1 : 0;
DEBUG_F("Zeitschaltuhr Automatik %saktiviert\n", fixed ? "de" : "");
if (SPIFFS.exists("/swtime.dat")) { // Einlesen aller Daten falls die Datei im Spiffs vorhanden ist.
File f = SPIFFS.open("/swtime.dat");
f.read(switchActive, sizeof(switchActive));
f.read(wday, sizeof(wday));
while (f.read() != '\n');
for (auto& elem : switchTime) f.readBytesUntil('\n', elem, sizeof(elem));
f.close();
} else { // Sollte die Datei nicht existieren
for (auto i = 0; i < count; i++) {
switchActive[i] = 1; // werden alle Schaltzeiten
wday[i] = ~wday[i]; // und alle Wochentage aktiviert.
}
}
server.on("/timer", HTTP_POST, []() {
if (server.args() == 1) {
switchActive[server.argName(0).toInt()] = server.arg(0).toInt();
printer();
String temp = "\"";
for (auto& elem : switchActive) temp += elem;
temp += "\"";
server.send(200, "application/json", temp);
}
if (server.hasArg("sTime")) {
byte i {0};
char str[count * 14];
strcpy (str, server.arg("sTime").c_str());
char* ptr = strtok(str, ",");
while (ptr != NULL) {
strcpy (switchTime[i++], ptr);
ptr = strtok(NULL, ",");
}
if (server.arg("sDay")) {
i = 0;
strcpy (str, server.arg("sDay").c_str());
char* ptr = strtok(str, ",");
while (ptr != NULL) {
wday[i++] = atoi(ptr);
ptr = strtok(NULL, ",");
}
printer();
}
}
String temp = "[";
for (auto& elem : switchTime) {
if (temp != "[") temp += ',';
temp += (String)"\"" + elem + "\"";
}
temp += ",\"";
for (auto& elem : switchActive) {
temp += elem;
}
for (auto& elem : wday) {
temp += "\",\"";
temp += elem;
}
temp += "\"]";
server.send(200, "application/json", temp);
});
server.on("/timer", HTTP_GET, []() {
if (server.arg(0) == "tog") relState = !relState; // Relais1 Status manuell ändern
if (server.arg(0) == "fix") { // alle Schalzeiten deaktivieren/aktivieren
fixed = !fixed;
DEBUG_F("Zeitschaltuhr Automatik %saktiviert\n", fixed ? "de" : "");
EEPROM.put(1, fixed);
EEPROM.commit();
}
server.send(200, "application/json", (String)"[\"" + (relState == aktiv) + "\",\"" + localTime() + "\",\"" + fixed + "\"]");
});
}
void printer() {
File file = SPIFFS.open("/swtime.dat", "w");
if (file) {
file.write(switchActive, sizeof(switchActive));
file.write(wday, sizeof(wday));
for (auto& elem : switchTime) file.printf("\n%s", elem);
file.close();
}
}
void timerSwitch() {
static uint8_t lastmin {60}, lastState {aktiv};
localTime(); // Funktionsaufruf Uhrzeit aktualisieren
if (tm.tm_min != lastmin && !fixed) {
lastmin = tm.tm_min;
char buf[6];
sprintf(buf, "%.2d:%.2d", tm.tm_hour, tm.tm_min);
for (auto i = 0; i < count * 2; i++) {
if (switchActive[i / 2] && !strcmp(switchTime[i], buf)) {
if (wday[i / 2] & (1 << (tm.tm_wday ? tm.tm_wday - 1 : 6))) relState = i & 1 ? !aktiv : aktiv; // Relais Status nach Zeit ändern
}
}
}
if (relState != lastState) { // Relais schalten wenn sich der Status geändert hat
lastState = relState;
digitalWrite(relPin, relState);
DEBUG_F("Relais a%s\n", digitalRead(relPin) == aktiv ? "n" : "us");
}
}
Die Dateien
- zeitschaltuhr.html
- zeitdatum32.html
- admin.html
- style32.css
- spiffs.html
im SPIFFS von der Seite von Fips.
Hey, sorry dass ich jetzt erst antworte. Die Treppenbeleuchtung ist erstmal in den Hintergrund gerückt, auch weil meine eigenen Versuche ins Leere geführt haben.
Evtl. schaffe ich es im Urlaub, das Projekt wieder anzugehen. Ich gebe Feedback, sobald ich das getestet habe. Danke für die Mühe!
Beste Grüße
Walli
Ist denn die Treppe fertig?
Sommer- oder Weihnachtsurlaub in welchem Jahr?