Home automation - #2 WiFi is in da house!

Posted by Sander van Kasteel on April 21, 2015 · 2207 words, 16 mins read

Categories:

Hij is binnen gekomen! De ESP8266 WiFi module is een aantal dagen geleden afgeleverd door de vriendelijke meneer van de PostNL.

IMG_20150418_031941

De ESP8266 is een hartstikke leuke chipset. In de basis is het een 32 bits microprocessor die een aantal netwerk functionaliteit voor zijn rekening neemt. Deze chip heeft de mogelijkheid om als accesspoint, als client of als beide. Daarnaast kan je met 3 simpele commando's een TCP of UDP connectie opzetten, wat data verzenden en deze zelfde connectie weer afsluiten. Daarnaast heeft de chip ook nog eens de mogelijkheid om buiten UART ook nog eens kan communiceren over SPI. Het leukste van dit alles is dat de ESP8266 bijna niks kost. Ik heb voor de mijne $3.04 inclusief verzendkosten vanuit China betaald. Omgerekend is dat nog geen 3 euro.

Dus dan wordt het maar eens tijd om te kijken hoe de module precies werkt. Laat ik als eerst maar eens even de nodige vertaalde documentatie er bij pakken en de pinout er bij pakken. Aangezien de originele datasheet alleen in Chinees te krijgen is, ben ik echter aangewezen op slecht vertaalde documentatie :(

esp8266-pinout

Om de module aan te sluiten moet je 3.3 volt op de VCC aansluiten, GND moet uiteraard op aarde / negatief aangesloten worden. URXD is de UART receive lijn en UTXD is de UART transmit lijn. Deze mogen aangesloten kruislings aangesloten worden. Dus ESP8266 URXD moet aan de TX van UART interface en de UTXD moet aan de RX van de UART interface. Daarnaast moet je niet vergeten om CH_PD aan VCC aan te sluiten anders wil de ESP8266 niet opstarten. Dat heeft mij namelijk een goed uur uitzoekwerk gekost ;) (lees: RTFM!)

DSC_7840

Na dit leuke puzzeltje wordt het maar eens tijd om te gaan kijken of de ESP8266 ook echt werkt. Om te testen of dat zo was, heb ik de ESP8266 aangesloten op een simpele
USB naar TTL interface gebasseerd op een PL2303 chip. Puur als test heb ik de ESP8266 mijn prive WiFi netwerk (genaamd "Starfleet") laten joinen. Wat je niet moet vergeten is dat de ESP8266 niet(!!) compatible is met een breadboard layout (zie foto hieronder). DSC_7826

Zoals je kan zien in de screenshots hieronder, heeft het redelijk wat moeite gekost om de ESP8266 module online te krijgen. Maar eerst even een kleine tabel waarin de meest gebruikte commando's kunnen bekijken (bron: nurdspace) .AT-commands

Hieronder zie je 2 screenshots waarin ik de ESP8266 verbinding heb laten leggen met mijn WiFi netwerk. Je ziet herin wel wat "ERROR" meldingen terug komen, ik vermoed dat dit gekomen is door het gebruik van backspace of dat ik te snel commando's wou doorgeven aan de module. Screen Shot 2015-04-21 at 01.38.18

Screen Shot 2015-04-21 at 01.38.21

Maar uiteindelijk is het dan ook gelukt!

Screen Shot 2015-04-21 at 12.14.04

Nu duidelijk is dat de ESP8266 werkt, wordt het maar eens tijd om in een Arduino omgeving te gaan verwerken. Gezien de hoeveelheid werk die het kostte om via een USB naar TTL seriele interface de ESP8266 te laten werken, heb ik besloten op zoek te gaan naar een Arduino library. Het eisenpakket stelt allemaal niet zo heel erg veel voor. De library moest vooral klein, functioneel en duidelijk zijn. Na wat googlen en rondspeuren op Github kwam ik ESP8266wifi van ekstrand tegen op Github (link).

Maar voor dat ik aan het programmeren kan, zal ik als eerst de ESP8266 aan mijn Arduino moeten aansluiten.  Hiervoor heb ik het volgende schema bedacht. fritzing_bb

Ik heb de RX (receive UART lijn) via een 74HC4050 (datasheet) aangesloten op de PWM pin 3 van de Arduino omdat de ESP8266 geen 5 volt tolerante inputs heeft. De TX (transmit UART lijn) zit direct op PWM pin 2 van de Arduino aangesloten. PWM pin 12 gebruik ik op een LED aan te sturen.

DSC_7883

Toen dat klaar was begon ik te bedenken hoe ik de Arduino ging aansturen. Mijn eerste gedachten was om dit over HTTP te doen. De reden om het over HTTP te gaan doen, was heel simpel. Het is een bewezen protocol, ik kan heel simpel met iedere taal die ik zou willen HTTP requests kunnen afhandelen en ik dacht dat de Arduino wel krachtig genoeg zou zijn om dat soort verzoeken af te handelen. HTTP-request-1

Zoals je echter kan zien in het screenshot hierboven, ging dat hem niet worden. Wat ik ook probeerde, zoals minder vaak een status ophalen of de HTTP encoding op ANSI / ISO-8851 zetten. Het mocht allemaal niet baten, mijn data kwam corrupt aan bij mijn Arduino. Dan maar terug naar de tekentafel. Na wat denken, peinzen en nog meer hersen gekraak, kwam ik tot geen andere conclusie dan zelf een simpele TCP server te gaan implementeren en om de uitdaging nog leuker te maken, heb ik besloten dit te gaan doen in Python.

