├── CallbackFunction.h ├── .gitignore ├── README.md ├── UpnpBroadcastResponder.h ├── webserver.html ├── LICENSE ├── Switch.h ├── UpnpBroadcastResponder.cpp ├── Switch.cpp └── AlexaESP8266WiFIRelay.ino /CallbackFunction.h: -------------------------------------------------------------------------------- 1 | #ifndef CALLBACKFUNCTION_H 2 | #define CALLBACKFUNCTION_H 3 | 4 | #include 5 | 6 | typedef bool (*CallbackFunction) (); 7 | 8 | #endif 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Compiled Object files 5 | *.slo 6 | *.lo 7 | *.o 8 | *.obj 9 | 10 | # Precompiled Headers 11 | *.gch 12 | *.pch 13 | 14 | # Compiled Dynamic libraries 15 | *.so 16 | *.dylib 17 | *.dll 18 | 19 | # Fortran module files 20 | *.mod 21 | *.smod 22 | 23 | # Compiled Static libraries 24 | *.lai 25 | *.la 26 | *.a 27 | *.lib 28 | 29 | # Executables 30 | *.exe 31 | *.out 32 | *.app 33 | 34 | # Visual Studio code 35 | .vscode -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AlexaESP8266WiFIRelay 2 | An Arduino code for a WiFi relay that can be controlled via a ESP8266, a button and Amazon Alexa 3 | 4 | ## Credits 5 | Used open source code: 6 | * Wemos Switch https://github.com/kakopappa/arduino-esp8266-alexa-multiple-wemo-switch (modified switch.h/.cpp) 7 | * ESP8266WiFi https://github.com/esp8266/Arduino 8 | * WiFiManager https://github.com/tzapu/WiFiManager 9 | * ArduinoJson https://github.com/bblanchon/ArduinoJson 10 | * pubsubclient https://github.com/knolleary/pubsubclient -------------------------------------------------------------------------------- /UpnpBroadcastResponder.h: -------------------------------------------------------------------------------- 1 | #ifndef UPNPBROADCASTRESPONDER_H 2 | #define UPNPBROADCASTRESPONDER_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include "Switch.h" 8 | 9 | class UpnpBroadcastResponder { 10 | private: 11 | WiFiUDP UDP; 12 | public: 13 | UpnpBroadcastResponder(); 14 | ~UpnpBroadcastResponder(); 15 | bool beginUdpMulticast(); 16 | void serverLoop(); 17 | void addDevice(Switch& device); 18 | }; 19 | 20 | #endif 21 | -------------------------------------------------------------------------------- /webserver.html: -------------------------------------------------------------------------------- 1 | 5 | 19 | 20 |

WiFi Relay control

21 |

State:

