Eine “selbstgebaute” WiFi-Steckdose

Ein Bekannter hatte neulich das Problem dass sein (Windows) Computer sich manchmal komplett aufhängt. Die einzige Möglichkeit ihn wieder zum Leben zu erwecken ist ihn hart auszuschalten. Das passiert meistens über Nacht und ist besonders ärgerlich wenn er den Computer übers Wochenende per Remote nutzen will da er dann nicht zu erreichen ist. Die Lösung? Eine Steckdose welche über WLAN verbunden ist und den Strom zum Computer einfach kurz unterbricht. Hart aber effektiv!
(Damit der Computer danach wieder hochfährt kann man entweder im BIOS einstellen dass er bei Power Failure restartet oder aber man weckt ihn per Wake-On-LAN auf)

Sowas selber zu bauen ist natürlich einfach, aber -da 220 Volt- eventuell keine gute Idee. Eine fertige Steckdose die man leicht umbauen kann wäre einfacher und man könnte sich sicher sein die gesetzlichen Bestimmungen einzuhalten.

Kurze Recherche führte einen Artikel der ct 02/2018 zu Tage der genau das beschreibt: Eine Steckdose mit dem Namen “Sonoff S20” mit einem integrierten ESP8266 Wifi-Chip den man problemlos mit eigener Software versorgen kann. Der ESP8266 ist bekanntlich der Bastler-Chip für Wlan schlechthin, denn bei nur ein paar Euro Kosten hat man einen fertigen Mikrocontroller mit Wlan on Board.
Details zur Steckdose findet man im Wiki des Herstellers ITEAD. Dieser ist sich wohl durchaus der Tatsache bewusst dass seine Steckdosen oft mit eigener Software ausgestattet werden und scheint sich daran nicht zu stören. Hier gehts zum Wiki: “ITEAD S20 Smart Socket“.
Dort fndet sich auch ein Schaltplan, sowie CE, ROHS und FCC Zertifikat!

Der Hersteller des ESP8266 (Espressif Systems) hilft der Maker-Community auch und mittlerweile kann man den Chip mit der Arduino IDE nutzen. Oder aber man kann verschiedene andere Sachen auf ihm installieren, z.B. Javascript (Espruino) oder Python (Micropython).
(es gibt hier auch fertige Heimautomationssysteme, aber die waren mir dann doch zu viel!)
Da ich ein Fan von Python bin sollte es auf meiner Steckdose Python sein. Hier kam mir natürlich sehr gelegen dass man die Steckdosen relativ günstig (~ 15 €) sogar direkt in Deutschland bekommt. Mit CE und ROHS Zertifikat! Zu diesem Preis selber bauen? Unmöglich!

Kurzum, erstmal die Steckdose aufgemacht und einen Header an die schon vorbereiteten Pins dran gelötet. Wenn man die Steckdose oben hat so sind die Pins:
– GND
– TX
– RX
– VCC (3.3 V)

Hier merkt man schon die erste Schwierigkeit: Der ESP8266 möchte mit 3.3 Volt versorgt werden. Mein USB-to-seriell Kabel hat zwar 3.3V Signalpegel, gibt aber USB 5V raus. Hier musste also ein Labornetzteil herhalten.
(Theoretisch kann man sich das sparen wenn man die Steckdose einfach programmiert während sie eingesteckt ist. Galvanisch getrennt von den 220V erzeugt sie ihre eigenen 3.3V. Ratsam ist das aber natürlich nicht und es soll hier nicht empfohlen werden!)

Nachdem die Steckdose verbunden ist kann man mit folgenden Kommandos ein frisch runtergeladenes Micropython für den ESP8266 installieren:

apt-get install python3-pip
pip install esptool
esptool.py --port /dev/ttyUSB0 erase_flash
esptool.py --port /dev/ttyUSB0 write_flash -fs 1MB -fm dout 0x0 /home/xxx/esp8266-20171101-v1.9.3.bin

ACHTUNG: Der Parameter ‘-fm dout’ war bei mir nötig. Sonst klappte zwar das flashen von Micropython aber die serielle Verbindung zeigte nichts sinnvolles an weil der Programmcode nicht richtig geflasht war!

