Martin Smaha
Published © GPL3+

Weather Aware Sprinkler Controller

6 station Photon controller uses the free OpenWeatherMap API to prevent watering when windy, rainy, or too cold.

IntermediateFull instructions provided4 hours5,369
Weather Aware Sprinkler Controller

Things used in this project

Hardware components

Photon
Particle Photon
×1
JBtek 8 Channel DC 5V Relay Module
×1

Story

Read more

Schematics

Sprinkler Control

Basic Wiring Schematic

Code

SprinklerControl.ino

C/C++
Main control file
/*
 * Project SprinklerControl
 * Description: Home Sprinkler System with weather modification
 * Author: Martin Smaha
 * Date: 8/21/18
 */
#include "SprinklerControl.h"

//STARTUP(WiFi.selectAntenna(ANT_EXTERNAL));//uncomment if using external antenna

TParameters Parameters;
bool sprinklers[8],manual[8],restart;
int lastMinute,DOW,Minute,Hour,Second,StateA,StateB,MinutesA,MinutesB,ZoneA,ZoneB,SystemState,WiFiRSSI,ManualMinutes;

// reset the system after 60 seconds if the application is unresponsive
ApplicationWatchdog wd(60000, System.reset);

// setup() runs once, when the device is first turned on.
void setup() {
  memset(&sprinklers[0],0,sizeof(sprinklers));
  memset(&manual[0],0,sizeof(manual));
  StateA = 0;
  StateB = 0;
  SystemState = -1;
  EEPROM.get(0, Parameters);
  if (Parameters.Magic != 0xA5A5) {
    Parameters.Magic = 0xA5A5;
    Parameters.isOperational = false;
    for (int i = 0; i < 6; i++) {
      Parameters.A_minutes[i] = 0;
      Parameters.B_minutes[i] = 0;
    }
    for (int i = 0; i < 7; i++) {
      strcpy(Parameters.A_hhmm[i],"off");
      strcpy(Parameters.B_hhmm[i],"off");
    }
    Parameters.isRain = true;//disable during Rain
    Parameters.isWind = true;//disable during wind
    Parameters.wind_mph = 10;
    Parameters.A_temp = 70;
    Parameters.B_temp = 80;
    EEPROM.put(0, Parameters);
  }
  // setup digital IO for zone control
  pinMode(D0, OUTPUT);
  pinMode(D1, OUTPUT);
  pinMode(D2, OUTPUT);
  pinMode(D3, OUTPUT);
  pinMode(D4, OUTPUT);
  pinMode(D5, OUTPUT);
  digitalWrite(D0, HIGH); //high is off
  digitalWrite(D1, HIGH);
  digitalWrite(D2, HIGH);
  digitalWrite(D3, HIGH);
  digitalWrite(D4, HIGH);
  digitalWrite(D5, HIGH);
  // start listening for clients
  WebInit();
  WeatherInit();
  lastMinute = Time.minute();
  Particle.variable("WiFiRSSI", WiFiRSSI);
  Time.zone(-8);//local time PST
  restart = true;
  ManualMinutes = 0;
}

// loop() runs over and over again, as quickly as it can execute.
void loop() {
  WiFiRSSI = WiFi.RSSI();
  Hour = Time.hour();
  Minute = Time.minute();
  Second = Time.second();
  DOW = Time.weekday() - 1;//we use 0-6 for their 1-7
  restart = false;
  if (Minute != lastMinute) {
    restart = true;
    WeatherMinute();
    MinutesA++;
    MinutesB++;
    if (Hour == 6 && Minute == 0) {//at 0600 each day
      high_temp = 0;//reset daily peak temperature
      temp_f = 0;//reset daily peak temperature
      isRain = false;//reset rain flag
      if (WiFi.ready() && Particle.connected()) Particle.syncTime();//and resync with time service
    }
    if (manual[0] || manual[1] || manual[2] || manual[3] || manual[4] || manual[5]){
      ManualMinutes++;
      if (ManualMinutes >= 5) {
        memset(manual,0,sizeof(manual));
      }
    } else {
      ManualMinutes = 0;
    }
  }
  lastMinute = Minute;
  WebTask(restart);
  ScheduleTask();
  UpdateZones();
  WeatherTask();
}

void SprinklersOff(void) {
  memset(sprinklers,0,sizeof(sprinklers));
}

