| 1 | |
| 2 | #include <TimeLib.h> |
| 3 | #include <Time.h> |
| 4 | #include <ESP8266WiFi.h> |
| 5 | #include <WiFiUdp.h> |
| 6 | #include <DNSServer.h> |
| 7 | #include <ESP8266WebServer.h> |
| 8 | #include <WiFiManager.h> |
| 9 | #include <ESP8266mDNS.h> |
| 10 | #include <FS.h> |
| 11 | |
| 12 | // prototytpes |
| 13 | String getDate(time_t when); |
| 14 | String getTime(time_t when); |
| 15 | |
| 16 | // The MOSFET that switches the appliance |
| 17 | #define MOSFET D1 |
| 18 | |
| 19 | #define MANAGER_AP "RemotePower" |
| 20 | #define CONFIG_PORT 80 |
| 21 | #define NTP_SERVER "1.uk.pool.ntp.org" |
| 22 | |
| 23 | #define DEVICE_ON HIGH |
| 24 | #define DEVICE_OFF LOW |
| 25 | #define DEFAULT_DELAY 10000 // 10 seconds |
| 26 | |
| 27 | |
| 28 | #define www_username "admin" |
| 29 | #define www_password "wibble" |
| 30 | |
| 31 | ESP8266WebServer server(CONFIG_PORT); |
| 32 | |
| 33 | unsigned long trigtime = 0; |
| 34 | unsigned long trighold = 0; |
| 35 | |
| 36 | // track the status of our output pin |
| 37 | bool mosfet_status = false; |
| 38 | |
| 39 | const unsigned int localPort = 2390; |
| 40 | IPAddress ntpServerIP; |
| 41 | |
| 42 | const int NTP_PACKET_SIZE = 48; // NTP time stamp is in the first 48 bytes of the message |
| 43 | |
| 44 | byte packetBuffer[ NTP_PACKET_SIZE]; //buffer to hold incoming and outgoing packets |
| 45 | |
| 46 | WiFiUDP udp; |
| 47 | |
| 48 | unsigned long ntp_lastset = 0; |
| 49 | unsigned long ntp_lasttry = 0; |
| 50 | |
| 51 | |
| 52 | |
| 53 | void output_set(bool on) |
| 54 | { |
| 55 | digitalWrite(MOSFET, on?DEVICE_ON:DEVICE_OFF); |
| 56 | mosfet_status = on; |
| 57 | } |
| 58 | |
| 59 | /* compose and send an NTP time request packet */ |
| 60 | void ntp_send() |
| 61 | { |
| 62 | if (ntpServerIP == INADDR_NONE) { |
| 63 | if (WiFi.hostByName(NTP_SERVER, ntpServerIP) == 1) { |
| 64 | if (ntpServerIP == IPAddress(1,0,0,0)) { |
| 65 | Serial.println("DNS lookup failed for " NTP_SERVER " try again later."); |
| 66 | ntpServerIP = INADDR_NONE; |
| 67 | return; |
| 68 | } |
| 69 | Serial.print("Got NTP server " NTP_SERVER " address "); |
| 70 | Serial.println(ntpServerIP); |
| 71 | } else { |
| 72 | Serial.println("DNS lookup of " NTP_SERVER " failed."); |
| 73 | return; |
| 74 | } |
| 75 | } |
| 76 | |
| 77 | ntp_lasttry = millis(); |
| 78 | memset(packetBuffer, 0, NTP_PACKET_SIZE); |
| 79 | // Initialize values needed to form NTP request |
| 80 | // (see URL above for details on the packets) |
| 81 | packetBuffer[0] = 0b11100011; // LI, Version, Mode |
| 82 | packetBuffer[1] = 0; // Stratum, or type of clock |
| 83 | packetBuffer[2] = 6; // Polling Interval |
| 84 | packetBuffer[3] = 0xEC; // Peer Clock Precision |
| 85 | // 8 bytes of zero for Root Delay & Root Dispersion |
| 86 | packetBuffer[12] = 49; |
| 87 | packetBuffer[13] = 0x4E; |
| 88 | packetBuffer[14] = 49; |
| 89 | packetBuffer[15] = 52; |
| 90 | |
| 91 | // all NTP fields have been given values, now |
| 92 | // you can send a packet requesting a timestamp: |
| 93 | udp.beginPacket(ntpServerIP, 123); //NTP requests are to port 123 |
| 94 | udp.write(packetBuffer, NTP_PACKET_SIZE); |
| 95 | udp.endPacket(); |
| 96 | |
| 97 | Serial.println("Sending NTP request"); |
| 98 | } |
| 99 | |
| 100 | /* request a time update from NTP and parse the result */ |
| 101 | time_t ntp_fetch() |
| 102 | { |
| 103 | while (udp.parsePacket() > 0); // discard old udp packets |
| 104 | ntp_send(); |
| 105 | |
| 106 | uint32_t beginWait = millis(); |
| 107 | |
| 108 | while (millis() - beginWait < 2500) { |
| 109 | int size = udp.parsePacket(); |
| 110 | if (size >= NTP_PACKET_SIZE) { |
| 111 | udp.read(packetBuffer, NTP_PACKET_SIZE); |
| 112 | |
| 113 | // this is NTP time (seconds since Jan 1 1900): |
| 114 | unsigned long secsSince1900 = packetBuffer[40] << 24 | packetBuffer[41] << 16 | packetBuffer[42] << 8 | packetBuffer[43]; |
| 115 | const unsigned long seventyYears = 2208988800UL; |
| 116 | time_t unixtime = secsSince1900 - seventyYears; |
| 117 | |
| 118 | ntp_lastset = millis(); |
| 119 | Serial.print("NTP update unixtime="); |
| 120 | Serial.println(unixtime); |
| 121 | return unixtime; |
| 122 | } |
| 123 | } |
| 124 | Serial.println("No NTP response"); |
| 125 | return 0; |
| 126 | } |
| 127 | |
| 128 | |
| 129 | String one_button_form(String target, String action, String blurb) |
| 130 | { |
| 131 | String out = ""; |
| 132 | out += "<form action=\"" + target + "\" method=\"POST\">"; |
| 133 | out += "<input type=hidden name=action value=\"" + action + "\">"; |
| 134 | out += "<input type=submit name=button value=\"" + blurb + "\">"; |
| 135 | out += "</form>\n"; |
| 136 | |
| 137 | return out; |
| 138 | } |
| 139 | |
| 140 | |
| 141 | /* HTTP page request for / */ |
| 142 | void handleRoot() |
| 143 | { |
| 144 | char mtime[16]; |
| 145 | int sec = millis() / 1000; |
| 146 | int mi = sec / 60; |
| 147 | int hr = mi / 60; |
| 148 | int day = hr / 24; |
| 149 | |
| 150 | snprintf(mtime, 16, "%dd %02d:%02d:%02d", day, hr % 24, mi % 60, sec % 60); |
| 151 | |
| 152 | if (server.hasArg("action")) { |
| 153 | String action = server.arg("action"); |
| 154 | |
| 155 | if (action == "on") { |
| 156 | output_set(DEVICE_ON); |
| 157 | trigtime = 0; |
| 158 | } else |
| 159 | if (action == "off") { |
| 160 | output_set(DEVICE_OFF); |
| 161 | trigtime = 0; |
| 162 | } else |
| 163 | if (action == "toggle") { |
| 164 | output_set(!mosfet_status); |
| 165 | trigtime = 0; |
| 166 | } else |
| 167 | if (action == "pulse") { |
| 168 | if (server.hasArg("dur")) { |
| 169 | trighold = server.arg("dur").toInt(); |
| 170 | } else { |
| 171 | trighold = 0; |
| 172 | } |
| 173 | if (trighold == 0) trighold = DEFAULT_DELAY; |
| 174 | trigtime = millis(); |
| 175 | output_set(DEVICE_OFF); |
| 176 | } |
| 177 | } |
| 178 | |
| 179 | String out = "<html>\ |
| 180 | <head>\ |
| 181 | <title>Remote Power</title>\ |
| 182 | <style>\ |
| 183 | body { background-color: #cccccc; font-family: Arial, Helvetica, Sans-Serif; }\ |
| 184 | </style>\ |
| 185 | </head>\ |
| 186 | <body>\ |
| 187 | <h1>Remote Power</h1>\ |
| 188 | <p>Uptime: " + (String)mtime + "</p>\n"; |
| 189 | |
| 190 | if (timeStatus() == timeSet) { |
| 191 | time_t when = now(); |
| 192 | out += "<p>Time now: " + getDate(when) + " " + getTime(when) + "</p>\n"; |
| 193 | } |
| 194 | |
| 195 | FSInfo fs_info; |
| 196 | if (SPIFFS.info(fs_info)) { |
| 197 | out += "<p>Onboard Flash disk: - Size:"+String(fs_info.totalBytes)+" Used:"+String(fs_info.usedBytes)+"</p>\n"; |
| 198 | } |
| 199 | |
| 200 | out += "<p>Powered device is "; |
| 201 | out += mosfet_status?"ON":"OFF"; |
| 202 | out += "("; |
| 203 | out += digitalRead(MOSFET) == HIGH?"HIGH":"LOW"; |
| 204 | out += ")"; |
| 205 | |
| 206 | if (!mosfet_status && trigtime != 0) { |
| 207 | out += " and is due to turn back ON in "; |
| 208 | out += (trigtime + trighold - millis() )/ 1000; |
| 209 | out += " seconds."; |
| 210 | } |
| 211 | |
| 212 | out += "</p>\n"; |
| 213 | |
| 214 | out += "<ul>\n"; |
| 215 | out += "<li><a href=\"/reset\">Reset Configuration</a>\n"; |
| 216 | out += "<li>" + one_button_form("/", "on", "Turn Device ON"); |
| 217 | out += "<li>" + one_button_form("/", "off", "Turn Device OFF"); |
| 218 | out += "<li>" + one_button_form("/", "toggle", "Toggle Device Status"); |
| 219 | out += "<li>" + one_button_form("/", "pulse", (String)"Turn Device OFF for " + (int)(DEFAULT_DELAY/1000) + (String)" seconds"); |
| 220 | |
| 221 | |
| 222 | out += "</ul>\n"; |
| 223 | out += "</body>\n</html>\n"; |
| 224 | |
| 225 | server.send( 200, "text/html", out); |
| 226 | } |
| 227 | |
| 228 | void handleNotFound() { |
| 229 | String out = "File Not found\n\n"; |
| 230 | server.send(404, "text/plain", out); |
| 231 | } |
| 232 | |
| 233 | // User wants to reset config |
| 234 | void handleReset() { |
| 235 | if (!server.authenticate(www_username, www_password)) |
| 236 | return server.requestAuthentication(); |
| 237 | |
| 238 | server.send(200, "text/plain", "Rebooting to config manager...\n\n"); |
| 239 | |
| 240 | WiFiManager wfm; |
| 241 | wfm.resetSettings(); |
| 242 | WiFi.disconnect(); |
| 243 | ESP.reset(); |
| 244 | delay(5000); |
| 245 | } |
| 246 | |
| 247 | void returnOK() { |
| 248 | server.send(200, "text/plain", ""); |
| 249 | } |
| 250 | |
| 251 | String getTime(time_t when) |
| 252 | { |
| 253 | String ans; |
| 254 | int h = hour(when); |
| 255 | int m = minute(when); |
| 256 | int s = second(when); |
| 257 | |
| 258 | if (h<10) ans += "0"; |
| 259 | ans += String(h) + ":"; |
| 260 | if (m<10) ans += "0"; |
| 261 | ans += String(m) + ":"; |
| 262 | if (s<10) ans += "0"; |
| 263 | ans += String(s); |
| 264 | |
| 265 | return ans; |
| 266 | } |
| 267 | |
| 268 | String getDate(time_t when) |
| 269 | { |
| 270 | String ans; |
| 271 | |
| 272 | ans += String(year(when)) + "-" + String(month(when)) + "-" + String(day(when)); |
| 273 | return ans; |
| 274 | } |
| 275 | |
| 276 | |
| 277 | void setup() { |
| 278 | // some serial, for debug |
| 279 | Serial.begin(115200); |
| 280 | |
| 281 | // The lock mechanism |
| 282 | pinMode(MOSFET, OUTPUT); |
| 283 | output_set(DEVICE_ON); |
| 284 | |
| 285 | Serial.println("Remote Power switcher"); |
| 286 | Serial.println("Test WiFi and enter manager mode."); |
| 287 | |
| 288 | WiFiManager wfm; |
| 289 | // Only wait in config mode for 3 minutes max |
| 290 | wfm.setConfigPortalTimeout(180); |
| 291 | // Try to connect to the old Ap for this long |
| 292 | wfm.setConnectTimeout(60); |
| 293 | // okay, lets try and connect... |
| 294 | wfm.autoConnect(MANAGER_AP); |
| 295 | |
| 296 | Serial.println("Entering normal operation mode."); |
| 297 | |
| 298 | // we have config and are running normally, setup web server |
| 299 | server.on( "/", handleRoot ); |
| 300 | server.on( "/reset", handleReset ); |
| 301 | server.onNotFound( handleNotFound ); |
| 302 | server.begin(); |
| 303 | |
| 304 | // advertise we exist via MDNS |
| 305 | if (!MDNS.begin("remotepower")) { |
| 306 | Serial.println("Error setting up MDNS responder."); |
| 307 | } else { |
| 308 | MDNS.addService("http", "tcp", 80); |
| 309 | } |
| 310 | |
| 311 | // enable internal flash |
| 312 | SPIFFS.begin(); |
| 313 | |
| 314 | Serial.println("Requesting time from network"); |
| 315 | udp.begin(localPort); |
| 316 | setSyncProvider(ntp_fetch); |
| 317 | |
| 318 | Serial.println("Remote power switch. Ready"); |
| 319 | } |
| 320 | |
| 321 | void loop() { |
| 322 | // put your main code here, to run repeatedly: |
| 323 | server.handleClient(); |
| 324 | |
| 325 | // a timed off state in progress |
| 326 | if (trigtime != 0) { |
| 327 | if (trigtime + trighold < millis()) { |
| 328 | trigtime = 0; |
| 329 | output_set(DEVICE_ON); |
| 330 | } |
| 331 | } |
| 332 | |
| 333 | // has ntp failed. do we need to try again? |
| 334 | if (ntp_lastset == 0 && ntp_lasttry + 300000 < millis()) { |
| 335 | Serial.println("Ask Time service to try again"); |
| 336 | setSyncProvider(ntp_fetch); |
| 337 | } |
| 338 | |
| 339 | } |