Zum bequemen übertragen von Dateien (Programme die micropython automatisch beim boot startet müssen ‘main.py’ heißen) installieren wir ‘rshell’:

# rshell installieren
pip3 install rshell
# rshell starten
rshell --buffer-size=30 -p /dev/ttyUSB0
# main.py übertragen
cp main.py /pyboard
# Die Kommandokonsole (REPL) von Micropython auf dem Chip aufrufen
repl
(Beenden mit Strg+X)

So, und das einzige was nun noch fehlt ist der -wirklich einfache- Programmcode meines Webservers. Leider gibt es in Micropython vom ESP8266 keinen Watchdog um sicherzugehen dass bei einem hängenden Programmcode die Steckdose resettet wird, deshalb ein großer Try-Block der quasi jede Exception fängt und einen Reset auslöst. Bis auf den Keyboard-Reset. Wenn man den abfängt kommt man nicht mehr auf die Micropython-REPL und auch rshell geht nicht mehr!
Wenn übrigens Strom an der Steckdose durchgegeben wird und der ESP8266 resettet bleibt das Relais trotzdem weiterhin ausgelöst und die Steckdose unterbricht den Strom zum Verbraucher nicht!

Die Steckdose hat das Relais zum Strom einschalten an Pin 12 angebunden (Active HIGH) und eine grüne LED an Pin 13 (Active LOW). Die blaue LED ist fest verdrahtet und leuchtet immer wenn der Strom eingeschalten ist.

Simepl mit dem Browser auf die IP-Adresse die man im Programmcode vergeben hat verbinden und der Rest ist selbsterklärend!

# Button is: PIN 0              -> INPUT, Active LOW
# Relais is: PIN 12             -> OUTPUT, Active High
# Green LED is: PIN 13          -> OUTPUT, Active LOW
# Blue LED is automaticly active when Relais ist active
import gc
import os
import time
import usocket
import ustruct
import network
import machine

# Reset-IRQ-Handler (called on btnpin)
def resetme(pin):
   print('Button reset!')
   machine.reset()

