Passaggio all'ora legale 26 marzo 2017 02:0003:00 sposta avanti l'orologio (si dorme un ora in meno)
Con questo progetto è possibile comandare 2 prese elettriche da remoto tramite cellulare collegato al WI-FI di casa oppure manualmente tramite 2 pulsanti presenti sulla presa stessa.

Questo è il risultato finale:
finitofinito
mentre questa è l'interfaccia web:
interfaccia webinterfaccia web
Per la realizzazione servono:
componenticomponenti
  • Nr. 1 contenitore da parete GEWIS 27005
  • Nr. 1 pulsante doppio GEWIS 20521
  • Nr. 2 prese 10/16A GEWIS 20203
  • Nr. 3 tasto copriforo GEWIS 20056
  • Nr. 1 ESP8266
  • Nr. 1 modulo 2 relè
  • Nr. 1 alimentatore USB compatto 800mA
  • Nr. 1 cavo micro USB
  • Nr. 1 cavo 3G1,5
  • Nr. 1 spina 10A
I collegamenti da realizzare sono i seguenti:
schema elettricoschema elettrico

Per far entrare l'alimentatore USB nella scatola ho dovuto tagliare i contatti dell'alimentatore e sadare i fili:
dettaglio alimentatoredettaglio alimentatore
Inolte il cavo USB va tagliato per derivare un positivo e un negativo che vanno alla scheda relè ed un altro negativo che va all'ESP8266.
dettaglio cavo USBdettaglio cavo USB
Isola bene tutti i collegamenti con del nastro isolante.
Verifica con attenzione le polarità con un tester per evitare di far danni.

Questo è il cablaggio interno:
cablaggio internocablaggio interno
Ho utilizzato un pezzo di plastica (una vecchia scheda punti del supermercato) per fissare la scheda relè. Gli altri componenti, una volta chiusa la scatola, non possono muoversi quindi non è necessario nessun fissaggio.
Fai solo attenzione quando richiudi il tutto a non schiacciare qualche cavo o piegare i piedini dell'ESP8266.
ATTENZIONE: La corrente è molto pericolosa e non si vede, quindi se non sai bene cosa stai facendo, EVITA di mettere mano ai circuiti elettrici.
Comunque quando lavori su un circuito elettrico assicurati che non sia sotto tensione, quindi togli la spina o abbassa l'interruttore generale sul contatore Enel.

In ogni caso non mi assumo nessuna responsabilità per eventuali inesattezze riportate, ogni modifica la fai a tuo rischio e pericolo.

Il codice per comandare i relè tramite WI-FI e generare l'interfaccia web è il seguente:
#include <ESP8266WiFi.h>
// prese comandate 

//----------------------------------------
// configurazione WI-FI
const char WiFiSSID[] = "ssid-xxx";
const char WiFiPSK[] = "password";
// IP statico
IPAddress ip(192, 168, 1, 50);
IPAddress gateway(192,168,1,254);
IPAddress subnet(255,255,255,0);

//----------------------------------------
// Definizione PIN I/O
const int RELE1_PIN = 16; // D0 - GPIO16
const int RELE2_PIN = 5;  // D1 - GPIO5
const int BTN1_PIN = 4;   // D2 - GPIO4
const int BTN2_PIN = 0;   // D3 - GPIO0

String printOnOff(int v){
  return String(v == HIGH ? "off" : "on");
}
//----------------------------------------
// gestione relè
int rele1 = HIGH;
int rele2 = HIGH;

void changeRele1(){
  rele1 = (rele1 == LOW) ? HIGH : LOW;
  digitalWrite(RELE1_PIN, rele1);
  Serial.println("Rele1: " + printOnOff(rele1));
}
void changeRele2(){
  rele2 = (rele2 == LOW) ? HIGH : LOW;
  digitalWrite(RELE2_PIN, rele2);
  Serial.println("Rele2: " + printOnOff(rele2));
}

//----------------------------------------
// gestione pulsanti
int button1prev = LOW;
int button2prev = LOW;

