SoC-Badge: Hausakku-Füllstand auf einen Blick – für unter 5 € selbst gebaut

Bastelecke > Arduino > SoC-Badge

Ein Tablet an die Wand zu nageln, ist eine universelle Lösung zur Smart-Home-Steuerung. Aber was passiert im nächsten Zimmer? Noch ein Tablet? Noch ein ausgedientes Handy? Irgendwann sieht’s aus wie auf der Brücke der Enterprise.

Dabei sind die einfachsten Lösungen doch oft die Besten. Selten braucht man alle Informationen auf einen Blick. Will man wissen wie warm es ist, schaut man aufs Thermometer, zum Licht schalten drückt man auf den Schalter… Doch wo schaue ich hin wenn ich wissen will wie voll mein Hausakku ist und ob ich noch genug Solarstrom für eine Waschmaschine gespeichert habe? – Dann rennt man wieder quer durch die Wohnung zum Tablet.

Es sei denn… man baut sich dieses kleine Helferchen nach, was man nach Herzenslust überall anpinnen kann, wo man den Ladezustand benötigt. Und das beliebig oft, denn in der Herstellung kostet es lediglich ein paar wenige Euro, vorausgesetzt man hat einen Lötkolben zur Hand.


Grundfunktion und Beschreibung

Der SoC-Badge basiert auf einem kleinen und preisgünstigen Wemos D1 mini Klon mit einem ESP8266 Chip. Dieser baut über W-LAN eine Verbindung zu einem MQTT-Broker auf und bekommt von da den Ladezustand in Prozent des Hausakkus übermittelt.

Die Art der Smarthome-Zentrale ist dabei relativ egal, solange MQTT zur Verfügung steht. Ich verwende es mit ioBroker, Node-RED oder HomeAssistant gehen aber auch.

Der Ladezustand wird dann mit 5 LEDs in 20%-Schritten signalisiert. Unter 80% erlischt die oberste Grüne, unter 60% auch die Zweitoberste, usw.. Unter 20% leuchtet nur noch die rote LED, welche dann unter 10% auch anfängt zu blinken.


Hardware: Teileliste und Aufbau

Lasst uns also mit dem Aufbau beginnen:

Stückliste

Was zum Nachbau für die Platine benötigt wird:

  • 1 x Wemos D1 mini auf ESP8266 Basis
    (oder vergleichbarer günstiger Nachbau, z. B. AZ-Delivery D1 mini auf Amazon)
  • 3 x grüne LED 5mm
  • 1 x gelbe LED 5mm
  • 1 x rote LED 5mm
  • 5 x Widerstand 1/4 Watt, 220 Ohm (Mindestens 160 Ohm!!)
  • Streifenrasterplatine durchgehend, 2,54mm Raster
  • Stiftleiste 4-Pin + 8-Pin (Meist schon beim D1 mini dabei)

Werkzeug:

  • Lötkolben und Lötzinn
  • Seitenschneider
  • 3mm Bohrer zum Trennen der Leiterbahnen (mit Akkuschrauber)
  • 3D-Drucker (optional fürs Gehäuse)

Arbeitsaufwand:

Die Platine für meinen zweiten Badge hatte ich in nicht einmal 20 Minuten fertig. Das Flashen der fertigen Firmware hat keine 5 Minuten gedauert, inklusive Einbinden ins Netzwerk.

Am längsten braucht der 3D-Drucker fürs Gehäuse mit ca. 3 Stunden.

Schaltplan und Platinenlayout

Das Schema ist sehr simpel:

GND (-)
|
o--------(Grün)-------[220 Ohm]------->  Pin-D8
|
o--------(Grün)-------[220 Ohm]------->  Pin-D7
|
o--------(Grün)-------[220 Ohm]------->  Pin-D6
|
o--------(Gelb)-------[220 Ohm]------->  Pin-D5
|
o--------(Rot)---------[220 Ohm]------->  Pin-D1

So sieht das ganze dann auf der Platine aus:

Zu beachten ist, das am D1 mini nur die untere Stiftleiste mit allen 8 Pins eingesetzt ist. Die obere Leiste muss halbiert werden und wird nur auf den Pins D5 bis D8 eingelötet.

Zwischen D1 mini und Streifenrasterplatine sitzt noch eine Drahtbrücke, welche an den zwei Lötpunkten auf der Rückseite in der zweituntersten Reihe zu erkennen ist. Damit wird Minus (GND) auf die rückseitig ganz rechte Leiterbahn gezogen, wo die LEDs alle mit dem kürzeren Bein an der abgeflachten Seite verlötet sind.

