Avatar
Aitor Fuentes, a.k.a kr0no.
En mis ratos libres, desarrollo videojuegos y cacharreo con cosas.
Miembro del Red Team de Telefónica Tech y del SRT de Synack.

Reprogramando switch Sonoff para MQTT local

Los dispositivos Sonoff de ITead nos permiten crear una “smart home” con un coste realmente bajo y de forma muy sencilla, sin embargo, la gestión de estos dispositivos se hace a través de los propios servidores de Sonoff, por lo que estarán constanmente enviando y recibiendo las órdenes desde un lugar que no está bajo nuestro control, y mejor evitar esto, ¿no?

En este caso vamos a ver como reprogramar el Sonoff Wifi Smart Switch, (el cual tiene un coste de 4,85$ (~4,50€)) para que utilice nuestro propio servidor MQTT y todo se gestione desde nuestra red local.

Switch Sonoff

Para poder reprogramar el dispositivo necesitaremos un adaptador USB a serial-TTL (yo utilizo este), y cuatro pines para soldarlos a la placa del switch.

Al lado del pulsador del switch hay cinco agujeros previstos para soldar una hilera de pines. Simplemente tendremos que soldar una hilera de cuatro pines en estos agujeros (el quinto no nos hace falta), tal y como está en la siguiente imagen: Pines

Una vez hecho esto tendremos que conectar el switch al adaptador USB serial-TTL de la siguiente forma:

  • El primer pin (empezamos por el que tenía el slot cuadrado, el más próximo al pulsador) a la salida 3,3v del adaptador USB.
  • El segundo pin es RX, lo conectaremos al TX del adaptador.
  • El tercer pin es TX, lo conectaremos al RX del adaptador.
  • El cuarto pin es GND, lo conectaremos a un GND del adaptador.

Una vez lo tengamos todo conectado, ya podemos pasar a programar el switch. Para encender el Sonoff en modo programación tendremos que mantener pulsado el pulsador antes de conectar el adaptador al PC.

Cuando al fin estemos en modo programación, podremos subir nuestro propio sketch al switch. La configuración para cargar correctamente el sketch en la placa es la siguiente:

  • Board: Generic ESP8266 Module
  • Flash mode: DIO
  • Flash size: 1M (64K SPIFFS)
  • Reset method: ck

Además necesitaremos usar un fork de la librería PubSubClient que tiene soporte para MQTT, disponible aquí.

Tras estos pasos ya podremos cargar el nuevo sketch en la placa sin ningún problema:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
//Based on a KmanOz work: https://github.com/KmanOz/Sonoff-HomeAssistant/blob/master/arduino/ESPsonoff-v1.01p/ESPsonoff-v1.01p.ino

//Needs PubSubClient with MQTT support: https://github.com/Imroy/pubsubclient

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

#define BUTTON          0
#define RELAY           12
#define LED             13

#define WIFI_SSID       "SSID"
#define WIFI_PASS       "PASSWORD"

#define MQTT_CLIENT     "MQTT_CLIENT"
#define MQTT_SERVER     "MQTT_SERVER_IP"
#define MQTT_PORT       1883
#define MQTT_TOPIC      "mqtt/topic"

#define MQTT_AUTH       true
#define MQTT_USER       "user"
#define MQTT_PASS       "pass"

//logic
bool sendStatus = true;
bool restart = false;
int lastState = true;
unsigned long TTasks;
unsigned long count = 0;

extern "C" {
  #include "user_interface.h"
}

WiFiClient wifiClient;
PubSubClient mqttClient(wifiClient, MQTT_SERVER, MQTT_PORT);
Ticker ticker;

void callback(const MQTT::Publish& pub) {
  if(pub.payload_string() == "stat") {
  } else if(pub.payload_string() == "on") {
    digitalWrite(LED, LOW);
    digitalWrite(RELAY, HIGH);
  } else if(pub.payload_string() == "off") {
    digitalWrite(LED, HIGH);
    digitalWrite(RELAY, LOW);
  } else if(pub.payload_string() == "reset") {
    restart = true;
  }
  sendStatus = true;
}

void setup() {
  pinMode(LED, OUTPUT);
  pinMode(RELAY, OUTPUT);
  pinMode(BUTTON, INPUT);
  digitalWrite(LED, HIGH);
  digitalWrite(RELAY, LOW);
  Serial.begin(115200);
  EEPROM.begin(8);
  lastState = EEPROM.read(0);
  if(lastState == 1) {
    digitalWrite(LED, LOW);
    digitalWrite(RELAY, HIGH);
  }
  ticker.attach(0.05, button);
  mqttClient.set_callback(callback);
  WiFi.mode(WIFI_STA);
  WiFi.begin(WIFI_SSID, WIFI_PASS);
  while(WiFi.status() != WL_CONNECTED) {
    delay(500);
  }
  if(WiFi.status() == WL_CONNECTED) {
    Serial.println("IP: " + WiFi.localIP());
    if(MQTT_AUTH) {
      while(!mqttClient.connect(MQTT::Connect(MQTT_CLIENT).set_keepalive(90).set_auth(MQTT_USER, MQTT_PASS))) {
        delay(1000);
      }
    } else {
      while(!mqttClient.connect(MQTT::Connect(MQTT_CLIENT).set_keepalive(90))) {
        delay(1000);
      }
    }
    if(mqttClient.connected()) {
      mqttClient.subscribe(MQTT_TOPIC);
      if(digitalRead(RELAY) == HIGH)  {
        digitalWrite(LED, LOW);
      } else {
        digitalWrite(LED, HIGH);
      }
    }
  }
}

void loop() {
  mqttClient.loop();
  timedTasks();
  checkStatus();
}

void button() {
  if(!digitalRead(BUTTON)) {
    count++;
  } else {
    if(count > 1 && count <= 40) {
      digitalWrite(LED, !digitalRead(LED));
      digitalWrite(RELAY, !digitalRead(RELAY));
      sendStatus = true;
    } else if(count > 40) {
      restart = true;
    }
    count = 0;
  }
}

void checkConnection() {
  if(WiFi.status() == WL_CONNECTED) {
    if(!mqttClient.connected()) {
      restart = true;    
    }
  } else {
    restart = true;
  }
}

void checkStatus() {
  if(sendStatus) {
    if(digitalRead(LED) == LOW) {
      EEPROM.write(0, 1);
      mqttClient.publish(MQTT::Publish(MQTT_TOPIC"/stat", "on").set_retain().set_qos(1));
    } else {
      EEPROM.write(0, 0);
      mqttClient.publish(MQTT::Publish(MQTT_TOPIC"/stat", "off").set_retain().set_qos(1));
    }
    EEPROM.commit();
    sendStatus = false;
  }
  if(restart) {
    ESP.restart();
  }
}

void timedTasks() {
  if ((millis() > TTasks + (60000)) || (millis() < TTasks)) { 
    TTasks = millis();
    checkConnection();
  }
}

Para terminar, reiniciaremos el switch para comprobar que se conecta automáticamente a la red wifi y al servidor MQTT indicados, quitándonos así los servidores de Sonoff de en medio :P

Si todo funciona correctamente, ya podemos conectarlo a la corriente alterna para darle el uso que teníamos previsto.

blog tags