void UpdateZones(void) {
  digitalWrite(D0, (sprinklers[0] || manual[0])?LOW:HIGH); //high is off
  digitalWrite(D1, (sprinklers[1] || manual[1])?LOW:HIGH);
  digitalWrite(D2, (sprinklers[2] || manual[2])?LOW:HIGH);
  digitalWrite(D3, (sprinklers[3] || manual[3])?LOW:HIGH);
  digitalWrite(D4, (sprinklers[4] || manual[4])?LOW:HIGH);
  digitalWrite(D5, (sprinklers[5] || manual[5])?LOW:HIGH);
}

void ScheduleTask(void) {
  if (Parameters.isOperational) {
    SystemState = 1;
    if (Parameters.isWind && wind_mph >= Parameters.wind_mph) {
      SystemState = 2;
      SprinklersOff();
      StateA = 0;
      StateB = 0;
    } else if (Parameters.isRain && isRain) {
      SystemState = 3;
      SprinklersOff();
      StateA = 0;
      StateB = 0;
    } else {
      ProgramTaskA();
      ProgramTaskB();
    }
  } else {
    SystemState = 0;
  }
}

void ProgramTaskA(void) {//entered once each minute when seconds are 0
  switch (StateA) {
    case 0: //look for start
      if (high_temp >= Parameters.A_temp && isDigit(Parameters.A_hhmm[DOW][0])) {
        int t = atoi(Parameters.A_hhmm[DOW]);//get 4 digit time
        int hr = t/100;
        int mn = t%100;
        if (Hour == hr && Minute == mn){
          MinutesA = 0;
          ZoneA = -1;
          StateA = 1;
          StartZoneA();
        }
      }
      break;
    case 1: //run for Duration
      if (MinutesA >= Parameters.A_minutes[ZoneA]) {
        MinutesA = 0;
        SprinklersOff();
        StartZoneA();//next zone
      }
      break;
  }
}

void StartZoneA(void) {
  for (ZoneA++; ZoneA < 6; ZoneA++) {
    if (Parameters.A_minutes[ZoneA] > 0) {
      sprinklers[ZoneA] = true;
      break;
    }
  }
  if (ZoneA >= 6) {
    StateA = 0;//all done
  }
}
void ProgramTaskB(void) {//entered once each minute when seconds are 0
  switch (StateB) {
    case 0: //look for start
      if (high_temp >= Parameters.B_temp && isDigit(Parameters.B_hhmm[DOW][0])) {
        int t = atoi(Parameters.B_hhmm[DOW]);//get 4 digit time
        int hr = t/100;
        int mn = t%100;
        if (Hour == hr && Minute == mn){
          MinutesB = 0;
          ZoneB = -1;
          StateB = 1;
          StartZoneB();
        }
      }
      break;
    case 1: //run for Duration
      if (MinutesB >= Parameters.B_minutes[ZoneB]) {
        MinutesB = 0;
        SprinklersOff();
        StartZoneB();//next zone
      }
      break;
  }
}

void StartZoneB(void) {
  for (ZoneB++; ZoneB < 6; ZoneB++) {
    if (Parameters.B_minutes[ZoneB] > 0) {
      sprinklers[ZoneB] = true;
      break;
    }
  }
  if (ZoneB >= 6) {
    StateB = 0;//all done
  }
}

SprinklerControl.h

C Header File
Include file
/*
 * Project SprinklerControl
 * Description: Home Sprinkler System with weather modification
 * Author: Martin Smaha
 * Date: 6/22/18
 * File: Project Includes
 */

typedef struct {
  int Magic;//should be 0xAA55
  bool isOperational;//sprinker system is operational
  int A_minutes[6];//minutes per zone
  int B_minutes[6];
  char A_hhmm[7][6];//daily start times in 24hr notation
  char B_hhmm[7][6];
  bool isRain;//disable during Rain
  bool isWind;//disable during wind
  int wind_mph;
  int A_temp;
  int B_temp;
} TParameters;

extern TParameters Parameters;
extern char icon_url[],weather_string[],observation_time[],WeatherBuf[];
extern float temp_f,wind_mph,precip_today_in,high_temp;
extern bool sprinklers[],manual[],isRain;
extern int DOW,Minute,Hour,Second,StateA,StateB,MinutesA,MinutesB,ZoneA,ZoneB,SystemState;

extern void WebInit(void);
extern void WebTask(bool restart);
extern void WeatherInit(void);
extern void WeatherMinute(void);
extern void WeatherTask(void);

