-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathangular_smart_home.ino
370 lines (295 loc) · 12.9 KB
/
angular_smart_home.ino
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
/* .
* angular webhost with index, script.js and action.php
* supports JSONP if required.
* able to update via wifi and respond to alexa requests
* also serves a simple dash that lets you request actions.
* uses wifi manager to set up wifi connections.
* uses corrected isDST algorithm.
* does not use 3 strike attempt at time
*/
/* based on https://github.com/kakopappa/arduino-esp8266-alexa-multiple-wemo-switch */
#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>
#include <ESP8266mDNS.h>
#include <ESP8266HTTPUpdateServer.h>
#include <WiFiUdp.h>
#include <TimeLib.h>
#include <functional>
#include "switch.h"
#include "UpnpBroadcastResponder.h"
#include "CallbackFunction.h"
#include <DNSServer.h>
#include <WiFiManager.h> //https://github.com/tzapu/WiFiManager
/* my pages */
#include "index.html.h" // the angular container page
#include "script.js.h" // my scripts.
/* create dummy functions, to be overwritten by specifics.h if needed. */
void RunImplementationSetup(); //repeat this in specifics.h if needed.
void RunImplementationLoop(); //repeat this in specifics.h if needed.
void handleFeatures(); //repeat this in specifics.h if needed.
void setupForNewDay(); //overwritten after specifics.h
byte updateEventTimes(); //overwritten after specifics.h
void handleScript(); //overwritten after specifics.h
void handleRoot(); //overwritten after specifics.h
int indexOfNextEvent; //allows me to find the details of the next event due.
int minsToNextEvent; //tracks when the next event will happen.
// prototypes
boolean connectWifi();
boolean wifiConnected = false;
UpnpBroadcastResponder upnpBroadcastResponder;
Switch *first_device = NULL;
/*Switch *second_device = NULL;*/
ESP8266WebServer httpServer(80);
ESP8266HTTPUpdateServer httpUpdater;
//time setup
static const char ntpServerName[] = "us.pool.ntp.org";
int timeZoneOffset = 0; // GMT
WiFiUDP Udp;
unsigned int localPort = 8888; // local port to listen for UDP packets
time_t getNtpTime();
void sendNTPpacket(IPAddress &address);
bool isDst(int day, int month, int dow); //defined below
time_t prevDisplay = 0; // when the digital clock was displayed
String padDigit(int digit){
String padChar = "";
if (digit<10){
padChar = "0";
}
return padChar + digit;
}
#include "specifics.h"
void setupForNewDay() {
/* Purpose: Resets everything for the new day */
Serial.println(F(" Its a new day!"));
lastDayFromWeb = day();
//Now reset the Events' enacted flags to false, as they've not happened for this new day.
for (byte i = 1; i < EVENT_COUNT; i++) {
Serial.print(F(" Setting the active flag for "));
Serial.print(dailyEvents[i].label);
Serial.println(F(" to false"));
dailyEvents[i].enacted = false;
}
//Check to see if we are in DST.
thisDevice.dst = isDst(day(), month(), weekday()); //option base 0.
Serial.print(F(" DST is "));
Serial.println(thisDevice.dst ? "ON" : "OFF");
timeZoneOffset = (thisDevice.dst ? 1 : 0);
}
byte isEventTime(int currentMinuteOfDay) {
/* purpose: update indexOfNextEvent and minsToNextEvent, and return the index of the due event (if it exists.
method: compare against my global struct for daily events.
*/
bool eventDue = false;
bool eventOverdue = false;
byte retval = 0;
int lowestFoundLag = 24*60; //start point. Assume no events today.
Serial.print(F("\n Checking the scheduled events for minute "));
Serial.print(currentMinuteOfDay);
for (byte i = 1; i < EVENT_COUNT; i++) { // iterate the daily events
int minuteOfEvent = (dailyEvents[i].h * 60) + dailyEvents[i].m;
Serial.print(F("\n Event labelled "));
Serial.print(dailyEvents[i].label);
eventDue = dailyEvents[i].enacted == false
&&
currentMinuteOfDay == minuteOfEvent;
if (eventDue) {
Serial.print(F(": Due "));
indexOfNextEvent = i;
minsToNextEvent = 0;
retval = i; //have this value to return later.
} else {
if ((minuteOfEvent - currentMinuteOfDay) > 0) {
Serial.print(F(": Not due for "));
Serial.print(minuteOfEvent - currentMinuteOfDay);
Serial.print(F(" minute(s)."));
} else {
Serial.print(F(": Missed today. Next due tomorrow."));
minuteOfEvent = minuteOfEvent + (24 * 60); //look at tomorrow's instead.
}
if ((minuteOfEvent - currentMinuteOfDay) < lowestFoundLag){
lowestFoundLag = (minuteOfEvent - currentMinuteOfDay);
indexOfNextEvent = i;
minsToNextEvent = lowestFoundLag;
}
}
}
return retval;
}
void handleScript(){
Serial.print(F("\nScript request"));
httpServer.sendHeader("Cache-Control","max-age=2628000");
httpServer.send_P ( 200, "application/javascript", SCRIPT_JS);
Serial.println(F("...done."));
}
void handleRoot(){
Serial.print(F("\nHomepage request"));
httpServer.sendHeader("Cache-Control","max-age=2628000");
httpServer.send_P ( 200, "text/html", INDEX_HTML);
Serial.println(F("...done."));
}
void handleFeatures(){
Serial.println(F("Features request"));
bool usingCallback = (httpServer.hasArg("callback"));
String events = "";
String features =
"{\"address\":\"" + WiFi.localIP().toString() + "\""
+ ",\"app_name\":\"" + (String) AP_NAME + "\""
+ ",\"app_version\":\"" + (String) AP_VERSION + "\""
+ ",\"app_desc\":\"" + (String) AP_DESC + "\""
+ ",\"time_of_day\":\"" + padDigit(hour()) + ":" + padDigit(minute()) + ":" + padDigit(second()) + "\""
+ ",\"mode\":\"" + (thisDevice.operating_mode) + "\""
+ ",\"is_powered\":" + (thisDevice.powered ? "true" : "false")
+ (thisDevice.operating_mode == "percentage" ?
+ ",\"percentage\":" + (String) thisDevice.percentage
+ ",\"perc_label\":\"" + (String) thisDevice.perc_label + "\""
: "")
+ ",\"is_dst\":" + (thisDevice.dst ? "true" : "false")
+ ",\"is_using_timer\":" + (thisDevice.usingTimer ? "true" : "false")
+ ",\"next_event_due\":" + minsToNextEvent
+ ",\"next_event_name\":\"" + dailyEvents[indexOfNextEvent].label + "\""
+ ",\"is_skipping_next\":" + (thisDevice.skippingNext ? "true" : "false")
+ ",\"last_action\":\"" + thisDevice.lastAction + "\""
+ ",\"request\":{\"base_url\":\"action.php\",\"master_param\":\"master\""
+ (thisDevice.operating_mode == "percentage" ?
",\"start_param\":\"start\",\"end_param\":\"end\",\"duration_param\":\"duration\""
: "")
+ "}"
+ (String) ",\"events\":[";
//attempt to iterate.
for (byte i = 1; i < EVENT_COUNT; i++) {
if (events!= ""){ events += ",";}
events += "{\"time\":\"" + (String) dailyEvents[i].h + ":" + padDigit(dailyEvents[i].m) + "\",\"label\":\"" + dailyEvents[i].label + "\",\"enacted\":" + (dailyEvents[i].enacted ? "true" : "false") + "}";
}
features = features + events + "]}";
if(usingCallback){
httpServer.send(200, "text/javascript", httpServer.arg("callback") + "(" + features + ");");
Serial.println(F("Sending response with callback"));
}else{
httpServer.sendHeader("Access-Control-Allow-Origin","*");
httpServer.sendHeader("Server","ESP8266-AA");
httpServer.send(200, "application/json", features);
Serial.println(F("Sending response without callback"));
}
}
void setup(){
Serial.begin(115200);
// Initialise wifi connection
WiFiManager wifiManager;
wifiConnected = wifiManager.autoConnect(AP_NAME);
//wifiManager.startConfigPortal(AP_NAME); //use to test wifi config capture.
if(wifiConnected){
upnpBroadcastResponder.beginUdpMulticast();
// Define your switches here. Max 14
// Format: Alexa invocation name, local port no, on callback, off callback
first_device = new Switch(ALEXA_DEVICE_1, 81, FirstDeviceOn, FirstDeviceOff);
/*second_device = new Switch(ALEXA_DEVICE_2, 82, SecondDeviceOn, SecondDeviceOff);*/
//Serial.println("Adding switches upnp broadcast responder");
upnpBroadcastResponder.addDevice(*first_device);
/*upnpBroadcastResponder.addDevice(*second_device);*/
Serial.println(F("Registering mDNS host"));
MDNS.begin(AP_NAME);
httpUpdater.setup(&httpServer);
httpServer.on("/", handleRoot);
httpServer.on("/script.js", handleScript);
httpServer.on("/sync", [](){
while (timeStatus() == timeNotSet); //keep trying to set the time
httpServer.send(200, "text/plain", "sync'd");
});
httpServer.on("/action.php", handleAction);
httpServer.on("/features.json",handleFeatures);
httpServer.begin();
MDNS.addService("http", "tcp", 80);
Serial.printf("HTTPUpdateServer ready! Open http://%s.local/update in your browser\n", AP_NAME);
Serial.println(F("Looking for NTP time signal"));
Udp.begin(localPort);
Serial.print(F("Local port: "));
Serial.println(Udp.localPort());
Serial.println(F("waiting for sync"));
setSyncProvider(getNtpTime);
setSyncInterval(10 * 60);
Serial.println(F("Running implementation setup"));
RunImplementationSetup();
Serial.println(F("Entering loop"));
}
}
void loop(){
yield(); //let the ESP8266 do its background tasks.
//connect wifi if not connected
if (WiFi.status() != WL_CONNECTED) {
delay(1);
return;
}else{
upnpBroadcastResponder.serverLoop();
first_device->serverLoop();
/*second_device->serverLoop();*/
httpServer.handleClient();
}
RunImplementationLoop();
}
/*-------- NTP code ----------*/
const int NTP_PACKET_SIZE = 48; // NTP time is in the first 48 bytes of message
byte ntp_packetBuffer[NTP_PACKET_SIZE]; //buffer to hold incoming & outgoing packets
time_t getNtpTime(){
IPAddress ntpServerIP; // NTP server's ip address
while (Udp.parsePacket() > 0) ; // discard any previously received packets
Serial.println(F("Transmit NTP Request"));
// get a random server from the pool
WiFi.hostByName(ntpServerName, ntpServerIP);
Serial.print(ntpServerName);
Serial.print(": ");
Serial.println(ntpServerIP);
sendNTPpacket(ntpServerIP);
uint32_t beginWait = millis();
while (millis() - beginWait < 2000) {
int size = Udp.parsePacket();
if (size >= NTP_PACKET_SIZE) {
Serial.println(F("Receive NTP Response"));
Udp.read(ntp_packetBuffer, NTP_PACKET_SIZE); // read packet into the buffer
unsigned long secsSince1900;
// convert four bytes starting at location 40 to a long integer
secsSince1900 = (unsigned long)ntp_packetBuffer[40] << 24;
secsSince1900 |= (unsigned long)ntp_packetBuffer[41] << 16;
secsSince1900 |= (unsigned long)ntp_packetBuffer[42] << 8;
secsSince1900 |= (unsigned long)ntp_packetBuffer[43];
return 1 + secsSince1900 - 2208988800UL + timeZoneOffset * SECS_PER_HOUR; //1 is the adjustment for lag.
}
}
Serial.println(F("No NTP Response :-("));
return 0; // return 0 if unable to get the time
}
// send an NTP request to the time server at the given address
void sendNTPpacket(IPAddress &address){
// set all bytes in the buffer to 0
memset(ntp_packetBuffer, 0, NTP_PACKET_SIZE);
// Initialize values needed to form NTP request
// (see URL above for details on the packets)
ntp_packetBuffer[0] = 0b11100011; // LI, Version, Mode
ntp_packetBuffer[1] = 0; // Stratum, or type of clock
ntp_packetBuffer[2] = 6; // Polling Interval
ntp_packetBuffer[3] = 0xEC; // Peer Clock Precision
// 8 bytes of zero for Root Delay & Root Dispersion
ntp_packetBuffer[12] = 49;
ntp_packetBuffer[13] = 0x4E;
ntp_packetBuffer[14] = 49;
ntp_packetBuffer[15] = 52;
// all NTP fields have been given values, now
// you can send a packet requesting a timestamp:
Udp.beginPacket(address, 123); //NTP requests are to port 123
Udp.write(ntp_packetBuffer, NTP_PACKET_SIZE);
Udp.endPacket();
}
bool isDst(int day, int month, int dow) {
/* Purpose: works out whether or not we are in UK daylight savings time SUMMER TIME.
using day, month and dayOfWeek
Expects: day (1-31), month(0-11), day of week (0-6)
Returns: boolean true if DST
*/
month++; //too tricky to do zero based.
if (month < 3 || month > 10) return false; //jan,feb, nov, dec are NOT DST.
if (month > 3 && month < 10) return true; //apr,may,june,july,august,september IS DST.
//now the picky ones.
int previousSunday = day - dow;
if (month == 3) return previousSunday >= 25; //the most recent sunday was the last sunday in the month.
if (month == 10) return previousSunday < 25; //the most recent sunday was not the last sunday in the month.
return false; // something went wrong.
}