22 | 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Andreas Taske 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 | -------------------------------------------------------------------------------- /Switch.h: -------------------------------------------------------------------------------- 1 | #ifndef SWITCH_H 2 | #define SWITCH_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include "CallbackFunction.h" 9 | 10 | 11 | class Switch { 12 | private: 13 | ESP8266WebServer *server = NULL; 14 | WiFiUDP UDP; 15 | String serial; 16 | String persistent_uuid; 17 | String device_name; 18 | unsigned int localPort; 19 | CallbackFunction onCallback; 20 | CallbackFunction offCallback; 21 | bool switchStatus; 22 | 23 | void startWebServer(); 24 | void handleEventservice(); 25 | void handleUpnpControl(); 26 | void handleRoot(); 27 | void handleSetupXml(); 28 | public: 29 | Switch(); 30 | Switch(String alexaInvokeName, unsigned int port, CallbackFunction onCallback, CallbackFunction offCallback); 31 | ~Switch(); 32 | String getAlexaInvokeName(); 33 | void serverLoop(); 34 | void respondToSearch(IPAddress& senderIP, unsigned int senderPort); 35 | void sendRelayState(); 36 | void setSwitchStatus(bool status); 37 | void stop(); 38 | void start(); 39 | }; 40 | 41 | #endif 42 | -------------------------------------------------------------------------------- /UpnpBroadcastResponder.cpp: -------------------------------------------------------------------------------- 1 | #include "UpnpBroadcastResponder.h" 2 | #include "Switch.h" 3 | #include 4 | 5 | // Multicast declarations 6 | IPAddress ipMulti(239, 255, 255, 250); 7 | const unsigned int portMulti = 1900; 8 | char packetBuffer[UDP_TX_PACKET_MAX_SIZE]; 9 | 10 | #define MAX_SWITCHES 14 11 | Switch switches[MAX_SWITCHES] = {}; 12 | int numOfSwitchs = 0; 13 | 14 | //#define numOfSwitchs (sizeof(switches)/sizeof(Switch)) //array size 15 | 16 | //<> 17 | UpnpBroadcastResponder::UpnpBroadcastResponder(){ 18 | 19 | } 20 | 21 | //<> 22 | UpnpBroadcastResponder::~UpnpBroadcastResponder(){/*nothing to destruct*/} 23 | 24 | bool UpnpBroadcastResponder::beginUdpMulticast(){ 25 | boolean state = false; 26 | 27 | Serial.println("Begin multicast .."); 28 | 29 | if(UDP.beginMulticast(WiFi.localIP(), ipMulti, portMulti)) { 30 | Serial.print("Udp multicast server started at "); 31 | Serial.print(ipMulti); 32 | Serial.print(":"); 33 | Serial.println(portMulti); 34 | 35 | state = true; 36 | } 37 | else{ 38 | Serial.println("Connection failed"); 39 | } 40 | 41 | return state; 42 | } 43 | 44 | //Switch *ptrArray; 45 | 46 | void UpnpBroadcastResponder::addDevice(Switch& device) { 47 | Serial.print("Adding switch : "); 48 | Serial.print(device.getAlexaInvokeName()); 49 | Serial.print(" index : "); 50 | Serial.println(numOfSwitchs); 51 | 52 | switches[numOfSwitchs] = device; 53 | numOfSwitchs++; 54 | } 55 | 56 | void UpnpBroadcastResponder::serverLoop(){ 57 | int packetSize = UDP.parsePacket(); 58 | if (packetSize <= 0) 59 | return; 60 | 61 | IPAddress senderIP = UDP.remoteIP(); 62 | unsigned int senderPort = UDP.remotePort(); 63 | 64 | // read the packet into the buffer 65 | UDP.read(packetBuffer, packetSize); 66 | 67 | // check if this is a M-SEARCH for WeMo device 68 | String request = String((char *)packetBuffer); 69 | 70 | if(request.indexOf('M-SEARCH') >= 0) { 71 | // Issue https://github.com/kakopappa/arduino-esp8266-alexa-multiple-wemo-switch/issues/22 fix 72 | if((request.indexOf("urn:Belkin:device:**") > 0) || (request.indexOf("ssdp:all") > 0) || (request.indexOf("upnp:rootdevice") > 0)) { 73 | Serial.println("Got UDP Belkin Request.."); 74 | 75 | // int arrSize = sizeof(switchs) / sizeof(Switch); 76 | 77 | for(int n = 0; n < numOfSwitchs; n++) { 78 | Switch &sw = switches[n]; 79 | 80 | if (&sw != NULL) { 81 | sw.respondToSearch(senderIP, senderPort); 82 | } 83 | } 84 | } 85 | } 86 | } 87 | 88 | -------------------------------------------------------------------------------- /Switch.cpp: -------------------------------------------------------------------------------- 1 | #include "Switch.h" 2 | #include "CallbackFunction.h" 3 | 4 | //<> 5 | Switch::Switch() 6 | { 7 | Serial.println("default constructor called"); 8 | } 9 | //Switch::Switch(String alexaInvokeName,unsigned int port){ 10 | Switch::Switch(String alexaInvokeName, unsigned int port, CallbackFunction oncb, CallbackFunction offcb) 11 | { 12 | uint32_t chipId = ESP.getChipId(); 13 | char uuid[64]; 14 | sprintf_P(uuid, PSTR("38323636-4558-4dda-9188-cda0e6%02x%02x%02x"), 15 | (uint16_t)((chipId >> 16) & 0xff), 16 | (uint16_t)((chipId >> 8) & 0xff), 17 | (uint16_t)chipId & 0xff); 18 | 19 | serial = String(uuid); 20 | persistent_uuid = "Socket-1_0-" + serial + "-" + String(port); 21 | 22 | device_name = alexaInvokeName; 23 | localPort = port; 24 | onCallback = oncb; 25 | offCallback = offcb; 26 | 27 | startWebServer(); 28 | } 29 | 30 | //<> 31 | Switch::~Switch() { /*nothing to destruct*/} 32 | 33 | void Switch::serverLoop() 34 | { 35 | if (server != NULL) 36 | { 37 | server->handleClient(); 38 | delay(1); 39 | } 40 | } 41 | 42 | void Switch::stop(){ 43 | if(server != NULL){ 44 | server->stop(); 45 | delete server; 46 | server = NULL; 47 | } 48 | } 49 | 50 | void Switch::start(){ 51 | if(server == NULL){ 52 | startWebServer(); 53 | } 54 | } 55 | 56 | void Switch::startWebServer() 57 | { 58 | server = new ESP8266WebServer(localPort); 59 | 60 | server->on("/", [&]() { 61 | handleRoot(); 62 | }); 63 | 64 | server->on("/setup.xml", [&]() { 65 | handleSetupXml(); 66 | }); 67 | 68 | server->on("/upnp/control/basicevent1", [&]() { 69 | handleUpnpControl(); 70 | }); 71 | 72 | server->on("/eventservice.xml", [&]() { 73 | handleEventservice(); 74 | }); 75 | 76 | //server->onNotFound(handleNotFound); 77 | server->begin(); 78 | 79 | Serial.println("WebServer started on port: "); 80 | Serial.println(localPort); 81 | } 82 | 83 | void Switch::handleEventservice() 84 | { 85 | Serial.println(" ########## Responding to eventservice.xml ... ########\n"); 86 | 87 | String eventservice_xml = "" 88 | "" 89 | "" 90 | "SetBinaryState" 91 | "" 92 | "" 93 | "" 94 | "BinaryState" 95 | "BinaryState" 96 | "in" 97 | "" 98 | "" 99 | "" 100 | "" 101 | "GetBinaryState" 102 | "" 103 | "" 104 | "" 105 | "BinaryState" 106 | "BinaryState" 107 | "out" 108 | "" 109 | "" 110 | "" 111 | "" 112 | "" 113 | "" 114 | "BinaryState" 115 | "Boolean" 116 | "0" 117 | "" 118 | "" 119 | "level" 120 | "string" 121 | "0" 122 | "" 123 | "" 124 | "\r\n" 125 | "\r\n"; 126 | 127 | server->send(200, "text/plain", eventservice_xml.c_str()); 128 | } 129 | 130 | void Switch::handleUpnpControl() 131 | { 132 | Serial.println("########## Responding to /upnp/control/basicevent1 ... ##########"); 133 | 134 | //for (int x=0; x <= HTTP.args(); x++) { 135 | // Serial.println(HTTP.arg(x)); 136 | //} 137 | 138 | String request = server->arg(0); 139 | Serial.print("request:"); 140 | Serial.println(request); 141 | 142 | if (request.indexOf("SetBinaryState") >= 0) 143 | { 144 | if (request.indexOf("1") >= 0) 145 | { 146 | Serial.println("Got Turn on request"); 147 | switchStatus = onCallback(); 148 | 149 | sendRelayState(); 150 | } 151 | 152 | if (request.indexOf("0") >= 0) 153 | { 154 | Serial.println("Got Turn off request"); 155 | switchStatus = offCallback(); 156 | 157 | sendRelayState(); 158 | } 159 | } 160 | 161 | if (request.indexOf("GetBinaryState") >= 0) 162 | { 163 | Serial.println("Got binary state request"); 164 | sendRelayState(); 165 | } 166 | 167 | server->send(200, "text/plain", ""); 168 | } 169 | 170 | void Switch::handleRoot() 171 | { 172 | server->send(200, "text/plain", "You should tell Alexa to discover devices"); 173 | } 174 | 175 | void Switch::handleSetupXml() 176 | { 177 | Serial.println(" ########## Responding to setup.xml ... ########\n"); 178 | 179 | IPAddress localIP = WiFi.localIP(); 180 | char s[16]; 181 | sprintf(s, "%d.%d.%d.%d", localIP[0], localIP[1], localIP[2], localIP[3]); 182 | 183 | String setup_xml = "" 184 | "" 185 | "" 186 | "urn:Belkin:device:controllee:1" 187 | "" + 188 | device_name + "" 189 | "Belkin International Inc." 190 | "Socket" 191 | "3.1415" 192 | "Belkin Plugin Socket 1.0\r\n" 193 | "uuid:" + 194 | persistent_uuid + "" 195 | "221517K0101769" 196 | "0" 197 | "" 198 | "" 199 | "urn:Belkin:service:basicevent:1" 200 | "urn:Belkin:serviceId:basicevent1" 201 | "/upnp/control/basicevent1" 202 | "/upnp/event/basicevent1" 203 | "/eventservice.xml" 204 | "" 205 | "" 206 | "" 207 | "\r\n" 208 | "\r\n"; 209 | 210 | server->send(200, "text/xml", setup_xml.c_str()); 211 | 212 | Serial.print("Sending :"); 213 | Serial.println(setup_xml); 214 | } 215 | 216 | String Switch::getAlexaInvokeName() 217 | { 218 | return device_name; 219 | } 220 | 221 | void Switch::setSwitchStatus(bool status) 222 | { 223 | switchStatus = status; 224 | } 225 | 226 | void Switch::sendRelayState() 227 | { 228 | String body = 229 | "\r\n" 230 | "\r\n" 231 | ""; 232 | 233 | body += (switchStatus ? "1" : "0"); 234 | 235 | body += "\r\n" 236 | "\r\n" 237 | " \r\n"; 238 | 239 | server->send(200, "text/xml", body.c_str()); 240 | 241 | Serial.print("Sending :"); 242 | Serial.println(body); 243 | } 244 | 245 | void Switch::respondToSearch(IPAddress &senderIP, unsigned int senderPort) 246 | { 247 | Serial.println(""); 248 | Serial.print("Sending response to "); 249 | Serial.println(senderIP); 250 | Serial.print("Port : "); 251 | Serial.println(senderPort); 252 | 253 | IPAddress localIP = WiFi.localIP(); 254 | char s[16]; 255 | sprintf(s, "%d.%d.%d.%d", localIP[0], localIP[1], localIP[2], localIP[3]); 256 | 257 | String response = 258 | "HTTP/1.1 200 OK\r\n" 259 | "CACHE-CONTROL: max-age=86400\r\n" 260 | "DATE: Sat, 26 Nov 2016 04:56:29 GMT\r\n" 261 | "EXT:\r\n" 262 | "LOCATION: http://" + 263 | String(s) + ":" + String(localPort) + "/setup.xml\r\n" 264 | "OPT: \"http://schemas.upnp.org/upnp/1/0/\"; ns=01\r\n" 265 | "01-NLS: b9200ebb-736d-4b93-bf03-835149d13983\r\n" 266 | "SERVER: Unspecified, UPnP/1.0, Unspecified\r\n" 267 | "ST: urn:Belkin:device:**\r\n" 268 | "USN: uuid:" + 269 | persistent_uuid + "::urn:Belkin:device:**\r\n" 270 | "X-User-Agent: redsonic\r\n\r\n"; 271 | 272 | UDP.beginPacket(senderIP, senderPort); 273 | UDP.write(response.c_str()); 274 | UDP.endPacket(); 275 | 276 | Serial.println("Response sent !"); 277 | } 278 | -------------------------------------------------------------------------------- /AlexaESP8266WiFIRelay.ino: -------------------------------------------------------------------------------- 1 | /*** 2 | * by Andreas Taske (andreas@taske.de) 2018 3 | * 4 | * used exp8266 with wifi relay: 5 | * **/ 6 | 7 | // TODO: 8 | // - add relay control - DONE 9 | // - add control led - DONE 10 | // - add button that controls relay - DONE 11 | // - add wifimanager - DONE 12 | // - add spdiff settings store - DONE 13 | // - add mqtt client - DONE 14 | // - add webServer that controls relay - DONE 15 | // - add alexa belkin simulator - DONE 16 | // - add hue-bridge support 17 | // - save error to log file and display it in the webserver 18 | 19 | // includes 20 | #include 21 | #include // https://github.com/esp8266/Arduino 22 | #include 23 | #include 24 | #include 25 | #include // https://github.com/tzapu/WiFiManager 26 | #include // https://github.com/bblanchon/ArduinoJson 27 | #include 28 | #include 29 | #include 30 | #include "switch.h" // https://github.com/kakopappa/arduino-esp8266-alexa-multiple-wemo-switch 31 | #include "UpnpBroadcastResponder.h" 32 | #include "CallbackFunction.h" 33 | 34 | // Debugging 35 | boolean debug = true; 36 | 37 | // startup 38 | bool initialSetup = true; 39 | 40 | // LED 41 | const int ledPin = 2; 42 | 43 | // button 44 | #define PUSH 0x1 45 | #define SWITCH 0x0 46 | #define TOGGLE_SWITCH 0x2 47 | 48 | const int btnPin = 0; // also used to enable flashing mode 49 | const int btnType = TOGGLE_SWITCH; // PUSH, SWITCH or TOGGLE_SWITCH 50 | int lastBtnState = HIGH; // because the pins are pulled high 51 | int btnState; // the current reading from the input pin 52 | int btnStateChangeCount = 0; 53 | unsigned long lastDebounceTime = 0; // the last time the output pin was toggled 54 | unsigned long debounceDelay = 50; // the debounce time; increase if the output flickers 55 | unsigned long lastBtnChangedMillis = 0; 56 | 57 | // relay 58 | byte relCmdON[] = {0xA0, 0x01, 0x01, 0xA2}; // Hex command to send to serial for open relay 59 | byte relCmdOFF[] = {0xA0, 0x01, 0x00, 0xA1}; // Hex command to send to serial for close relay 60 | 61 | #define RELAY_ON 0x1 62 | #define RELAY_OFF 0x0 63 | int relayState = RELAY_OFF; 64 | 65 | // wifi 66 | char cfg_ap_name[14] = "ESPWiFiRelay"; 67 | 68 | // config 69 | char configFilename[14] = "/config.json"; 70 | 71 | // mqtt 72 | char cfg_mqtt_host[40] = "broker.mqttdashboard.com"; 73 | char cfg_mqtt_port[6] = "1883"; 74 | const char *inTopic = "controlRelay"; 75 | const char *outTopic = "relayStatus"; 76 | const int maxMqttRetries = 5; 77 | int mqttRetry = 0; 78 | 79 | WiFiClient wifiClient; 80 | PubSubClient mqttClient(wifiClient); 81 | 82 | // webServer 83 | ESP8266WebServer webServer(80); 84 | bool serverIsRunning = false; 85 | 86 | // spdiff 87 | bool shouldSaveConfig = false; // flag for saving data 88 | 89 | // wemos belkin simulator 90 | UpnpBroadcastResponder *upnpBroadcastResponder = NULL; 91 | Switch *wemosSwitch = NULL; 92 | char cfg_wemos_name[20] = "Deckenlampe"; // rename 93 | 94 | // Methods 95 | void ledBlinkTest(); 96 | void relayToggleTest(); 97 | bool turnRelayOn(); 98 | bool turnRelayOff(); 99 | void buttonLoop(); 100 | void mqttLoop(); 101 | void webServerLoop(); 102 | void reconnectMqttClient(); 103 | void toggleRelay(); 104 | void setupWifi(); 105 | void setupMqtt(); 106 | void setupWemos(); 107 | void setupWebServer(); 108 | void reset(); 109 | void wifiSaveConfigCallback(); 110 | void mqttCallback(char *topic, byte *payload, unsigned int length); 111 | void println(); 112 | void readConfig(); 113 | void saveConfig(); 114 | String getRelayState(); 115 | String restartEsp(); 116 | 117 | void setup() 118 | { 119 | // serial settings 120 | Serial.begin(9600); // 9600 because the relay module communicates with this baud rate 121 | 122 | // FOR DEBUG: wait serial monitor to settle down 123 | delay(1000); 124 | 125 | // Setup pins 126 | pinMode(ledPin, OUTPUT); 127 | pinMode(btnPin, INPUT); 128 | 129 | // Turn off 130 | turnRelayOff(); 131 | 132 | // do tests 133 | ledBlinkTest(); 134 | relayToggleTest(); 135 | 136 | // read config 137 | readConfig(); 138 | 139 | // wifi 140 | setupWifi(initialSetup); 141 | 142 | // webServer 143 | setupWebServer(); 144 | } 145 | 146 | void loop() 147 | { 148 | buttonLoop(); 149 | mqttLoop(); 150 | webServerLoop(); 151 | wemosLoop(); 152 | } 153 | 154 | void setupWemos() 155 | { 156 | upnpBroadcastResponder = new UpnpBroadcastResponder(); 157 | upnpBroadcastResponder->beginUdpMulticast(); 158 | 159 | // Define your switches here. Max 10 160 | // Format: Alexa invocation name, local port no, on callback, off callback 161 | wemosSwitch = new Switch(cfg_wemos_name, 8080, turnRelayOn, turnRelayOff); 162 | 163 | println("Adding switches upnp broadcast responder"); 164 | upnpBroadcastResponder->addDevice(*wemosSwitch); 165 | } 166 | 167 | void wemosLoop() 168 | { 169 | upnpBroadcastResponder->serverLoop(); 170 | wemosSwitch->serverLoop(); 171 | } 172 | 173 | void mqttLoop() 174 | { 175 | 176 | if (!mqttClient.connected()) 177 | { 178 | reconnectMqttClient(); 179 | } 180 | mqttClient.loop(); 181 | } 182 | 183 | void webServerLoop() 184 | { 185 | if (serverIsRunning) 186 | { 187 | webServer.handleClient(); 188 | } 189 | } 190 | 191 | void handleRoot() 192 | { 193 | webServer.send(200, "text/html", "