WeatherClient.cpp

C/C++
Weather API interface
/*
 * Project SprinklerControl
 * Description: Home Sprinkler System with weather modification
 * Author: Martin Smaha
 * Date: 8/21/18
 * File: Weather Client
 */
#include "SprinklerControl.h"
#include "application.h"

static TCPClient WeatherClient;
char WeatherBuf[4096],icon_url[128],weather_string[64],observation_time[64];
float temp_f,wind_mph,precip_today_in,high_temp;
int WeatherState,WeatherTimer,WeatherBufCount,TZOffset;
bool isRain;

void WeatherTask(void);
void ParseWeather(void);
void ParseFloat(const char* key, float* value);
void ParseInt(const char* key, int* value);
void ParseString(const char* key, char* value);

int WReset(String extra) {
  WeatherState = 3;
  return 0;
}

void WeatherInit(void) {
  WeatherState = 3;
  WeatherTimer = 0;
  isRain = false;

  Particle.variable("Weather", WeatherState);
  Particle.variable("iconurl",icon_url);
  Particle.function("WReset", WReset);

}

void WeatherMinute(void) {
  WeatherTimer++;
}

// Parse Weather into the values we need
void ParseWeather(void) {
  char buf[32];
  ParseFloat("temp",&temp_f);
  ParseFloat("speed",&wind_mph);
  precip_today_in = 0.0;
  memset(weather_string,0,sizeof(weather_string));
  ParseString("main",weather_string);
  memset(icon_url,0,sizeof(icon_url));
  memset(buf,0,sizeof(buf));
  ParseString("icon",buf);
  sprintf(icon_url,"http://openweathermap.org/img/w/%s.png",buf);
  /* these parameters are not available at openweathermap
  memset(observation_time,0,sizeof(observation_time));
  ParseString("observation_time",observation_time);
  strcpy(observation_time,observation_time+5);//remove the word "Last"
  ParseString("local_tz_offset",buf);
  TZOffset = atoi(buf)/100;
  if (TZOffset == -8) {
    Time.endDST();
  }
  if (TZOffset == -7) {
    Time.beginDST();
  }
*/
  if (temp_f > high_temp) {
    high_temp = temp_f;
  }
  if ((strcmp(weather_string,"Rain") == 0) || precip_today_in > 0.02) {
    isRain = true;
  }
}

// ParseFloat is a simple single level finder
void ParseFloat(const char* key, float* value) {
  char* p = strstr(WeatherBuf,key);
  if (p) {
    p = strstr(p,":");
    if (p) {
      *value = atof(p+1);
    }
  }
}
// ParseInt is a simple single level finder
void ParseInt(const char* key, int* value) {
  char* p = strstr(WeatherBuf,key);
  if (p) {
    p = strstr(p,":");
    if (p) {
      *value = atoi(p+1);
    }
  }
}
// ParseString is a simple single level finder
void ParseString(const char* key, char* value) {
  char keyword[32];
  memset(keyword,0,32);
  sprintf(keyword,"\"%s\"",key);
  char* p = strstr(WeatherBuf,keyword);
  if (p) {
    p = strstr(p,":");
    if (p) {
      p = strstr(p+1,"\"");
      if (p) {
        p++;
        char *p2 = strstr(p,"\"");
        if (p2) {
          strncpy(value,p,p2-p);
        }
      }
    }
  }
}

// Get current weather conditions every 10 minutes
void WeatherTask(void) {
  if (!WiFi.ready()){
    WeatherTimer = 0;
    WeatherState = 3;
    return;// go to 3 when WiFi returns
  } else {
  switch (WeatherState) {
    case 0: // connect to server
      if (WeatherClient.connect("api.openweathermap.org", 80)) {
        WeatherClient.println("GET /data/2.5/weather?id=5338783&units=imperial&APPID=your_key_here"); //by station example
        WeatherClient.println("Host: api.openweathermap.org");
        WeatherClient.println("Content-Length: 0");
        WeatherClient.println();
        WeatherBuf[0] = 0;
        WeatherBufCount = 0;
        WeatherState = 1;
      }
      break;
    case 1: // get returned data
      if (WeatherClient.connected()) {
        if (WeatherClient.available()) { //get each data segment
          char buf[128];
          int n = WeatherClient.read((uint8_t*)buf,sizeof(buf)-1);
          if ((WeatherBufCount + n) <= sizeof(WeatherBuf)) { //prevent array overflow
            buf[n] = 0;
            strcat(WeatherBuf,buf);
            WeatherBufCount += n;
          } else {
            WeatherClient.stop();//force closed
            WeatherState = 3;
          }
        }
        if (WeatherTimer >= 2) {//timeout if response is taking too long
          WeatherClient.stop();//force closed
          WeatherState = 3;
        }
      } else {
        WeatherState = 2;
      }
      break;
    case 2: // parse data
      if (strstr(WeatherBuf,"404 Not Found")) {
        WeatherTimer = 8;//try again sooner
      } else {
        ParseWeather();
      }
      WeatherState = 3;
      break;
    case 3: // wait 15 minutes and update
      if (WeatherTimer >= 15) {
        WeatherTimer = 0;
        WeatherState = 0;
      }
      break;
  }
  }
}