Op dit moment is de TCP server vrij simpel geïmplementeerd en op basis van het voorbeeld wat ik vond in de Python documentatie. Hij ondersteund op dit moment alleen maar 1 commando en dat is "get light" en dan de naam van lamp waar je de status van wilt opvragen. Op dat moment wordt er instantie aangemaakt van de klasse "Light" en in zijn constructor wordt er 1 parameter meegegeven namelijk "lightname". Op het moment dat de functie getStatus() wordt aangeroepen van de klasse Light, wordt er een gelijknamig bestand geopend en wordt deze uitgelezen. In mijn opzet is dat de "kitchen" light. Vervolgens wordt het resultaat via een TCP connectie terug gezonden naar de Arduino. Op basis van dat resultaat zal de Arduino de LED aan of uit schakelen.

En nu komt het moment waarop ik daden bij mijn woorden zet. De source van de TCP server:

home-automation.py

__author__ = 'Sander van Kasteel'
import SocketServer
import time
from Lights import Lights
class EchoRequestHandler(SocketServer.BaseRequestHandler):
    def handle(self):
        data = self.request.recv(1024).strip('\r\n') # Get the data from the client and immediatly strip off \r\n chars
        now = time.strftime("%c")
        print "[" + now + "] " + "request for '" + data + "' from client "  + self.client_address[0]
        if(len(data) > 1):
            lightName = Lights(data.rsplit('get light ')[1])
            self.request.send(lightName.getStatus())
        else :
            self.request.send("false")
if __name__ == '__main__':
    import socket
    import threading
    # address = ('localhost', 8000) # let the kernel give us a port
    address = ('', 7000) # let the kernel give us a port
    server = SocketServer.TCPServer(address, EchoRequestHandler)
    ip, port = server.server_address # find out what port we were given
    t = threading.Thread(target=server.serve_forever)
    t.setDaemon(True) # don't hang on exit
    t.start()
    server.serve_forever()

lights.py

__author__ = 'Sander van Kasteel'
import io
class Lights:
    light = ''
    def __init__(self, lightname):
        self.light = lightname
        # self.getStatus(lightName)
        # return self
    def getStatus(self):
        try:
            file = io.open(self.light, 'rt')
            fileContents = file.read().strip('\n')
            if(fileContents == "true"):
                file.close()
                return "true"
            else:
                file.close()
                return "false"
        except IOError as e:
            print "Oops! The file for this light doesn't exist"
            print e.filename
            return "false"

En dan de source van de Arduino client

#include <ESP8266wifi.h>
#include <SoftwareSerial.h>
#define RST_pin 7 // RESET pin on the ESP8266
SoftwareSerial swSerial(2, 3);
int ledPin = 12; // pin to the LED
boolean newStatus;
boolean oldStatus = false; // default state is FALSE
String returnData;
ESP8266wifi wifi(swSerial, swSerial, RST_pin);
//ESP8266wifi wifi(swSerial, swSerial, RST_pin, Serial); // DEBUGGING!
void setup() {
  // put your setup code here, to run once
  pinMode(ledPin, OUTPUT); // setup ledPin as output pin
  swSerial.begin(9600);
  Serial.begin(9600);
  wifi.setTransportToTCP();
  wifi.endSendWithNewline(true);
  wifi.begin();
  wifi.connectToAP("Starfleet", "xxxxxx"); // WiFi name and password
}
void loop() {
  // put your main code here, to run repeatedly:
  if (!wifi.isStarted() || !wifi.isConnectedToAP())
  {
    Serial.println(F("Starting WiFi again..."));
    wifi.begin();
  }
  newStatus = getNewStatus();
  if (newStatus == true)
  {
    digitalWrite(ledPin, HIGH); // turn ON the LED
  } else {
    digitalWrite(ledPin, LOW); // turn OFF the LED
  }
}
boolean getNewStatus()
{
  boolean isConnected = wifi.connectToServer("10.13.37.172", "7000");
  if (wifi.isConnectedToServer())
  {
    wifi.send(SERVER, "get light kitchen");
    WifiMessage in = wifi.listenForIncomingMessage(1000);
    if (in.hasData)
    {
        returnData = in.message;
    }
    Serial.println("Return data is " + returnData);
    if(returnData == "true")
    {
      // If the returnData is TRUE
      oldStatus = true;
      return true;
    } else if (returnData == "false") {
      // If the returnData is FALSE
      oldStatus = false;
      return false;
    } else {
       // incase of data corruption or some other error, then return "oldStatus"
       return oldStatus;
    }
  } else {
    // if there is no connection to the server, then return oldStatus
    return oldStatus;
  }
}

En uiteraard hoort daar een ook een video bij hoe het er in het echt uit ziet.

Al met al is dit geen super nette implementatie, maar het voldoet voor het moment. 1 van de features die ik nog wil toevoegen is logging. Daarnaast moet er nog een complete webinterface bij komen en eigenlijk moet de huidige "bestanden" bron vervangen door een (No)SQL database. Zodat ik ook nog wat meta data kan gaan opslaan zoals de tijden waarop een lamp aan en uitgeschakeld wordt. En zoals je in de video kan zien zit er soms wat vertraging tussen de wijziging van de file en de werkelijke verandering van de LED.

Er is dus nog genoeg te doen ;)