Wichtig sind die Trennungen an der zweiten Leiterbahn rückseitig von rechts. Hier sind die LEDs mit ihren Widerständen verbunden. Dazwischen muss immer getrennt werden. Am besten mit einem 3mm Bohrer vorsichtig anbohren bis die Kupferschicht durch ist.

Ebenso müssen mittig unter dem D1 Mini die 4 Leiterbahnen getrennt werden, wo oben und unten ein Pin der Stiftleiste ist.

Damit später alles gut im Gehäuse sitzt, dürfen die Pins der Stiftleisten nicht gekürzt werden. Sie stehen später nach hinten etwas weiter raus und stützen sich somit an der Gehäuserückseite ab. Somit bleibt alles in Position.

Vor der ersten Inbetriebnahme empfehle ich dringend alles nochmal genau unter einer Lupe zu betrachten und ggf. sauber zu machen. Verbleibende Kupferspäne vom Bohren können später Kurzschlüsse verursachen und schlimmstenfalls den Controller zerstören.


Firmware: Einrichtung mit der Arduino IDE

Nichts läuft hier ohne Programmcode. Also schreiben wir nun unser Programm auf den Controller:

Arduino-IDE vorbereiten

Solltest du noch keine Unterstützung für den ESP8266 installiert haben, gehe hierfür folgendermaßen vor:

Gehe auf Datei Einstellungen.

Anschließend trage in die unterste Zeile (Zusätzliche Boardverwalter-URLs) folgendes ein:

http://arduino.esp8266.com/stable/package_esp8266com_index.json

Nun gehe auf Werkzeuge Boards Boardverwaltung und suche dort nach ESP8266.

Das dort angezeigte Paket muss installiert werden.

Außerdem werden noch folgende Bibliotheken benötigt: PubSubClient + WifiManager

In den Bibliotheksverwalter gelangt man durch Klicken auf das dritte (mittlere) Symbol in der Toolleiste an der linken Fensterseite. Dort einfach nach den Bibliotheken suchen und installieren.

Der Programmcodes

Wir erstellen einen neuen Sketch und wählen zunächst das richtige Board aus. Dieses geschieht wieder unter Werkzeuge Boards, wo nun auch unser ESP8266 auftauchen sollte. In dieser Kategorie nun den D1 mini auswählen.

Anschließend kannst du diesen Code einfach in deinen Sketch kopieren:

#include <ESP8266WiFi.h>
#include <WiFiManager.h>
#include <PubSubClient.h>
#include <EEPROM.h>

// EEPROM Reset Detection
#define RESET_COUNTER_ADDR 200
#define RESET_THRESHOLD 5
#define RESET_CLEAR_DELAY 10000  // 10 Sekunden nach Start wird Counter zurückgesetzt

unsigned long resetStartTime;

// LED-Pins
const int led_rot = D1;
const int led_gelb = D5;
const int led_gruen1 = D6;
const int led_gruen2 = D7;
const int led_gruen3 = D8;

WiFiClient espClient;
PubSubClient client(espClient);

// Konfiguration (EEPROM)
char mqtt_server[40] = "192.168.1.10";
char mqtt_user[20] = "user";
char mqtt_pass[20] = "";
char mqtt_topic[50] = "akku/soc";
int mqtt_port = 1883;
bool shouldSaveConfig = false;

// Zustände
int soc = 0;
bool socReceived = false;
bool mqttConnected = false;
unsigned long lastBlink = 0;
bool blinkState = false;
unsigned long lastSocUpdate = 0;
const unsigned long socTimeout = 15000;
int knightPos = 0;
int knightDir = 1;
unsigned long lastEffect = 0;
unsigned long lastResetSave = 0;
bool shouldStartWiFiManager = false;

void saveConfigCallback() {
  shouldSaveConfig = true;
}

void reconnect() {
  while (!client.connected()) {
    Serial.print("Versuche MQTT-Verbindung ... ");
    String clientId = "ESPDisplay-" + String(ESP.getChipId());
    if (client.connect(clientId.c_str(), mqtt_user, mqtt_pass)) {
      Serial.println("erfolgreich!");
      client.subscribe(mqtt_topic);
      mqttConnected = true;
    } else {
      Serial.print("fehlgeschlagen, rc=");
      Serial.print(client.state());
      Serial.println(" -> neuer Versuch in 5 Sekunden");
      Serial.print("Verbindung zu MQTT Broker ");
      Serial.print(mqtt_server);
      Serial.print(":");
      Serial.print(mqtt_port);
      Serial.println(" ...");
      mqttConnected = false;
      delay(5000);
    }
  }
}