WebServer.cpp

C/C++
User interface
/*
 * Project SprinklerControl
 * Description: Home Sprinkler System with weather modification
 * Author: Martin Smaha
 * Date: 8/21/18
 * File: Web Server
 */
#include "SprinklerControl.h"
#include "application.h"

static TCPServer server = TCPServer(8080);//web page port
static TCPClient WebClient;
static char LANBuf[4096];
static int WebState,ContentLength,ContentStart,LANBufCount,next_hhmm,current_hhmm;
static const char *dowString[] = {"SUN","MON","TUE","WED","THU","FRI","SAT"};

void ParseWeb(void);
void WebServer(void);
void WebTask(void);
void ParseForm(void);
void ParseManual(void);

int MyReset(String extra) {
  System.reset();
  return 0;
}
int WebReset(String extra) {
  WebState = 3;
  return 0;
}

void WebInit(void) {
  WebState = 0;
  LANBufCount = 0;
  ContentLength = 0;
  ContentStart = 0;
  server.begin();
  Particle.variable("Web", WebState);
  Particle.function("MyReset", MyReset);
  Particle.function("WebReset", WebReset);

}

// Web Header
void WebHeader(void) {
  WebClient.print("HTTP/1.1 200 OK\r\n");
  WebClient.print("Cache-Control: max-age=0, no-cache\r\n");
  WebClient.print("Pragma: no-cache\r\n");
  WebClient.print("Connection: close\r\n\r\n");
  WebClient.print("<html><head><title>Sprinkler System</title><style>div {border: 2px solid #a1a1a1;padding: 10px 20px;width: 300px;border-radius: 25px;}button {width: 100px;height: 40px;background: blue;color: white;}button:hover {color: black;}</style>");
  WebClient.print("<meta http-equiv=\"Cache-Control\" content=\"no-cache, no-store, must-revalidate\" />");
  WebClient.print("<meta http-equiv=\"Pragma\" content=\"no-cache\" />");
  WebClient.print("<meta http-equiv=\"Expires\" content=\"0\" />");
  WebClient.print("<meta content='text/html; charset=ISO-8859-1' http-equiv='Content-Type'>");
  WebClient.print("<meta name='viewport' content='width=device-width, initial-scale=1'></head>");
  WebClient.print("<body><div>");
}

void WebFooter(void) {
  WebClient.print("</div></body></html>");
}