# Make sure the REPL is available via UART (for debugging purposes!)
# Already done in boot.py!
#uart = machine.UART(0, 115200)
#os.dupterm(uart)
try:
   print('Startup!')

   # Configuration
   Wifi_SSID = "xxx"
   Wifi_Pass = "yyy"
   myip = "192.168.123.456"
   mysubnet = "255.255.255.0"
   myrouter = "192.168.123.1"
   mydns = "192.168.123.1"

   # Set Pins, acivate Relais (value = 1 => active)
   btnpin = machine.Pin(0, machine.Pin.IN)
   relaispin = machine.Pin(12, machine.Pin.OUT, value = 1)     # Relais on
   greenledpin = machine.Pin(13, machine.Pin.OUT, value = 1)   # green LED off

   print('Try to connect to WiFi: ' + Wifi_SSID)

   # Prepare reset on btnpin
   btnpin.irq(trigger=machine.Pin.IRQ_FALLING, handler=resetme)

   # Connect to WiFi
   wlan = network.WLAN(network.STA_IF)

   wlan.active(True)
   wlan.ifconfig((myip, mysubnet, myrouter, mydns))
   wlan.connect(Wifi_SSID, Wifi_Pass)
   while not wlan.isconnected():
       pass

   print('WiFi connected:')
   print('IP-Address: ' + str(wlan.ifconfig()[0]))
   print('Subnet Mask: ' + str(wlan.ifconfig()[1]))
   print('Gateway: ' + str(wlan.ifconfig()[2]))
   print('DNS: ' + str(wlan.ifconfig()[3]))
   print()

   # Start Webserver
   s = usocket.socket()
   ai = usocket.getaddrinfo("0.0.0.0", 80)
   print("Bind address info:", ai)
   addr = ai[0][-1]

   s.setsockopt(usocket.SOL_SOCKET, usocket.SO_REUSEADDR, 1)
   s.bind(addr)
   s.listen(5)
   print("Webserver ready.")

   # Endless-Loop for Webserver hosting Website
   outputheader = 'HTTP/1.0 200 OK\r\n\r\n'
   while True:
       res = s.accept()
       client_sock = res[0]
       client_addr = res[1]
       client_port = client_addr[2:4]
       client_ip = client_addr[4:]
       print("Client IP:", str(client_addr))
       print("Client Port:", str(client_port))
       print()
       client_stream = client_sock
       print("Request:")
       req = client_stream.readline()
       print(req)
       output = '<html><head><title>Network-Outlet</title></head><body>'
       if req[:7] == b'GET /on':
           print('--> Switching Relais ON')
           relaispin.value(1)
           greenledpin.value(1)
           output += '<p>Switched Relais ON</p><hr>'
       elif req[:8] == b'GET /off':
           print('--> Switching Relais OFF')
           relaispin.value(0)
           greenledpin.value(0)
           output += '<p>Switched Relais OFF</p><hr>'
       elif req[:10] == b'GET /cycle':
           print('--> Switching Relais OFF')
           relaispin.value(0)
           greenledpin.value(0)
           output += '<p>Switched Relais OFF</p>'
           print('--> Switching Relais OFF')
           time.sleep(2)
           relaispin.value(1)
           greenledpin.value(1)
           output += '<p>Switched Relais ON</p><hr>'
       elif req[:11] == b'GET /status':
           relaisstatus = 'undefined'
           ledstatus = 'undefined'
           buttonstatus = 'undefined'
           # RELAIS
           if relaispin.value() == 0:
               relaisstatus = 'OFF'
           if relaispin.value() == 1:
               relaisstatus = 'ON'
           # LED
           if greenledpin.value() == 0:
               ledstatus = 'ON'
           if greenledpin.value() == 1:
               ledstatus = 'OFF'
           # BUTTON
           if btnpin.value() == 0:
               buttonstatus = 'PRESSED'
           if btnpin.value() == 1:
               buttonstatus = 'NOT PRESSED'
           print('-> relaispin: ' + str(relaispin.value()))
           print('-> greenledpin: ' + str(greenledpin.value()))
           print('-> btnpin: ' + str(btnpin.value()))
           print('--> Relais Status is: ' + relaisstatus)
           print('--> Green LED Status is: ' + ledstatus)
           print('--> Button Status is: ' + buttonstatus)
           print('--> Free Mem is: ' + str(gc.mem_free()))
           print('--> Uptime in ms: ' + str(time.ticks_ms()))
           output += '<p>Relais Status is: ' + relaisstatus + '</p>'
           output += '<p>Green LED Status is: ' + ledstatus + '</p>'
           output += '<p>Button Status is: ' + buttonstatus + '</p>'
           output += '<p>Free Mem is: ' + str(gc.mem_free()) + '</p>'
           output += '<p>Uptime in ms: ' + str(time.ticks_ms()) + '</p><hr>'
       output += '<p>Usage:</p><p>Switch on: <a href="/on">/on</a></p><p>Switch off: <a href="/off">/off</a></p><p>Power cycle: <a href="/cycle">/cycle</a></p><p>Status: <a href="/status">/status</a></p></body></html>'
       while True:
           h = client_stream.readline()
           if h == b"" or h == b"\r\n":
               break
           print(h)
       print('Answering: ')
       print('**************************************')
       print(outputheader + output)
       print('**************************************')
       client_stream.write(outputheader + output)
       client_stream.close()
       gc.collect()
       print('Free mem: ' + str(gc.mem_free()))
       print()
except KeyboardInterrupt:
   print('Keyboard Interrupt!')
except:
   print('Exception happend! Rebooting!')
   machine.reset()

PS: Der Uptime Zähler (in ms) sollte nach ca. 49 Tagen überlaufen und wieder bei 0 anfangen da es ein unsigned int32 ist. So meine Berechnungen jedenfalls, da die Steckdose noch nicht so lange läuft kann ichs noch nicht bestätigen 🙂

 

2 Antworten auf „Eine “selbstgebaute” WiFi-Steckdose“

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.