void callback(char* topic, byte* payload, unsigned int length) {
  if (String(topic) != String(mqtt_topic)) return;
  String value;
  for (unsigned int i = 0; i < length; i++) value += (char)payload[i];
  soc = value.toInt();
  socReceived = true;
  lastSocUpdate = millis();
  updateLEDs();

  Serial.print("MQTT empfangen auf Topic: ");
  Serial.println(topic);
  Serial.print("Payload als String: ");
  Serial.println(value);
  Serial.print("SOC als Integer: ");
  Serial.println(soc);
}

void updateLEDs() {
  digitalWrite(led_gruen1, soc > 39 ? HIGH : LOW);
  digitalWrite(led_gruen2, soc > 59 ? HIGH : LOW);
  digitalWrite(led_gruen3, soc > 79 ? HIGH : LOW);
  digitalWrite(led_gelb,   soc > 19 ? HIGH : LOW);
}

void knightRiderEffect() {
  if (millis() - lastEffect < 100) return;
  digitalWrite(led_gruen1, LOW);
  digitalWrite(led_gruen2, LOW);
  digitalWrite(led_gruen3, LOW);
  digitalWrite(led_gelb, LOW);

  if (knightPos == 0) digitalWrite(led_gruen3, HIGH);
  else if (knightPos == 1) digitalWrite(led_gruen2, HIGH);
  else if (knightPos == 2) digitalWrite(led_gruen1, HIGH);
  else if (knightPos == 3) digitalWrite(led_gelb, HIGH);

  knightPos += knightDir;
  if (knightPos >= 3 || knightPos <= 0) knightDir *= -1;
  lastEffect = millis();
}

void blinkErrorPattern() {
  bool phase = (millis() / 500) % 2;
  digitalWrite(led_rot, phase ? HIGH : LOW);
  digitalWrite(led_gelb, phase ? HIGH : LOW);
  digitalWrite(led_gruen1, phase ? LOW : HIGH);
  digitalWrite(led_gruen2, phase ? LOW : HIGH);
  digitalWrite(led_gruen3, phase ? LOW : HIGH);
}

void checkMultipleReset() {
  byte counter = EEPROM.read(RESET_COUNTER_ADDR);
  Serial.print("Reset count read: ");
  Serial.println(counter);
  counter++;
  EEPROM.write(RESET_COUNTER_ADDR, counter);
  EEPROM.commit();
  Serial.print("Reset count increased to: ");
  Serial.println(counter);

  resetStartTime = millis();  // Zeit merken, um nach 10 Sekunden zurückzusetzen

  if (counter >= RESET_THRESHOLD) {
    Serial.println("Reset threshold erreicht, WLAN-Einstellungen löschen...");
    WiFiManager wm;
    wm.resetSettings();
    EEPROM.write(RESET_COUNTER_ADDR, 0);
    EEPROM.commit();
    ESP.restart();
  }
}

void resetCounterResetTask() {
  if (millis() - resetStartTime > RESET_CLEAR_DELAY) {
    //Serial.println("Resetcounter wird nach 10 Sekunden auf 0 zurückgesetzt");
    EEPROM.write(RESET_COUNTER_ADDR, 0);
    EEPROM.commit();
    resetStartTime = millis() + 99999999; // verhindern, dass es nochmal resetet
  }
}

void setup() {
  Serial.begin(115200);
  EEPROM.begin(512);

  checkMultipleReset();

  pinMode(led_rot, OUTPUT);
  pinMode(led_gelb, OUTPUT);
  pinMode(led_gruen1, OUTPUT);
  pinMode(led_gruen2, OUTPUT);
  pinMode(led_gruen3, OUTPUT);

  EEPROM.get(0, mqtt_server);
  EEPROM.get(40, mqtt_user);
  EEPROM.get(60, mqtt_pass);
  EEPROM.get(80, mqtt_topic);
  EEPROM.get(130, mqtt_port);

  WiFiManager wm;
  wm.setSaveConfigCallback(saveConfigCallback);

  char mqtt_port_str[6];
  snprintf(mqtt_port_str, sizeof(mqtt_port_str), "%d", mqtt_port);

  WiFiManagerParameter custom_mqtt_server("server", "MQTT Server", mqtt_server, 40);
  WiFiManagerParameter custom_mqtt_user("user", "MQTT User", mqtt_user, 20);
  WiFiManagerParameter custom_mqtt_pass("pass", "MQTT Passwort", mqtt_pass, 20);
  WiFiManagerParameter custom_mqtt_topic("topic", "MQTT Topic", mqtt_topic, 50);
  WiFiManagerParameter custom_mqtt_port("port", "MQTT Port", mqtt_port_str, 6);

  wm.addParameter(&custom_mqtt_server);
  wm.addParameter(&custom_mqtt_user);
  wm.addParameter(&custom_mqtt_pass);
  wm.addParameter(&custom_mqtt_topic);
  wm.addParameter(&custom_mqtt_port);

  wm.setConfigPortalTimeout(600);

  if (!wm.autoConnect("SoC-Light")) {
    Serial.println("Keine WLAN-Verbindung, Neustart...");
    ESP.restart();
  }

  strncpy(mqtt_server, custom_mqtt_server.getValue(), 40);
  strncpy(mqtt_user, custom_mqtt_user.getValue(), 20);
  strncpy(mqtt_pass, custom_mqtt_pass.getValue(), 20);
  strncpy(mqtt_topic, custom_mqtt_topic.getValue(), 50);
  mqtt_port = atoi(custom_mqtt_port.getValue());

  if (shouldSaveConfig) {
    EEPROM.put(0, mqtt_server);
    EEPROM.put(40, mqtt_user);
    EEPROM.put(60, mqtt_pass);
    EEPROM.put(80, mqtt_topic);
    EEPROM.put(130, mqtt_port);
    EEPROM.commit();
  }

  client.setServer(mqtt_server, mqtt_port);
  client.setCallback(callback);
}

