Remote DC power control with a WeMos D1 mini
[remote_power] / remote_power.ino
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 }