diff --git a/RootWebPage.ino b/RootWebPage.ino new file mode 100644 index 0000000..238d89b --- /dev/null +++ b/RootWebPage.ino @@ -0,0 +1,176 @@ +/* +Copyright (c) 2023 David Carson (dacarson) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#if defined (ESP8266) + #include + extern ESP8266WebServer server; +#else if defined (ESP32_DEV) + #include + extern WebServer server; +#endif + +// Default Settings defined in WeatherFlow-RainSensor.ino +// Keep track of when the Rain started +extern long rainStartEpoch; + +// Need a stack of the last 24 hrs +// that has the rainfall total. +extern float rainInHour[]; +extern int currentHour; // so we can see if we have moved into a new hour + +// Amount of rainfall needed over 24hr period +extern int rainFallAmount; //(5 - 20mm) +// The time after it has stopped raining before the sprinklers can turn back on +extern int dryingTime; //(1 - 23 hrs) + +void handleRoot() { + char temp[100]; + + // Handle any arguements passed in + if (server.args() > 1) { + String rainFallAmountStr = server.arg("rainFallAmount"); + int tempRainFall = rainFallAmountStr.toInt(); + if (tempRainFall > 4 && tempRainFall < 21) { + rainFallAmount = tempRainFall; + } + String dryingTimeStr = server.arg("dryingTime"); + int tempDryingTime = dryingTimeStr.toInt(); + if (tempDryingTime > 0 && tempDryingTime < 24) { + dryingTime = tempDryingTime; + } + } + + server.setContentLength(CONTENT_LENGTH_UNKNOWN); + server.send ( 200, "text/html", "" ); + +// Send HTML header + server.sendContent(F("\ + \ + WeatherFlow Rain Sensor\ + \ + \ +")); + +// Send current configuation +server.sendContent(F("

Configuation

Rain fall needs to exceed ")); +server.sendContent(F("")); +server.sendContent(F("mm over last 24hrs to trigger rain sensor

Rain must have dropped below trigger for ")); +server.sendContent(F("")); +server.sendContent(F("hrs to reset rain sensor (drying time)

")); +server.sendContent(F("
")); + +// Send current status +server.sendContent(F("

Status

Rain Sensor: ")); +if (relayOffTime == -1) { + server.sendContent(F("Not ")); +} +server.sendContent(F("Triggered

")); +if (relayOffTime > 0) { + server.sendContent(F("

Rain sensor will reset at:

")); + server.sendContent(F("")); +} +server.sendContent(F("

Last time rain was detected:

")); +server.sendContent(F("")); +server.sendContent(F("

Rain fall total in last hour: ")); +sprintf(temp, "%.2f", rainInHour[currentHour]); +server.sendContent(temp); +server.sendContent(F("mm

Rain fall total in last 24hrs: ")); +float total = 0; +for (int hour = 0; hour < 24; hour++) + total += rainInHour[hour]; +sprintf(temp, "%.2f", total); +server.sendContent(temp); +server.sendContent(F("mm


")); + +// Testing +// Send current status +server.sendContent(F("

Testing

Send Rain start event

")); +server.sendContent(F("")); + +server.sendContent(F("

Send observation event with rain total 3.8 ")); +server.sendContent(F("")); + +server.sendContent(F("

Reset all stored rain data ")); +server.sendContent(F("")); + +// Send build info + server.sendContent(F("

Compiled: "));
+  server.sendContent(F(__DATE__));
+  server.sendContent( F(", "));
+  server.sendContent(F(__TIME__));
+  server.sendContent( F(", GCC: "));
+  server.sendContent(F(__VERSION__));
+  server.sendContent(F("

")); + server.client().stop(); + +} \ No newline at end of file diff --git a/WeatherFlow-RainSensor.ino b/WeatherFlow-RainSensor.ino new file mode 100644 index 0000000..fd2dd0a --- /dev/null +++ b/WeatherFlow-RainSensor.ino @@ -0,0 +1,250 @@ +/* +Copyright (c) 2023 David Carson (dacarson) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +/* +WeatherFlow-RainSensor + +Listen to Rain events/rain fall count, and toggle the Rain sensor on my +automatic irrigation system. + +Using a relay shield on a Wemos mini D1, wiring the NC (Normally Closed) +side to the Rain sensor on the irrigation system. + +Using a DC Power Shield on the Wemos, the Wemos can be powered from the +irrigation system with an AC/DC to convert it's 24VAC to DC and attaching +to DC Power Shield (7-24V) +*/ + +#if defined (ESP8266) + #include + #include + #include +#else if defined (ESP32_DEV) + #include + #include + #include +#endif + +#include +#include +#include +#include "WeatherFlowUdp.h" + +// Pins +const int led = LED_BUILTIN; +#if defined (ESP8266) + const int relayPin = D1; +#else if defined (ESP32_DEV) + const int relayPin = 22; +#endif + +const char *ssid = "your_wifi_name"; +const char *password = "your_wifi_password"; + +// We'll use time provided by Network Time Protocol to get EpochTime +// and use that with WeatherFlowData as that uses EpochTime too. +WiFiUDP ntpUDP; +NTPClient timeClient(ntpUDP); + +// Construct a webserver +#if defined (ESP8266) + ESP8266WebServer server(80); +#else if defined (ESP32_DEV) + WebServer server(80); +#endif +// defined in RootWebPage +void handleRoot(); + + +// Construct a weather object +WeatherFlowUdp currentWeather; + +// Keep track of when the Rain started +long rainStartEpoch = 0; + +// Need a stack of the last 24 hrs +// that has the rainfall total. +float rainInHour[24]; +int currentHour; // so we can see if we have moved into a new hour + +// When it increases above rainFallAmount, turn on sensor. +// When it drops below rainFallAmount, +// for longer than dryingTime, turn off sensor +long relayOffTime = -1; //EpochTime + +// Default Settings +// Amount of rainfall needed over 24hr period +int rainFallAmount = 12; //(5 - 20mm) +// The time after it has stopped raining before the sprinklers can turn back on +int dryingTime = 3; //(1 - 23 hrs) + +// Callback for when new WeatherFlow data arrives +void handleNewObject(WeatherFlowData::Object obj, void* context) { + if (obj == WeatherFlowData::RAIN) { + rainStartEpoch = (long)currentWeather.getValue(WeatherFlowData::Time_Epoch); + } + if (obj == WeatherFlowData::TEMPEST||obj == WeatherFlowData::SKY) { + int hour = timeClient.getHours(); + float rainLastMinute = (float)currentWeather.getValue(WeatherFlowData::Rain_Last_Minute); + if (currentHour == hour) + rainInHour[hour] += rainLastMinute; + else + rainInHour[hour] = rainLastMinute; + + // If we got some rain, but not triggered yet, + // double blink the LED + if (rainLastMinute && relayOffTime < 0) { + digitalWrite (led, LOW); + delay(250); + digitalWrite (led, HIGH); + delay(250); + digitalWrite (led, LOW); + delay(250); + digitalWrite (led, HIGH); + } + + // If we got no rain, but we are triggered, + // single slower LED blink + if (!rainLastMinute && relayOffTime >= 0) { + digitalWrite (led, LOW); + delay(1000); + digitalWrite (led, HIGH); + } + + // Last time we saw that it was raining. + if (rainLastMinute) + rainStartEpoch = (long)currentWeather.getValue(WeatherFlowData::Time_Epoch); + + currentHour = hour; + + // Checked 24hr rain fall total + float totalAccumulated = 0; + for (int i = 0; i < 24; i++) { + totalAccumulated += rainInHour[i]; + } + if (totalAccumulated >= rainFallAmount) + enableRainSensor(); + + if (relayOffTime > 0 && relayOffTime <= timeClient.getEpochTime()) + disableRainSensor(); + } +} + +// OK to call this even if rain sensor is already tripped +void enableRainSensor() { + Serial.println("Rain Detected: Yes"); + // Calculate the new rain sensing off time + relayOffTime = timeClient.getEpochTime() + (60 * 60 * dryingTime); + digitalWrite(relayPin, HIGH); // turn on relay + digitalWrite (led, LOW); +} + +// OK to call this even if rain sensor is already tripped +void disableRainSensor() { + Serial.println("Rain Detected: No"); + relayOffTime = -1; + digitalWrite(relayPin, LOW); // turn on relay + digitalWrite (led, HIGH); +} + +void rainStartTest() { + Serial.println("Testing Rain Start"); + const char *rainStartTest = + "{\"serial_number\": \"SK-00008453\", \"type\":\"evt_precip\", \"hub_sn\": \"HB-00000001\",\"evt\":[%ld]}"; + char *temp = (char*)malloc(strlen(rainStartTest) + 10); + sprintf(temp, rainStartTest, timeClient.getEpochTime()); + currentWeather.processPacket(temp); + free(temp); + server.send ( 200, "text/plain", "OK" ); + server.client().stop(); +} + +void observationTest() { + Serial.println("Testing Rain Observation event with 3.8mm of rain"); + const char *observationTest = + "{\"serial_number\": \"SK-00008453\",\"type\":\"obs_sky\",\"hub_sn\": \"HB-00000001\",\ + \"obs\":[[%ld,9000,10,3.8,2.6,4.6,7.4,187,3.12,1,130,null,0,3]], \"firmware_revision\": 29}"; + char *temp = (char*)malloc(strlen(observationTest) + 10); + sprintf(temp, observationTest, timeClient.getEpochTime()); + currentWeather.processPacket(temp); + free(temp); + server.send ( 200, "text/plain", "OK" ); + server.client().stop(); +} + +void resetRainData() { + Serial.println("Reseting all rain data"); + for (int i = 0; i < 24; i++) + rainInHour[i] = 0; + rainStartEpoch = 0; + disableRainSensor(); + + server.send ( 200, "text/plain", "OK" ); + server.client().stop(); +} + +void setup() { + pinMode ( led, OUTPUT ); + pinMode ( relayPin, OUTPUT); + Serial.begin ( 115200 ); + WiFi.begin ( ssid, password ); + Serial.println ( F("") ); + Serial.print ( F("Connecting to WiFi ")); + + // Wait for connection + while ( WiFi.status() != WL_CONNECTED ) { + delay ( 500 ); + Serial.print ( F(".") ); + } + Serial.println ( F("") ); + Serial.print (F("Connected to ") ); + Serial.println ( ssid ); + Serial.print ( F("IP address: ") ); + Serial.println ( WiFi.localIP() ); + + // Start the timeclient + timeClient.begin(); + // update every 7 days (1000ms * 60mins * 60hrs * 24hrs * 5days) + timeClient.setUpdateInterval(1000 * 60 * 60 * 24 * 5); + + // Register the handler function + currentWeather.registerCallback(handleNewObject); + currentWeather.begin(); + + // Reset state + disableRainSensor(); + + // Setup Web Server + server.on ("/", handleRoot); + server.on ("/rainstart", rainStartTest); + server.on ("/obstest", observationTest); + server.on ("/reset", resetRainData); + server.begin(); + Serial.println ( "HTTP server started" ); + +} + +void loop() { + timeClient.update(); + currentWeather.update(); + server.handleClient(); +}