// Web server
void ParseWeb(void) {
  if (WebClient.connected()) {
    char buf[256];
    int isRequest = 0;
    if (strstr(LANBuf,"GET /status.html") != NULL) {
        isRequest = 1;
    } else if (strstr(LANBuf,"GET /manual.html") != NULL) {
        isRequest = 2;
    } else if (strstr(LANBuf,"GET /schedule.html") != NULL) {
        isRequest = 3;
    } else if (strstr(LANBuf,"POST /schedule.cgi") != NULL) {
        ParseForm();
        isRequest = 3;
    } else if (strstr(LANBuf,"GET /manual.cgi") != NULL) {
        ParseManual();
        isRequest = 2;
    } else if (strstr(LANBuf,"GET /weather.html") != NULL) {
        isRequest = 4;
    }
    //send response
    if (isRequest == 0) { //standard http error message for page not found
      WebClient.print("HTTP/1.1 404 \r\n\r\n");
      WebClient.stop();
    } else if (isRequest == 1) { //status.html
      WebHeader();
      //Header
      WebClient.print("<H1>Sprinkler System</H1>");
      //Current Weather
      sprintf(buf,"<table><tr><td><a href=weather.html><b>Current Weather</b></a></td><td><img src=\"%s\"</td></tr>",icon_url);
      WebClient.print(buf);
      sprintf(buf,"<tr><td>Weather:</td><td>%s</td></tr>",weather_string);
      WebClient.print(buf);
      sprintf(buf,"<tr><td>Temperature:</td><td>%.1f F</td></tr><tr><td>Wind Speed:</td><td>%.1f mph</td></tr>",temp_f,wind_mph);//todays rain removed 8/21/18
      WebClient.print(buf);
      sprintf(buf,"<tr><td>Today's High:</td><td>%.1f F</td></tr></table>",high_temp);
      WebClient.print(buf);
      sprintf(buf,"<font>%s</font><br>",observation_time);
      WebClient.print(buf);
      sprintf(buf,"<font>Time is: %s %02d:%02d:%02d</font>",dowString[DOW],Hour,Minute,Second);
      WebClient.print(buf);
      //current running program
      if (StateA > 0) {
        sprintf(buf,"<hr><font>Program A is Running: Zone %d</font>",ZoneA+1);
        WebClient.print(buf);
      } else if (StateB > 0) {
        sprintf(buf,"<hr><font>Program B is Running: Zone %d</font>",ZoneB+1);
        WebClient.print(buf);
      } else {
        switch (SystemState) {
          case -1:
            WebClient.print("<hr>System is Initializing");
            break;
          case 0:
            WebClient.print("<hr>System is DISABLED");
            break;
          case 1:
            WebClient.print("<hr>System is Operational");
            if (isDigit(Parameters.A_hhmm[DOW][0])) {
              next_hhmm = atoi(Parameters.A_hhmm[DOW]);
              current_hhmm = Minute + (Hour * 100);
              if (next_hhmm > current_hhmm) {
                if (high_temp >= Parameters.A_temp) {
                  sprintf(buf,"<br>A Prog starts at: %s",Parameters.A_hhmm[DOW]);
                  WebClient.print(buf);
                } else {
                  WebClient.print("<br>A Prog DISABLED due to temperature");
                }
              }
            }
            if (isDigit(Parameters.B_hhmm[DOW][0])) {
              next_hhmm = atoi(Parameters.B_hhmm[DOW]);
              current_hhmm = Minute + (Hour * 100);
              if (next_hhmm > current_hhmm) {
                if (high_temp >= Parameters.B_temp) {
                  sprintf(buf,"<br>B Prog starts at: %s",Parameters.B_hhmm[DOW]);
                  WebClient.print(buf);
                } else {
                  WebClient.print("<br>B Prog DISABLED due to temperature");
                }
              }
            }
            break;
          case 2:
            WebClient.print("<hr>System is Wind DISABLED");
            break;
          case 3:
            WebClient.print("<hr>System is Rain DISABLED");
            break;
        }
      }
      //menu area
      WebClient.print("<hr>");
      WebClient.print("<table><tr><td><button onclick=location.href='manual.html'>MANUAL</button><td>");
      WebClient.print("<td><button  onclick=location.href='schedule.html'>SCHEDULE</button></td></tr></table>");
      //Footer
      WebFooter();
      WebClient.stop();
    } else if (isRequest == 2) { //manual.html
      WebHeader();
      WebClient.print("<H1>Manual Control</H1><table>");
      for (int i = 0; i < 6; i++) {
        if (manual[i]) {
          sprintf(buf,"<tr><td><button  onclick=location.href='manual.cgi?zone=%d'>ZONE %d</button></td><td><span style=background:#00ff00;> &nbsp &nbsp </span><font>&nbsp ON</font></td></tr>",i+1,i+1);
        } else {
          sprintf(buf,"<tr><td><button  onclick=location.href='manual.cgi?zone=%d'>ZONE %d</button></td><td><span style=background:#cccccc;> &nbsp &nbsp </span><font>&nbsp OFF</font></td></tr>",i+1,i+1);
        }
        WebClient.print(buf);
      }
      WebClient.print("</table>");
      //menu area
      WebClient.print("<hr>");
      WebClient.print("<table><tr><td><button onclick=location.href='status.html'>STATUS</button><td>");
      WebClient.print("<td><button  onclick=location.href='schedule.html'>SCHEDULE</button></td></tr></table>");
      WebFooter();
      WebClient.stop();
    } else if (isRequest == 3){ //schedule.html
      WebHeader();
      WebClient.print("<H1>Schedule</H1><form action=schedule.cgi method=post>");
      //enable operation
      if (Parameters.isOperational) {
        WebClient.print("<input type=checkbox name=isOperational value=1 checked>Enable Operation<br>");
      } else {
        WebClient.print("<input type=checkbox name=isOperational value=1 >Enable Operation<br>");
      }
      //zone times
      WebClient.print("<hr><table><tr><td><b>Zone</b></td><td><b>Prog A</b></td><td><b>Prog B</b></td></tr>");
      for (int i = 0; i < 6; i++) {
        sprintf(buf,"<tr><td>%d</td><td><input type=text size=3 value=%d name=A_minutes%d></td><td><input type=text size=3 value=%d name=B_minutes%d></td></tr>",i+1,Parameters.A_minutes[i],i+1,Parameters.B_minutes[i],i+1);
        WebClient.print(buf);
      }
      WebClient.print("</table>Enter zone duration in minutes<hr>");
      //start times
      WebClient.print("<table><tr><td><b>Day</b></td><td><b>Start A</b></td><td><b>Start B</b></td></tr>");
      for (int i = 0; i < 7; i++) {
        char startA[8],startB[8];
        if (isdigit(Parameters.A_hhmm[i][0]) || Parameters.A_hhmm[i][0] == 'o') {
          strcpy(startA,Parameters.A_hhmm[i]);
        } else {
          strcpy(startA,"off");
        }
        if (isdigit(Parameters.B_hhmm[i][0]) || Parameters.B_hhmm[i][0] == 'o') {
          strcpy(startB,Parameters.B_hhmm[i]);
        } else {
          strcpy(startB,"off");
        }
        sprintf(buf,"<tr><td>%s</td><td><input type=text size=5 value=%s name=A_hhmm%d></td><td><input type=text size=5 value=%s name=B_hhmm%d></td></tr>",dowString[i],startA,i+1,startB,i+1);
        WebClient.print(buf);
      }
      WebClient.print("</table>Enter time as 24hr HHMM or off<hr>");
      //weather options
      WebClient.print("<b>Weather Options</b><br>");
      if (Parameters.isRain) {
        WebClient.print("<input type=checkbox name=isRain value=1 checked>Disable start if it has rained<br>");
      } else {
        WebClient.print("<input type=checkbox name=isRain value=1 >Disable start if it has rained<br>");
      }
      if (Parameters.isWind) {
        WebClient.print("<input type=checkbox name=isWind value=1 checked>Disable start if it is windy<br>");
      } else {
        WebClient.print("<input type=checkbox name=isWind value=1 >Disable start if it is windy<br>");
      }
      sprintf(buf,"<input type=text size=3 value=%d name=wind_mph>Wind MPH to disable at<br>",Parameters.wind_mph);
      WebClient.print(buf);
      sprintf(buf,"<input type=text size=3 value=%d name=A_temp>Temperature to enable A program<br>",Parameters.A_temp);
      WebClient.print(buf);
      sprintf(buf,"<input type=text size=3 value=%d name=B_temp>Temperature to enable B program<br>",Parameters.B_temp);
      WebClient.print(buf);
      //submit button
      WebClient.print("<hr><input type=submit value='Submit Changes'></form>");
      //menu area
      WebClient.print("<hr>");
      WebClient.print("<table><tr><td><button onclick=location.href='status.html'>STATUS</button><td>");
      WebClient.print("<td><button  onclick=location.href='manual.html'>MANUAL</button></td></tr></table>");
      WebFooter();
      WebClient.stop();
    } else if (isRequest == 4) {//weather.html
      WebHeader();
      WebClient.print("<H1>Weather Response</H1>Data response from openweathermap</div>");
      WebClient.print("<pre>");
      WebClient.print(WeatherBuf);
      WebClient.print("</pre><button onclick=location.href='status.html'>STATUS</button></body></html>");
      WebClient.stop();
    }
  }
}

