├── LICENSE ├── README.md ├── Sonoff_Basic ├── README.md ├── Sonoff_Basic.ino └── images │ ├── Schematic.jpg │ └── Steps.png ├── Sonoff_TH ├── README.md ├── Sonoff.cpp ├── Sonoff.h ├── Sonoff_TH.ino ├── config.example.h └── th.jpg └── images ├── .DS_Store ├── basic.jpg └── th.jpg /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ITEAD Smart Home 2 | **THIS REPOSITORY IS NOT MAINTAINED ANYMORE, SEE [ESPHome](https://esphome.io/) AS AN ALTERNATIVE.** 3 | 4 | ## Alternative firmwares for [itead.cc](http://sonoff.itead.cc/) Sonoff products 5 | This repository offers alternative and open-source firmwares for iTead Sonoff products, based on the MQTT protocol and are natively compatible with [Home Assistant](https://home-assistant.io), which is an amazing open-source home automation platform. 6 | 7 | | Product | Presentation | Firmware | Preview | 8 | |---------------|-----------------------------------------------------------------------|-------------------------|------------------------------- 9 | | Sonoff Basic | [itead.cc](http://sonoff.itead.cc/en/products/sonoff/sonoff-basic) | [GitHub](Sonoff_Basic/) | ![Basic](images/basic.jpg) | 10 | | Sonoff TH | [itead.cc](http://sonoff.itead.cc/en/products/sonoff/sonoff-th) | [GitHub](Sonoff_TH/) | ![Basic](images/th.jpg) | 11 | 12 | *If you like the content of this repo, please add a star! Thank you!* 13 | -------------------------------------------------------------------------------- /Sonoff_Basic/README.md: -------------------------------------------------------------------------------- 1 | # Itead Sonoff 2 | ## Alternative firmware 3 | Alternative firmware for Itead Sonoff switches, based on the MQTT protocol and a TLS connection. 4 | Sonoff is a small ESP8266 based module, that can toggle mains power and costs only $4.85. More information can be found [here](https://www.itead.cc/sonoff-wifi-wireless-switch.html). 5 | 6 | ## Features 7 | - Wi-Fi credentials configuration using WiFiManager 8 | - Web configuration portal to setup MQTT username, password, broker IP address and port 9 | - TLS support for CloudMQTT. For any other MQTT brokers, you need to change: 10 | - `broker`: IP address of the MQTT broker, and 11 | - `fingerprint`: Fingerprint of its certificate (`openssl x509 -fingerprint -in .crt`) 12 | - Onboard button: 13 | - Short press: Toggle the state of the relay 14 | - Medium press: Restart the relay (~3 [s]) 15 | - Long press: Reset the relay 16 | - PIR motion sensor support. Make sure to connect your sensor on GPIO14 17 | 18 | ## Steps 19 | - Connect the Sonoff to a FTDI adapter and hold down the button, while powering it (programing mode) 20 | - Upload the firmware with the Arduino IDE (use the settings below) 21 | - Connect to the new Wi-Fi AP and memorize its name (1) 22 | - Select `Configure WiFi`(2) 23 | - Choose your network (3) and enter your MQTT username, password, broker IP address and broker port (4) 24 | - Update your configuration in your home automation system. An example for `Home Assistant`is available below 25 | 26 | ### Settings for the Arduino IDE 27 | 28 | | Parameter | Value | 29 | | ----------------|--------------------------| 30 | | Board | Generic ESP8266 Module | 31 | | Flash Mode | DIO | 32 | | Flash Frequency | 40 MHz | 33 | | Upload Using | Serial | 34 | | CPU Frequency | 80 MHz | 35 | | Flash Size | 512K (64K SPIFFS) | 36 | | Reset Method | ck | 37 | | Upload Speed | 115200 | 38 | | Port | COMX, /dev/ttyUSB0, etc. | 39 | 40 | 41 | ### Wi-Fi and MQTT Configuration 42 | ![Steps](images/Steps.png) 43 | 44 | ### MQTT topics 45 | | # | Topic | Payload | 46 | | -----------|---------------------------|-----------| 47 | | State | `/switch/state` | `ON`/`OFF`| 48 | | Command | `/switch/switch` | `ON`/`OFF`| 49 | 50 | ### Configuration (Home Assistant) 51 | configuration.yaml : 52 | 53 | ```yaml 54 | switch: 55 | platform: mqtt 56 | name: 'Switch' 57 | state_topic: 'CBF777/switch/state' 58 | command_topic: 'CBF777/switch/switch' 59 | optimistic: false 60 | ``` 61 | 62 | ## Schematic 63 | - VCC (Sonoff) -> VCC (FTDI) 64 | - RX (Sonoff) -> TX (FTDI) 65 | - TX (Sonoff) -> RX (FTDI) 66 | - GND (Sonoff) -> GND (FTDI) 67 | 68 | ![Schematic](images/Schematic.jpg) 69 | 70 | ## Versions 71 | - 1.0: Initial version 72 | - 1.1: Add TLS support 73 | - 1.2: Add PIR motion sensor support 74 | 75 | > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 76 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 77 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 78 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 79 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 80 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 81 | SOFTWARE. -------------------------------------------------------------------------------- /Sonoff_Basic/Sonoff_Basic.ino: -------------------------------------------------------------------------------- 1 | /* 2 | Alternative firmware for Itead Sonoff switches, based on the MQTT protocol and a TLS connection 3 | The very initial version of this firmware was a fork from the SonoffBoilerplate (tzapu) 4 | 5 | This firmware can be easily interfaced with Home Assistant, with the MQTT switch 6 | component: https://home-assistant.io/components/switch.mqtt/ 7 | 8 | CloudMQTT (free until 10 connections): https://www.cloudmqtt.com 9 | 10 | Libraries : 11 | - ESP8266 core for Arduino : https://github.com/esp8266/Arduino 12 | - PubSubClient: https://github.com/knolleary/pubsubclient 13 | - WiFiManager: https://github.com/tzapu/WiFiManager 14 | 15 | Sources : 16 | - File > Examples > ES8266WiFi > WiFiClient 17 | - File > Examples > PubSubClient > mqtt_auth 18 | - https://github.com/tzapu/SonoffBoilerplate 19 | - https://io.adafruit.com/blog/security/2016/07/05/adafruit-io-security-esp8266/ 20 | 21 | Schematic: 22 | - VCC (Sonoff) -> VCC (FTDI) 23 | - RX (Sonoff) -> TX (FTDI) 24 | - TX (Sonoff) -> RX (FTDI) 25 | - GND (Sonoff) -> GND (FTDI) 26 | 27 | Steps: 28 | - Upload the firmware 29 | - Connect to the new Wi-Fi AP and memorize its name 30 | - Choose your network and enter your MQTT username, password, broker 31 | IP address and broker port 32 | - Update your configuration in Home Assistant 33 | 34 | MQTT topics and payload: 35 | - State: /switch/state ON/OFF 36 | - Command: /switch/switch ON/OFF 37 | 38 | Configuration (Home Assistant) : 39 | switch: 40 | platform: mqtt 41 | name: 'Switch' 42 | state_topic: 'CBF777/switch/state' 43 | command_topic: 'CBF777/switch/switch' 44 | optimistic: false 45 | 46 | Versions: 47 | - 1.0: Initial version 48 | - 1.1: Add TLS support 49 | - 1.2: Add PIR sensor support 50 | 51 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 52 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 53 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 54 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 55 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 56 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 57 | SOFTWARE. 58 | 59 | Samuel M. - v1.2 - 11.2016 60 | If you like this example, please add a star! Thank you! 61 | https://github.com/mertenats/sonoff 62 | */ 63 | 64 | #include // https://github.com/esp8266/Arduino 65 | #include // https://github.com/tzapu/WiFiManager 66 | #include // https://github.com/knolleary/pubsubclient/releases/tag/v2.6 67 | #include 68 | #include 69 | //#include 70 | 71 | // TLS support, make sure to edit the fingerprint and the broker address if 72 | // you are not using CloudMQTT 73 | #define TLS 74 | #ifdef TLS 75 | const char* broker = "m21.cloudmqtt.com"; 76 | 77 | // SHA1 fingerprint of the certificate 78 | // openssl x509 -fingerprint -in .crt 79 | const char* fingerprint = "A5 02 FF 13 99 9F 8B 39 8E F1 83 4F 11 23 65 0B 32 36 FC 07"; 80 | #endif 81 | 82 | // PIR motion sensor support, make sure to connect a PIR motion sensor to the GPIO14 83 | //#define PIR 84 | #ifdef PIR 85 | const uint8_t PIR_SENSOR_PIN = 14; 86 | #endif 87 | 88 | #define DEBUG // enable debugging 89 | #define STRUCT_CHAR_ARRAY_SIZE 24 // size of the arrays for MQTT username, password, etc. 90 | 91 | // macros for debugging 92 | #ifdef DEBUG 93 | #define DEBUG_PRINT(x) Serial.print(x) 94 | #define DEBUG_PRINTLN(x) Serial.println(x) 95 | #else 96 | #define DEBUG_PRINT(x) 97 | #define DEBUG_PRINTLN(x) 98 | #endif 99 | 100 | // Sonoff properties 101 | const uint8_t BUTTON_PIN = 0; 102 | const uint8_t RELAY_PIN = 12; 103 | const uint8_t LED_PIN = 13; 104 | 105 | // MQTT 106 | char MQTT_CLIENT_ID[7] = {0}; 107 | char MQTT_SWITCH_STATE_TOPIC[STRUCT_CHAR_ARRAY_SIZE] = {0}; 108 | char MQTT_SWITCH_COMMAND_TOPIC[STRUCT_CHAR_ARRAY_SIZE] = {0}; 109 | const char* MQTT_SWITCH_ON_PAYLOAD = "ON"; 110 | const char* MQTT_SWITCH_OFF_PAYLOAD = "OFF"; 111 | 112 | // Settings for MQTT 113 | typedef struct { 114 | char mqttUser[STRUCT_CHAR_ARRAY_SIZE] = "";//{0}; 115 | char mqttPassword[STRUCT_CHAR_ARRAY_SIZE] = "";//{0}; 116 | char mqttServer[STRUCT_CHAR_ARRAY_SIZE] = "";//{0}; 117 | char mqttPort[6] = "";//{0}; 118 | char mqttStateTopic[STRUCT_CHAR_ARRAY_SIZE] = ""; 119 | char mqttCommandTopic[STRUCT_CHAR_ARRAY_SIZE] = ""; 120 | } Settings; 121 | 122 | enum CMD { 123 | CMD_NOT_DEFINED, 124 | CMD_PIR_STATE_CHANGED, 125 | CMD_BUTTON_STATE_CHANGED, 126 | }; 127 | volatile uint8_t cmd = CMD_NOT_DEFINED; 128 | 129 | uint8_t relayState = HIGH; // HIGH: closed switch 130 | uint8_t buttonState = HIGH; // HIGH: opened switch 131 | uint8_t currentButtonState = buttonState; 132 | long buttonStartPressed = 0; 133 | long buttonDurationPressed = 0; 134 | #ifdef PIR 135 | uint8_t pirState = LOW; 136 | uint8_t currentPirState = pirState; 137 | #endif 138 | 139 | Settings settings; 140 | Ticker ticker; 141 | #ifdef TLS 142 | WiFiClientSecure wifiClient; 143 | #else 144 | WiFiClient wifiClient; 145 | #endif 146 | PubSubClient mqttClient(wifiClient); 147 | 148 | /////////////////////////////////////////////////////////////////////////// 149 | // Adafruit IO with SSL/TLS 150 | /////////////////////////////////////////////////////////////////////////// 151 | /* 152 | Function called to verify the fingerprint of the MQTT server certificate 153 | */ 154 | #ifdef TLS 155 | void verifyFingerprint() { 156 | DEBUG_PRINT(F("INFO: Connecting to ")); 157 | DEBUG_PRINTLN(settings.mqttServer); 158 | 159 | if (!wifiClient.connect(settings.mqttServer, atoi(settings.mqttPort))) { 160 | DEBUG_PRINTLN(F("ERROR: Connection failed. Halting execution")); 161 | reset(); 162 | } 163 | 164 | if (wifiClient.verify(fingerprint, settings.mqttServer)) { 165 | DEBUG_PRINTLN(F("INFO: Connection secure")); 166 | } else { 167 | DEBUG_PRINTLN(F("ERROR: Connection insecure! Halting execution")); 168 | reset(); 169 | } 170 | } 171 | #endif 172 | 173 | /////////////////////////////////////////////////////////////////////////// 174 | // MQTT 175 | /////////////////////////////////////////////////////////////////////////// 176 | /* 177 | Function called when a MQTT message arrived 178 | @param p_topic The topic of the MQTT message 179 | @param p_payload The payload of the MQTT message 180 | @param p_length The length of the payload 181 | */ 182 | void callback(char* p_topic, byte* p_payload, unsigned int p_length) { 183 | // handle the MQTT topic of the received message 184 | if (String(settings.mqttCommandTopic).equals(p_topic)) { 185 | //if (String(MQTT_SWITCH_COMMAND_TOPIC).equals(p_topic)) { 186 | if ((char)p_payload[0] == (char)MQTT_SWITCH_ON_PAYLOAD[0] && (char)p_payload[1] == (char)MQTT_SWITCH_ON_PAYLOAD[1]) { 187 | if (relayState != HIGH) { 188 | relayState = HIGH; 189 | setRelayState(); 190 | } 191 | } else if ((char)p_payload[0] == (char)MQTT_SWITCH_OFF_PAYLOAD[0] && (char)p_payload[1] == (char)MQTT_SWITCH_OFF_PAYLOAD[1] && (char)p_payload[2] == (char)MQTT_SWITCH_OFF_PAYLOAD[2]) { 192 | if (relayState != LOW) { 193 | relayState = LOW; 194 | setRelayState(); 195 | } 196 | } 197 | } 198 | } 199 | 200 | /* 201 | Function called to publish the state of the Sonoff relay 202 | */ 203 | void publishSwitchState() { 204 | if (relayState == HIGH) { 205 | if (mqttClient.publish(settings.mqttStateTopic, MQTT_SWITCH_ON_PAYLOAD, true)) { 206 | // if (mqttClient.publish(MQTT_SWITCH_STATE_TOPIC, MQTT_SWITCH_ON_PAYLOAD, true)) { 207 | DEBUG_PRINT(F("INFO: MQTT message publish succeeded. Topic: ")); 208 | DEBUG_PRINT(settings.mqttStateTopic); 209 | //DEBUG_PRINT(MQTT_SWITCH_STATE_TOPIC); 210 | DEBUG_PRINT(F(". Payload: ")); 211 | DEBUG_PRINTLN(MQTT_SWITCH_ON_PAYLOAD); 212 | } else { 213 | DEBUG_PRINTLN(F("ERROR: MQTT message publish failed, either connection lost, or message too large")); 214 | } 215 | } else { 216 | if (mqttClient.publish(settings.mqttStateTopic, MQTT_SWITCH_OFF_PAYLOAD, true)) { 217 | //if (mqttClient.publish(MQTT_SWITCH_STATE_TOPIC, MQTT_SWITCH_OFF_PAYLOAD, true)) { 218 | DEBUG_PRINT(F("INFO: MQTT message publish succeeded. Topic: ")); 219 | //DEBUG_PRINT(MQTT_SWITCH_STATE_TOPIC); 220 | DEBUG_PRINT(settings.mqttStateTopic); 221 | DEBUG_PRINT(F(". Payload: ")); 222 | DEBUG_PRINTLN(MQTT_SWITCH_OFF_PAYLOAD); 223 | } else { 224 | DEBUG_PRINTLN(F("ERROR: MQTT message publish failed, either connection lost, or message too large")); 225 | } 226 | } 227 | } 228 | 229 | /* 230 | Function called to connect/reconnect to the MQTT broker 231 | */ 232 | void reconnect() { 233 | // test if the module has an IP address 234 | // if not, restart the module 235 | if (WiFi.status() != WL_CONNECTED) { 236 | DEBUG_PRINTLN(F("ERROR: The module isn't connected to the internet")); 237 | restart(); 238 | } 239 | 240 | // try to connect to the MQTT broker 241 | // if the connection is not possible, it will reset the settings 242 | uint8_t i = 0; 243 | while (!mqttClient.connected()) { 244 | if (mqttClient.connect(MQTT_CLIENT_ID, settings.mqttUser, settings.mqttPassword)) { 245 | DEBUG_PRINTLN(F("INFO: The client is successfully connected to the MQTT broker")); 246 | } else { 247 | DEBUG_PRINTLN(F("ERROR: The connection to the MQTT broker failed")); 248 | DEBUG_PRINT(F("Username: ")); 249 | DEBUG_PRINTLN(settings.mqttUser); 250 | DEBUG_PRINT(F("Password: ")); 251 | DEBUG_PRINTLN(settings.mqttPassword); 252 | DEBUG_PRINT(F("Broker: ")); 253 | DEBUG_PRINTLN(settings.mqttServer); 254 | delay(1000); 255 | if (i == 3) { 256 | reset(); 257 | } 258 | i++; 259 | } 260 | } 261 | 262 | if (mqttClient.subscribe(settings.mqttCommandTopic)) { 263 | //if (mqttClient.subscribe(MQTT_SWITCH_COMMAND_TOPIC)) { 264 | DEBUG_PRINT(F("INFO: Sending the MQTT subscribe succeeded. Topic: ")); 265 | DEBUG_PRINTLN(settings.mqttCommandTopic); 266 | //DEBUG_PRINTLN(MQTT_SWITCH_COMMAND_TOPIC); 267 | } else { 268 | DEBUG_PRINT(F("ERROR: Sending the MQTT subscribe failed. Topic: ")); 269 | DEBUG_PRINTLN(settings.mqttCommandTopic); 270 | //DEBUG_PRINTLN(MQTT_SWITCH_COMMAND_TOPIC); 271 | } 272 | } 273 | 274 | /////////////////////////////////////////////////////////////////////////// 275 | // WiFiManager 276 | /////////////////////////////////////////////////////////////////////////// 277 | /* 278 | Function called to toggle the state of the LED 279 | */ 280 | void tick() { 281 | digitalWrite(LED_PIN, !digitalRead(LED_PIN)); 282 | } 283 | 284 | // flag for saving data 285 | bool shouldSaveConfig = false; 286 | 287 | // callback notifying us of the need to save config 288 | void saveConfigCallback () { 289 | shouldSaveConfig = true; 290 | } 291 | 292 | void configModeCallback (WiFiManager *myWiFiManager) { 293 | ticker.attach(0.2, tick); 294 | } 295 | 296 | /////////////////////////////////////////////////////////////////////////// 297 | // Sonoff switch 298 | /////////////////////////////////////////////////////////////////////////// 299 | /* 300 | Function called to set the state of the relay 301 | */ 302 | void setRelayState() { 303 | digitalWrite(RELAY_PIN, relayState); 304 | digitalWrite(LED_PIN, (relayState + 1) % 2); 305 | publishSwitchState(); 306 | } 307 | 308 | /* 309 | Function called to restart the switch 310 | */ 311 | void restart() { 312 | DEBUG_PRINTLN(F("INFO: Restart...")); 313 | ESP.reset(); 314 | delay(1000); 315 | } 316 | 317 | /* 318 | Function called to reset the configuration of the switch 319 | */ 320 | void reset() { 321 | DEBUG_PRINTLN(F("INFO: Reset...")); 322 | WiFi.disconnect(); 323 | delay(1000); 324 | ESP.reset(); 325 | delay(1000); 326 | } 327 | 328 | /////////////////////////////////////////////////////////////////////////// 329 | // ISR 330 | /////////////////////////////////////////////////////////////////////////// 331 | /* 332 | Function called when the button is pressed/released 333 | */ 334 | void buttonStateChangedISR() { 335 | cmd = CMD_BUTTON_STATE_CHANGED; 336 | } 337 | 338 | /* 339 | Function called when the PIR sensor detects the biginning/end of a mouvement 340 | */ 341 | #ifdef PIR 342 | void pirStateChangedISR() { 343 | cmd = CMD_PIR_STATE_CHANGED; 344 | } 345 | #endif 346 | 347 | /////////////////////////////////////////////////////////////////////////// 348 | // Setup() and loop() 349 | /////////////////////////////////////////////////////////////////////////// 350 | void setup() { 351 | #ifdef DEBUG 352 | Serial.begin(115200); 353 | #endif 354 | 355 | // init the I/O 356 | pinMode(LED_PIN, OUTPUT); 357 | pinMode(RELAY_PIN, OUTPUT); 358 | pinMode(BUTTON_PIN, INPUT); 359 | attachInterrupt(BUTTON_PIN, buttonStateChangedISR, CHANGE); 360 | #ifdef PIR 361 | pinMode(PIR_SENSOR_PIN, INPUT); 362 | attachInterrupt(PIR_SENSOR_PIN, pirStateChangedISR, CHANGE); 363 | #endif 364 | ticker.attach(0.6, tick); 365 | 366 | // get the Chip ID of the switch and use it as the MQTT client ID 367 | sprintf(MQTT_CLIENT_ID, "%06X", ESP.getChipId()); 368 | DEBUG_PRINT(F("INFO: MQTT client ID/Hostname: ")); 369 | DEBUG_PRINTLN(MQTT_CLIENT_ID); 370 | 371 | // set the state topic: /switch/state 372 | sprintf(MQTT_SWITCH_STATE_TOPIC, "%06X/switch/state", ESP.getChipId()); 373 | DEBUG_PRINT(F("INFO: MQTT state topic: ")); 374 | DEBUG_PRINTLN(MQTT_SWITCH_STATE_TOPIC); 375 | 376 | // set the command topic: /switch/switch 377 | sprintf(MQTT_SWITCH_COMMAND_TOPIC, "%06X/switch/switch", ESP.getChipId()); 378 | DEBUG_PRINT(F("INFO: MQTT command topic: ")); 379 | DEBUG_PRINTLN(MQTT_SWITCH_COMMAND_TOPIC); 380 | 381 | // load custom params 382 | EEPROM.begin(512); 383 | EEPROM.get(0, settings); 384 | EEPROM.end(); 385 | 386 | #ifdef TLS 387 | WiFiManagerParameter custom_text("

MQTT username, password and broker port

"); 388 | WiFiManagerParameter custom_mqtt_server("mqtt-server", "MQTT Broker IP", "m21.cloudmqtt.com", STRUCT_CHAR_ARRAY_SIZE, "disabled"); 389 | #else 390 | WiFiManagerParameter custom_text("

MQTT username, password, broker IP address and broker port

"); 391 | WiFiManagerParameter custom_mqtt_server("mqtt-server", "MQTT Broker IP", settings.mqttServer, STRUCT_CHAR_ARRAY_SIZE); 392 | #endif 393 | WiFiManagerParameter custom_mqtt_user("mqtt-user", "MQTT User", settings.mqttUser, STRUCT_CHAR_ARRAY_SIZE); 394 | WiFiManagerParameter custom_mqtt_password("mqtt-password", "MQTT Password", settings.mqttPassword, STRUCT_CHAR_ARRAY_SIZE, "type = \"password\""); 395 | WiFiManagerParameter custom_mqtt_port("mqtt-port", "MQTT Broker Port", settings.mqttPort, 6); 396 | 397 | WiFiManagerParameter custom_mqtt_topics("

MQTT state and command topics

"); 398 | WiFiManagerParameter custom_mqtt_state_topic("mqtt-state-topic", "MQTT State Topic", MQTT_SWITCH_STATE_TOPIC, STRUCT_CHAR_ARRAY_SIZE); 399 | WiFiManagerParameter custom_mqtt_command_topic("mqtt-command-topic", "MQTT Command Topic", MQTT_SWITCH_COMMAND_TOPIC, STRUCT_CHAR_ARRAY_SIZE); 400 | 401 | WiFiManager wifiManager; 402 | 403 | wifiManager.addParameter(&custom_text); 404 | wifiManager.addParameter(&custom_mqtt_user); 405 | wifiManager.addParameter(&custom_mqtt_password); 406 | wifiManager.addParameter(&custom_mqtt_server); 407 | wifiManager.addParameter(&custom_mqtt_port); 408 | 409 | wifiManager.addParameter(&custom_mqtt_topics); 410 | wifiManager.addParameter(&custom_mqtt_state_topic); 411 | wifiManager.addParameter(&custom_mqtt_command_topic); 412 | 413 | wifiManager.setAPCallback(configModeCallback); 414 | wifiManager.setConfigPortalTimeout(180); 415 | // set config save notify callback 416 | wifiManager.setSaveConfigCallback(saveConfigCallback); 417 | 418 | if (!wifiManager.autoConnect(MQTT_CLIENT_ID)) { 419 | reset(); 420 | } 421 | 422 | if (shouldSaveConfig) { 423 | #ifdef TLS 424 | strcpy(settings.mqttServer, broker); 425 | #else 426 | strcpy(settings.mqttServer, custom_mqtt_server.getValue()); 427 | #endif 428 | strcpy(settings.mqttUser, custom_mqtt_user.getValue()); 429 | strcpy(settings.mqttPassword, custom_mqtt_password.getValue()); 430 | strcpy(settings.mqttPort, custom_mqtt_port.getValue()); 431 | 432 | strcpy(settings.mqttStateTopic, custom_mqtt_state_topic.getValue()); 433 | strcpy(settings.mqttCommandTopic, custom_mqtt_command_topic.getValue()); 434 | 435 | EEPROM.begin(512); 436 | EEPROM.put(0, settings); 437 | EEPROM.end(); 438 | } 439 | 440 | #ifdef TLS 441 | // check the fingerprint of io.adafruit.com's SSL cert 442 | verifyFingerprint(); 443 | #endif 444 | 445 | // configure MQTT 446 | mqttClient.setServer(settings.mqttServer, atoi(settings.mqttPort)); 447 | mqttClient.setCallback(callback); 448 | 449 | // connect to the MQTT broker 450 | reconnect(); 451 | 452 | //ArduinoOTA.setHostname(MQTT_CLIENT_ID); 453 | //ArduinoOTA.begin(); 454 | 455 | ticker.detach(); 456 | 457 | setRelayState(); 458 | } 459 | 460 | 461 | void loop() { 462 | //ArduinoOTA.handle(); 463 | 464 | //yield(); 465 | 466 | switch (cmd) { 467 | case CMD_NOT_DEFINED: 468 | // do nothing 469 | break; 470 | #ifdef PIR 471 | case CMD_PIR_STATE_CHANGED: 472 | currentPirState = digitalRead(PIR_SENSOR_PIN); 473 | if (pirState != currentPirState) { 474 | if (pirState == LOW && currentPirState == HIGH) { 475 | if (relayState != HIGH) { 476 | relayState = HIGH; // closed 477 | setRelayState(); 478 | } 479 | } else if (pirState == HIGH && currentPirState == LOW) { 480 | if (relayState != LOW) { 481 | relayState = LOW; // opened 482 | setRelayState(); 483 | } 484 | } 485 | pirState = currentPirState; 486 | } 487 | cmd = CMD_NOT_DEFINED; 488 | break; 489 | #endif 490 | case CMD_BUTTON_STATE_CHANGED: 491 | currentButtonState = digitalRead(BUTTON_PIN); 492 | if (buttonState != currentButtonState) { 493 | // tests if the button is released or pressed 494 | if (buttonState == LOW && currentButtonState == HIGH) { 495 | buttonDurationPressed = millis() - buttonStartPressed; 496 | if (buttonDurationPressed < 500) { 497 | relayState = relayState == HIGH ? LOW : HIGH; 498 | setRelayState(); 499 | } else if (buttonDurationPressed < 3000) { 500 | restart(); 501 | } else { 502 | reset(); 503 | } 504 | } else if (buttonState == HIGH && currentButtonState == LOW) { 505 | buttonStartPressed = millis(); 506 | } 507 | buttonState = currentButtonState; 508 | } 509 | cmd = CMD_NOT_DEFINED; 510 | break; 511 | } 512 | 513 | yield(); 514 | 515 | // keep the MQTT client connected to the broker 516 | if (!mqttClient.connected()) { 517 | reconnect(); 518 | } 519 | mqttClient.loop(); 520 | 521 | yield(); 522 | } 523 | -------------------------------------------------------------------------------- /Sonoff_Basic/images/Schematic.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SamZorSec/Itead_Sonoff/63f162f1a2f023e03f97979a82d4838addff9243/Sonoff_Basic/images/Schematic.jpg -------------------------------------------------------------------------------- /Sonoff_Basic/images/Steps.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SamZorSec/Itead_Sonoff/63f162f1a2f023e03f97979a82d4838addff9243/Sonoff_Basic/images/Steps.png -------------------------------------------------------------------------------- /Sonoff_TH/README.md: -------------------------------------------------------------------------------- 1 | # Alternative firmware for Itead Sonoff TH switches 2 | This is an alternative firmware for Itead Sonoff TH switches, which uses MQTT instead of the eWeLink mobile application. The switch is a very affordable (7.50$) product available on [itead.cc](http://sonoff.itead.cc/en/products/sonoff/sonoff-th), which can be easily reprogrammed as it is based on the popular ESP8266 Wi-Fi chip. 3 | 4 | ![TH](th.jpg) 5 | 6 | ## Features 7 | - Remote control over the MQTT protocol 8 | - TLS support (uncomment `#define TLS` in `config.h` and change the fingerprint if not using CloudMQTT) 9 | - Debug printing over Telnet or Serial (uncomment `#define DEBUG_TELNET` or `#define DEBUG_SERIAL` in `config.h`) 10 | - ArduinoOTA support for over-the-air firmware updates (enabled, `#define OTA` in `config.h`) 11 | - Native support for Home Assistant, including MQTT discovery (enabled, `#define MQTT_HOME_ASSISTANT_SUPPORT` in `config.h`) 12 | - Store the current state of the switch in memory to prevent from losing its state in case of power cut (enabled, `#define SAVE_STATE` in `config.h`) 13 | 14 | ### Last Will and Testament 15 | 16 | The firmware will publish a *MQTT Last Will and Testament* at `/rgbw/status`. 17 | When the device successfully connects it will publish `alive` to that topic and when it disconnects, `dead` will automatically be published. 18 | 19 | ### Discovery 20 | 21 | This firmware supports *Home Assistant's MQTT discovery functionality*, added in 0.40. 22 | This allows for instant setup and use of your device without requiring any manual configuration in Home Assistant. 23 | To get this working, `discovery: true` must be defined in your `mqtt` configuration in Home Assistant 24 | 25 | *configuration.yaml* 26 | 27 | ```yaml 28 | mqtt: 29 | broker: 'm21.cloudmqtt.com' 30 | username: '[REDACTED]' 31 | password: '[REDACTED]' 32 | port: '[REDACTED]' 33 | discovery: true 34 | ``` 35 | 36 | ## How to 37 | 1. Install the [Arduino IDE](https://www.arduino.cc/en/Main/Software) and the [ESP8266 core for Arduino](https://github.com/esp8266/Arduino) 38 | 2. Rename `config.example.h`to `config.h` 39 | 3. Define your WiFi SSID and password (`#define WIFI_SSID ""`and `#define WIFI_PASSWORD ""`in `config.h`) 40 | 4. Define your MQTT settings (`#define MQTT_USERNAME ""`, `#define MQTT_PASSWORD ""`, `#define MQTT_SERVER ""` and `#define MQTT_SERVER_PORT `) 41 | 5. Install the external libraries ([PubSubClient](https://github.com/knolleary/pubsubclient) and [ArduinoJson](https://github.com/bblanchon/ArduinoJson)) 42 | 5. Define `MQTT_MAX_PACKET_SIZE` to `256`instead of `128`in `Arduino/libraries/pubsubclient/src/PubSubClient.h` 43 | 6. Connect cables from (Sonoff TH) `3V3` to `3V3` (FTDI), `GND` to `GND`, `RX` to `TX` and `TX` to `RX` 44 | 7. Press the button while connecting the FTDI to the computer 45 | 8. Flash the firmware (board `Generic ESP8266 module`, Upload Speed `115200` and Flash Size `1M (64K SPIFFS)`) 46 | 47 | **FOR SECURITY REASONS, IT'S HIGHLY RECOMMENDED TO HAVE A STRONG WI-FI PASSWORD, TO ENABLE TLS, TO DEACTIVATE THE DEBUGGING OUTPUT AND TO SET A PASSWORD FOR OTA.** 48 | 49 | ## Licence 50 | > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 51 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 52 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 53 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 54 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 55 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 56 | SOFTWARE. 57 | 58 | *If you like the content of this repo, please add a star! Thank you!* 59 | -------------------------------------------------------------------------------- /Sonoff_TH/Sonoff.cpp: -------------------------------------------------------------------------------- 1 | #include "Sonoff.h" 2 | 3 | /////////////////////////////////////////////////////////////////////////// 4 | // CONSTRUCTOR, INIT() AND LOOP() 5 | /////////////////////////////////////////////////////////////////////////// 6 | Sonoff::Sonoff(void) { 7 | pinMode(SONOFF_TH_BUTTON, INPUT_PULLUP); 8 | attachInterrupt(digitalPinToInterrupt(SONOFF_TH_BUTTON), buttonStateChangedISR, RISING); 9 | pinMode(SONOFF_TH_RELAY, OUTPUT); 10 | pinMode(SONOFF_TH_LED, OUTPUT); 11 | } 12 | 13 | void Sonoff::init(void) { 14 | digitalWrite(SONOFF_TH_RELAY, SONOFF_TH_RELAY_OFF); 15 | setState(false); 16 | } 17 | 18 | /////////////////////////////////////////////////////////////////////////// 19 | // STATE 20 | /////////////////////////////////////////////////////////////////////////// 21 | bool Sonoff::getState(void) { 22 | return m_state; 23 | } 24 | 25 | bool Sonoff::setState(bool p_state) { 26 | if (p_state != m_state) { 27 | m_state = p_state; 28 | if (m_state) 29 | digitalWrite(SONOFF_TH_RELAY, SONOFF_TH_RELAY_ON); 30 | else 31 | digitalWrite(SONOFF_TH_RELAY, SONOFF_TH_RELAY_OFF); 32 | } else { 33 | return false; 34 | } 35 | return true; 36 | } 37 | 38 | /////////////////////////////////////////////////////////////////////////// 39 | // MQTT DISCOVERY 40 | /////////////////////////////////////////////////////////////////////////// 41 | bool Sonoff::isDiscovered(void) { 42 | return m_isDiscovered; 43 | } 44 | 45 | void Sonoff::isDiscovered(bool p_isDiscovered) { 46 | m_isDiscovered = p_isDiscovered; 47 | } 48 | 49 | /////////////////////////////////////////////////////////////////////////// 50 | // ISR 51 | /////////////////////////////////////////////////////////////////////////// 52 | /* 53 | Function called when the button is pressed/released 54 | */ 55 | void buttonStateChangedISR(void) { 56 | cmd = CMD_BUTTON_STATE_CHANGED; 57 | } 58 | -------------------------------------------------------------------------------- /Sonoff_TH/Sonoff.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #ifndef _SONOFF_ 3 | #define _SONOFF_ 4 | 5 | #include // https://github.com/esp8266/Arduino 6 | #include "config.h" 7 | 8 | #define SONOFF_TH_BUTTON 0 9 | #define SONOFF_TH_RELAY 12 10 | #define SONOFF_TH_LED 13 11 | 12 | #define SONOFF_TH_LED_ON LOW 13 | #define SONOFF_TH_LED_OFF HIGH 14 | #define SONOFF_TH_RELAY_ON HIGH 15 | #define SONOFF_TH_RELAY_OFF LOW 16 | 17 | enum CMD { 18 | CMD_NOT_DEFINED, 19 | CMD_STATE_CHANGED, 20 | CMD_BUTTON_STATE_CHANGED, 21 | CMD_SAVE_STATE 22 | }; 23 | extern volatile uint8_t cmd; 24 | 25 | void buttonStateChangedISR(); 26 | 27 | class Sonoff { 28 | public: 29 | Sonoff(void); 30 | 31 | void init(void); 32 | 33 | bool getState(void); 34 | bool setState(bool p_state); 35 | 36 | bool isDiscovered(void); 37 | void isDiscovered(bool p_isDiscovered); 38 | private: 39 | bool m_state = false; 40 | bool m_isDiscovered = false; 41 | }; 42 | 43 | #endif 44 | -------------------------------------------------------------------------------- /Sonoff_TH/Sonoff_TH.ino: -------------------------------------------------------------------------------- 1 | /* 2 | Alternative firmware for Itead Sonoff TH switches, based on ESP8266. 3 | See the README at https://github.com/mertenats/Itead_Sonoff for more information. 4 | Licensed under the MIT license. 5 | 6 | If you like the content of this repo, please add a star! Thank you! 7 | 8 | Samuel Mertenat 9 | 06.2017 10 | */ 11 | 12 | #include // https://github.com/esp8266/Arduino 13 | #include // https://github.com/knolleary/pubsubclient 14 | #include // https://github.com/bblanchon/ArduinoJson 15 | #include 16 | #include "FS.h" 17 | #include "Sonoff.h" 18 | 19 | #if defined(DEBUG_TELNET) 20 | WiFiServer telnetServer(DEBUG_TELNET_PORT); 21 | WiFiClient telnetClient; 22 | #define DEBUG_PRINT(x) telnetClient.print(x) 23 | #define DEBUG_PRINTLN(x) telnetClient.println(x) 24 | 25 | #elif defined(DEBUG_SERIAL) 26 | #define DEBUG_PRINT(x) Serial.print(x) 27 | #define DEBUG_PRINTLN(x) Serial.println(x) 28 | #else 29 | #define DEBUG_PRINT(x) 30 | #define DEBUG_PRINTLN(x) 31 | #endif 32 | 33 | StaticJsonBuffer<96> staticJsonBuffer; 34 | char jsonBuffer[96] = {0}; 35 | 36 | volatile uint8_t cmd = CMD_STATE_CHANGED; 37 | 38 | Sonoff sonoff; 39 | #if defined(TLS) 40 | WiFiClientSecure wifiClient; 41 | #else 42 | WiFiClient wifiClient; 43 | #endif 44 | PubSubClient mqttClient(wifiClient); 45 | 46 | /////////////////////////////////////////////////////////////////////////// 47 | // TELNET 48 | /////////////////////////////////////////////////////////////////////////// 49 | /* 50 | Function called to handle Telnet clients 51 | https://www.youtube.com/watch?v=j9yW10OcahI 52 | */ 53 | #if defined(DEBUG_TELNET) 54 | void handleTelnet(void) { 55 | if (telnetServer.hasClient()) { 56 | if (!telnetClient || !telnetClient.connected()) { 57 | if (telnetClient) { 58 | telnetClient.stop(); 59 | } 60 | telnetClient = telnetServer.available(); 61 | } else { 62 | telnetServer.available().stop(); 63 | } 64 | } 65 | } 66 | #endif 67 | 68 | 69 | /////////////////////////////////////////////////////////////////////////// 70 | // TLS 71 | /////////////////////////////////////////////////////////////////////////// 72 | /* 73 | Function called to verify the fingerprint of the MQTT server certificate 74 | */ 75 | #ifdef TLS 76 | void verifyFingerprint() { 77 | DEBUG_PRINT(F("INFO: Connecting to ")); 78 | DEBUG_PRINTLN(MQTT_SERVER); 79 | 80 | if (!wifiClient.connect(MQTT_SERVER, MQTT_SERVER_PORT)) { 81 | DEBUG_PRINTLN(F("ERROR: Connection failed. Halting execution")); 82 | delay(1000); 83 | ESP.reset(); 84 | /* 85 | TODO: Doing something smarter than rebooting the device 86 | */ 87 | } 88 | 89 | if (wifiClient.verify(TLS_FINGERPRINT, MQTT_SERVER)) { 90 | DEBUG_PRINTLN(F("INFO: Connection secure")); 91 | } else { 92 | DEBUG_PRINTLN(F("ERROR: Connection insecure! Halting execution")); 93 | delay(1000); 94 | ESP.reset(); 95 | /* 96 | TODO: Doing something smarter than rebooting the device 97 | */ 98 | } 99 | } 100 | #endif 101 | 102 | /////////////////////////////////////////////////////////////////////////// 103 | // SAVE/LOAD SAVED CONFIGURATION 104 | /////////////////////////////////////////////////////////////////////////// 105 | #if defined(SAVE_STATE) 106 | bool loadConfig() { 107 | if (!SPIFFS.exists("/config.json")) { 108 | sonoff.init(); 109 | } else { 110 | File configFile = SPIFFS.open("/config.json", "r"); 111 | if (!configFile) { 112 | DEBUG_PRINTLN(F("ERROR: Failed to open config file")); 113 | return false; 114 | } 115 | 116 | size_t size = configFile.size(); 117 | std::unique_ptr buf(new char[size]); 118 | 119 | configFile.readBytes(buf.get(), size); 120 | 121 | DynamicJsonBuffer dynamicJsonBuffer; 122 | JsonObject& root = dynamicJsonBuffer.parseObject(buf.get()); 123 | 124 | if (!root.success()) { 125 | DEBUG_PRINTLN(F("ERROR: parseObject() failed")); 126 | return false; 127 | } 128 | 129 | bool isDiscovered = root["isDiscovered"]; 130 | sonoff.isDiscovered(isDiscovered); 131 | 132 | if (strcmp(root["state"], MQTT_STATE_ON_PAYLOAD) == 0) { 133 | sonoff.setState(true); 134 | } else if (strcmp(root["state"], MQTT_STATE_OFF_PAYLOAD) == 0) { 135 | sonoff.setState(false); 136 | } 137 | 138 | return true; 139 | } 140 | 141 | } 142 | 143 | bool saveConfig() { 144 | DynamicJsonBuffer dynamicJsonBuffer; 145 | JsonObject& root = dynamicJsonBuffer.createObject(); 146 | root["state"] = sonoff.getState() ? MQTT_STATE_ON_PAYLOAD : MQTT_STATE_OFF_PAYLOAD; 147 | root["isDiscovered"] = sonoff.isDiscovered(); 148 | 149 | File configFile = SPIFFS.open("/config.json", "w"); 150 | if (!configFile) { 151 | DEBUG_PRINTLN(F("ERROR: Failed to open config file for writing")); 152 | return false; 153 | } 154 | 155 | root.printTo(configFile); 156 | return true; 157 | } 158 | #endif 159 | 160 | /////////////////////////////////////////////////////////////////////////// 161 | // WiFi 162 | /////////////////////////////////////////////////////////////////////////// 163 | /* 164 | Function called to handle WiFi events 165 | */ 166 | void handleWiFiEvent(WiFiEvent_t event) { 167 | switch (event) { 168 | case WIFI_EVENT_STAMODE_GOT_IP: 169 | DEBUG_PRINTLN(F("INFO: WiFi connected")); 170 | DEBUG_PRINT(F("INFO: IP address: ")); 171 | DEBUG_PRINTLN(WiFi.localIP()); 172 | break; 173 | case WIFI_EVENT_STAMODE_DISCONNECTED: 174 | DEBUG_PRINTLN(F("ERROR: WiFi losts connection")); 175 | /* 176 | TODO: Doing something smarter than rebooting the device 177 | */ 178 | delay(5000); 179 | ESP.restart(); 180 | break; 181 | default: 182 | DEBUG_PRINT(F("INFO: WiFi event: ")); 183 | DEBUG_PRINTLN(event); 184 | break; 185 | } 186 | } 187 | 188 | /* 189 | Function called to setup the connection to the WiFi AP 190 | */ 191 | void setupWiFi() { 192 | DEBUG_PRINT(F("INFO: WiFi connecting to: ")); 193 | DEBUG_PRINTLN(WIFI_SSID); 194 | 195 | delay(10); 196 | 197 | WiFi.mode(WIFI_STA); 198 | WiFi.onEvent(handleWiFiEvent); 199 | WiFi.begin(WIFI_SSID, WIFI_PASSWORD); 200 | 201 | randomSeed(micros()); 202 | } 203 | 204 | /////////////////////////////////////////////////////////////////////////// 205 | // OTA 206 | /////////////////////////////////////////////////////////////////////////// 207 | #if defined(OTA) 208 | /* 209 | Function called to setup OTA updates 210 | */ 211 | void setupOTA() { 212 | #if defined(OTA_HOSTNAME) 213 | ArduinoOTA.setHostname(OTA_HOSTNAME); 214 | DEBUG_PRINT(F("INFO: OTA hostname sets to: ")); 215 | DEBUG_PRINTLN(OTA_HOSTNAME); 216 | #endif 217 | 218 | #if defined(OTA_PORT) 219 | ArduinoOTA.setPort(OTA_PORT); 220 | DEBUG_PRINT(F("INFO: OTA port sets to: ")); 221 | DEBUG_PRINTLN(OTA_PORT); 222 | #endif 223 | 224 | #if defined(OTA_PASSWORD) 225 | ArduinoOTA.setPassword((const char *)OTA_PASSWORD); 226 | DEBUG_PRINT(F("INFO: OTA password sets to: ")); 227 | DEBUG_PRINTLN(OTA_PASSWORD); 228 | #endif 229 | 230 | ArduinoOTA.onStart([]() { 231 | DEBUG_PRINTLN(F("INFO: OTA starts")); 232 | }); 233 | ArduinoOTA.onEnd([]() { 234 | DEBUG_PRINTLN(F("INFO: OTA ends")); 235 | }); 236 | ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) { 237 | DEBUG_PRINT(F("INFO: OTA progresses: ")); 238 | DEBUG_PRINT(progress / (total / 100)); 239 | DEBUG_PRINTLN(F("%")); 240 | }); 241 | ArduinoOTA.onError([](ota_error_t error) { 242 | DEBUG_PRINT(F("ERROR: OTA error: ")); 243 | DEBUG_PRINTLN(error); 244 | if (error == OTA_AUTH_ERROR) 245 | DEBUG_PRINTLN(F("ERROR: OTA auth failed")); 246 | else if (error == OTA_BEGIN_ERROR) 247 | DEBUG_PRINTLN(F("ERROR: OTA begin failed")); 248 | else if (error == OTA_CONNECT_ERROR) 249 | DEBUG_PRINTLN(F("ERROR: OTA connect failed")); 250 | else if (error == OTA_RECEIVE_ERROR) 251 | DEBUG_PRINTLN(F("ERROR: OTA receive failed")); 252 | else if (error == OTA_END_ERROR) 253 | DEBUG_PRINTLN(F("ERROR: OTA end failed")); 254 | }); 255 | ArduinoOTA.begin(); 256 | } 257 | 258 | /* 259 | Function called to handle OTA updates 260 | */ 261 | void handleOTA() { 262 | ArduinoOTA.handle(); 263 | } 264 | #endif 265 | 266 | /////////////////////////////////////////////////////////////////////////// 267 | // MQTT 268 | /////////////////////////////////////////////////////////////////////////// 269 | 270 | char MQTT_CLIENT_ID[7] = {0}; 271 | #if defined(MQTT_HOME_ASSISTANT_SUPPORT) 272 | char MQTT_CONFIG_TOPIC[sizeof(MQTT_HOME_ASSISTANT_DISCOVERY_PREFIX) + sizeof(MQTT_CLIENT_ID) + sizeof(MQTT_CONFIG_TOPIC_TEMPLATE) - 4] = {0}; 273 | #endif 274 | 275 | char MQTT_STATE_TOPIC[sizeof(MQTT_CLIENT_ID) + sizeof(MQTT_STATE_TOPIC_TEMPLATE) - 2] = {0}; 276 | char MQTT_COMMAND_TOPIC[sizeof(MQTT_CLIENT_ID) + sizeof(MQTT_COMMAND_TOPIC_TEMPLATE) - 2] = {0}; 277 | char MQTT_STATUS_TOPIC[sizeof(MQTT_CLIENT_ID) + sizeof(MQTT_STATUS_TOPIC_TEMPLATE) - 2] = {0}; 278 | 279 | volatile unsigned long lastMQTTConnection = MQTT_CONNECTION_TIMEOUT; 280 | 281 | /* 282 | Function called when a MQTT message has arrived 283 | @param p_topic The topic of the MQTT message 284 | @param p_payload The payload of the MQTT message 285 | @param p_length The length of the payload 286 | */ 287 | void handleMQTTMessage(char* p_topic, byte* p_payload, unsigned int p_length) { 288 | // concatenates the payload into a string 289 | String payload; 290 | for (uint8_t i = 0; i < p_length; i++) { 291 | payload.concat((char)p_payload[i]); 292 | } 293 | 294 | DEBUG_PRINTLN(F("INFO: New MQTT message received")); 295 | DEBUG_PRINT(F("INFO: MQTT topic: ")); 296 | DEBUG_PRINTLN(p_topic); 297 | DEBUG_PRINT(F("INFO: MQTT payload: ")); 298 | DEBUG_PRINTLN(payload); 299 | 300 | if (String(MQTT_COMMAND_TOPIC).equals(p_topic)) { 301 | if (payload.equals(String(MQTT_STATE_ON_PAYLOAD))) { 302 | if (sonoff.setState(true)) { 303 | DEBUG_PRINT(F("INFO: State changed to: ")); 304 | DEBUG_PRINTLN(sonoff.getState()); 305 | cmd = CMD_STATE_CHANGED; 306 | } 307 | } else if (payload.equals(String(MQTT_STATE_OFF_PAYLOAD))) { 308 | if (sonoff.setState(false)) { 309 | DEBUG_PRINT(F("INFO: State changed to: ")); 310 | DEBUG_PRINTLN(sonoff.getState()); 311 | cmd = CMD_STATE_CHANGED; 312 | } 313 | } 314 | } 315 | } 316 | 317 | /* 318 | Function called to subscribe to a MQTT topic 319 | */ 320 | void subscribeToMQTT(char* p_topic) { 321 | if (mqttClient.subscribe(p_topic)) { 322 | DEBUG_PRINT(F("INFO: Sending the MQTT subscribe succeeded for topic: ")); 323 | DEBUG_PRINTLN(p_topic); 324 | } else { 325 | DEBUG_PRINT(F("ERROR: Sending the MQTT subscribe failed for topic: ")); 326 | DEBUG_PRINTLN(p_topic); 327 | } 328 | } 329 | 330 | /* 331 | Function called to publish to a MQTT topic with the given payload 332 | */ 333 | void publishToMQTT(char* p_topic, char* p_payload) { 334 | if (mqttClient.publish(p_topic, p_payload, true)) { 335 | DEBUG_PRINT(F("INFO: MQTT message published successfully, topic: ")); 336 | DEBUG_PRINT(p_topic); 337 | DEBUG_PRINT(F(", payload: ")); 338 | DEBUG_PRINTLN(p_payload); 339 | } else { 340 | DEBUG_PRINTLN(F("ERROR: MQTT message not published, either connection lost, or message too large. Topic: ")); 341 | DEBUG_PRINT(p_topic); 342 | DEBUG_PRINT(F(" , payload: ")); 343 | DEBUG_PRINTLN(p_payload); 344 | } 345 | } 346 | 347 | /* 348 | Function called to connect/reconnect to the MQTT broker 349 | */ 350 | void connectToMQTT() { 351 | if (!mqttClient.connected()) { 352 | if (lastMQTTConnection + MQTT_CONNECTION_TIMEOUT < millis()) { 353 | if (mqttClient.connect(MQTT_CLIENT_ID, MQTT_USERNAME, MQTT_PASSWORD, MQTT_STATUS_TOPIC, 0, 1, "dead")) { 354 | DEBUG_PRINTLN(F("INFO: The client is successfully connected to the MQTT broker")); 355 | publishToMQTT(MQTT_STATUS_TOPIC, "alive"); 356 | 357 | #if defined(MQTT_HOME_ASSISTANT_SUPPORT) 358 | if (!sonoff.isDiscovered()) { 359 | sonoff.isDiscovered(true); 360 | // MQTT discovery for Home Assistant 361 | JsonObject& root = staticJsonBuffer.createObject(); 362 | root["name"] = FRIENDLY_NAME; 363 | root["state_topic"] = MQTT_STATE_TOPIC; 364 | root["command_topic"] = MQTT_COMMAND_TOPIC; 365 | root.printTo(jsonBuffer, sizeof(jsonBuffer)); 366 | publishToMQTT(MQTT_CONFIG_TOPIC, jsonBuffer); 367 | } 368 | #endif 369 | subscribeToMQTT(MQTT_COMMAND_TOPIC); 370 | } else { 371 | DEBUG_PRINTLN(F("ERROR: The connection to the MQTT broker failed")); 372 | DEBUG_PRINT(F("INFO: MQTT username: ")); 373 | DEBUG_PRINTLN(MQTT_USERNAME); 374 | DEBUG_PRINT(F("INFO: MQTT password: ")); 375 | DEBUG_PRINTLN(MQTT_PASSWORD); 376 | DEBUG_PRINT(F("INFO: MQTT broker: ")); 377 | DEBUG_PRINTLN(MQTT_SERVER); 378 | } 379 | lastMQTTConnection = millis(); 380 | } 381 | } 382 | } 383 | 384 | /////////////////////////////////////////////////////////////////////////// 385 | // CMD 386 | /////////////////////////////////////////////////////////////////////////// 387 | 388 | void handleCMD() { 389 | switch (cmd) { 390 | case CMD_NOT_DEFINED: 391 | break; 392 | #if defined(SAVE_STATE) 393 | case CMD_SAVE_STATE: 394 | cmd = CMD_NOT_DEFINED; 395 | saveConfig(); 396 | break; 397 | case CMD_BUTTON_STATE_CHANGED: 398 | cmd = CMD_STATE_CHANGED; 399 | sonoff.setState(!sonoff.getState()); 400 | break; 401 | #endif 402 | case CMD_STATE_CHANGED: 403 | #if defined(SAVE_STATE) 404 | cmd = CMD_SAVE_STATE; 405 | #else 406 | cmd = CMD_NOT_DEFINED; 407 | #endif 408 | if (sonoff.getState()) 409 | publishToMQTT(MQTT_STATE_TOPIC, MQTT_STATE_ON_PAYLOAD); 410 | else 411 | publishToMQTT(MQTT_STATE_TOPIC, MQTT_STATE_OFF_PAYLOAD); 412 | break; 413 | } 414 | } 415 | 416 | /////////////////////////////////////////////////////////////////////////// 417 | // SETUP() AND LOOP() 418 | /////////////////////////////////////////////////////////////////////////// 419 | 420 | void setup() { 421 | #if defined(DEBUG_SERIAL) 422 | Serial.begin(115200); 423 | #elif defined(DEBUG_TELNET) 424 | telnetServer.begin(); 425 | telnetServer.setNoDelay(true); 426 | #endif 427 | 428 | #if defined(SAVE_STATE) 429 | SPIFFS.begin(); 430 | loadConfig(); 431 | #else 432 | sonoff.init(); 433 | #endif 434 | 435 | setupWiFi(); 436 | 437 | #if defined(TLS) 438 | verifyFingerprint(); 439 | #endif 440 | 441 | sprintf(MQTT_CLIENT_ID, "%06X", ESP.getChipId()); 442 | 443 | #if defined(MQTT_HOME_ASSISTANT_SUPPORT) 444 | sprintf(MQTT_CONFIG_TOPIC, MQTT_CONFIG_TOPIC_TEMPLATE, MQTT_HOME_ASSISTANT_DISCOVERY_PREFIX, MQTT_CLIENT_ID); 445 | DEBUG_PRINT(F("INFO: MQTT config topic: ")); 446 | DEBUG_PRINTLN(MQTT_CONFIG_TOPIC); 447 | #endif 448 | 449 | sprintf(MQTT_STATE_TOPIC, MQTT_STATE_TOPIC_TEMPLATE, MQTT_CLIENT_ID); 450 | sprintf(MQTT_COMMAND_TOPIC, MQTT_COMMAND_TOPIC_TEMPLATE, MQTT_CLIENT_ID); 451 | sprintf(MQTT_STATUS_TOPIC, MQTT_STATUS_TOPIC_TEMPLATE, MQTT_CLIENT_ID); 452 | 453 | DEBUG_PRINT(F("INFO: MQTT state topic: ")); 454 | DEBUG_PRINTLN(MQTT_STATE_TOPIC); 455 | DEBUG_PRINT(F("INFO: MQTT command topic: ")); 456 | DEBUG_PRINTLN(MQTT_COMMAND_TOPIC); 457 | DEBUG_PRINT(F("INFO: MQTT status topic: ")); 458 | DEBUG_PRINTLN(MQTT_STATUS_TOPIC); 459 | 460 | mqttClient.setServer(MQTT_SERVER, MQTT_SERVER_PORT); 461 | mqttClient.setCallback(handleMQTTMessage); 462 | 463 | connectToMQTT(); 464 | 465 | #if defined(OTA) 466 | setupOTA(); 467 | #endif 468 | } 469 | 470 | void loop() { 471 | connectToMQTT(); 472 | mqttClient.loop(); 473 | 474 | yield(); 475 | 476 | handleCMD(); 477 | 478 | yield(); 479 | 480 | #if defined(DEBUG_TELNET) 481 | // handle the Telnet connection 482 | handleTelnet(); 483 | #endif 484 | 485 | yield(); 486 | 487 | #if defined(OTA) 488 | handleOTA(); 489 | #endif 490 | 491 | yield(); 492 | } 493 | -------------------------------------------------------------------------------- /Sonoff_TH/config.example.h: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////////// 2 | // MISC 3 | /////////////////////////////////////////////////////////////////////////// 4 | #define FRIENDLY_NAME "Sonoff TH" 5 | 6 | /////////////////////////////////////////////////////////////////////////// 7 | // WiFi 8 | /////////////////////////////////////////////////////////////////////////// 9 | #define WIFI_SSID "" 10 | #define WIFI_PASSWORD "" 11 | 12 | /////////////////////////////////////////////////////////////////////////// 13 | // MQTT 14 | /////////////////////////////////////////////////////////////////////////// 15 | #define MQTT_USERNAME "" 16 | #define MQTT_PASSWORD "" 17 | #define MQTT_SERVER "" 18 | #define MQTT_SERVER_PORT 1883 19 | 20 | #define MQTT_HOME_ASSISTANT_SUPPORT 21 | 22 | #if defined(MQTT_HOME_ASSISTANT_SUPPORT) 23 | // template: /switch//config, status, state or set 24 | #define MQTT_CONFIG_TOPIC_TEMPLATE "%s/switch/%s/config" 25 | #define MQTT_HOME_ASSISTANT_DISCOVERY_PREFIX "homeassistant" 26 | #endif 27 | 28 | #define MQTT_STATE_TOPIC_TEMPLATE "%s/switch/state" 29 | #define MQTT_COMMAND_TOPIC_TEMPLATE "%s/switch/set" 30 | #define MQTT_STATUS_TOPIC_TEMPLATE "%s/switch/status" // MQTT connection: alive/dead 31 | 32 | #define MQTT_STATE_ON_PAYLOAD "ON" 33 | #define MQTT_STATE_OFF_PAYLOAD "OFF" 34 | 35 | #define MQTT_CONNECTION_TIMEOUT 5000 36 | 37 | /////////////////////////////////////////////////////////////////////////// 38 | // DEBUG 39 | /////////////////////////////////////////////////////////////////////////// 40 | //#define DEBUG_SERIAL 41 | //#define DEBUG_TELNET 42 | 43 | #if defined(DEBUG_TELNET) 44 | #define DEBUG_TELNET_PORT 23 45 | #endif 46 | 47 | /////////////////////////////////////////////////////////////////////////// 48 | // OTA 49 | /////////////////////////////////////////////////////////////////////////// 50 | #define OTA 51 | //#define OTA_HOSTNAME "hostname" // hostname esp8266-[ChipID] by default 52 | //#define OTA_PASSWORD "password" // no password by default 53 | //#define OTA_PORT 8266 // port 8266 by default 54 | 55 | /////////////////////////////////////////////////////////////////////////// 56 | // TLS 57 | /////////////////////////////////////////////////////////////////////////// 58 | //#define TLS 59 | //#define TLS_FINGERPRINT "A5 02 FF 13 99 9F 8B 39 8E F1 83 4F 11 23 65 0B 32 36 FC 07" 60 | 61 | /////////////////////////////////////////////////////////////////////////// 62 | // RECORDING STATE 63 | /////////////////////////////////////////////////////////////////////////// 64 | #define SAVE_STATE 65 | -------------------------------------------------------------------------------- /Sonoff_TH/th.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SamZorSec/Itead_Sonoff/63f162f1a2f023e03f97979a82d4838addff9243/Sonoff_TH/th.jpg -------------------------------------------------------------------------------- /images/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SamZorSec/Itead_Sonoff/63f162f1a2f023e03f97979a82d4838addff9243/images/.DS_Store -------------------------------------------------------------------------------- /images/basic.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SamZorSec/Itead_Sonoff/63f162f1a2f023e03f97979a82d4838addff9243/images/basic.jpg -------------------------------------------------------------------------------- /images/th.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SamZorSec/Itead_Sonoff/63f162f1a2f023e03f97979a82d4838addff9243/images/th.jpg --------------------------------------------------------------------------------