void readButtons(){
  int button1 = digitalRead(BTN1_PIN);
  int button2 = digitalRead(BTN2_PIN);
  bool wait = false;

  if (button1 != button1prev) {
    Serial.println("Button1: " + printOnOff(button1));
    if(button1 == LOW){
      changeRele1();
    }
    wait=true;
  }
  button1prev = button1;

  if (button2 != button2prev){
    Serial.println("Button2: " + printOnOff(button2));
    if(button2 == LOW) {
      changeRele2();
    }
    wait=true;
  }
  button2prev = button2;

  if(wait){
    delay(300); //per evitare rimbalzi
  }
}
//----------------------------------------
// inizializzazione server
WiFiServer server(80);

void initWiFi() {
  // tento di connetermi alla WI-FI
  Serial.println();
  Serial.println("Attempting to connect to: " + String(WiFiSSID) + " ...");
  //attendo la disponibilità del wifi
  while ( WiFi.status() != WL_CONNECTED) {
    Serial.print(".");
    delay(1000);
  }
  // Mi connetto alla WIFI ( WPA/WPA2 )
  WiFi.begin(WiFiSSID, WiFiPSK);
  // setto l'IP statico
  WiFi.config(ip,gateway,subnet);
  Serial.println();
  Serial.print("WiFi connected IP:");
  Serial.print(WiFi.localIP());
}

void initServer(){
  // avvio il server
  server.begin();
  // visualizzo l'indirizzo IP
  Serial.println();
  Serial.print("Use this URL to connect: http://");
  Serial.print(WiFi.localIP());
  Serial.print("/");
  Serial.println();
}