// Get web server requests
void WebTask(bool restart) {
  if (!WiFi.ready()){
    WebState = 3;
    return;//leave now but go to 3 when WiFi returns
  }
  switch (WebState) {
    case 0: // connect to server
      if (!WebClient.connected()) {//look for connection
        if (restart){
          WebState = 3;
        } else {
          WebClient = server.available();
        }
      } else {//we are connected
        memset(LANBuf,0,sizeof(LANBuf));
        LANBufCount = 0;
        ContentLength = 0;
        ContentStart = 0;
        WebState = 1;
      }
      break;
    case 1: // get returned data
      if (WebClient.connected()) {
        if (WebClient.available()) { //get each data segment
          char buf[256];
          int n = WebClient.read((uint8_t*)buf,sizeof(buf));
          if ((LANBufCount + n) <= sizeof(LANBuf)) { //prevent array overflow
            memcpy(LANBuf+LANBufCount,buf,n);
            LANBufCount += n;
          }
          if (ContentLength == 0) {
            char *p = strstr(LANBuf,"Content-Length:");
            if (p) {
              ContentLength = atoi(p+15);
            }
          }
          if (ContentStart == 0) {
            char *p = strstr(LANBuf,"\r\n\r\n");
            if (p) {
              ContentStart = p + 4 - LANBuf;
            }
          }
        } else if (LANBufCount >= (ContentLength + ContentStart) && LANBufCount > 0) {
          WebState = 2;
        }
      } else {
        WebState = 3;
      }
      break;
    case 2: // parse data
      ParseWeb();
      WebState = 0;
      LANBufCount = 0;
      ContentStart = 0;
      ContentLength = 0;
      break;
    case 3: // restart server
      server.stop();
      WebState = 4;
      break;
    case 4: // restart server
      delay(1000);
      server.begin();
      WebState = 0;
      break;
  }
}