WiFi Relay control


State:

"); 194 | } 195 | 196 | void handleNotFound() 197 | { 198 | String message = "File Not Found\n\n"; 199 | message += "URI: "; 200 | message += webServer.uri(); 201 | message += "\nMethod: "; 202 | message += (webServer.method() == HTTP_GET) ? "GET" : "POST"; 203 | message += "\nArguments: "; 204 | message += webServer.args(); 205 | message += "\n"; 206 | for (uint8_t i = 0; i < webServer.args(); i++) 207 | { 208 | message += " " + webServer.argName(i) + ": " + webServer.arg(i) + "\n"; 209 | } 210 | webServer.send(404, "text/plain", message); 211 | } 212 | 213 | void setupWebServer() 214 | { 215 | webServer.on("/", handleRoot); 216 | 217 | webServer.on("/relay/on", []() { 218 | turnRelayOn(); 219 | webServer.send(200, "text/plain", "relay is on"); 220 | }); 221 | webServer.on("/relay/off", []() { 222 | turnRelayOff(); 223 | webServer.send(200, "text/plain", "relay is off"); 224 | }); 225 | webServer.on("/relay/toggle", []() { 226 | toggleRelay(); 227 | webServer.send(200, "text/plain", getRelayState()); 228 | }); 229 | webServer.on("/relay/state", []() { 230 | webServer.send(200, "text/plain", getRelayState()); 231 | }); 232 | webServer.on("/esp/restart", []() { 233 | webServer.send(200, "text/plain", restartEsp()); 234 | }); 235 | 236 | webServer.onNotFound(handleNotFound); 237 | } 238 | 239 | String getRelayState() 240 | { 241 | if (relayState == RELAY_ON) 242 | { 243 | return "relay is on"; 244 | } 245 | else 246 | { 247 | return "relay is off"; 248 | } 249 | } 250 | 251 | void stopWebServer() 252 | { 253 | if (!serverIsRunning) 254 | { 255 | return; 256 | } 257 | 258 | webServer.stop(); 259 | 260 | serverIsRunning = false; 261 | 262 | if (debug) 263 | { 264 | Serial.println("HTTP webServer stopped"); 265 | } 266 | } 267 | 268 | void startWebServer() 269 | { 270 | webServer.begin(); 271 | 272 | serverIsRunning = true; 273 | 274 | if (debug) 275 | { 276 | Serial.println("HTTP webServer started"); 277 | } 278 | } 279 | 280 | void reconnectMqttClient() 281 | { 282 | // Loop until we're reconnected 283 | while (!mqttClient.connected() && mqttRetry < maxMqttRetries) 284 | { 285 | mqttRetry++; 286 | 287 | if (debug) 288 | { 289 | Serial.print("Attempting MQTT connection..."); 290 | } 291 | 292 | // Create a random client ID 293 | String clientId = "ESP8266Client-"; 294 | clientId += String(random(0xffff), HEX); 295 | 296 | // Attempt to connect 297 | if (mqttClient.connect(clientId.c_str())) 298 | { 299 | if (debug) 300 | { 301 | Serial.println("connected"); 302 | } 303 | // Once connected, publish an announcement... 304 | mqttClient.publish(outTopic, "UNKNOWN"); 305 | // ... and resubscribe 306 | mqttClient.subscribe(inTopic); 307 | 308 | mqttRetry = 0; 309 | } 310 | else 311 | { 312 | 313 | if (mqttRetry >= maxMqttRetries) 314 | { 315 | if (debug) 316 | { 317 | Serial.println("mqtt connection failed. Check config."); 318 | Serial.print(cfg_mqtt_host); 319 | Serial.print(":"); 320 | Serial.println(cfg_mqtt_port); 321 | } 322 | } 323 | else 324 | { 325 | if (debug) 326 | { 327 | Serial.print("failed, rc="); 328 | Serial.print(mqttClient.state()); 329 | Serial.println(" try again in 5 seconds"); 330 | } 331 | 332 | // Wait 5 seconds before retrying 333 | delay(5000); 334 | } 335 | } 336 | } 337 | } 338 | 339 | void setupMqtt() 340 | { 341 | mqttRetry = 0; 342 | 343 | String port_str(cfg_mqtt_port); 344 | int port = port_str.toInt(); 345 | if (debug) 346 | { 347 | 348 | Serial.print("mqtt server"); 349 | Serial.println(cfg_mqtt_host); 350 | Serial.print("mqtt port"); 351 | Serial.println(port); 352 | } 353 | mqttClient.setServer(cfg_mqtt_host, port); 354 | mqttClient.setCallback(mqttCallback); 355 | } 356 | 357 | void mqttCallback(char *topic, byte *payload, unsigned int length) 358 | { 359 | if (debug) 360 | { 361 | Serial.print("Message arrived ["); 362 | Serial.print(topic); 363 | Serial.print("] "); 364 | 365 | for (int i = 0; i < length; i++) 366 | { 367 | Serial.print((char)payload[i]); 368 | } 369 | 370 | Serial.println(); 371 | } 372 | 373 | // TODO better conversion 374 | String payloadStr = ""; 375 | for (int i = 0; i < length; i++) 376 | { 377 | payloadStr += (char)payload[i]; 378 | } 379 | 380 | Serial.println(payloadStr); 381 | const char *p = payloadStr.c_str(); 382 | 383 | if (strcmp(topic, inTopic) == 0) 384 | { 385 | Serial.println("Found in topic"); 386 | if (strcmp(p, "ON") == 0 || strcmp(p, "1") == 0) 387 | { 388 | turnRelayOn(); 389 | } 390 | else if (strcmp(p, "OFF") == 0 || strcmp(p, "0") == 0) 391 | { 392 | turnRelayOff(); 393 | } 394 | else if (strcmp(p, "TOGGLE") == 0 || strcmp(p, "2") == 0) 395 | { 396 | toggleRelay(); 397 | } 398 | } 399 | } 400 | 401 | void println(const char c[]) 402 | { 403 | if (debug) 404 | { 405 | Serial.println(c); 406 | } 407 | } 408 | 409 | void readConfig() 410 | { 411 | bool success = false; 412 | 413 | if (SPIFFS.begin()) 414 | { 415 | if (SPIFFS.exists(configFilename)) 416 | { 417 | // file exists, reading and loading 418 | 419 | println("reading config file"); 420 | File configFile = SPIFFS.open(configFilename, "r"); 421 | 422 | if (configFile) 423 | { 424 | println("opened config file"); 425 | size_t size = configFile.size(); 426 | 427 | // Allocate a buffer to store contents of the file. 428 | std::unique_ptr buf(new char[size]); 429 | 430 | configFile.readBytes(buf.get(), size); 431 | DynamicJsonBuffer jsonBuffer; 432 | JsonObject &json = jsonBuffer.parseObject(buf.get()); 433 | 434 | if (debug) 435 | { 436 | json.printTo(Serial); 437 | } 438 | 439 | if (json.success()) 440 | { 441 | println("\nparsed json"); 442 | if (!json.containsKey("mqtt_host") || !json.containsKey("mqtt_port") || !json.containsKey("ap_name") || !json.containsKey("wemos_name")) 443 | { 444 | success = false; 445 | println("One key is missing in the config file."); 446 | } 447 | else 448 | { 449 | strcpy(cfg_mqtt_host, json["mqtt_host"]); 450 | strcpy(cfg_mqtt_port, json["mqtt_port"]); 451 | strcpy(cfg_ap_name, json["ap_name"]); 452 | strcpy(cfg_wemos_name, json["wemos_name"]); 453 | success = true; 454 | } 455 | } 456 | else 457 | { 458 | println("failed to load json config"); 459 | } 460 | } 461 | } 462 | } 463 | else 464 | { 465 | println("Failed to mount FS. Did you flash with the correct flash size? E.g. 1M (128k SPIFFS)"); 466 | } 467 | 468 | initialSetup = !success; 469 | } 470 | 471 | void saveConfig() 472 | { 473 | //save the custom parameters to FS 474 | println("saving config"); 475 | 476 | DynamicJsonBuffer jsonBuffer; 477 | JsonObject &json = jsonBuffer.createObject(); 478 | 479 | json["mqtt_host"] = cfg_mqtt_host; 480 | json["mqtt_port"] = cfg_mqtt_port; 481 | json["ap_name"] = cfg_ap_name; 482 | json["wemos_name"] = cfg_wemos_name; 483 | 484 | File configFile = SPIFFS.open("/config.json", "w"); 485 | if (!configFile) 486 | { 487 | println("failed to open config file for writing"); 488 | } 489 | 490 | if (debug) 491 | { 492 | json.printTo(Serial); 493 | } 494 | 495 | json.printTo(configFile); 496 | configFile.close(); 497 | } 498 | 499 | void setupWifi(boolean useConfigPortal) 500 | { 501 | // stop services 502 | mqttClient.disconnect(); 503 | stopWebServer(); 504 | 505 | // stop wemos 506 | if (upnpBroadcastResponder != NULL) 507 | { 508 | delete upnpBroadcastResponder; 509 | upnpBroadcastResponder = NULL; 510 | } 511 | if (wemosSwitch != NULL) 512 | { 513 | wemosSwitch->stop(); 514 | delete wemosSwitch; 515 | wemosSwitch = NULL; 516 | } 517 | 518 | // Local intialization. Once its business is done, there is no need to keep it around 519 | WiFiManager wifiManager; 520 | //set config save notify callback 521 | wifiManager.setSaveConfigCallback(wifiSaveConfigCallback); 522 | wifiManager.setDebugOutput(debug); 523 | 524 | // add custom parameters 525 | WiFiManagerParameter custom_text("

