Braucht man für jeden Shelly eigentlich einen eigenen Arduino oder ESP32?
Diese Frage kam unter meinem YouTube-Video zur lokalen Shelly-Steuerung mit Arduino UNO und ESP32-Alternative auf. Und die kurze Antwort lautet: Nein.
Ein einzelner Arduino UNO kann mehrere Shelly-Geräte im lokalen Netzwerk steuern. Dafür muss nicht vor jedem Shelly ein eigener Mikrocontroller sitzen. Entscheidend ist nur, dass jeder Shelly eine eigene IP-Adresse besitzt und der Arduino diese Geräte im Netzwerk erreichen kann.
Genau darum geht es in diesem Beitrag.
Im ersten Projekt habe ich gezeigt, wie ein Arduino UNO mit Ethernet Shield einen Shelly Plug S über eine kleine Webseite lokal schalten kann „Shelly Plug S Gen3 mit Arduino UNO und Ethernet Shield steuern“. In diesem Beitrag erweitern wir die Idee: Statt nur einen Shelly anzusteuern, wird der Arduino-Webserver um weitere Schaltflächen ergänzt.
Hinweis: Im ersten Projekt kam ein Arduino UNO R3 mit Ethernet Shield zum Einsatz. Für die erweiterte Variante in diesem Beitrag verwende ich den Arduino UNO R4 WiFi aus dem Arduino Plug and Make Kit. Das Grundprinzip bleibt gleich: Der Arduino sendet lokale HTTP-Requests an die Shelly-Geräte.
So wird aus dem Arduino UNO eine kleine lokale Steuerzentrale für mehrere Shelly-Geräte – direkt im Heimnetz und ohne Cloud.
Die Grundidee: Ein Arduino-Webserver für mehrere Shellys
Die Erweiterung von einem auf mehrere Shellys ist einfacher, als es zunächst klingt. Der Arduino UNO muss nicht „wissen“, welches Gerät ein Shelly Plug S, ein Shelly 1 Mini oder ein anderer Shelly Aktor ist. Für den Arduino zählt im ersten Schritt nur eines: Welche IP-Adresse soll aufgerufen werden und welcher Schaltbefehl soll gesendet werden?
Im ersten Projekt gab es nur einen Shelly und damit auch nur eine Zieladresse. Klickt man auf der Webseite des Arduino-Webservers auf einen Button, sendet der Arduino einen HTTP-Request an genau diesen Shelly.
Bei mehreren Shellys wird dieses Prinzip einfach erweitert.
Jeder Shelly bekommt eine eigene IP-Adresse im lokalen Netzwerk. Die Webseite auf dem Arduino bekommt mehrere Schaltflächen. Jede Schaltfläche steht dann für ein bestimmtes Gerät oder eine bestimmte Aktion.
Ein einfaches Beispiel:
| Button auf der Webseite | Zielgerät | Aktion |
|---|---|---|
| Shelly 1 EIN | Shelly 1 | einschalten |
| Shelly 1 AUS | Shelly 1 | ausschalten |
| Shelly 2 EIN | Shelly 2 | einschalten |
| Shelly 2 AUS | Shelly 2 | ausschalten |
| Shelly 3 EIN | Shelly 3 | einschalten |
| Shelly 3 AUS | Shelly 3 | ausschalten |
Der Browser ruft dabei weiterhin nur den Arduino-Webserver auf. Der eigentliche Schaltbefehl wird wieder vom Arduino gesendet.
Das ist der entscheidende Punkt: Der Arduino wird dadurch nicht nur zu einer kleinen Webseite mit ein paar Links, sondern zu einer einfachen lokalen Steuerzentrale. Er nimmt den Klick entgegen, entscheidet anhand des aufgerufenen Pfads, welcher Shelly gemeint ist, und sendet dann den passenden HTTP-Befehl an die richtige IP-Adresse.
Der Ablauf bleibt also überschaubar:

Damit lässt sich der Aufbau sehr einfach erweitern. Für einen weiteren Shelly wird keine zusätzliche Mikrocontroller-Hardware benötigt. Es werden lediglich eine weitere IP-Adresse im Sketch und weitere Buttons auf der Webseite ergänzt.
Sicherheitshinweis zu Shelly-Geräten an 230 V
Bevor wir uns den Beispielaufbau anschauen, ein wichtiger Hinweis: Nicht jedes Shelly-Gerät ist gleich zu behandeln.
Ein Shelly Plug S wird einfach wie ein Zwischenstecker in die Steckdose gesteckt. Für Testaufbauten ist das vergleichsweise einfach und sicher zu handhaben.
Anders sieht es bei Shelly-Modulen wie dem Shelly 1PM, Shelly Plus 1PM oder anderen Einbaumodulen aus. Diese werden direkt an 230 V Netzspannung angeschlossen. Arbeiten daran dürfen nur von Personen durchgeführt werden, die dafür fachlich qualifiziert sind.

