Moin Leute,
ich habe von der Schule aus einen Infinity Mirror gemacht. Diesen habe ich mit einiger Hilfe von @cooper realisieren können. Er ist kreisförmig mit einem ⌀9,3cm. Das ganze wurde mit einer ws2812b LED Streifen umgesetzt und einem ESP8266 welcher einen Webserver erzeugt über welchen man die LED Streifen steuern kann. Der Webserver funktioniert hierbei mit .html, .js und .css Dateien, welche sich alle im SPIFFS (Serial Peripheral Interface Flash File System) befinden. Bei folgendem Video könnt ihr euch einen Einblick ergattern.
Dort seht ihr zum Einen den Aufbau der Webseite, als auch am Ende das fertige Produkt und seine Funktionsweise.
Im folgenden werde ich den Code zeigen und zudem eine .zip File zur Verfügung stellen, falls sich das einer in einem Bearbeitungsprogramm anschauen möchte oder selbst etwas mit einem Webserver oder einer LED Streifen machen möchte und davon nicht so viel Ahnung hat. In der Zip Datei befindet sich auch eine Powerpoint Datei, welche einige Dinge näher erklärt.
Zuerst der Aufbau des Projekts mit dem Programm Fritzing

Elektronik:
¹Produktempfehlungen
Produkte zum Warenkorb hinzufügen
Baumaterialien:
¹Produktempfehlungen
Produkte zum Warenkorb hinzufügen
Danach der Arduino Code, sieht nach einer Menge aus. Ist es auch, aber an sich gar nicht so schwer zu verstehen.
#include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h>
#include <ESPAsyncWebServer.h>
#include <Adafruit_NeoPixel.h>
#include <ArduinoOTA.h>
#define LED_PIN 2
#define LED_COUNT 14
Adafruit_NeoPixel strip(LED_COUNT, LED_PIN, NEO_GRB + NEO_KHZ800);
bool gerade = true;
int id = 0;
int idsafe = -1;
int color1[3] = {0,0,0};
int color2[3] = {0,0,0};
int brightness;
int parameter = 0;
// ÄNDERN, FALLS MAN ES NACHMACHEN MÖCHTE!
const char * ssid = "YOUR_WLAN_SSID";
const char * password = "YOUR_WLAN_PASSWORD";
AsyncWebServer server(80);
void rainbow(int wait) {
for (long firstPixelHue = 0; firstPixelHue < 5 * 65536; firstPixelHue += 256) {
for (int i = 0; i < strip.numPixels(); i++) {
if (id != 2) break;
int pixelHue = firstPixelHue + (i * 65536L / strip.numPixels());
strip.setPixelColor(i, strip.gamma32(strip.ColorHSV(pixelHue)));
}
if (id != 2) break;
strip.show();
delay(wait);
}
}
void farbenUebergang() {
strip.clear();
for (int k = 1; k <= 2; k++) {
for (int i = 0; i <= 255; i++) {
if (id != 3) break;
switch (k) {
case 1:
strip.fill(strip.Color((int)(i * ((double)color1[0] / 255)), (int)(i * ((double)color1[1] / 255)), (int)(i * ((double)color1[2] / 255))), 0, LED_COUNT);
break;
case 2:
strip.fill(strip.Color((int)(i * ((double)color2[0] / 255)), (int)(i * ((double)color2[1] / 255)), (int)(i * ((double)color2[2] / 255))), 0, LED_COUNT);
break;
}
strip.show();
delay(3);
}
if (id != 3) break;
for (int i = 255; i >= 0; i--) {
if (id != 3) break;
switch (k) {
case 1:
strip.fill(strip.Color((int)(i * ((double)color1[0] / 255)), (int)(i * ((double)color1[1] / 255)), (int)(i * ((double)color1[2] / 255))), 0, LED_COUNT);
break;
case 2:
strip.fill(strip.Color((int)(i * ((double)color2[0] / 255)), (int)(i * ((double)color2[1] / 255)), (int)(i * ((double)color2[2] / 255))), 0, LED_COUNT);
break;
}
strip.show();
delay(3);
}
if (id != 3) break;
}
}
void farbenVerfolgung() {
strip.clear();
for (int k = 1; k <= 2; k++) {
for (int i = 0; i < 15; i++) {
if (id != 4) break;
switch (k) {
case 1:
strip.setPixelColor(i, strip.Color(color1[0], color1[1], color1[2]));
break;
case 2:
strip.setPixelColor(i, strip.Color(color2[0], color2[1], color2[2]));
break;
}
strip.show();
delay(100);
}
if (id != 4) break;
strip.clear();
}
}
void farbenVerfolgungZweiFarben(uint32_t color1, uint32_t color2) {
strip.clear();
for (int k = 0; k < 1; k++) {
for (int i = 0; i < LED_COUNT; i++) {
if (id != 5) break;
if (gerade == true) {
strip.setPixelColor(i, color1);
} else {
strip.setPixelColor(i, color2);
}
gerade = !gerade;
}
if (id != 5) break;
strip.show();
delay(200);
if (LED_COUNT % 2 == 0) {
gerade = !gerade;
}
}
}
void flashing() {
strip.clear();
for (int k = 0; k < 1; k++) {
for (int i = 0; i < LED_COUNT; i++) {
if (id != 6) break;
if (gerade == true) {
strip.setPixelColor(i, strip.Color(0, 0, 0));
} else {
strip.setPixelColor(i, strip.Color(255, 255, 255));
}
gerade = !gerade;
}
if (id != 6) break;
strip.show();
delay(50);
if (LED_COUNT % 2 == 0) {
gerade = !gerade;
}
}
}
void randomPixel() {
for (parameter = 0; parameter < LED_COUNT; parameter++) {
strip.setPixelColor(parameter, strip.Color(random(255), random(255), random(255)));
strip.show();
if (id != 8) break;
delay(100);
}
}
void setup() {
Serial.begin(115200);
strip.begin();
strip.show();
strip.setBrightness(5);
if (!SPIFFS.begin()) {
Serial.println("An Error has occurred while mounting SPIFFS");
return;
}
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.println("Connecting to WiFi..");
}
// zeigt IP Adresse vom EPS8266 im Seriellen Monitor an mit welcher man auf den Webserver gelangt
Serial.println(WiFi.localIP());
// Ermöglicht es updates Over The Air zu machen -> Controller muss nicht angeschlossen sein
ArduinoOTA.onStart([]() {
String type;
if (ArduinoOTA.getCommand() == U_FLASH)
type = "sketch";
else // U_SPIFFS
type = "filesystem";
// NOTE: if updating SPIFFS this would be the place to unmount SPIFFS using SPIFFS.end()
Serial.println("Start updating " + type);
});
ArduinoOTA.onEnd([]() {
Serial.println("\nEnd");
});
ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) {
Serial.printf("Progress: %u%%\r", (progress / (total / 100)));
});
ArduinoOTA.onError([](ota_error_t error) {
Serial.printf("Error[%u]: ", error);
if (error == OTA_AUTH_ERROR)
Serial.println("Auth Failed");
else if (error == OTA_BEGIN_ERROR)
Serial.println("Begin Failed");
else if (error == OTA_CONNECT_ERROR)
Serial.println("Connect Failed");
else if (error == OTA_RECEIVE_ERROR)
Serial.println("Receive Failed");
else if (error == OTA_END_ERROR)
Serial.println("End Failed");
});
ArduinoOTA.begin();
DefaultHeaders::Instance().addHeader("Access-Control-Allow-Origin", "*");
server.on("/set", HTTP_GET, [](AsyncWebServerRequest * request) {
int params = request -> params();
for (int i = 0; i < params; i++) {
AsyncWebParameter * param = request -> getParam(i);
if (param -> name() == "1red") {
color1[0] = param -> value().toInt();
}
if (param -> name() == "1green") {
color1[1] = param -> value().toInt();
}
if (param -> name() == "1blue") {
color1[2] = param -> value().toInt();
}
if (param -> name() == "2red") {
color2[0] = param -> value().toInt();
}
if (param -> name() == "2green") {
color2[1] = param -> value().toInt();
}
if (param -> name() == "2blue") {
color2[2] = param -> value().toInt();
}
if (param -> name() == "brightness") {
brightness = param -> value().toInt();
strip.setBrightness(brightness);
strip.show();
}
}
request -> send(SPIFFS, "/index.html", String());
});
// Route for root / web page
server.on("/", HTTP_GET, [](AsyncWebServerRequest * request) {
request -> send(SPIFFS, "/index.html", String());
});
server.on("/style.css", HTTP_GET, [](AsyncWebServerRequest * request) {
request -> send(SPIFFS, "/style.css", "text/css");
});
server.on("/script.js", HTTP_GET, [](AsyncWebServerRequest * request) {
request -> send(SPIFFS, "/script.js", "text/javascript");
});
server.on("/data.json", HTTP_GET, [](AsyncWebServerRequest * request) {
request -> send(SPIFFS, "/data.json", "text/plain");
});
server.on("/wled_logo.png", HTTP_GET, [](AsyncWebServerRequest * request) {
request -> send(SPIFFS, "/wled_logo.png", String());
});
server.on("/JetBrainsMono-Regular.woff", HTTP_GET, [](AsyncWebServerRequest * request) {
request -> send(SPIFFS, "/JetBrainsMono-Regular.woff", "text/plain");
});
server.on("/favicon.png", HTTP_GET, [](AsyncWebServerRequest * request) {
request -> send(SPIFFS, "/favicon.png", String());
});
server.onNotFound([](AsyncWebServerRequest * request) {
request->send(404);
});
server.on("/on", HTTP_GET, [](AsyncWebServerRequest * request) {
request -> send(SPIFFS, "/index.html", String());
id = idsafe;
});
server.on("/off", HTTP_GET, [](AsyncWebServerRequest * request) {
request -> send(SPIFFS, "/index.html", String());
if (idsafe == -1) {
idsafe = 1;
} else {
idsafe = id;
}
id = 0;
});
server.on("/solid", HTTP_GET, [](AsyncWebServerRequest * request) {
request -> send(SPIFFS, "/index.html", String());
id = 1;
});
server.on("/rainbow", HTTP_GET, [](AsyncWebServerRequest * request) {
request -> send(SPIFFS, "/index.html", String());
id = 2;
});
server.on("/farbenUebergang", HTTP_GET, [](AsyncWebServerRequest * request) {
request -> send(SPIFFS, "/index.html", String());
id = 3;
});
server.on("/farbenVerfolgung", HTTP_GET, [](AsyncWebServerRequest * request) {
request -> send(SPIFFS, "/index.html", String());
id = 4;
});
server.on("/farbenVerfolgungZweiFarben", HTTP_GET, [](AsyncWebServerRequest * request) {
request -> send(SPIFFS, "/index.html", String());
id = 5;
});
server.on("/flashing", HTTP_GET, [](AsyncWebServerRequest * request) {
request -> send(SPIFFS, "/index.html", String());
id = 6;
});
server.on("/randomFill", HTTP_GET, [](AsyncWebServerRequest * request) {
request -> send(SPIFFS, "/index.html", String());
id = 7;
});
server.on("/randomPixel", HTTP_GET, [](AsyncWebServerRequest * request) {
request -> send(SPIFFS, "/index.html", String());
strip.clear();
parameter = -1;
id = 8;
});
// Start Webserver
server.begin();
}
void loop() {
ArduinoOTA.handle();
switch (id) {
case 0:
strip.fill(strip.Color(0, 0, 0), 0, LED_COUNT);
strip.show();
delay(10);
break;
case 1:
strip.fill(strip.Color(color1[0], color1[1], color1[2]), 0, LED_COUNT);
strip.show();
break;
case 2:
rainbow(10);
break;
case 3:
farbenUebergang();
break;
case 4:
farbenVerfolgung();
break;
case 5:
farbenVerfolgungZweiFarben(strip.Color(color1[0], color1[1], color1[2]), strip.Color(color2[0], color2[1], color2[2]));
break;
case 6:
flashing();
break;
case 7:
strip.fill(strip.Color(random(255), random(255), random(255)), 0, LED_COUNT);
strip.show();
delay(400);
break;
case 8:
randomPixel();
break;
}
}
Hierbei nutze ich die Library von Adafruit um die LED Leiste zu steuern. Alle Methoden über dem void setup() sind dafür da um die LED Streifen zu steuern. Hierbei sind die Effekte, welche dann auf dem Streifen angezeigt, alle selbstgemacht bis auf den in der Raindbow Methode. Hierbei sollte man also nicht zu viel von mir erwarten, da es nur so paar basic Effekte sind.
In der setup Methode wird dann erst einmal der ganze spaß initialisiert was quasi copy paste aus dem Internet ist.
Mit server.on kann man bestimmen, was passieren soll, wenn man die IP Adresse im Browser aufruft mit der Endung, welche man in die Anführungszeichen setzt, aufruft. Mit der “/set” Methode werden hierbei die Variablen mit den neuen Werten aus der Javascript file überschrieben. Beispielsweise werden hierbei die rgb Werte der Farbe 1 mittels eines Colorpickers (iro Colorpicker) überschrieben oder die Helligkeit mittels Schiebereglers verändert.
Die folgende Zeile Code findet man öfters und stellt dar, welche html Datei aufgerufen werden soll, wenn man die unterschiedlichen Webseitenendungen aufruft.
request -> send(SPIFFS, "/index.html", String());
Beim Aufruf der Webseitenendungen, welche den gleichen Namen haben wie die Methoden der Effekte, wird eine Variable “id” überschrieben. Diese sagt dem ESP8266, welche Methode er in der void loop() Methode aufrufen soll. Dies funktioniert mittels einer Switch Case Abfrage.
Naja kommen wir mal zur weiteren wichtigen Datei undzwar die Javascript Datei.
var values = document.getElementById("values");
var hexInput = document.getElementById("hexInput");
var color1 = document.getElementById("color1");
var color2 = document.getElementById("color2");
var color1Wert = "#0099FF";
var color2Wert = "#" + invertHex(color1Wert);
function invertHex(hex) {
hex = hex.replace("#", "");
return (Number(`0x1${hex}`) ^ 0xffffff).toString(16).substr(1).toUpperCase();
}
// Initialisierung des Colorpickers
var colorPicker = new iro.ColorPicker(".colorPicker", {
width: 440,
color: color1Wert,
borderWidth: 1,
borderColor: "#fff",
});
// colorPicker
colorPicker.on(["color:init", "input:end"], function (color) {
hexInput.value = color.hexString;
farbVeraenderung(color);
});
hexInput.addEventListener("change", function () {
colorPicker.color.hexString = this.value;
var color = colorPicker.color;
farbVeraenderung(color);
});
function farbVeraenderung(color) {
values.innerHTML = [
"hex: " + color.hexString,
"rgb: " + color.rgbString,
"hsl: " + color.hslString,
].join("<br>");
if (color1.style.border === "7px solid white") {
color1.style.background = color.hexString;
$.get("/set?1red=" + color.red, function (data) {});
$.get("/set?1green=" + color.green, function (data) {});
$.get("/set?1blue=" + color.blue, function (data) {});
} else {
color2.style.background = color.hexString;
$.get("/set?2red=" + color.red, function (data) {});
$.get("/set?2green=" + color.green, function (data) {});
$.get("/set?2blue=" + color.blue, function (data) {});
}
}
// Wechselt die URL ohne Seite reloaden (URL Methode wird dennoch im Sketch aufgerufen)
function getURL(url) {
$.get("/" + url, function (data) {});
}
// Text unter Helligkeitsregler wird geupdated
function updateTextInput(val) {
document.getElementById("schiebereglerValue").value = val;
}
/ /Helligkeit verändert
function updateBrightness(val) {
$.get("/set?brightness=" + val, function (data) {});
}
function updateTextColorPicker() {
values.innerHTML = [
"hex: " + colorPicker.color.hexString,
"rgb: " + colorPicker.color.rgbString,
"hsl: " + colorPicker.color.hslString,
].join("<br>");
hexInput.value = colorPicker.color.hexString;
}
function swap1() {
if (color2.style.border === "7px solid white") {
color2.style.border = "2px solid white";
color1.style.border = "7px solid white";
colorPicker.color.rgbString = color1.style.background;
updateTextColorPicker();
}
}
function swap2() {
if (color1.style.border === "7px solid white") {
color1.style.border = "2px solid white";
color2.style.border = "7px solid white";
colorPicker.color.rgbString = color2.style.background;
updateTextColorPicker();
}
}
// Startup
$(document).ready(function () {
color1.style.border = "7px solid white";
color2.style.border = "2px solid white";
color1.style.background = color1Wert;
color2.style.background = color2Wert;
colorPicker.color.hexString = color2Wert;
var color = colorPicker.color;
$.get("/set?2red=" + color.red, function (data) {});
$.get("/set?2green=" + color.green, function (data) {});
$.get("/set?2blue=" + color.blue, function (data) {});
colorPicker.color.hexString = color1Wert;
color = colorPicker.color;
$.get("/set?1red=" + color.red, function (data) {});
$.get("/set?1green=" + color.green, function (data) {});
$.get("/set?1blue=" + color.blue, function (data) {});
var val = document.getElementById("schieberegler").value;
document.getElementById("schiebereglerValue").value = val;
$.get("/set?brightness=" + val, function (data) {});
$.get("/off", function (data) {});
console.log("ready");
});
Hier sieht man oben wieder Initialisierungen von beispielsweise der Anfangsfarben. Diese können beliebig gewählt werden. In meinem Beispiel kann ich nur die erste Farbe wählen, die zweite wird automatisch erstellt indem es den Gegenwert der ersten Farbe auswählt.
Bei der Methode farbVeraenderung(color) wird, wenn man entweder den Hexcode im Textfeld ändert oder den Colorpicker bewegt, der Text neben dem Colorpicker verändert, als auch die jeweilige Farbe an den ESP8266 weitergegeben. Dies habe ich mittels jQuery und dessen get Methode gemacht. Dies sorgt dafür, dass die Webseite quasi aufgerufen wird, aber sich die Seite bei dem Benutzer nicht reloaded. Hierbei werden die rgb Werte getrennt übergeben, da dies leichter zum Umsetzen war
Das Programm weiß, welche Farbe ausgewählt ist, das die ausgewählte Farbe eine weiße Umrandung auf der Webseite hat, welche beim Anklicken der anderen Farbe sich verkleinert und bei der angeklickten Farbe vergrößert.
Die weiteren Methoden benutzen auch die get Methode um die Helligkeit zu ändern oder aber um die unterschiedlichen Effekte aufzurufen.
Beim refreshen/erstmaligen Laden des Webservers werden die Standardwerte an den Mikrocontroller übertragen, sodass dieser schon einmal etwas hat, womit er arbeiten kann.
Hier der Link zu der .zip File https://www.dropbox.com/s/el15nvrszrh2lyg/CT_Projekt_Makesmart.zip?dl=0 (hoffe der klappt)
Ehm hier sieht man den Webserver vielleicht etwas besser.

Das wärs soweit mit meinem Projekt, falls jemand das Ganze genauer erklärt haben möchte schreibt mich gerne auf Discord oder so an. Ich bin auf dem Community Discord von cooper. Ich antworte auch recht schnell und bin oft online.
Wer bis hier hin gescrollt hat, dem wünsche ich noch einen schönen Tag/Abend.
¹Affiliate Link. Affiliate Links sind Referenzen des Autors. Bei Kauf wird eine Provision ausgeschüttet. Mehr Informationen.