//----------------------------------------
// pagina web
void sendResponse(WiFiClient client){
  // invio la risposta
  String c1 = String(rele1 == HIGH ? "btn-default" : "btn-success");
  String c2 = String(rele2 == HIGH ? "btn-default" : "btn-success");
  
  client.flush();
  client.println("HTTP/1.1 200 OK");
  client.println("Content-Type: text/html");
  client.println("Connection: close");
  client.println(); // questo è indispensabile

  client.println("<!DOCTYPE HTML>");
  client.println("<html lang=\"it\">");
  client.println("<head>");
  client.println("<meta charset=\"utf-8\">");
  client.println("<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">");
  client.println("<meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\">");
  client.println("<title>Prese comandate - Sgart.it</title>");
  client.println("<meta name='description' content='Prese comandate - IoT - WiFi - Sgart.it'>");
  client.println("<link  rel=\"stylesheet\" href=\"https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css\">");
  client.println("<link rel=\"icon\" href=\"http://www.sgart.it/favicon.ico\" type=\"image/x-icon\">");
  client.println("<style>body{padding-top:50px;background-color:#fafafa;color: #606060;} .navbar-brand{padding-top:10px;} ");
  client.println(".btn-height{height:90px;line-height:76px;margin-bottom:15px;font-size:28px;} .btn-default{background-color:#ffffff;color:#333333;} #box-wait{display:none;position:absolute;top:0;left:0;width:100%;height:5px;background-color:#0f0;z-index:1500;}");
  client.println(".footer{position: fixed;width:100%;height:40px;line-height:40px;bottom:0;left:0;background-color:#222;color:#606060;vertical-align:middle;padding-left: 5px;</style>");
  //script
  client.println("<script>function doCommand(obj,n){");
  client.println("var url = /rele/+n;");
  client.println("var objWait = document.getElementById(\"box-wait\")");
  client.println("objWait.style.display = \"inline\";");
  client.println("var xmlhttp = new XMLHttpRequest();");
  client.println("xmlhttp.onreadystatechange = function(){");
  client.println("  if (xmlhttp.readyState == 4){");
  client.println("    if (xmlhttp.status == 200){");
  client.println("      var result = xmlhttp.responseText.replace(/^\\s+|\\s+$/gm,'');");
  client.println("      obj.className = \"btn btn-block btn-height \" + (result===\"on\" ? \"btn-success\" : \"btn-default\" ); ");
  client.println("    }");
  client.println("    objWait.style.display = \"none\";");
  client.println("  }");
  client.println("}");
  client.println("xmlhttp.open(\"GET\", url, true);");
  client.println("xmlhttp.send();");
  client.println("}</script>");
  
  client.println("</head>");
  client.println("</body>");
  //waiting
  client.println("<div id=\"box-wait\"></div>");
  //nav bar
  client.println("<div class=\"navbar navbar-inverse navbar-fixed-top\"><div class=\"container\"><div class=\"navbar-header\">");
  client.println("<a class=\"navbar-brand\" href=\"http://www.sgart.it\"><img width=\"32\" height=\"32\" src=\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAadEVYdFNvZnR3YXJlAFBhaW50Lk5FVCB2My41LjEwMPRyoQAAAMxJREFUSEvdVsERgzAM807slJ26Ezu5pr7jCJEDAtP0yunlJFIkB4LInzyqko4qmnR2IwQCWe3w7X5ZYC46l9aBFQ8RraodjBWIetM3RzgYJkCcOjd7ql0E62aqvsQQnqLPKIu6B6ME0vKIIkoWYFMOO9cGfo2aELgYRREFZ3spwjqtEhGBupf6AG/JeQeH7FQUyMEkOoUR2VAHwFnL5uvDr+bTAmz3wXb7DjIERHa/ASzpdr5R7dlA6YZCKOADWajvgzzedX83IvippW8ssVKXGc5FQwAAAABJRU5ErkJggg==\" /></a>");
  client.println("</div></div></div>");
  //header
  client.println("<div class=\"container\"><div class=\"page-header\"><h1>Prese comandate</h1></div></div>");
  //body
  client.println("<div class=\"container\"><div class=\"row\">");

  client.println("<div class=\"col-md-6\"><button id=\"btn1\" type=\"button\" onclick=\"doCommand(this,1)\" class=\"btn btn-block btn-height " + c1 + "\">Presa 1</button></div>");
  client.println("<div class=\"col-md-6\"><button id=\"btn2\" type=\"button\" onclick=\"doCommand(this,2)\" class=\"btn btn-block btn-height " + c2 +"\">Presa 2</button></div>");
  
  client.println("</div></div>");

  client.println("<div class=\"footer\"><div class=\"content-wrapper\">@2016 <a href=\"http://www.sgart.it/?prese-comandate\">Sgart.it</a> - Prese comandate - v. 1.0.0</div></div>");
  client.println("</body></html>");
  delay(1);
  client.stop();
}

void sendReleStatus(WiFiClient client, int releStatus){
  client.flush();
  client.println("HTTP/1.1 200 OK");
  client.println("Content-Type: text/html");
  client.println("Connection: close");
  client.println(); // questo è indispensabile

  client.println(printOnOff(releStatus));
  delay(1);
  client.stop();
}

//----------------------------------------
// inizio
void setup() {
  Serial.begin(9600);
  delay(10);
  Serial.println("INIT I/O");

  // imposto le uscite
  pinMode(RELE1_PIN, OUTPUT);
  digitalWrite(RELE1_PIN, rele1);
  pinMode(RELE2_PIN, OUTPUT);
  digitalWrite(RELE2_PIN, rele2);
  //imposto gli ingressi
  pinMode(BTN1_PIN, INPUT_PULLUP);
  pinMode(BTN2_PIN, INPUT_PULLUP);

  initWiFi();
  initServer();
}

//----------------------------------------
// codice eseguito ciclicamente
void loop() {
  readButtons();
  
  // aspetto che un client si connetta
  WiFiClient client = server.available();
  if (!client) {
    return;
  }
  // attendo che invii qualche dato
  while(!client.available()){
    delay(1);
  }
  // leggo la prima linea della richiesta (attenzione è CASE SENSITIVE)
  String request = client.readStringUntil('\r');
  Serial.println(request);

  // routing
  if (request.indexOf("/rele/1") != -1) {
    changeRele1();
    sendReleStatus(client, rele1);
  } else if (request.indexOf("/rele/2") != -1) {
    changeRele2();
    sendReleStatus(client, rele2);
  }else{
    sendResponse(client);
  }
}
La pagina web utilizza la libreria Bootstrap per l'interfaccia grafica. Avendo l'ESP8266 solo 4MB di memoria, la libreria risiede su una CDN e quindi per la corretta visualizzazione è necessario l'accesso a internet.
Per funzionare va impostato l'SSID e la password della tua rete WI-FI oltre all'IP e netmask.