Bei Arbeiten an elektrischen Anlagen gelten die 5 Sicherheitsregeln:
- Freischalten
- Gegen Wiedereinschalten sichern
- Spannungsfreiheit feststellen
- Erden und kurzschließen
- Benachbarte, unter Spannung stehende Teile abdecken oder abschranken
Besonders wichtig: Die Spannungsfreiheit muss fachgerecht geprüft werden. Dafür ist ein zweipoliger Spannungsprüfer zu verwenden, zum Beispiel ein Duspol. Ein einpoliger Phasenprüfer, oft auch „Lügenstift“ genannt, ist dafür nicht geeignet.
Was wird benötigt?
Für den Aufbau verwende ich:
- Arduino UNO R4 WiFi*
- Ethernet Shield W5100*
- Arduino Plug and Make Kit*
- Rotary Encoder Modulino
- Button Modulino
- RGB LED / Pixel Modulino
- zwei Shelly Plug S*
- einen Shelly 1PM* mit Lampe
- lokales Netzwerk mit festen IP-Adressen
Hinweis von mir: Die mit einem Sternchen (*) markierten Links sind Affiliate-Links. Wenn du über diese Links einkaufst, erhalte ich eine kleine Provision, die dazu beiträgt, diesen Blog zu unterstützen. Der Preis für dich bleibt dabei unverändert. Vielen Dank für deine Unterstützung!

Hinweis: Wenn du das Arduino Plug and Make Kit verwendest, sind der Arduino UNO R4 WiFi sowie die benötigten Modulinos bereits im Set enthalten. Für diesen Aufbau musst du dann nicht jedes Modul einzeln kaufen. Zusätzlich wird nur noch ein passendes Ethernet Shield benötigt, wenn du die kabelgebundene LAN-Variante nachbauen möchtest.
Beispielaufbau mit drei Shelly-Geräten
Für meinen Testaufbau verwende ich insgesamt drei Shelly-Geräte, die alle über einen Arduino UNO im lokalen Netzwerk geschaltet werden:
- zwei Shelly Plug S
- einen Shelly 1PM mit angeschlossener Lampe
Die beiden Shelly Plug S eignen sich für so einen Test besonders gut, weil sie einfach in eine Steckdose gesteckt werden können. Zusätzlich besitzen sie eine LED-Beleuchtung, deren Farbe je nach Schaltzustand angepasst werden kann. In meinem Aufbau bedeutet:
| Schaltzustand | LED-Farbe am Shelly Plug S |
|---|---|
| Ausgang EIN | Grün |
| Ausgang AUS | Rot |
Dadurch lässt sich im Video und im Testaufbau sehr schnell erkennen, ob der jeweilige Ausgang gerade eingeschaltet oder ausgeschaltet ist.
Beim Shelly 1PM ist zusätzlich eine Lampe angeschlossen. Hier erkennt man den Zustand direkt an der Lampe: Leuchtet sie, ist der Schaltausgang des Shelly 1PM aktiv. Leuchtet sie nicht, ist der Schaltausgang deaktiviert.
Wichtig: Der Shelly selbst bleibt dabei natürlich weiterhin mit Spannung versorgt und im Netzwerk erreichbar. Es wird nur der Ausgang beziehungsweise das Relais geschaltet.
Die Einrichtung der Shelly-Geräte selbst ist nicht Bestandteil dieses Beitrags. Die Shellys müssen bereits im lokalen Netzwerk eingerichtet und erreichbar sein. Dazu gehört auch, dass jedes Gerät eine eigene feste IP-Adresse oder eine feste DHCP-Zuweisung im Router bekommt.
Wie man einen Shelly grundsätzlich einrichtet, habe ich bereits ausführlich in meinem YouTube-Video gezeigt:
In diesem Beitrag konzentrieren wir uns daher auf die Erweiterung des Arduino-Webservers: Aus einer Steuerung für einen einzelnen Shelly wird eine kleine lokale Oberfläche für mehrere Shelly-Geräte.
Erweiterung mit dem Arduino Plug and Make Kit
Bisher wird die Steuerung der Shelly-Geräte über eine Webseite gelöst. Das funktioniert gut, ist aber noch recht browserlastig: Man ruft die Arduino-Webseite auf und klickt dort auf die jeweiligen Buttons.
Für eine erweiterte Steuerung bietet sich das Arduino Plug and Make Kit an. Dieses enthält mit dem Arduino UNO R4 WiFi und den sogenannten Modulinos bereits viele Bausteine, die man für eine einfache lokale Steuerung nutzen kann.
Der große Vorteil: Die Modulinos werden über Qwiic / I2C verbunden. Dadurch muss nicht jedes Modul einzeln mit vielen Jumper-Kabeln verdrahtet werden. Für ein Lern- oder Demo-Projekt ist das sehr angenehm, weil der Fokus stärker auf der eigentlichen Logik liegt.

Für dieses Projekt sind besonders drei Modulinos interessant:
| Modulino | Aufgabe im Projekt |
|---|---|
| Rotary Encoder Modulino | Auswahl des Shelly-Geräts |
| Button Modulino | Eingabe zum Schalten der Shelly-Geräte |
| RGB LED Modulino | Anzeige des aktuellen Zustands oder einer Aktion |