void loop() {
  resetCounterResetTask();

  if (WiFi.status() != WL_CONNECTED) {
    blinkErrorPattern();
    return;
  }

  if (!client.connected()) {
    mqttConnected = false;
    reconnect();
  }

  client.loop();

  if (socReceived && (millis() - lastSocUpdate > socTimeout)) {
    socReceived = false;
  }

  if (!socReceived) {
    knightRiderEffect();
    return;
  }

  if (soc <= 9) {
    if (millis() - lastBlink >= 500) {
      lastBlink = millis();
      blinkState = !blinkState;
      digitalWrite(led_rot, blinkState ? HIGH : LOW);
    }
  } else {
    digitalWrite(led_rot, soc > 0 ? HIGH : LOW);
  }
}

Klickst du nun auf Hochladen, wird das Programm Kompiliert und über Micro-USB an den Controller gesendet.

Sollte keine Übertragung möglich sein, kann es unter Umständen daran liegen, dass das falsche Gerät ausgewählt ist. Oben in der Titelzeile ist ein Dropdown-Feld, wo der Anschluss ausgewählt werden muss. Taucht dort dein Controller nicht auf, obwohl er angeschlossen ist, liegt das meist am verwendeten Micro-USB Kabel. Viele im Umlauf befindliche Kabel sind nur noch zum Laden von irgendwelchen Geräten ausgelegt und können keine Daten mehr übertragen. Dann einfach anderes Kabel verwenden und den richtigen COM-Port auswählen.

Wenn alles passt, steht unten rechts, in der Statuszeile die aktive Verbindung. Anderenfalls würde dort (nicht verbunden) stehen. Dann kann mit dem Hochladen fortgefahren werden.


Bedienung und Inbetriebnahme

Solange noch kein W-LAN verbunden ist, startet unser SoC-Badge nun in den WifiManager. Das heißt, es wird ein Accesspoint mit dem Namen SoC-Light auf gemacht, auf welchen wir uns nun mit dem Smartphone, Tablet, PC, oder was auch immer verbinden können. Im Browser rufen wir nun die Konfigurationsseite unter 192.168.4.1 auf, wo wir unsere W-LAN Verbindungsdaten, sowie den MQTT-Server eingeben. Eine ähnliche Funktionsweise kennen wir ggf. schon von Shelly oder Tasmota, was nicht wirklich verwunderlich ist. Immerhin basieren die Geräte auf einem ESP Controller.

Das ausgewählte Topic muss ein Zahlenwert zwischen 0 und 100 sein, welcher regelmäßig, alle paar Sekunden, aktualisiert wird. Daher sollten wir uns im ioBroker selbst einen Wert hierfür in den Objekten anlegen, welchen wir per Script alle paar Sekunden aktualisieren. Das ich wichtig, da der Badge spätestens alle 15 Sekunden einen neuen Wert erwartet. Sollte dieser Wert ausbleiben, glaubt er das etwas mit der Verbindung nicht stimmt und wechselt in eine Art Wartemodus, welchen er durch ein Lauflicht signalisiert. So erkennt man auf den ersten Blick ob die Verbindung steht und der angezeigte Wert noch aktuell ist.

Solltest du nicht wissen wie man einen MQTT-Broker mit Vollzugriff einrichtet, so dass du später auch auf 0_userdata/0/ zugriff hast, kannst du das hier im Kapitel zur Verbindung zwischen Node-RED und ioBroker nachlesen:

