HomeWaterLeaksDetection
WebServer.cpp
Go to the documentation of this file.
1 #include "WebServer.h"
2 
4 
6  online = 0; // the server needs to be initialized first
7 }
8 
10  if (instance == NULL)
11  instance = new WebServer;
12  return instance;
13 }
14 
16  pinMode(SS_SD_CARD, OUTPUT);
17  pinMode(SS_ETHERNET, OUTPUT);
18  digitalWrite(SS_SD_CARD, HIGH); // SD Card not active
19  digitalWrite(SS_ETHERNET, HIGH); // Ethernet not active
20 
21  Ethernet.begin(mac); // set the mac address of the shield
22 
23  // shield not found
24  if (Ethernet.hardwareStatus() == EthernetNoHardware) {
25  #ifdef DEBUG
26  Serial.println("Ethernet shield was not found");
27  #endif
28  return 0;
29  }
30 
31  // ethernet cabel not connected
32  if (Ethernet.linkStatus() == LinkOFF) {
33  #ifdef DEBUG
34  Serial.println("Ethernet cable is not connected.");
35  #endif
36  return 0;
37  }
38  return 1; // the ethernet shield has been initialized successfully
39 }
40 
41 void WebServer::setup(LeaksController *leaksController) {
42  online = 0;
43 
44  // store the leaks controller so it can be
45  // update simultaneously with the webpage loading
46  // off the SD card
47  this->leaksController = leaksController;
48 
49  // initialize the ethernet shield
50  if (!setEthernet())
51  return;
52 
53  // start the server
54  server.begin();
55  // print out the IP address
56  #ifdef DEBUG
57  Serial.print("server is at ");
58  Serial.println(Ethernet.localIP());
59  #endif
60 
61  // check if the SD card is working
62  if (!SD.begin(SS_SD_CARD)) {
63  #ifdef DEBUG
64  Serial.println("ERROR - SD card initialization failed!");
65  #endif
66  return;
67  }
68  #ifdef DEBUG
69  Serial.println("SUCCESS - SD card initialized.");
70  #endif
71 
72  // check it there's an index.html
73  // file (webpage) stored on the SD card
74  if (!SD.exists(INDEX_FILE_NAME)) {
75  #ifdef DEBUG
76  Serial.print(INDEX_FILE_NAME);
77  Serial.println(" doesn't exist.");
78  #endif
79  return;
80  }
81  #ifdef DEBUG
82  Serial.print(INDEX_FILE_NAME);
83  Serial.println(" exists.");
84  #endif
85 
86  digitalWrite(SS_SD_CARD, HIGH); // SD Card not active
87  online = 1; // the server is up and running
88 }
89 
91  htmlDataSources.push_back(source);
92 }
93 
95  // don't do anything if the
96  // server is not active
97  if (!online)
98  return;
99 
100  pinMode(SS_SD_CARD, OUTPUT);
101  pinMode(SS_ETHERNET, OUTPUT);
102  digitalWrite(SS_SD_CARD, HIGH); // SD Card not active
103  digitalWrite(SS_ETHERNET, HIGH); // Ethernet not active
104 
105  String httpRequest = "";
106  String httpRequestStart = "";
107 
108  EthernetClient client = server.available();
109 
110  int counter = 0;
111  int state = 0;
112 
113  if (client) {
114  bool currentLineIsBlank = true;
115  while (client.connected()) { // check if the client is connected
116  if (client.available()) { // and if it's available
117  char c = client.read(); // read one character sent from the client
118  switch (state) {
119  case 0:
120  // read first n bytes which are supposed to be "GET "
121  if (counter < (int)String(HTTP_RQ_GET_START).length()) {
122  counter++;
123  httpRequestStart += c;
124  }
125  // once you've read the first n bytes (4),
126  // check if the message says "GET "
127  if (counter == (int)String(HTTP_RQ_GET_START).length()) {
128  if (httpRequestStart == HTTP_RQ_GET_START)
129  state = 2; // "GET " has been found, move on to processing the request
130  else state = 1; // keep looking for "GET "
131  }
132  break;
133  case 1:
134  // seek the start of the actual HTTP request ("GET ")
135  httpRequestStart += c; // add a new character
136  httpRequestStart.remove(0); // remove the first character
137  // check if the string is "GET "
138  if (httpRequestStart == HTTP_RQ_GET_START)
139  state = 2; // "GET " has been found, move on to processing the request
140  break;
141  case 2:
142  if (c == ' ')
143  state = -1;
144  else {
145  httpRequest += c;
146  // check if the HTTP request exceeded 160B (max size)
147  if ((int)httpRequestStart.length() > MAX_HTTP_RQ_LENGTH) {
148  state = -1;
149  httpRequestStart = "";
150  }
151  }
152  break;
153  }
154  // reply to the client
155  // HTTP header (200 OK) + the content of the website
156  if (c == '\n' && currentLineIsBlank) {
157  client.println("HTTP/1.1 200 OK");
158  client.println("Content-Type: text/html");
159  client.println("Connection: close");
160  client.println();
161 
162  webFile = SD.open(INDEX_FILE_NAME); // open the index.html file
163  if (webFile) {
164  while (webFile.available()) {
165  // keep updating the water-leak detection
166  // algorithms while the HTML content is
167  // being read off the SD card
169 
170  buff = webFile.readStringUntil('\n'); // read nother line of the HTML file
171  client.println(replaceHTMLWithData(buff)); // insert data into the line and send it to the client
172  }
173  webFile.close(); // close the file
174 
175  } else {
176  #ifdef DEBUG
177  Serial.print("error opening ");
178  Serial.println(INDEX_FILE_NAME);
179  #endif
180  }
181  break;
182  }
183  if (c == '\n')
184  currentLineIsBlank = true;
185  else if (c != '\r')
186  currentLineIsBlank = false;
187  }
188  }
189  delay(1); // delay 1ms
190  client.stop(); // dissconet the client
191 
192  // analyze the HTTP request sent from the client
193  processHTTPRequest(httpRequest);
194  }
195 }
196 
197 int WebServer::processHTTPRequest(String &httpRequest) {
198  // print out the HTTP request
199  #ifdef DEBUG
200  Serial.print("HTTP Request: ");
201  Serial.println(httpRequest);
202  Serial.print("Size of the HTTP request: ");
203  Serial.print(httpRequest.length());
204  Serial.println(" B");
205  #endif
206 
207  bool acceptedHttpRequest = 0; // indication if the HTTP request is valid
208 
209  // check if the HTTP request is a valid request
210  // for changing the current settings of the water-leak
211  // detection algorithms
212  int startPos = httpRequest.indexOf(HTTP_RQ_SETTINGS);
213  if (startPos != -1) {
214  acceptedHttpRequest = processHTTPRequestSettings(startPos, httpRequest, leaksController->getNumberOfLeakDetections() * 4);
215  }
216 
217  // check if the HTTP request is a valid request
218  // for changing the current settings the e-mail
219  // notifications
220  #ifdef EMAIL_NOTIFICATION
221  if (acceptedHttpRequest == 0) {
222  startPos = httpRequest.indexOf(HTTP_RQ_NOTIFICATION);
223  if (startPos != -1) {
224  acceptedHttpRequest = processHTTPRequestNotification(startPos, httpRequest, EmailSender::Type::COUNT + 1);
225  }
226  }
227  #endif
228 
229  // if the HTTP request has been accepted
230  // store the settings on the SD card
231  if (acceptedHttpRequest)
232  saveSettings();
233  return acceptedHttpRequest;
234 }
235 
237  File file; // the config file on the SD card
238 
239  // get all the settings to be stored on the SD card
240  String data = leaksController->getFormatOfSettingsToSave() + "\n" +
242 
243  #ifdef DEBUG
244  Serial.println("saving settings...");
245  Serial.println("raw data:");
246  Serial.println(data);
247  #endif
248 
249  // first of all, store it all into a backup file
250  file = SD.open(BACKUP_SETTINGS_FILE_NAME, FILE_WRITE);
251  file.println(data);
252  file.close();
253 
254  // then erase the actual config file
255  // and store the new settings into it
256  SD.remove(SETTINGS_FILE_NAME);
257  file = SD.open(SETTINGS_FILE_NAME, FILE_WRITE);
258  file.println(data);
259  file.close();
260 
261  // lastly remove the backup file
262  SD.remove(BACKUP_SETTINGS_FILE_NAME);
263 
264  #ifdef DEBUG
265  Serial.println("settings saved successfully...");
266  #endif
267 }
268 
269 #ifdef EMAIL_NOTIFICATION
270 int WebServer::processHTTPRequestNotification(int startPos, String &httpRequest, int numberOfValues) {
271  int numberOfSemicolons = 0; // total number of semicolons found so far
272  int length = httpRequest.length(); // length of the HTTP request
273  String data[numberOfValues]; // tokens (individual parameters)
274  data[0] = ""; // the first initialization of the first parameter
275 
276  for (int i = startPos + String(HTTP_RQ_NOTIFICATION).length(); i < length; i++) {
277  if (httpRequest[i] == ';') {
278  // check what you have after ';' has been found (end of a parameter) validate it
279  // if it's empty or it contains more than one character ('1' or '0')
280  // discard it (the last one is an exception since it's an e-mail address)
281  if (data[numberOfSemicolons] == "" || (numberOfSemicolons != numberOfValues - 1 && data[numberOfSemicolons].length() > 1))
282  return 0;
283 
284  // check if all the parameters have been read
285  if (++numberOfSemicolons == numberOfValues) {
286  #ifndef UNIT_TEST
297  #endif
298  return 1; // the HTTP request is valid
299  }
300  data[numberOfSemicolons] = ""; // initialize the next parameter
301  continue;
302  }
303  data[numberOfSemicolons] += httpRequest[i]; // add another character to the current parameter
304  }
305  return 0; // the HTTP request is NOT valid
306  }
307 #endif
308 
309 int WebServer::processHTTPRequestSettings(int startPos, String &httpRequest, int numberOfValues) {
310  int numberOfSemicolons = 0; // total number of semicolons found so far
311  int length = httpRequest.length(); // length of the HTTP request
312  String data[numberOfValues]; // tokens (individual parameters as strings)
313  time_t values[numberOfValues]; // tokens (individual parameters as numbers)
314  data[0] = ""; // the first initialization of the first parameter
315 
316  for (int i = startPos + String(HTTP_RQ_SETTINGS).length(); i < length; i++) {
317  // the list of allowed characters is only '0'-'9' and ';'
318  // if an invalid character has been found discard the HTTP request
319  if (!((httpRequest[i] >= '0' && httpRequest[i] <= '9') || httpRequest[i] == HTTP_RQ_SEPARATOR))
320  return 0;
321 
322  // another separator of the parameters has been found
323  if (httpRequest[i] == ';') {
324  if (data[numberOfSemicolons] == "") //;; in a row (empty parameter)
325  return 0;
326 
327  // all the parameters have been found
328  if (++numberOfSemicolons == numberOfValues) {
329  // check if all the parameters are positive
330  // integers fitting into the data type
331  for (int j = 0; j < numberOfValues; j++) {
332  values[j] = strtoul(data[j].c_str(), NULL, 10);
333  if (values[j] == OUT_OF_RANGE_ERR || values[j] == 0)
334  return 0; // invalid number found
335  }
336  // do not apply any changes if it has been
337  // called as a part of unit testing
338  #ifndef UNIT_TEST
339  // high-water leak detection algorithm
340  leaksController->changeSettings(ALeakDetectable::High, LITER_TO_PULSE(values[0]), values[1], false);
342  // low-water leak detection algorithm
343  leaksController->changeSettings(ALeakDetectable::Low, values[4], values[5], false);
344  leaksController->changeSettings(ALeakDetectable::Low, values[6], values[7], true);
345  // total-water leak detection algorithms
347  leaksController->changeSettings(ALeakDetectable::Total, LITER_TO_PULSE(values[10]), values[11], true);
348  #endif
349  return 1; // the HTTP request is valid
350  }
351  data[numberOfSemicolons] = ""; // initialize the next parameter
352  continue;
353  }
354  data[numberOfSemicolons] += httpRequest[i]; // add another character to the current parameter
355  }
356  return 0; // the HTTP request is NOT valid
357 }
358 
359 const String WebServer::replaceHTMLWithData(String html) const {
360  int start; // start of the unique sequence "%*120*%"
361  int end; // end of the unique sequence "%*120*%"
362  int key; // the id of the data
363  String htmlData = HTMLDataSource::UNDEFINED_DATA; // "UNDEFINED"
364  String sub; // the id as a string (it'll be yank out)
365  bool found; // flag if the data associated with the id has been found
366 
367  // while there's is something to be replaced "%*120*%"
368  while ((start = html.indexOf(DATA_PREF)) != -1) {
369  // find the start and last index of
370  // the sequence - "%*120*%". Everything in between
371  // has to be the id, e.g. 120
372  start += 2;
373  for (end = start; html[end] != DATA_POST[0]; end++)
374  ;
375 
376  sub = html.substring(start, end);
377  key = sub.toInt(); // get the id of the data
378  String old = DATA_PREF + sub + DATA_POST; // the entire string to be replaced
379 
380  // find the associated value for the id
381  for (auto source : htmlDataSources) {
382  htmlData = source->getHTMLData(key);
383  found = false;
384  if (htmlData != HTMLDataSource::UNDEFINED_DATA) {
385  html.replace(old, htmlData);
386  found = true;
387  break;
388  }
389  }
390  // if the value has not been found
391  // replace it with "UNDEFINED" insted
392  if (found == false)
393  html.replace(old, htmlData);
394  }
395  return html;
396 }
EmailSender::CHANGED_SETTINGS
@ CHANGED_SETTINGS
when the user changes settings
Definition: EmailSender.h:51
ALeakDetectable::Total
@ Total
total water leak detection algorithm
Definition: ALeakDetectable.h:60
HTTP_RQ_SETTINGS
#define HTTP_RQ_SETTINGS
begining of an HTTP request for changing settings
Definition: WebServer.h:22
INDEX_FILE_NAME
#define INDEX_FILE_NAME
file on the SD card containing the HTML website
Definition: WebServer.h:18
EmailSender::BYPASS
@ BYPASS
when a bypass changes
Definition: EmailSender.h:53
EmailSender::getFormatOfSettingsToSave
String getFormatOfSettingsToSave()
Returns settings.
Definition: EmailSender.cpp:248
WebServer::saveSettings
void saveSettings()
Saves settings on the SD card.
Definition: WebServer.cpp:236
WebServer::webFile
File webFile
file
Definition: WebServer.h:42
SS_SD_CARD
#define SS_SD_CARD
SD card pin (arduino documentation)
Definition: Setup.h:30
ALeakDetectable::Low
@ Low
low water leak detection algorithm
Definition: ALeakDetectable.h:58
DATA_PREF
#define DATA_PREF
start of a sequence to be replaces with an actual value
Definition: WebServer.h:15
LeaksController::getNumberOfLeakDetections
int getNumberOfLeakDetections() const
Return the number of water leak detection algorithm.
Definition: LeaksController.cpp:37
WebServer::buff
String buff
buffer for parsing an HTTP request
Definition: WebServer.h:43
EmailSender::enableNotification
void enableNotification(Type type, bool state)
Enables/disables the particular type of e-mail notification.
Definition: EmailSender.cpp:43
SETTINGS_FILE_NAME
#define SETTINGS_FILE_NAME
the main settings file stored on the SD card
Definition: Setup.h:27
WebServer::processHTTPRequest
int processHTTPRequest(String &httpRequest)
Processes an HTTP request.
Definition: WebServer.cpp:197
HTTP_RQ_GET_START
#define HTTP_RQ_GET_START
the very start of t
Definition: WebServer.h:21
EmailSender::BOOTING
@ BOOTING
when the system boots up
Definition: EmailSender.h:45
HTTP_RQ_SEPARATOR
#define HTTP_RQ_SEPARATOR
separator between individual parameters in an HTTP request
Definition: WebServer.h:24
ALeakDetectable::High
@ High
high water leak detection algorithm
Definition: ALeakDetectable.h:59
WebServer::htmlDataSources
std::vector< HTMLDataSource * > htmlDataSources
collection of classing providing data for the HTML content
Definition: WebServer.h:39
EmailSender::setReceiverEmailAddress
void setReceiverEmailAddress(String email)
Changes the user's e-mail address.
Definition: EmailSender.cpp:83
HTTP_RQ_NOTIFICATION
#define HTTP_RQ_NOTIFICATION
begining of an HTTP request for changing e-mail notifications
Definition: WebServer.h:23
WebServer::leaksController
LeaksController * leaksController
instance of LeaksController so it can be updated with each line read off the file
Definition: WebServer.h:44
WebServer::server
EthernetServer server
instance of EthernetServer listening on port 80
Definition: WebServer.h:41
WebServer::setEthernet
int setEthernet()
Sets up the ethernet shield.
Definition: WebServer.cpp:15
EmailSender::LEAK_DETECTED
@ LEAK_DETECTED
when a leak has been detected
Definition: EmailSender.h:47
HTMLDataSource::UNDEFINED_DATA
static const String UNDEFINED_DATA
string "UNDEFINED"
Definition: HTMLDataSource.h:17
WebServer::addHTMLSource
void addHTMLSource(HTMLDataSource *source)
Adds another source of data for the HTML content.
Definition: WebServer.cpp:90
WebServer::update
void update() override
Updates the class.
Definition: WebServer.cpp:94
WebServer
Definition: WebServer.h:34
time_t
unsigned long time_t
Referring to the data type unsigned long as time_t.
Definition: DateTime.h:20
LeaksController::update
void update() override
Updates the class.
Definition: LeaksController.cpp:94
WebServer::online
bool online
flag if the server is up and running
Definition: WebServer.h:45
WebServer::processHTTPRequestSettings
int processHTTPRequestSettings(int startPos, String &httpRequest, int numberOfValues)
Processes an HTTP request for changing settings of the water leak detection algorithms.
Definition: WebServer.cpp:309
OUT_OF_RANGE_ERR
#define OUT_OF_RANGE_ERR
maximum integer values allowed to be passed to Arduino
Definition: WebServer.h:19
SS_ETHERNET
#define SS_ETHERNET
etherent pin (arduino documentation)
Definition: Setup.h:31
EmailSender::VALVE_STATE
@ VALVE_STATE
when the state of the valve has changed
Definition: EmailSender.h:48
EmailSender::RESET
@ RESET
when the device resets
Definition: EmailSender.h:49
EmailSender::APPLIED_NEW_SETTING
@ APPLIED_NEW_SETTING
when the new settings are applied
Definition: EmailSender.h:52
WebServer.h
LeaksController::getFormatOfSettingsToSave
String getFormatOfSettingsToSave()
Returns all settings.
Definition: LeaksController.cpp:84
HTMLDataSource
Definition: HTMLDataSource.h:15
WebServer::getInstance
static WebServer * getInstance()
Returns the instance of the class.
Definition: WebServer.cpp:9
LeaksController
Definition: LeaksController.h:67
LITER_TO_PULSE
#define LITER_TO_PULSE(l)
Converts liters to pulses.
Definition: LimitsDefinition.h:12
EmailSender::DAILY_OVERVIEW
@ DAILY_OVERVIEW
daily overview about the system
Definition: EmailSender.h:46
MAX_HTTP_RQ_LENGTH
#define MAX_HTTP_RQ_LENGTH
max size of an HTTP request (160B)
Definition: WebServer.h:20
WebServer::instance
static WebServer * instance
the instance of the class
Definition: WebServer.h:38
EmailSender::ALARM
@ ALARM
when the state of the home alarm changes
Definition: EmailSender.h:50
DATA_POST
#define DATA_POST
end of a sequence to be replaces with an actual value
Definition: WebServer.h:16
WebServer::replaceHTMLWithData
const String replaceHTMLWithData(String html) const
Inserts a piece of data into a line of the HTML content.
Definition: WebServer.cpp:359
BACKUP_SETTINGS_FILE_NAME
#define BACKUP_SETTINGS_FILE_NAME
temporary backup file for the settings
Definition: Setup.h:28
WebServer::mac
byte mac[6]
MAC address of the Ethernet shield.
Definition: WebServer.h:40
LeaksController::changeSettings
void changeSettings(ALeakDetectable::Type type, unsigned long detected, unsigned long reset, bool alarm)
Changes settings of a water leak detection algorithm.
Definition: LeaksController.cpp:51
EmailSender::getInstance
static EmailSender * getInstance()
Returns the instance of the class.
Definition: EmailSender.cpp:87
WebServer::WebServer
WebServer()
Constructor of the class.
Definition: WebServer.cpp:5
WebServer::setup
void setup(LeaksController *leaksController)
Sets the pointer to an instance of LeaksController.
Definition: WebServer.cpp:41