Enter the WiFi ssid and password. Use 'scan' to display network around you.

You can also rename the ap ssid

MQTT settings are optional.

"); 526 | WiFiManagerParameter back_link("

Back to home

"); 527 | WiFiManagerParameter mqtt_host("mqtt_host", "mqtt host", cfg_mqtt_host, 40); // id, placeholder, defaultValue, length, customAttr 528 | WiFiManagerParameter mqtt_port("mqtt_port", "mqtt port", cfg_mqtt_port, 6); 529 | WiFiManagerParameter ap_name("ap_name", "ap_name port", cfg_ap_name, 14); 530 | WiFiManagerParameter wemos_name("wemos_name", "Device name", cfg_wemos_name, 20); 531 | 532 | wifiManager.addParameter(&mqtt_host); 533 | wifiManager.addParameter(&mqtt_port); 534 | wifiManager.addParameter(&ap_name); 535 | wifiManager.addParameter(&wemos_name); 536 | wifiManager.addParameter(&custom_text); 537 | wifiManager.addParameter(&back_link); 538 | 539 | // one minute timeout 540 | // wifiManager.setTimeout(120); 541 | if (useConfigPortal) 542 | { 543 | println("Entering config portal"); 544 | 545 | wifiManager.startConfigPortal(cfg_ap_name); // TODO check why old ap name is used instead of cfg ap name 546 | } 547 | else 548 | { 549 | // try to autoconnect to last known configuration 550 | if (!wifiManager.autoConnect(cfg_ap_name)) 551 | { 552 | delay(3000); 553 | 554 | //reset whole device and try again, or maybe put it to deep sleep 555 | restartEsp(); 556 | } 557 | } 558 | 559 | println("Local ip"); 560 | if (debug) 561 | { 562 | Serial.println(WiFi.localIP()); 563 | } 564 | 565 | if (shouldSaveConfig) 566 | { 567 | strcpy(cfg_mqtt_host, mqtt_host.getValue()); 568 | strcpy(cfg_mqtt_port, mqtt_port.getValue()); 569 | strcpy(cfg_ap_name, ap_name.getValue()); 570 | strcpy(cfg_wemos_name, wemos_name.getValue()); 571 | 572 | saveConfig(); 573 | } 574 | 575 | // All done start services 576 | 577 | // webserver 578 | startWebServer(); 579 | 580 | // mqtt 581 | setupMqtt(); 582 | 583 | // wemos 584 | setupWemos(); 585 | } 586 | 587 | String restartEsp() 588 | { 589 | // restart esp 590 | ESP.reset(); 591 | 592 | // restart after x seconds 593 | delay(5000); 594 | 595 | return "ESP restarts"; 596 | } 597 | 598 | void wifiSaveConfigCallback() 599 | { 600 | // callback notifying us of the need to save config 601 | shouldSaveConfig = true; 602 | } 603 | 604 | void reset() 605 | { 606 | /** 607 | * NOT TESTED YET 608 | **/ 609 | 610 | // reset wifimanager 611 | WiFiManager wifiManager; 612 | wifiManager.resetSettings(); 613 | 614 | // format storage 615 | SPIFFS.format(); 616 | 617 | // clear EEPROM 618 | EEPROM.begin(512); // size 619 | for (int i = 0; i < 96; ++i) // position of ssid and password 620 | { 621 | EEPROM.write(i, 0); 622 | } 623 | EEPROM.commit(); 624 | 625 | // restart esp 626 | ESP.reset(); 627 | } 628 | 629 | void buttonLoop() 630 | { 631 | int currentBtnState = digitalRead(btnPin); 632 | 633 | // If the switch changed, due to noise or pressing: 634 | if (currentBtnState != lastBtnState) 635 | { 636 | // reset the debouncing timer 637 | lastDebounceTime = millis(); 638 | } 639 | 640 | unsigned long currentMillis = millis(); 641 | 642 | if ((currentMillis - lastDebounceTime) > debounceDelay) 643 | { 644 | // whatever the currentBtnState is at, it's been there for longer than the debounce 645 | // delay, so take it as the actual current state: 646 | 647 | // if the button state has changed: 648 | if (currentBtnState != btnState) 649 | { 650 | 651 | btnState = currentBtnState; 652 | 653 | // check if button state changes rapidly 10 times, enter ap mode 654 | if (currentMillis - lastBtnChangedMillis < 500) 655 | { 656 | btnStateChangeCount++; 657 | 658 | if (btnStateChangeCount > 10) 659 | { 660 | btnStateChangeCount = 0; 661 | setupWifi(true); 662 | } 663 | } 664 | else 665 | { 666 | btnStateChangeCount = 0; 667 | } 668 | 669 | lastBtnChangedMillis = currentMillis; 670 | 671 | // relay change 672 | if (btnType == SWITCH) 673 | { 674 | if (btnState == LOW) 675 | { 676 | turnRelayOn(); 677 | } 678 | else 679 | { 680 | turnRelayOff(); 681 | } 682 | } 683 | else if (btnType == PUSH) 684 | { 685 | // only toggle the relay if the new button state is HIGH 686 | if (btnState == LOW) 687 | { 688 | toggleRelay(); 689 | } 690 | } 691 | else // TOGGLE_SWITCH 692 | { 693 | toggleRelay(); 694 | } 695 | } 696 | } 697 | 698 | lastBtnState = currentBtnState; 699 | } 700 | 701 | void toggleRelay() 702 | { 703 | if (relayState == RELAY_OFF) 704 | { 705 | turnRelayOn(); 706 | } 707 | else 708 | { 709 | turnRelayOff(); 710 | } 711 | } 712 | 713 | void ledBlinkTest() 714 | { 715 | int speed = 250; 716 | 717 | // blink 5 times 718 | for (int i = 0; i < 1; ++i) 719 | { 720 | delay(speed); 721 | digitalWrite(ledPin, HIGH); 722 | delay(speed); 723 | digitalWrite(ledPin, LOW); 724 | } 725 | } 726 | 727 | bool turnRelayOn() 728 | { 729 | Serial.write(relCmdON, sizeof(relCmdON)); // turns the relay ON 730 | digitalWrite(ledPin, HIGH); 731 | relayState = RELAY_ON; 732 | 733 | if (debug) 734 | { 735 | Serial.println("RELAY_ON"); 736 | } 737 | 738 | // send status via mqtt 739 | mqttClient.publish(outTopic, "ON"); 740 | 741 | // set wemos status 742 | if (wemosSwitch != NULL) 743 | { 744 | wemosSwitch->setSwitchStatus(true); 745 | } 746 | 747 | // return value for wemos 748 | return true; 749 | } 750 | 751 | bool turnRelayOff() 752 | { 753 | Serial.write(relCmdOFF, sizeof(relCmdOFF)); // turns the relay OFF 754 | digitalWrite(ledPin, LOW); 755 | relayState = RELAY_OFF; 756 | 757 | if (debug) 758 | { 759 | Serial.println("RELAY_OFF"); 760 | } 761 | 762 | // send status via mqtt 763 | mqttClient.publish(outTopic, "OFF"); 764 | 765 | // set wemos status 766 | if (wemosSwitch != NULL) 767 | { 768 | wemosSwitch->setSwitchStatus(false); 769 | } 770 | 771 | // return value for wemos 772 | return false; 773 | } 774 | 775 | void relayToggleTest() 776 | { 777 | int speed = 1000; 778 | 779 | // blink 5 times 780 | for (int i = 0; i < 1; ++i) 781 | { 782 | delay(speed); 783 | turnRelayOn(); 784 | delay(speed); 785 | turnRelayOff(); 786 | } 787 | } 788 | --------------------------------------------------------------------------------