Node-RED als eigenständige Instanz mit dem ioBroker verbinden


Debugging: Fehlersuche leicht gemacht

Du hast deinen SoC-Badge fertig programmiert und eingerichtet, er zeigt aber trotzdem keine Werte an, und du weißt nicht woran es liegt? Kein Problem.

Solange er noch über USB am PC hängt, kannst du den seriellen Monitor verwenden. Gehe hierfür in der Arduino IDE auf Werkzeuge Serieller Monitor und stelle diesen auf 115200 Baud ein. Jetzt kannst du die Statusmeldungen sehen, die der Controller ausgibt. Hier siehst du auch wo etwas hängt.

Hast du beim MQTT-Server etwas falsch eingegeben und möchtest die Werte korrigieren, musst du den Controller zurücksetzen um wieder in den Wifi Manager zu gelangen. Drücke hierfür 5 mal hintereinander, in Abständen von ca. 1 Sekunde, den Resetknopf am Controller. Alternativ kannst du auch 5 mal die Stromversorgung trennen. Er geht dann wieder in den Einrichtungsmodus, wo der Accesspoint aktiv ist. Einfach wieder per W-LAN verbinden und die Konfigurationsseite unter 192.168.4.1 aufrufen.

Das gleiche passiert auch, wenn er beim Start mit keinem W-LAN verbinden kann. Dann startet er auch automatisch in den Wifi-Manager, wo die Konfigurationsseite über den Accesspoint aktiv ist.


Gehäuse: 3D-Druck oder Eigenbau

Jetzt noch das passende Gehäuse drum herum, denn eine nackte Platine sieht auf Dauer einfach nicht schön aus.

Wer einen 3D-Drucker sein Eigen nennt, kann sich hier die druckfertigen STL-Files runterladen:

https://www.mykaefer.net/wp-content/uploads/2025/07/SOC-Light.zip

Alternativ gibt es die gesamte Projektdatei auf Onshape unter:

https://cad.onshape.com/documents/f97af31f3bafdd864f833a17/w/edbe428bb36828e1ff73bcb8/e/11ec26e1ace3ff42958dce21
(kostenloser Account erforderlich)

Das ganze Teil kann man sich dann mit doppelseitigem Klebeband dort hin kleben, wo man es benötigt. Oder einfach selbstklebende Magnete auf die Rückseite, dann kann man es frei auf allen ferromagnetischen Oberflächen anpinnen.

Die Stromversorgung geschieht durch das Micro-USB Kabel, welches wir zuvor schon zum Programmieren verwendet haben. Hier einfach eine beliebige USB Stromquelle verwenden.

Ohne einen zur Verfügung stehenden 3D-Drucker, kann man sich alternativ auch die Teile bei einem 3D-Druck-Dienst im Internet bestellen. Hier gibt es zahlreiche Anbieter. Oder mit etwas handwerklichem Geschick kann man sich sicher selbst auch von Hand ein passendes kleines Gehäuse basteln.

Oder alternativ, gerne per Email anfragen. (kleinvieh@googlemail.com)

Für eine Aufwandspauschale von 15 Euro inkl. Rüstkosten und Versand drucke und verschicke ich beide Gehäuseteile. 

Farbe nach Wahl und Verfügbarkeit. Vorlaufzeit 7 Tage nach Zahlungseingang auf Paypal (mit Käuferschutz). Mengenrabatt auf Anfrage.

Fazit

Mit dem SoC-Badge steht dir ein minimalistisches, aber extrem nützliches Werkzeug zur Verfügung, das den Ladezustand deines Hausakkus zuverlässig und auf einen Blick anzeigt – ganz ohne App, ohne Touchscreen und ohne unnötigen Schnickschnack.

Dank MQTT ist es völlig systemunabhängig einsetzbar und lässt sich beliebig oft nachbauen. Die Kosten sind lächerlich gering, der Aufbau simpel, und selbst der Einstieg in die Programmierung hält sich in Grenzen.

Wer ernsthaft mit Solarstrom arbeitet und seinen Energiehaushalt im Blick behalten will, kommt an so einem kleinen Helfer eigentlich nicht vorbei.

Wer das ganze in einem etwas professionellerem Design und einer erweiterten Funktionalität haben möchte, kann jetzt hier weiter lesen:

Schreibe einen Kommentar

Diese Website verwendet Akismet, um Spam zu reduzieren. Erfahre, wie deine Kommentardaten verarbeitet werden.

WordPress Appliance - Powered by TurnKey Linux