Zusätzlich wird für diesen Aufbau noch ein Ethernet Shield mit W5100-Chip* benötigt. Dieses sorgt dafür, dass der Arduino per LAN-Kabel mit dem lokalen Netzwerk verbunden wird. Solche W5100 Ethernet Shields sind häufig recht günstig erhältlich, zum Beispiel bei eBay bereits ab etwa 10 Euro zuzüglich Versandkosten.
Damit kombiniert der Aufbau zwei Dinge: Die einfache Bedienung über Modulinos und die stabile Netzwerkverbindung über Ethernet.
Das Plug and Make Kit ersetzt dabei nicht die Shelly-Geräte selbst. Die Shellys bleiben weiterhin im lokalen Netzwerk erreichbar und werden per HTTP-Request geschaltet. Das Kit erweitert nur die Art der Bedienung und Anzeige.
Das ist vielleicht keine klassische Smart-Home-Endlösung für den Alltag, aber als Lernprojekt ist es sehr spannend. Man sieht direkt, wie Eingabe, Netzwerkkommunikation und Ausgabe zusammenspielen. Genau dadurch eignet sich der Aufbau sehr gut, um lokale Smart-Home-Steuerung ohne Cloud verständlich zu erklären.
Bedienkonzept: Shelly auswählen, schalten und Status anzeigen
Damit die Steuerung mehrerer Shellys nicht unübersichtlich wird, soll nicht für jeden Shelly ein eigener Button auf dem Arduino-Aufbau verwendet werden. Stattdessen wird der gewünschte Shelly über den Rotary Encoder ausgewählt.
Das Bedienprinzip ist damit recht einfach:
- Mit dem Rotary Encoder wird der gewünschte Shelly ausgewählt.
- Das Button Modulino schaltet den ausgewählten Shelly.
- Das RGB LED Modulino zeigt eine Aktion oder den Zustand an.
Dadurch entsteht ein kleines lokales Bedienpanel. Der Arduino UNO R4 WiFi übernimmt dabei weiterhin die zentrale Logik. Er merkt sich, welcher Shelly aktuell ausgewählt ist, und sendet beim Tastendruck den passenden HTTP-Request an genau dieses Gerät.
Ein möglicher Ablauf sieht so aus:
Den Sketch für mehrere Shellys erweitern
Im ersten Projekt war im Sketch nur ein einzelner Shelly hinterlegt. Für einen einfachen Test reicht das völlig aus. Sobald aber mehrere Shelly-Geräte gesteuert werden sollen, muss der Sketch etwas flexibler aufgebaut werden.
Der wichtigste Unterschied ist: Die Shelly-IP-Adresse wird nicht mehr als einzelne feste Variable verwendet. Stattdessen wird eine kleine Liste mit mehreren Shelly-Geräten angelegt.
Im Sketch sieht das so aus:
struct ShellyGeraet {
const char* name;
IPAddress ip;
};
ShellyGeraet shellys[] = {
{ "Shelly 1", IPAddress(192, 168, 178, 199) },
{ "Shelly 2", IPAddress(192, 168, 178, 106) },
{ "Shelly 3", IPAddress(192, 168, 178, 163) },
};
Jeder Eintrag besteht aus einem Namen und der IP-Adresse des jeweiligen Shelly-Geräts. Genau hier müssen später die eigenen Geräte eingetragen werden.
Der Vorteil dieser Lösung: Der restliche Code muss nicht für jeden Shelly komplett neu geschrieben werden. Der Arduino kann einfach über den Index auf das gewünschte Gerät zugreifen.
Die Anzahl der Shellys wird automatisch aus der Liste berechnet:
const uint8_t ANZAHL_SHELLYS = sizeof(shellys) / sizeof(shellys[0]);
Dadurch weiß der Sketch, wie viele Shelly-Geräte konfiguriert wurden.
In diesem Aufbau ist die Anzahl zusätzlich auf maximal acht Shellys begrenzt. Der Grund dafür ist das Pixel-Modulino, denn dieses besitzt acht LEDs. Für jeden Shelly kann dadurch eine eigene LED-Position verwendet werden.
static_assert(sizeof(shellys) / sizeof(shellys[0]) <= 8,
"Maximal 8 Shellys moeglich (8 LEDs am Pixel-Modulino)");
Damit ist der Sketch schon deutlich flexibler als die erste Version. Möchte man später einen weiteren Shelly ergänzen, muss lediglich ein weiterer Eintrag in der Liste hinzugefügt werden.
Zum Beispiel:
{ "Shelly 4", IPAddress(192, 168, 178, 164) },
Wichtig ist auch hier wieder: Die IP-Adressen müssen zum eigenen Netzwerk passen. Außerdem sollten die Shelly-Geräte feste IP-Adressen oder feste DHCP-Zuweisungen im Router bekommen, damit die Zuordnung dauerhaft funktioniert.
Webseite für mehrere Shellys
Neben der internen Liste wird auch die Webseite erweitert. Im ersten Projekt gab es nur Buttons für einen einzelnen Shelly. In dieser Version erzeugt der Arduino für jeden konfigurierten Shelly automatisch einen eigenen Block mit drei Schaltflächen:
- EIN
- AUS
- 3s
Im Browser entstehen dadurch mehrere Shelly-Blöcke auf einer Seite. Jeder Block gehört zu einem bestimmten Shelly aus der Liste.
Die Aufrufe folgen dabei diesem Muster:
/shelly/<index>/an /shelly/<index>/aus /shelly/<index>/an3
Der Platzhalter <index> steht für die Position des Shellys in der Liste. Der erste Shelly hat den Index 0, der zweite Shelly den Index 1, der dritte Shelly den Index 2.
Ein Beispiel:
/shelly/0/an
schaltet den ersten Shelly ein.
/shelly/1/aus
schaltet den zweiten Shelly aus.
/shelly/2/an3
schaltet den dritten Shelly für drei Sekunden ein.
Der Arduino wertet diese Route im Sketch aus und entscheidet darüber, welches Gerät geschaltet werden soll. Gleichzeitig wird der per Webseite ausgewählte Shelly auch als aktiver Shelly übernommen. Dadurch passen Weboberfläche, Rotary Encoder und LED-Anzeige zusammen.
Rotary Encoder und Button Modulino
Die Weboberfläche ist aber nur eine Möglichkeit zur Bedienung. Zusätzlich kann der gewünschte Shelly über den Rotary Encoder Modulino ausgewählt werden.
Der Sketch merkt sich die aktuelle Auswahl in dieser Variable:
uint8_t aktiverShellyIndex = 0;
Dreht man am Rotary Encoder, wird dieser Index verändert. Dadurch wechselt die Auswahl zwischen den konfigurierten Shelly-Geräten.
Das Pixel-Modulino zeigt die aktuelle Auswahl an. Die LED an der passenden Position leuchtet gelb:
leds.set(aktiverShellyIndex, YELLOW, ledHelligkeit);
Das Button Modulino löst anschließend die eigentliche Aktion aus:
| Taste | Index | Aktion |
|---|---|---|
| Taste A | 0 | ausgewählten Shelly einschalten |
| Taste B | 1 | ausgewählten Shelly ausschalten |
| Taste C | 2 | ausgewählten Shelly für 3 Sekunden einschalten |
Damit kann der Shelly also nicht nur über die Webseite, sondern auch direkt über die angeschlossenen Modulinos gesteuert werden.
Das Bedienprinzip lautet damit:
- Rotary Encoder drehen → Shelly auswählen
- Button drücken → Aktion ausführen
- Pixel-Modulino → aktuelle Auswahl anzeigen
Eine zentrale Schaltfunktion
Besonders wichtig ist, dass am Ende alle Wege dieselbe Schaltlogik verwenden. Es ist also egal, ob der Befehl über die Webseite oder über das Button Modulino ausgelöst wird.
Die eigentliche Schaltfunktion bekommt die Ziel-IP-Adresse als Parameter:
void schalteShelly(IPAddress ip, bool einschalten, unsigned int timerSekunden)
Dadurch kann dieselbe Funktion jeden Shelly aus der Liste schalten. Der HTTP-Aufruf bleibt dabei derselbe wie im ersten Projekt:
/relay/0?turn=on /relay/0?turn=off /relay/0?turn=on&timer=3
Der Unterschied ist nur, dass die Ziel-IP-Adresse nun flexibel aus der Shelly-Liste kommt.
Genau dadurch wird aus dem einfachen Sketch für einen Shelly eine erweiterbare lokale Steuerung für mehrere Shelly-Geräte.
Quellcode – mehrere Shellys via Arduino lokal steuern
/*
arduinoR4WifiShelly.ino
Arduino UNO R4 WiFi + Ethernet Shield (W5100) + Modulino Buttons +
Modulino Knob (Rotary Encoder) + Modulino Pixels (8x RGB LED)
-----------------------------------------------------------------------------------------------
Portierung von "shellyWebserver.ino" (UNO R3) auf den UNO R4 WiFi.
Der Arduino stellt weiterhin eine Weboberflaeche per Ethernet bereit
(http://<arduinoIp>/). Dort gibt es fuer JEDEN konfigurierten Shelly einen
eigenen Block mit drei Buttons (EIN / AUS / EIN fuer 3s), erreichbar unter:
GET /shelly/<index>/an -> Shelly <index> EIN
GET /shelly/<index>/aus -> Shelly <index> AUS
GET /shelly/<index>/an3 -> Shelly <index> EIN fuer 3 Sekunden
<index> ist die Position in der Liste "shellys" weiter unten (0, 1, 2, ...).
Ein Klick auf einen dieser Buttons waehlt den betroffenen Shelly zugleich
als "aktiven" Shelly aus (siehe naechster Absatz).
Zusaetzlich koennen die drei physischen Taster des Button-Modulinos
(I2C/Qwiic) dieselben Aktionen fuer den aktuell ausgewaehlten Shelly
ausloesen:
Taste 0 (A) -> ausgewaehlter Shelly EIN
Taste 1 (B) -> ausgewaehlter Shelly AUS
Taste 2 (C) -> ausgewaehlter Shelly EIN fuer 3 Sekunden
Mit dem Modulino Knob (Rotary Encoder) wird ebenfalls ausgewaehlt, welcher
von bis zu 8 Shellys gerade der "aktive" ist (Liste "shellys" weiter
unten). Drehen wechselt die Auswahl (mit Wraparound), das Pixel-Modulino
zeigt den ausgewaehlten Shelly als GELBE LED an der zugehoerigen Position
an (LED-Index = Index des Eintrags in "shellys").
Web-Buttons, physische Taster und Encoder-Auswahl rufen letztlich alle
dieselbe zentrale Funktion schalteUndAktualisiere() auf, die den jeweiligen
Shelly per HTTP-Client-Anfrage schaltet (klassische Gen1-Relay-API):
http://<SHELLY_IP>/relay/0?turn=on
http://<SHELLY_IP>/relay/0?turn=on&timer=3
http://<SHELLY_IP>/relay/0?turn=off
Alle Modulino-Module (Buttons, Knob, Pixels) haengen ueber Qwiic-Kabel in
Reihe am selben I2C-Bus des Arduino und werden gemeinsam ueber
Modulino.begin() initialisiert.
Anpassen muss man nur den Block "Konfiguration" weiter unten - dort auch
die Liste "shellys" um weitere Geraete ergaenzen (max. 8 Stueck, da das
Pixel-Modulino 8 LEDs hat).
*/
#include <SPI.h>
#include <Ethernet.h>
#include <Wire.h>
#include <Arduino_Modulino.h>
#include <stdio.h>
// ---------- Konfiguration ----------
// MAC-Adresse des Ethernet Shields (frei waehlbar, muss im Netz eindeutig sein)
byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };
// Feste IP-Adresse, unter der der Arduino-Webserver erreichbar sein soll
IPAddress arduinoIp(192, 168, 178, 50);
// Ein Eintrag pro steuerbarem Shelly. Die Reihenfolge legt fest, an welcher
// LED-Position (0..7) des Pixel-Modulinos der Eintrag ausgewaehlt angezeigt
// wird, wenn man ihn per Rotary Encoder anwaehlt.
struct ShellyGeraet {
const char* name;
IPAddress ip;
};
ShellyGeraet shellys[] = {
{ "Shelly 1", IPAddress(192, 168, 178, 199) },
{ "Shelly 2", IPAddress(192, 168, 178, 106) },
{ "Shelly 3", IPAddress(192, 168, 178, 163) },
};
const uint8_t ANZAHL_SHELLYS = sizeof(shellys) / sizeof(shellys[0]);
static_assert(sizeof(shellys) / sizeof(shellys[0]) <= 8,
"Maximal 8 Shellys moeglich (8 LEDs am Pixel-Modulino)");
const uint16_t shellyPort = 80;
// Helligkeit der Auswahl-LED (0-100)
const uint8_t ledHelligkeit = 25;
// ------------------------------------
EthernetServer server(80);
ModulinoButtons buttons;
ModulinoKnob knob;
ModulinoPixels leds;
// Puffer fuer die erste Zeile der eingehenden HTTP-Anfrage, z.B.
// "GET /heizung/an HTTP/1.1"
const size_t REQUEST_BUFFER_SIZE = 64;
char requestBuffer[REQUEST_BUFFER_SIZE];
// Index des per Rotary Encoder ausgewaehlten Shellys (Index in "shellys").
uint8_t aktiverShellyIndex = 0;
void setup() {
Serial.begin(9600);
Modulino.begin();
buttons.begin();
knob.begin();
leds.begin();
aktualisiereAuswahlLed();
Ethernet.begin(mac, arduinoIp);
delay(1000); // dem Shield etwas Zeit zur Initialisierung geben
if (Ethernet.hardwareStatus() == EthernetNoHardware) {
Serial.println(F("Kein Ethernet Shield gefunden."));
while (true) {
delay(1);
}
}
if (Ethernet.linkStatus() == LinkOFF) {
Serial.println(F("Achtung: Ethernet-Kabel scheint nicht verbunden zu sein."));
}
server.begin();
Serial.print(F("Webserver gestartet: http://"));
Serial.println(Ethernet.localIP());
}
void loop() {
behandleTaster();
behandleEncoder();
EthernetClient client = server.available();
if (client) {
liesAnfrageZeile(client, requestBuffer, REQUEST_BUFFER_SIZE);
if (behandleShellyRoute(requestBuffer)) {
sendeRedirect(client);
} else {
sendeStartseite(client);
}
delay(1);
client.stop();
}
}
// Fragt die drei Taster des Button-Modulinos ab und loest bei einem
// Tastendruck dieselbe Aktion aus wie der entsprechende Web-Button.
void behandleTaster() {
if (!buttons.update()) {
return;
}
if (buttons.isPressed(0)) {
Serial.println(F("Taste 0 (A) gedrueckt: Shelly EIN"));
schalteUndAktualisiere(true, 0);
} else if (buttons.isPressed(1)) {
Serial.println(F("Taste 1 (B) gedrueckt: Shelly AUS"));
schalteUndAktualisiere(false, 0);
} else if (buttons.isPressed(2)) {
Serial.println(F("Taste 2 (C) gedrueckt: Shelly EIN fuer 3 Sekunden"));
schalteUndAktualisiere(true, 3);
}
}
// Liest die Drehrichtung des Rotary-Encoder-Modulinos aus und wechselt
// damit den aktuell ausgewaehlten Shelly (mit Wraparound ueber die Liste).
void behandleEncoder() {
int8_t richtung = knob.getDirection();
if (richtung == 0) {
return;
}
aktiverShellyIndex = (aktiverShellyIndex + ANZAHL_SHELLYS + richtung) % ANZAHL_SHELLYS;
aktualisiereAuswahlLed();
Serial.print(F("Ausgewaehlter Shelly: "));
Serial.print(shellys[aktiverShellyIndex].name);
Serial.print(F(" ("));
Serial.print(shellys[aktiverShellyIndex].ip);
Serial.println(F(")"));
}
// Schaltet den aktuell ausgewaehlten Shelly (per Encoder oder zuletzt per
// Web-Button gewaehlt).
void schalteUndAktualisiere(bool einschalten, unsigned int timerSekunden) {
schalteShelly(shellys[aktiverShellyIndex].ip, einschalten, timerSekunden);
}
// Erkennt Web-Anfragen der Form "GET /shelly/<index>/<aktion> HTTP/1.1"
// (z.B. "/shelly/1/an3") und schaltet damit gezielt einen der konfigurierten
// Shellys. Waehlt den betroffenen Shelly dabei zugleich als "aktiv" aus, so
// dass Auswahl-LED und physische Taster ab sofort auf ihn wirken.
// Gibt true zurueck, wenn eine gueltige Schaltanfrage erkannt wurde.
bool behandleShellyRoute(const char *zeile) {
if (strncmp(zeile, "GET /shelly/", 12) != 0) {
return false;
}
int index = -1;
char aktion[8] = "";
if (sscanf(zeile + 12, "%d/%7s", &index, aktion) != 2) {
return false;
}
if (index < 0 || index >= ANZAHL_SHELLYS) {
return false;
}
aktiverShellyIndex = (uint8_t)index;
aktualisiereAuswahlLed();
if (strcmp(aktion, "an3") == 0) {
schalteUndAktualisiere(true, 3);
} else if (strcmp(aktion, "an") == 0) {
schalteUndAktualisiere(true, 0);
} else if (strcmp(aktion, "aus") == 0) {
schalteUndAktualisiere(false, 0);
} else {
return false;
}
return true;
}
// Zeigt auf dem Pixel-Modulino (8 LEDs), welcher Shelly aktuell ausgewaehlt
// ist: Die LED an der Position von aktiverShellyIndex leuchtet GELB, alle
// anderen LEDs sind aus.
void aktualisiereAuswahlLed() {
leds.clear();
leds.set(aktiverShellyIndex, YELLOW, ledHelligkeit);
leds.show();
}
// Liest die erste Zeile der HTTP-Anfrage (z.B. "GET /shelly/an HTTP/1.1")
// in buffer und verwirft anschliessend den Rest der Header, damit der
// Client sauber geschlossen werden kann.
void liesAnfrageZeile(EthernetClient &client, char *buffer, size_t bufferSize) {
size_t index = 0;
buffer[0] = '\0';
unsigned long start = millis();
while (client.connected() && millis() - start < 2000) {
if (client.available()) {
char c = client.read();
if (c == '\n') {
break;
}
if (c != '\r' && index < bufferSize - 1) {
buffer[index++] = c;
}
}
}
buffer[index] = '\0';
// restliche Header-Zeilen einfach verwerfen
start = millis();
while (client.connected() && millis() - start < 200) {
while (client.available()) {
client.read();
start = millis();
}
}
}
// Schickt als HTTP-Client eine Anfrage an den Shelly mit der angegebenen IP,
// um ihn ein- oder auszuschalten. timerSekunden > 0 schaltet den Shelly nach
// der angegebenen Zeit automatisch wieder aus (nur bei einschalten == true).
void schalteShelly(IPAddress ip, bool einschalten, unsigned int timerSekunden) {
EthernetClient shellyClient;
if (!shellyClient.connect(ip, shellyPort)) {
Serial.println(F("Verbindung zum Shelly fehlgeschlagen."));
return;
}
shellyClient.print(F("GET /relay/0?turn="));
shellyClient.print(einschalten ? F("on") : F("off"));
if (einschalten && timerSekunden > 0) {
shellyClient.print(F("&timer="));
shellyClient.print(timerSekunden);
}
shellyClient.println(F(" HTTP/1.1"));
shellyClient.print(F("Host: "));
shellyClient.println(ip);
shellyClient.println(F("Connection: close"));
shellyClient.println();
// Antwort des Shelly abwarten und verwerfen, bevor die Verbindung
// geschlossen wird
unsigned long start = millis();
while (shellyClient.connected() && millis() - start < 2000) {
while (shellyClient.available()) {
shellyClient.read();
start = millis();
}
}
shellyClient.stop();
Serial.print(F("Shelly geschaltet: "));
if (einschalten && timerSekunden > 0) {
Serial.print(F("AN (Timer "));
Serial.print(timerSekunden);
Serial.println(F("s)"));
} else {
Serial.println(einschalten ? F("AN") : F("AUS"));
}
}
// Leitet den Browser nach einer Aktion zurueck auf die Startseite,
// damit ein Neuladen der Seite die Aktion nicht wiederholt.
void sendeRedirect(EthernetClient &client) {
client.println(F("HTTP/1.1 303 See Other"));
client.println(F("Location: /"));
client.println(F("Connection: close"));
client.println();
}
// Liefert die Startseite mit den Schalt-Buttons aus.
void sendeStartseite(EthernetClient &client) {
client.println(F("HTTP/1.1 200 OK"));
client.println(F("Content-Type: text/html; charset=UTF-8"));
client.println(F("Connection: close"));
client.println();
client.println(F("<!DOCTYPE html><html lang=\"de\"><head>"));
client.println(F("<meta charset=\"UTF-8\">"));
client.println(F("<meta name=\"viewport\" content=\"width=device-width,initial-scale=1\">"));
client.println(F("<title>Shelly Control</title>"));
client.println(F("<style>"));
client.println(F("*{box-sizing:border-box;}"));
client.println(F("body{margin:0;min-height:100vh;display:flex;align-items:center;"));
client.println(F("justify-content:center;background:radial-gradient(circle at top,#1b2735,#090a0f);"));
client.println(F("font-family:'Segoe UI',Roboto,Consolas,monospace;color:#e6edf3;}"));
client.println(F(".card{background:#111820;border:1px solid #1f2937;border-radius:16px;"));
client.println(F("padding:36px 40px;box-shadow:0 0 40px rgba(61,220,132,.08),0 8px 24px rgba(0,0,0,.6);"));
client.println(F("max-width:460px;width:90%;text-align:center;}"));
client.println(F(".badge{display:inline-flex;align-items:center;gap:6px;background:#0d2818;"));
client.println(F("color:#3ddc84;border:1px solid #1f6b45;padding:4px 12px;border-radius:20px;"));
client.println(F("font-size:11px;letter-spacing:1px;text-transform:uppercase;margin-bottom:18px;}"));
client.println(F(".dot{width:8px;height:8px;border-radius:50%;background:#3ddc84;"));
client.println(F("box-shadow:0 0 8px #3ddc84;}"));
client.println(F("h1{font-size:22px;letter-spacing:2px;margin:0 0 4px;text-transform:uppercase;}"));
client.println(F("p.sub{color:#6b7684;font-size:13px;margin:0 0 28px;}"));
client.println(F(".devices{display:flex;flex-direction:column;gap:16px;}"));
client.println(F(".device{border:1px solid #1f2937;border-radius:12px;padding:14px 16px;"));
client.println(F("background:#0d131a;text-align:left;}"));
client.println(F(".device.aktiv{border-color:#d4b106;box-shadow:0 0 0 1px #d4b106 inset;}"));
client.println(F(".device-name{font-size:14px;font-weight:600;letter-spacing:.3px;margin-bottom:10px;}"));
client.println(F(".device-ip{color:#6b7684;font-weight:400;font-size:12px;}"));
client.println(F(".device-buttons{display:flex;gap:8px;}"));
client.println(F(".device-buttons a.btn{flex:1;display:flex;align-items:center;justify-content:center;"));
client.println(F("gap:6px;padding:10px 4px;border-radius:8px;text-decoration:none;font-size:13px;"));
client.println(F("font-weight:600;letter-spacing:.3px;color:#fff;border:1px solid rgba(255,255,255,.08);}"));
client.println(F(".an{background:linear-gradient(135deg,#1f9d55,#0c6b34);"));
client.println(F("box-shadow:0 4px 14px rgba(31,157,85,.35);}"));
client.println(F(".aus{background:linear-gradient(135deg,#d64545,#8f1f1f);"));
client.println(F("box-shadow:0 4px 14px rgba(214,69,69,.35);}"));
client.println(F(".timer{background:linear-gradient(135deg,#2f7fd6,#14417d);"));
client.println(F("box-shadow:0 4px 14px rgba(47,127,214,.35);}"));
client.println(F("</style></head><body>"));
client.println(F("<div class=\"card\">"));
client.println(F("<div class=\"badge\"><span class=\"dot\"></span>Gateway Online</div>"));
client.println(F("<h1>Shelly Control</h1>"));
client.println(F("<p class=\"sub\">Arduino UNO R4 WiFi · Ethernet Relay Gateway</p>"));
client.println(F("<div class=\"devices\">"));
for (uint8_t i = 0; i < ANZAHL_SHELLYS; i++) {
client.print(F("<div class=\"device"));
if (i == aktiverShellyIndex) {
client.print(F(" aktiv"));
}
client.println(F("\">"));
client.print(F("<div class=\"device-name\">"));
client.print(shellys[i].name);
client.print(F(" <span class=\"device-ip\">("));
client.print(shellys[i].ip);
client.println(F(")</span></div>"));
client.println(F("<div class=\"device-buttons\">"));
client.print(F("<a class=\"btn an\" href=\"/shelly/"));
client.print(i);
client.println(F("/an\">⚡ EIN</a>"));
client.print(F("<a class=\"btn aus\" href=\"/shelly/"));
client.print(i);
client.println(F("/aus\">⏹ AUS</a>"));
client.print(F("<a class=\"btn timer\" href=\"/shelly/"));
client.print(i);
client.println(F("/an3\">⏱ 3s</a>"));
client.println(F("</div>")); // device-buttons
client.println(F("</div>")); // device
}
client.println(F("</div>")); // devices
client.println(F("</div>")); // card
client.println(F("</body></html>"));
}
Häufige Fragen
Braucht jeder Shelly einen eigenen Arduino?
Nein. Ein Arduino kann mehrere Shelly-Geräte lokal steuern, solange jedes Gerät eine eigene IP-Adresse besitzt.
Funktioniert das ohne Shelly Cloud?
Ja. Die Steuerung erfolgt lokal im Netzwerk per HTTP-Request.
Warum braucht jeder Shelly eine feste IP-Adresse?
Der Arduino sendet den Schaltbefehl direkt an die eingetragene IP-Adresse. Ändert sich diese, erreicht der Befehl das Gerät nicht mehr.
Kann ich mehr als drei Shellys steuern?
Ja. Im gezeigten Aufbau ist die Anzahl durch das Pixel-Modulino sinnvoll auf acht begrenzt, weil dieses acht LEDs besitzt.
Wird der echte Zustand des Shelly abgefragt?
In diesem Sketch steht die Auswahl und das Schalten im Vordergrund. Eine echte Statusabfrage wäre eine sinnvolle Erweiterung.
Ist das eine fertige Smart-Home-Lösung?
Eher ein Lern- und Demonstrationsprojekt. Für produktive Installationen können Home Assistant, Node-RED oder feste Bedienpanels sinnvoller sein.
Fazit: Shelly lokal steuern – ganz ohne Cloud
Das Projekt zeigt sehr schön, dass Shelly-Geräte nicht zwingend über eine Cloud oder App gesteuert werden müssen. Wenn die Geräte im lokalen Netzwerk erreichbar sind, kann ein Arduino die Schaltbefehle direkt per HTTP-Request senden.
Damit bleibt die Steuerung im eigenen Netzwerk und ist unabhängiger von externen Diensten. Gerade für einfache Automationen, Testaufbauten oder Lernprojekte ist das ein großer Vorteil.
Aus dem Arduino wird so eine kleine lokale Steuerzentrale, die mehrere Shellys ansprechen kann – nachvollziehbar, erweiterbar und ohne Cloud-Zwang.
Ausblick: Rückmeldung vom Shelly auswerten
Bisher sendet der Arduino den Schaltbefehl einfach an den jeweiligen Shelly und geht davon aus, dass dieser Befehl auch angekommen ist. Für einen ersten Testaufbau reicht das aus, für eine saubere Steuerung fehlt damit aber noch eine wichtige Prüfung.
Denn aktuell wissen wir nur:
Der Arduino hat einen HTTP-Request gesendet.
Was wir noch nicht sicher wissen:
Hat der Shelly den Befehl wirklich angenommen?
Ist der Ausgang danach tatsächlich eingeschaltet oder ausgeschaltet?
War das Gerät überhaupt erreichbar?
Genau diese Rückmeldung ist der nächste sinnvolle Schritt. Im nächsten Beitrag soll der Arduino daher nicht nur Befehle senden, sondern auch die Antwort des Shelly auswerten beziehungsweise den aktuellen Zustand gezielt abfragen.
Damit wird aus dem einfachen „Befehl senden und hoffen“ eine deutlich zuverlässigere lokale Steuerung. Besonders spannend wird das in Kombination mit den Modulinos: Das RGB LED Modulino könnte dann nicht nur eine Aktion anzeigen, sondern den echten Zustand des Shelly visualisieren.
Zum Beispiel:
| Zustand | Anzeige |
|---|---|
| Shelly Ausgang EIN | Grün |
| Shelly Ausgang AUS | Rot |
| Shelly nicht erreichbar | Blau oder Gelb |
| Fehler beim Schalten | Blinksignal |
Damit wäre die lokale Shelly-Steuerung nicht nur bedienbar, sondern auch überprüfbar. Genau darum geht es dann im nächsten Schritt: Shelly-Status mit dem Arduino abfragen und auswerten.
Letzte Aktualisierung am: 19. Juli 2026