// Get web form response
void ParseForm(void) {
  char buf[32], *p;
  p = strstr(LANBuf,"isOperational=");
  if (p) {
    Parameters.isOperational = true;
  } else {
    Parameters.isOperational = false;
  }
  for (int i = 0; i < 6; i++) {
    sprintf(buf,"A_minutes%d=",i+1);
    p = strstr(LANBuf,buf);
    if (p) {
      p += strlen(buf);
      Parameters.A_minutes[i] = atoi(p);
    }
  }
  for (int i = 0; i < 6; i++) {
    sprintf(buf,"B_minutes%d=",i+1);
    p = strstr(LANBuf,buf);
    if (p) {
      p += strlen(buf);
      Parameters.B_minutes[i] = atoi(p);
    }
  }
  for (int i = 0; i < 7; i++) {
    sprintf(buf,"A_hhmm%d=",i+1);
    p = strstr(LANBuf,buf);
    if (p) {
      p += strlen(buf);
      char *end = strchr(p,'&');
      int n = 4;
      if (end) {
        n = min(end - p,4);
      }
      memset(Parameters.A_hhmm[i],0,6);
      strncpy(Parameters.A_hhmm[i],p,n);
    }
  }
  for (int i = 0; i < 7; i++) {
    sprintf(buf,"B_hhmm%d=",i+1);
    p = strstr(LANBuf,buf);
    if (p) {
      p += strlen(buf);
      char *end = strchr(p,'&');
      int n = 4;
      if (end) {
        n = min(end - p,4);
      }
      memset(Parameters.B_hhmm[i],0,6);
      strncpy(Parameters.B_hhmm[i],p,n);
    }
  }
  p = strstr(LANBuf,"isRain=");
  if (p) {
    Parameters.isRain = true;
  } else {
    Parameters.isRain = false;
  }
  p = strstr(LANBuf,"isWind=");
  if (p) {
    Parameters.isWind = true;
  } else {
    Parameters.isWind = false;
  }
  p = strstr(LANBuf,"wind_mph=");
  if (p) {
    p += 9;
    Parameters.wind_mph = atoi(p);
  }
  p = strstr(LANBuf,"A_temp=");
  if (p) {
    p += 7;
    Parameters.A_temp = atoi(p);
  }
  p = strstr(LANBuf,"B_temp=");
  if (p) {
    p += 7;
    Parameters.B_temp = atoi(p);
  }
  EEPROM.put(0, Parameters);
}

//look for zone control command
void ParseManual(void) {
  char *p = strstr(LANBuf,"?zone=");
  if (p) {
    int x = atoi(p+6) - 1;
    if (x >= 0 && x <= 5) {
      for (int i = 0; i <= 5; i++) {//check all 6 zones
        if (i == x) {
          manual[i] = !manual[i];
        } else {
          manual[i] = false;
        }
      }
    }
  }
}

Credits

Martin Smaha

Martin Smaha

4 projects • 9 followers
Designing hardware and software since 1975.

Comments